关于函数形参的一些讨论
形參的初始化與變量的初始化一樣:如果形參具有非引用類型,則復(fù)制實參的值,如果形參為引用類型,則它只是實參的別名。
- 非引用形參:
普通的非引用類型的參數(shù)通過復(fù)制對應(yīng)的實參實現(xiàn)初始化。當(dāng)用實參副本初始化形參時,函數(shù)并沒有訪問調(diào)用所傳遞的實參本身,因此不會修改實參的值。
while 循環(huán)體雖然修改了 v1 與 v2 的值,但這些變化僅限于局部參數(shù),而對調(diào)用 gcd 函數(shù)使用的實參沒有任何影響。于是,如果有函數(shù)調(diào)用gcd(i, j),i 與 j 的值不受 gcd 內(nèi)執(zhí)行的賦值操作的影響。
#include <iostream> #include <string>using namespace std;int gcd(int v1, int v2) {while (v2) {int temp = v2;v2 = v1 % v2;v1 = temp;}cout << "v1:" << v1 << endl;cout << "v2:" << v2 << endl;return v1; }int main() {int i = 100;int j = 3;gcd(i, j);cout << "i:" << i << endl;cout << "j:" << j << endl;system("pause");return 0; }輸出結(jié)果:
所以,非引用形參表示對應(yīng)實參的局部副本。對這類形參的修改僅僅改變了局部副本的值。一旦函數(shù)執(zhí)行結(jié)束,這些局部變量的值也就沒有了。
- 指針形參:
函數(shù)的形參可以是指針,此時將復(fù)制實參指針。與其他非引用類型的形參一樣,該類形參的任何改變也僅作用于局部副本。如果函數(shù)將新指針賦給形參,主調(diào)函數(shù)使用的實參指針的值沒有改變。但如果對局部副本的指針?biāo)赶虻膶ο蟮闹颠M(jìn)行修改,則實參指針?biāo)笇ο蟮闹狄矔鄳?yīng)的被修改。
如果函數(shù)形參是非 const 類型的指針,則函數(shù)可通過指針實現(xiàn)賦值,修改指針?biāo)赶驅(qū)ο蟮闹?#xff1a;
void reset(int *ip) {*ip = 0; // changes the value of the object to which ip pointsip = 0; // changes only the local value of ip; the argument is unchangedcout << "local ip: " << ip << endl; }int main() {int i = 42;int *p = &i;cout << "i: " << *p << '\n'; // prints i: 42cout << "p: " << p << endl;reset(p); // changes *p but not pcout << "i: " << *p << endl; // ok: prints i: 0cout << "p: " << p << endl;//并沒有改動 system("pause");return 0; }如果保護指針指向的值,則形參需定義為指向 const 對象的指針:
?
指針形參是指向 const 類型還是非 const 類型,將影響函數(shù)調(diào)用所使用的實參。我們既可以用 int* 也可以用 const int* 類型的實參調(diào)用 use_ptr 函數(shù);但僅能將 int* 類型的實參傳遞給 reset 函數(shù)。這個差別來源于指針的初始化規(guī)則——可以將指向 const 對象的指針初始化為指向非 const 對象的指針,但不可以讓指向非 const 對象的指針初始化為指向 const 對象的指針。
?
- const形參:
在調(diào)用函數(shù)時,如果該函數(shù)使用非引用的非 const 形參,則既可給該函數(shù)傳遞 const 實參也可傳遞非 const 的實參。例如,可以傳遞兩個 int 型 const 對象調(diào)用 gcd:
int gcd(int v1, int v2) {while (v2) {int temp = v2;v2 = v1 % v2;v1 = temp;}return v1; }int main() {const int i = 3, j = 6;int k = gcd(3, 6); system("pause");return 0; }這種行為源于 const 對象的標(biāo)準(zhǔn)初始化規(guī)則。因為初始化復(fù)制了初始化時的對象,僅僅是要用那個值,即便修改也是對拷貝后的對象進(jìn)行修改,而不對原對象進(jìn)行修改,所以可用 const 對象初始化非 const 對象,反之亦然。
如果將形參定義為非引用的 const 類型:
void fcn(const int i) {}則在函數(shù)中,不可以改變實參的局部副本。由于實參仍然是以副本的形式傳遞,因此傳遞給 fcn 的既可以是 const 對象也可以是非 const 對象。
盡管函數(shù)的形參是 const,但是編譯器卻將 fcn 的定義視為其形碼被聲明為普通的 int 型:
void fcn(const int i) { } void fcn(int i) { }?
?
這種用法是為了支持對 C 語言的兼容,因為在 C 語言中,具有 const 形參或非 const 形參的函數(shù)并無區(qū)別。
- 復(fù)制實參的局限性:
當(dāng)需要在函數(shù)中修改實參的值時。
當(dāng)需要以大型對象作為實參傳遞時。對實際的應(yīng)用而言,復(fù)制對象所付出的時間和存儲空間代價往往過大。
當(dāng)沒有辦法實現(xiàn)對象的復(fù)制時。
- 引用形參
考慮下面不適宜復(fù)制實參的例子,該函數(shù)希望交換兩個實參的值:
void swap(int v1, int v2){int tmp = v2;v2 = v1; v1 = tmp;}這個例子期望改變實參本身的值。但對于上述的函數(shù)定義,swap 無法影響實參本身。執(zhí)行 swap 時,只交換了其實參的局部副本,而傳遞 swap 的實參并沒有修改:
int main() {int i = 10;int j = 20;cout << "Before swap():\ti: "<< i << "\tj: " << j << endl;swap(i, j);cout << "After swap():\ti: "<< i << "\tj: " << j << endl;system("pause");return 0; }輸出結(jié)果:
為了使 swap 函數(shù)以期望的方式工作,交換實參的值,需要將形參定義為引用類型:
void swap(int &v1, int &v2) {int tmp = v2;v2 = v1;v1 = tmp; }int main() {int i = 10;int j = 20;cout << "Before swap():\ti: "<< i << "\tj: " << j << endl;swap(i, j);cout << "After swap():\ti: "<< i << "\tj: " << j << endl;system("pause");return 0; }輸出結(jié)果:
與所有引用一樣,引用形參直接關(guān)聯(lián)到其所綁定的實參,而并非這些對象的副本。定義引用時,必須用與該引用綁定的對象初始化該引用。引用形參完全以相同的方式工作。每次調(diào)用函數(shù),引用形參被創(chuàng)建并與相應(yīng)實參關(guān)聯(lián)。此時,當(dāng)調(diào)用swap(i, j); 形參 v1 只是對象 i 的另一個名字,而 v2 則是對象 j 的另一個名字。對 v1 的任何修改實際上也是對 i 的修改。同樣地,v2 上的任何修改實際上也是對 j 的修改。
從 C 語言背景轉(zhuǎn)到 C++ 的程序員習(xí)慣通過傳遞指針來實現(xiàn)對實參的訪問。在 C++ 中,使用引用形參則更安全和更自然。
- 使用引用形參返回額外的信息
引用形參的另一種用法是向主調(diào)函數(shù)返回額外的結(jié)果。函數(shù)只能返回單個值,但有些時候,函數(shù)有不止一個的內(nèi)容需要返回。
如何定義既返回一個迭代器又返回出現(xiàn)次數(shù)的函數(shù)?我們可以定義一種包含一個迭代器和一個計數(shù)器的新類型。而更簡便的解決方案是給 find_val 傳遞一個額外的引用實參,用于返回出現(xiàn)次數(shù)的統(tǒng)計結(jié)果:
vector<int>::const_iterator find_val(vector<int>::const_iterator beg, vector<int>::const_iterator end, int value, vector<int>::size_type &occurs) {vector<int>::const_iterator res_iter = end;occurs = 0; for (; beg != end; ++beg)if (*beg == value) {if (res_iter == end)res_iter = beg;++occurs; }return res_iter; }int main() {vector<int> ivec = { 1, 42, 3, 42, 5, 6, 7, 8 };vector<int>::size_type ctr = 0;vector<int>::const_iterator it = find_val(ivec.begin(), ivec.end(), 42, ctr);cout << ctr << endl;system("pause");return 0; }輸出結(jié)果:
- 利用const引用避免復(fù)制
在向函數(shù)傳遞大型對象時,需要使用引用形參,這是引用形參適用的另一種情況。雖然復(fù)制實參對于內(nèi)置數(shù)據(jù)類型的對象或者規(guī)模較小的類類型對象來說沒有什么問題,但是對于大部分的類類型或者大型數(shù)組,它的效率(通常)太低了;此外,某些類類型是無法復(fù)制的。使用引用形參,函數(shù)可以直接訪問實參對象,而無須復(fù)制它。編寫一個比較兩個 string 對象長度的函數(shù)作為例子。這個函數(shù)需要訪問每個 string 對象的 size,但不必修改這些對象。由于 string 對象可能相當(dāng)長,所以我們希望避免復(fù)制操作。使用 const 引用就可避免復(fù)制:
bool isShorter(const string &s1, const string &s2){return s1.size() < s2.size();}其每一個形參都是 const string 類型的引用。因為形參是引用,所以不復(fù)制實參。又因為形參是 const 引用,所以 isShorter 函數(shù)不能使用該引用來修改實參。如果使用引用形參的唯一目的是避免復(fù)制實參,則應(yīng)將形參定義為 const 引用。
- 更靈活的指向const的引用
如果函數(shù)具有普通的非 const 引用形參,則顯然不能通過 const 對象進(jìn)行調(diào)用。畢竟,此時函數(shù)可以修改傳遞進(jìn)來的對象,這樣就違背了實參的 const 特性。
int incr(int &val) {return ++val; }int main() {const int v2 = 42;int v3 = incr(v2); // error: v1 is not an int }會報錯:
但比較容易忽略的是,調(diào)用這樣的函數(shù)時,傳遞一個右值或具有需要轉(zhuǎn)換的類型的對象同樣是不允許的:
int main() {short v1 = 0;const int v2 = 42;int v3 = incr(v1); // error: v1 is not an intv3 = incr(0); // error: literals are not lvaluesv3 = incr(v1 + v2); // error: addition doesn't yield an lvalueint v4 = incr(v3); // ok: v3 is a non const object type intsystem("pause");return 0; }會提示錯誤:
問題的關(guān)鍵是非 const 引用形參只能與完全同類型的非 const 對象關(guān)聯(lián)。
應(yīng)該將不修改相應(yīng)實參的形參定義為 const 引用。如果將這樣的形參定義為非 const 引用,則毫無必要地限制了該函數(shù)的使用。例如,可編寫下面的程序在一個 string 對象中查找一個指定的字符:
string::size_type find_char(string &s, char c) {string::size_type i = 0;while (i != s.size() && s[i] != c)++i; // not found, look at next characterreturn i; }這個函數(shù)將其 string 類型的實參當(dāng)作普通(非 const)的引用,盡管函數(shù)并沒有修改這個形參的值。這樣的定義帶來的問題是不能通過字符串字面值來調(diào)用這個函數(shù):
if (find_char("Hello World", 'o'))雖然字符串字面值可以轉(zhuǎn)換為 string 對象,但上述調(diào)用仍然會導(dǎo)致編譯失敗:
繼續(xù)將這個問題延伸下去會發(fā)現(xiàn),即使程序本身沒有 const 對象,而且只使用 string 對象(而并非字符串字面值或產(chǎn)生 string 對象的表達(dá)式)調(diào)用 find_char 函數(shù),編譯階段的問題依然會出現(xiàn)。例如,可能有另一個函數(shù) is_sentence 調(diào)用 find_char 來判斷一個 string 對象是否是句子:
bool is_sentence (const string &s){return (find_char(s, '.') == s.size() - 1);}如上代碼,函數(shù) is_sentence 中 find_char 的調(diào)用是一個編譯錯誤。傳遞進(jìn) is_sentence 的形參是指向 const string 對象的引用,不能將這種類型的參數(shù)傳遞給 find_char,因為后者期待得到一個指向非 const string 對象的引用。
應(yīng)該將不需要修改的引用形參定義為 const 引用。普通的非 const 引用形參在使用時不太靈活。這樣的形參既不能用 const 對象初始化,也不能用字面值或產(chǎn)生右值的表達(dá)式實參初始化。
- 傳遞指向指針的引用:
假設(shè)我們想編寫一個與前面交換兩個整數(shù)的 swap 類似的函數(shù),實現(xiàn)兩個指針的交換。已知需用 * 定義指針,用 & 定義引用。現(xiàn)在,問題在于如何將這兩個操作符結(jié)合起來以獲得指向指針的引用。這里給出一個例子:
void ptrswap(int *&v1, int *&v2) {int *tmp = v2;v2 = v1;v1 = tmp; }形參int *&v1的定義應(yīng)從右至左理解:v1 是一個引用,與指向 int 型對象的指針相關(guān)聯(lián)。也就是說,v1 只是傳遞進(jìn) ptrswap 函數(shù)的任意指針的別名。調(diào)用 ptrswap 交換分別指向值 10 和 20 的指針:
int main() {int i = 10;int j = 20;int *pi = &i; // pi points to iint *pj = &j; // pj points to jcout << "Before ptrswap():\t*pi: "<< *pi << "\t*pj: " << *pj << endl;ptrswap(pi, pj); // now pi points to j; pj points to icout << "After ptrswap():\t*pi: "<< *pi << "\t*pj: " << *pj << endl;system("pause");return 0; }輸出結(jié)果:
即指針的值被交換了。在調(diào)用 ptrswap 時,pi 指向 i,而 pj 則指向 j。在 ptrswap 函數(shù)中,指針被交換,使得調(diào)用 ptrswap 結(jié)束后,pi 指向了原來 pj 所指向的對象。換句話說,現(xiàn)在 pi 指向 j,而 pj 則指向了 i。
- vector和其它類型容器的形參
通常,函數(shù)不應(yīng)該有 vector 或其他標(biāo)準(zhǔn)庫容器類型的形參。調(diào)用含有普通的非引用 vector 形參的函數(shù)將會復(fù)制 vector 的每一個元素。從避免復(fù)制 vector 的角度出發(fā),應(yīng)考慮將形參聲明為引用類型。然而,事實上,C++ 程序員傾向于通過傳遞指向容器中需要處理的元素的迭代器來傳遞容器:
void print(vector<int>::const_iterator beg,vector<int>::const_iterator end){while (beg != end) {cout << *beg++;if (beg != end) cout << " "; // no space after last element }cout << endl;}這個函數(shù)將輸出從 beg 指向的元素開始到 end 指向的元素(不含)為止的范圍內(nèi)所有的元素。除了最后一個元素外,每個元素后面都輸出一個空格。
- 數(shù)組形參
數(shù)組有兩個特殊的性質(zhì),影響我們定義和使用作用在數(shù)組上的函數(shù):一是不能復(fù)制數(shù)組;二是使用數(shù)組名字時,數(shù)組名會自動轉(zhuǎn)化為指向其第一個元素的指針。因為數(shù)組不能復(fù)制,所以無法編寫使用數(shù)組類型形參的函數(shù)。因為數(shù)組會被自動轉(zhuǎn)化為指針,所以處理數(shù)組的函數(shù)通常通過操縱指向數(shù)組指向數(shù)組中的元素的指針來處理數(shù)組。
如果要編寫一個函數(shù),輸出 int 型數(shù)組的內(nèi)容,可用下面三種方式指定數(shù)組形參:
void printValues(int*) { /* ... */ } void printValues(int[]) { /* ... */ } void printValues(int[10]) { /* ... */ }雖然不能直接傳遞數(shù)組,但是函數(shù)的形參可以寫成數(shù)組的形式。雖然形參表示方式不同,但可將使用數(shù)組語法定義的形參看作指向數(shù)組元素類型的指針。上面的三種定義是等價的,形參類型都是 int*。
形參的長度會引起誤會。編譯器忽略為任何數(shù)組形參指定的長度。根據(jù)數(shù)組長度(權(quán)且這樣說),可將函數(shù) printValues 編寫為:
void printValues(const int ia[10]){for (size_t i = 0; i != 10; ++i){cout << ia[i] << endl;}}盡管上述代碼假定所傳遞的數(shù)組至少含有 10 個元素,但 C++ 語言沒有任何機制強制實現(xiàn)這個假設(shè)。下面的調(diào)用都是合法的:
int main(){int i = 0, j[2] = {0, 1};printValues(&i); // ok: &i is int*; probable run-time errorprintValues(j); // ok: j is converted to pointer to 0th// element; argument has type int*;// probable run-time errorreturn 0;}雖然編譯沒有問題,但是這兩個調(diào)用都是錯誤的,可能導(dǎo)致運行失敗。在這兩個調(diào)用中,由于函數(shù) printValues 假設(shè)傳遞進(jìn)來的數(shù)組至少含有 10 個元素,因此造成數(shù)組內(nèi)在的越界訪問。程序的執(zhí)行可能產(chǎn)生錯誤的輸出,也可能崩潰,這取決于越界訪問的內(nèi)存中恰好存儲的數(shù)值是什么。
當(dāng)編譯器檢查數(shù)組形參關(guān)聯(lián)的實參時,它只會檢查實參是不是指針、指針的類型和數(shù)組元素的類型時是否匹配,而不會檢查數(shù)組的長度。
和其他類型一樣,數(shù)組形參可定義為引用或非引用類型。大部分情況下,數(shù)組以普通的非引用類型傳遞,此時數(shù)組會悄悄地轉(zhuǎn)換為指針。一般來說,非引用類型的形參會初始化為其相應(yīng)實參的副本。而在傳遞數(shù)組時,實參是指向數(shù)組第一個元素的指針,形參復(fù)制的是這個指針的值,而不是數(shù)組元素本身。函數(shù)操縱的是指針的副本,因此不會修改實參指針的值。然而,函數(shù)可通過該指針改變它所指向的數(shù)組元素的值。通過指針形參做的任何改變都在修改數(shù)組元素本身。不需要修改數(shù)組形參的元素時,函數(shù)應(yīng)該將形參定義為指向 const 對象的指針:
void f(const int*) { /* ... */ }- 通過引用傳遞數(shù)組
和其他類型一樣,數(shù)組形參可聲明為數(shù)組的引用。如果形參是數(shù)組的引用,編譯器不會將數(shù)組實參轉(zhuǎn)化為指針,而是傳遞數(shù)組的引用本身。在這種情況下,數(shù)組大小成為形參和實參類型的一部分。編譯器檢查數(shù)組的實參的大小與形參的大小是否匹配:
void printValues(int(&arr)[10]) {} int main() {int i = 0, j[2] = { 0, 1 };int k[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };printValues(&i); // error: argument is not an array of 10 intsprintValues(j); // error: argument is not an array of 10 intsprintValues(k); // ok: argument is an array of 10 intssystem("pause");return 0; }報錯:
這個版本的 printValues 函數(shù)只嚴(yán)格地接受含有 10 個 int 型數(shù)值的數(shù)組,這限制了哪些數(shù)組可以傳遞。
- 多維數(shù)組的傳遞
所謂多維數(shù)組實際是指數(shù)組的數(shù)組。和其他數(shù)組一樣,多維數(shù)組以指向 0 號元素的指針方式傳遞。多維數(shù)組的元素本身就是數(shù)組。除了第一維以外的所有維的長度都是元素類型的一部分,必須明確指定:
void printValues(int (matrix*)[10], int rowSize);上面的語句將 matrix 聲明為指向含有 10 個 int 型元素的數(shù)組的指針。//如果傳遞的數(shù)組時int a[10],那么形參應(yīng)該是void printValues(int locala[10], int rowSize);,locala就是指向int類型的指針。傳遞數(shù)組,形參類型就是數(shù)組元素的指針類型。因為int a[10]是整型數(shù)組,所以形參是整形指針,因為這里要傳遞的是數(shù)組的數(shù)組,所以形參應(yīng)該是數(shù)組的指針。
我們也可以用數(shù)組語法定義多維數(shù)組。與一維數(shù)組一樣,編譯器忽略第一維的長度,所以最好不要把它包括在形參表內(nèi):
void printValues(int matrix[][10], int rowSize);這條語句把 matrix 聲明為二維數(shù)組的形式。實際上,形參是一個指針,指向數(shù)組的數(shù)組中的元素。數(shù)組中的每個元素本身就是含有 10 個 int 型對象的數(shù)組。
舉例:
int matrix[3][10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0,11, 12, 13, 14, 15, 16, 17, 18, 18, 20,21, 22, 23, 24, 25, 26, 27, 28, 29, 30 };其實二維數(shù)組matrix中的數(shù)據(jù)是現(xiàn)行排放的,而matrix則是首元素的地址:
非引用數(shù)組形參的類型檢查只是確保實參是和數(shù)組元素具有同樣類型的指針,而不會檢查實參實際上是否指向指定大小的數(shù)組。任何處理數(shù)組的程序都要確保程序停留在數(shù)組的邊界內(nèi)。
有三種常見的編程技巧確保函數(shù)的操作不超出數(shù)組實參的邊界。第一種方法是在數(shù)組本身放置一個標(biāo)記來檢測數(shù)組的結(jié)束。C 風(fēng)格字符串就是采用這種方法的一個例子,它是一種字符數(shù)組,并且以空字符 null 作為結(jié)束的標(biāo)記。處理 C 風(fēng)格字符串的程序就是使用這個標(biāo)記停止數(shù)組元素的處理。
第二種方法是傳遞指向數(shù)組第一個和最后一個元素的下一個位置的指針。如:
void printValues(const int *beg, const int *end){while (beg != end) {cout << *beg++ << endl;}}int main(){int j[2] = {0, 1};// ok: j is converted to pointer to 0th element in j// j + 2 refers one past the end of jprintValues(j, j + 2);return 0;}第三種方法是將第二個形參定義為表示數(shù)組的大小,這種用法在 C 程序和標(biāo)準(zhǔn)化之前的 C++ 程序中十分普遍。
- main處理命令行選項
傳統(tǒng)上,主函數(shù)的實參是可選的,用來確定程序要執(zhí)行的操作。比如,假設(shè)我們的主函數(shù) main 位于名為 prog 的可執(zhí)行文件中,可如下將實參選項傳遞給程序:
prog -d -o ofile data0這種用法的處理方法實際上是在主函數(shù) main 中定義了兩個形參:
int main(int argc, char *argv[]) { ... }第二個形參 argv 是一個 C 風(fēng)格字符串?dāng)?shù)組。第一個形參 argc 則用于傳遞該數(shù)組中字符串的個數(shù)。由于第二個參數(shù)是一個數(shù)組,主函數(shù) main 也可以這樣定義:
int main(int argc, char **argv) { ... }表示 argv 是指向 char* 的指針。//形參是char*,其原型可以是char類型的數(shù)組,argv是char類型數(shù)組的指針,那么原型可以是char類型數(shù)組的數(shù)組。簡化思考,可以發(fā)現(xiàn),參數(shù)中有幾個“*”,傳遞的就可以是幾維數(shù)組。
以前面的命令行為例,argc 應(yīng)設(shè)為 5,argv 會保存下面幾個 C 風(fēng)格字符串:
argv[0] = "prog";argv[1] = "-d";argv[2] = "-o";argv[3] = "ofile";argv[4] = "data0";- 含有可變形參的函數(shù)
在無法列舉出傳遞給函數(shù)的所有實參的類型和數(shù)目時,可以使用省略符形參。省略符暫停了類型檢查機制。它們的出現(xiàn)告知編譯器,當(dāng)調(diào)用函數(shù)時,可以有 0 或多個實參,而實參的類型未知。省略符形參有下列兩種形式:
void foo(parm_list, ...);void foo(...);第一種形式為特定數(shù)目的形參提供了聲明。在這種情況下,當(dāng)函數(shù)被調(diào)用時,對于與顯示聲明的形參相對應(yīng)的實參進(jìn)行類型檢查,而對于與省略符對應(yīng)的實參則暫停類型檢查。在第一種形式中,形參聲明后面的逗號是可選的。大部分帶有省略符形參的函數(shù)都利用顯式聲明的參數(shù)中的一些信息,來獲取函數(shù)調(diào)用中提供的其他可選實參的類型和數(shù)目。因此帶有省略符的第一種形式的函數(shù)聲明是最常用的。
轉(zhuǎn)載于:https://www.cnblogs.com/predator-wang/p/5195830.html
總結(jié)
以上是生活随笔為你收集整理的关于函数形参的一些讨论的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C# 地磅串口编程
- 下一篇: vivado编译出错 [Synth 8-