天天看點

C++程式設計:函數的參數傳遞

作者:尚矽谷教育

函數在每次調用時,都會重新建立形參,并且用傳入的實參對它進行初始化。形參的類型,決定了形參和實參互動的方式;也決定了函數的不同功能。

可以先回憶一下對變量的初始化:對一個變量做初始化,如果用另一個變量給它賦初值,意味着值的拷貝;也就是說,此後這兩個變量各自一份資料,各自管理,互不影響。而如果是定義一個引用,綁定另一個變量做初始化,并不會引發值的拷貝;引用和原變量管理的是同一個資料對象。

int i1 = 0;

int i2 = i1;

i2 = 1; // i1的值仍然是0

int& i3 = i1;

i3 = 10; // i1的值也變為10

參數傳遞和變量的初始化類似,根據形參的類型可以分為兩種方式:傳值(value)和傳引用(reference)。

C++程式設計:函數的參數傳遞

圖檔來源于網絡,侵删

1. 傳值參數

直接将一個實參的值,拷貝給形參做初始化的傳參方式,就被稱為“值傳遞”,這樣的參數被稱為“傳值參數”。之前我們練習過的所有函數,都是采用這種傳值調用的方式。

int square(int x)

{

return x * x;

}

int main()

{

int n = 6;

cout << n << "的平方是:" << square(n) << endl;

}

在上面平方函數的調用中,實參n的值(6)被拷貝給了形參x。

(1)傳值的困擾

值傳遞這種方式非常簡單,但是面對這樣的需求會有些麻煩:傳入一個資料對象,讓它經過函數處理之後發生改變。例如,傳入一個整數x,調用之後它自己的值要加1。這看起來很簡單,但如果直接:

void increase(int x)

{

++x;

}

int main()

{

int n = 6;

increase(n); // n的值不會增加

}

這樣做并不能實作需求。因為實參n的值是拷貝給形參x的,之後x的任何操作,都不會改變n。

(2)指針形參

使用指針形參可以解決這個問題。如果我們把指向資料對象的指針作為形參,那麼初始化時拷貝的就是指針的值;複制之後的指針,依然指向原始資料對象,這樣就可以保留它的更改了。

// 指針形參

void increase(int* p)

{

++(*p);

}

int main()

{

int n = 0;

increase( &n ); // 傳入n的位址,調用函數後n的值會加1

}

2. 傳引用參數

使用指針形參可以解決值傳遞的問題,不過這種方式函數定義顯得有些繁瑣,每次調用還需要記住傳入變量的位址,使用起來不夠友善。

(1)傳引用友善函數調用

C++新增了引用的概念,可以替換必須使用指針的場景。采用引用作為函數形參,可以使函數調用更加友善。這種傳參方式叫做“傳引用參數”。之前的例子就可以改寫成:

// 傳引用

void increase(int& x)

{

++x;

}

int main()

{

int n = 0;

increase( n ); // 調用函數後n的值會加1

}

由于使用了引用作為形參,函數調用時就可以直接傳入n的值,而不用傳位址了;x隻是n的一個别名,修改x就修改了n。對比可以發現,這段代碼相比最初嘗試寫出的傳值實作,隻是多了一個引用聲明&而已。

(2)傳引用避免拷貝

使用引用還有一個非常重要的場景,就是不希望進行值拷貝的時候。實際應用中,很多時候函數要操作的對象可能非常龐大,如果做值拷貝會使得效率大大降低;這時使用引用就是一個好方法。

比如,想要定義一個函數比較兩個字元串的長度,需要将兩個字元串作為參數傳入。因為字元串有可能非常長,直接做值拷貝并不是一個好選擇,最好的方式就是傳遞引用:

// 比較兩個字元串的長度

bool isLonger(const string & str1, const string & str2)

{

return str1.size() > str2.size();

}

(3)使用常量引用做形參

在上面的例子中,比較兩個字元串長度,并不會更改字元串本身的内容,是以可以把形參定義為常量引用。

這樣的好處是,既避免了對資料對象可能的更改,也擴大了調用時能傳的實參的範圍。因為之前讨論過常量引用的特點,可以用字面值常量對它做初始化,也可以用變量做初始化。

是以在代碼中,一般要盡量使用常量引用作為形參。

3. 數組形參

之前已經介紹過,數組是不允許做直接拷貝的,是以如果想要把數組作為函數的形參,使用值傳遞的方式是不可行的。與此同時,數組名可以解析成一個指針,是以可以用傳遞指針的方式來處理數組。

比如一個簡單的函數,需要周遊int類型數組所有元素并輸出,就可以這樣聲明:

void printArray(const int*); // 指向int類型常量的指針

void printArray(const int[]);

void printArray(const int[5]);

由于隻是周遊輸出,不需要修改數組内容,是以這裡使用了const。

以上三種聲明方式,本質上是一樣的,形參的類型都是const int *;雖然第三種方式指定了數組長度,但由于編譯器會把傳入的數組名解析成指針,事實上的數組長度還是無法确定的。

這就帶來另一個問題:在函數中,周遊元素時怎樣确定數組的結束?

(1)規定結束标記

一種簡單思路是,規定一個特殊的“結束标記”,遇到這個标記就代表目前數組已經周遊完了。典型代表就是C語言風格的字元串,是以空字元’\0’為結束标志的char數組。

這種方式比較麻煩,而且太多特殊規定也不适合像int這樣的資料類型。

(2)把數組長度作為形參

除指向數組的指針外,可以再增加一個形參,專門表示數組的長度,這樣就可以友善地周遊數組了。

void printArray(const int* arr, int size)

{

for (int i = 0; i < size; i++)

cout << arr[i] << "\t";

cout << endl;

}

int main()

{

int arr[6] = { 1,2,3,4,5,6 };

printArray(arr, 6);

}

在C語言和老式的C++程式中,經常使用這種方法來處理數組。

(3)使用數組引用作為形參

之前的方法依賴指針,是以都顯得比較麻煩。更加友善的做法,還是用引用來替代指針的功能。

C++允許使用數組的引用作為函數形參,這樣一來,引用作為别名綁定在數組上,使用引用就可以直接周遊數組了。

// 使用數組引用作為形參

void printArray(const int(&arr)[6])

{

for (int num : arr)

cout << num << "\t";

cout << endl;

}

int main()

{

int arr[6] = { 1,2,3,4,5,6 };

printArray(arr);

}

這裡需要注意的是,定義一個數組引用時需要用括号将&和引用名括起來:

int(&arr)[6] // 正确,arr是一個引用,綁定的是長度為6的int數組

// int & arr[6] // 錯誤,這是引用的數組,不允許使用

使用數組引用之後,調用函數直接傳入數組名就可以了。

4. 可變形參

有時候我們并不确定函數中應該有幾個形參,這時就需要使用“可變形參”來表達。

C++中表示可變形參的方式主要有三種:

  • 省略符(…):相容C語言的用法,隻能出現在形參清單的最後一個位置;
  • 初始化清單initializer_list:跟vector類似,也是一種标準庫模闆類型;initializer_list對象中的元素隻能是常量值,不能更改;
  • 可變參數模闆:這是一種特殊的函數,後面會詳細介紹。

繼續閱讀