1. 程式人生 > >C++ Primer Plus 學習筆記(函式)(一)

C++ Primer Plus 學習筆記(函式)(一)

1. 基本知識

使用 C++ 函式,必須完成以下工作:

  • 提供函式定義
  • 提供函式原型
  • 呼叫函式

庫函式是已經定義和編譯好的函式,同時可以使用標準庫標頭檔案提供其原型,因此只需正確地呼叫這種函式即可。

1. 1 定義函式

可以將函式分成兩類:沒有返回值的函式和有返回值的函式。沒有返回值的函式被稱為 void 函式,其通用格式如下:

void functionName (parameterList)
{
    statement(s)
    return;
}

其中 parameterList 指定了傳遞給函式的引數型別和數量,可選的返回語句標記了函式的結尾。

有返回值的函式將生成一個值,並將它返回給呼叫函式。這種函式的型別被宣告為返回值的型別,其通用格式如下:

typeName functionName (parameterList)
{
    statement(s)
    return value;
} 

C++ 對於返回值的型別有一定的限制:不能是陣列,但可以使其他型別——整型、浮點型、指標,甚至可以使結構和物件(C++ 函式不能直接返回陣列,但可以將陣列作為結構或物件組成部分類返回。

函式在執行返回語句後結束。如果函式包含多條返回語句(例如,它們位於不同的 if else 選項中),則函式在執行遇到的第一條返回語句後結束。

1. 2 函式原型和呼叫

原型描述了函式到編譯器的介面,也就是說,它將函式返回值的型別以及引數的型別和數量告訴編譯器。

C++ 允許將一個程式放在多個檔案中,單獨編譯這些檔案,然後將它們組合起來。在這種情況下,編譯器在編譯 main() 時,可能無權訪問函式程式碼。如果函式位於庫中,情況也將如此。避免使用函式原型的唯一方法是,在首次使用函式之前定義它,但這並不總是可行的。另外,C++ 的變成風格是將 main() 放在最前面,因為它通常提供了程式的整體結構。

原型確保以下幾點:

  • 編譯器正確處理函式返回值
  • 編譯器檢查使用的引數數目是否正確
  • 編譯器檢車使用的引數型別是否正確,如果不正確則轉換為正確的型別

2. 函式引數和按值傳遞

C++ 通過長安之傳遞引數,這意味著將數值引數傳遞給函式,而後者將其賦給一個新的變數。用於接受傳遞值的參量被稱為形參,傳遞給函式的值被稱為實參。出於簡化的目的,C++ 標準使用引數(argument)來表示實參,使用參量(parameter)來表示形參,因此引數傳遞將引數賦給參量。

在函式中宣告的變數(包括引數)是該函式私有的。在函式被呼叫的時候,計算機姜維這些變數分配記憶體;在函式結束時,計算機將釋放這些變數使用的記憶體。這樣的變數被稱為區域性變數,因為它們被限制在函式中。

3. 函式和陣列

函式頭:

int sum_arr(int arr[], int ArSize); // arr = array name, n = size

可以將陣列作為函式的形參傳入函式。上面語句中,方括號指出 arr 是一個數組,而方括號為空則表明,可以將任何長度的陣列傳遞給該函式。但實際情況並非如此,arr 實際上並不是陣列,而是一個指標。在編寫函式的其餘部分時,可以將 arr 看作是陣列。

函式呼叫如:

int sum = sum_arr(array, size);

對於陣列,array 是陣列名,根據 C++ 規則,array是其第一個元素的地址,因此函式傳遞的是地址。由於陣列的元素的型別為 int, 因此 array 的型別必須是 int 指標,即 int*。這表明正確的函式頭應該是這樣的:

int sum_arr(int * arr, int n);  // arr = array name, n = size

其中用 int* arr 替換了int arr[]。這兩個函式頭都是正確的。因為在 C++ 中,當且僅當用於函式頭或者函式原型中,int * arr 和 int arr[] 的含義才是相同的。另外,必須顯式地傳遞陣列長度,而不能在函式中使用 sizeof,因為指標本身並沒有指出陣列長度。

使用 const 保護陣列

void show_array(const double ar[], int n);

使用普通引數時,不修改原始資料的保護將會自動實現,因為 C++ 按值傳遞資料,而且函式使用資料的副本。然而接受陣列名的函式將使用原始資料。為防止函式無意中修改陣列的內容,可以在宣告形參時使用關鍵字 const。

陣列區間

一種給函式提供陣列資訊的方法,即指定元素區間,可以通過傳遞兩個指標來完成:一個指標標識陣列的開頭,另一個指標表示陣列的尾部。
假設有如下宣告:

double elboud[20];
begin = elboud;
end = elboud + 20;

則指標 elboud 和 elboud + 20 定義了區間。陣列名 elboud 指向第一個元素,表示式 elboud + 19 指向最後一個元素(elboud[19]),因此,elboud + 20 指向陣列結尾後面的一個位置。
根據減法規則,end - begin 是一個整數值,等於陣列的元素數目。

指標和 const

有兩種方式將 const 關鍵字用於指標。第一種方法是讓指標指向一個常量物件,這樣可以防止使用該指標來修改所指向的值,第二種方法是將指標本身宣告為常量,這樣可以防止改變指標指向的位置

int age = 39;
const int * pt = &age;

*pt += 1;   // invalid
cin >> *pt; //  invalid

*pt = 20;   // invalid
age = 20;   // valid

pt 指向一個 const int,因此不能使用 pt 來修改這個值,*pt 的值為 const,不能修改。

以前,我們將常規變數的地址賦給常規指標,而這裡將常規變數的地址賦給指向 const 的指標。因此有兩種可能:

const float g_earth = 9.80;
const float * pe = &g_earth;    // valid

const float g_moon = 1.63;
float * pm = &g_moon;           // invalid

對於第一種情況來說,既不能使用 g_earth 來修改值 9.80,也不能使用 pe 來修改。對於第二種情況,C++ 禁止將 const 的地址賦給非 const 指標。

如果將指標指向指標,則情況將更加複雜,假如設計的是一級間接關係,則將非 const 指標賦給 const 指標是可以的。

int age = 39;           // age++ is a valid operation
int * pd = &age;        // *pd = 41 is a valid operation
const int * pt = pd;    // *pt = 42 is an invalid operation

進入兩級間接關係時,與一級間接關係一樣將 const 和非 const 混合的指標複製方式將不再安全。如果允許這樣做,則可以編寫這樣的程式碼:

const int **pp2;
int *p1;
const int n = 13;
pp2 = &p1;      // not allowed, but suppose it were
*pp2 = &n;      // valid, both const, but sets p1 to point at n
*p1 = 10;       // valid but changes const n

上述程式碼將非 const 地址(&p1)賦給了 const 指標(pp2),因此可以使用 p1 來修改const 資料。

注意:如果資料型別本省並不是指標,則可以將 const 資料或非 const 資料的地址賦給指向 const 的指標,但只能將非 const 資料的地址賦給非 const 指標。

對於由 const 資料組成的陣列,則禁止將常量陣列的地址賦給非常量指標將意味著不能將陣列名作為引數傳遞給使用非常量形參的函式。

4. 函式和二位陣列

為編寫將二位陣列作為引數的函式,相應的形參是一個指標,因為陣列名被視為其地址。假設有如下程式碼:

int data[3][4] = {{1,2,3,4}, {9,8,7,6}, {2,4,6,8}};
int total = sum(data, 3);

對於函式 sum() 的函式,data 是一個數組名,該陣列有 3 個元素,每一個元素本身是一個數組,由 4 個 int 值組成。因此 data 的型別是指向由 4 個 int 組成的陣列的指標,因此,函式原型如下:

int sum(int (*ar2)[4], int size);

其中的括號是必不可少的,因為下面的宣告將宣告一個由 4 個指向 int 的指標組成的陣列,而不是由一個指向由 4 個 int 組成的陣列的指標,另外函式引數不能是陣列:

int *ar2[4];

還有另外一種格式,與上述原型的含義完全相同,但可讀性更強:

int sum(int ar2[][4], int size);

上述兩個原型都指出,ar2 是指標而不是陣列。指標型別指出,它指向由 4 個 int 組成的陣列。因此,指標型別指定了獵術,這就是沒有將列數作為獨立的函式引數進行傳遞的原因。由於指標型別指定了列數,因此 sum() 函式只能接受由 4 列組成的陣列。但長度變數指定了行數,因此 sum() 對陣列的行數沒有限制。
行數被傳遞給 size 引數,但列數都是固定的——4 列。由於 ar2 指向陣列的第一個元素,因此表示式 ar2 + r 指向編號為 r 的元素。

5. 函式和 C-風格字串

假設要將字串作為引數傳遞給函式,則表示字串的方式有三種:

  • char 陣列
  • 用引號括起的字串常量(也稱字串字面值)
  • 被設定為字元創的地址的 char 指標。

但上述 3 種選擇的型別都是 char 指標(char*)。可以說是將字串作為引數來傳遞,但實際傳遞的是字串第一個字元的地址。這意味著字串函式原型應將其表示字串的形參宣告為 char* 型別。

返回 C-風格字串

可以在函式中建立 C-風格字串並返回其地址,如:

char * buildstr(char ch, int n)
{
    char * pstr = new char[n+1];
    pstr[n] = '\0';
    while (n-- > 0)
        pstr[n] = ch;
    return pstr;
}

變數 pstr 的作用域為 buildstr 內,因此在函式結束時,pstr(而不是字串)使用的記憶體將被釋放。但由於函式返回了 pstr 的值,因此程式仍可以通過 main() 中的指標來訪問新建的字串。