傳值參數
首先你肯定明白一個道理:當初始化一個非引用類型的變量時,初始值被拷貝給變量,此時對變量的改動不會涌向初始值
int n = 0;
int i = 1; // i是n的副本
i = 42; // i的值改變,n的值不改變
傳值參數的機理完全一樣,由於每次調用函數時都會重新創建它的形參,並用傳入的實參對形參進行初始化,所以函數對形參做的所有操作不會影響實參,如果我們想讓函數改變我們傳入的實參本身們就可以用到指針形參訪問函數外部對象
指針形參
先看一段代碼:
int n = 0;
int i = 1;
int *p1 = &n; //p1指向n
int *p2 = &i; //p2指向i
*p1 = 42; //n的值改變,p1不變
p1 = p2 //現在p1指向了i,但是i和n的值都不變
當執行指針拷貝操作時,拷貝的是指針的值(地址),拷貝之后,兩個指針是不同的指針,因為指針可以使我們間接地訪問所指向的對象,所以通過指針可以修改對象的值
指針形參也差不都
// 接受一個int類型的指針,然后將指針所指向的對象置0
void reset(int* ip)
{
*ip = 0; // 改變指針ip所指向對象的值
ip = 0; // 只改變了ip的局部拷貝,實參未被改變
}
void reset(int i)
{
i = 0;
}
int main()
{
int i = 42;
reset(i); // 改變i的值而非i的地址
cout << "i = " << i << endl;
reset(&i); // 改變i的值而非i的地址
cout << "i = " << i << endl;
}
調用這個reset函數之后,實參所指向的對象被置為0,但是實參本身並沒有改變,輸出如下
i = 42
i = 0
熟悉C語言的程序員可能經常使用指針形參訪問函數外部的對象,但在C++中建議使用引用類型的形參代替指針
傳引用參數
我們知道對於引用的操作實際上是作用在所引用的對象上的:
int n = 0;
int i = 1;
int &r = n; // r綁定了n
r = 42; // 現在n=42
r = i; // 現在n的值和i相同=1
i = r; // i的值和n相同
引用形參與之類似,通過引用形參,允許函數改變實參的值
// 接受一個int對象的引用,然后將對象的值置為0
void reset(int& i)
{
i = 0; //改變了i所引對象的值
}
當調用這一版本的reset時,直接傳入對象而無須傳遞地址,被改變的對象是傳入reset的實參
void reset(int& j)
{
j = 0; //改變了i所引對象的值
}
int main()
{
int j = 42;
reset(j);
cout << "j = " << j << endl;
}
輸出:
j = 0
避免拷貝
舉個例子,我們准備編寫一個函數比較兩個string對象的長度,對象可能會非常長,所以應該盡量避免直接拷貝它們,這時候引用形參就可以避免拷貝
因為無需改變對象內容,所以把形參定義為對常量的引用
bool isShoter(const string &s1, const string &s2)
{
return s1.size() < s2.size();
}
const形參和實參
先簡單回顧下const
const int ci = 42; //頂層const,不能改變ci
int i = ci; //√ 拷貝時候忽略了ci的頂層
int* const p = &i; //頂層const,不能給p賦值(改變p指向的對象)
*p = 0; //√ 可以通過p改變對象內容,i = 0
關於const的詳解:https://www.cnblogs.com/zhxmdefj/p/11524403.html
一樣,形參初始化時會忽略掉頂層const
void fucn(const int i) {
//fucn能讀i,但不能向i寫值
}
//調用fucn時,既可以傳入const int,也可以傳入int
但因此也要注意一個問題
void fucn(const int i) {
//fucn能讀i,但不能向i寫值
}
void fucn(int i) {}
因為頂層const被忽略掉了,所以上面兩個fucn的參數可以完全一樣,因此第二個fucn會報錯
指針或引用形參與const
形參初始化方式和變量初始化方式是一樣的,先回顧下初始化規則:
int i = 42;
const int* cp = &i; //√ cp不能改變i
const int& r = i; //√ r能改變i
const int& r2 = 42; //√
int* p = cp; //× p的類型和cp不匹配
int& r3 = r; //× r3的類型和r不匹配
int& r4 = 42; //× 不能用字面值初始化非常量引用
具體解析:https://www.cnblogs.com/zhxmdefj/p/11524403.html
將同樣的規則應用到參數傳遞:
void reset(int* ip)
{
*ip = 0; // 改變指針ip所指向對象的值
ip = 0; // 只改變了ip的局部拷貝,實參未被改變
}
void reset(int& j)
{
j = 0; //改變了i所引對象的值
}
int main()
{
int i = 0;
const int ci = i;
string::size_type ctr = 0;
reset(&i); //√ 調用void reset(int* ip)
reset(&ci); //× 不能用指向const int對象的指針初始化int*
reset(i); //√ 調用void reset(int& j)
reset(ci); //× 不能把普通引用綁定到const對象ci上
reset(42); //× 不能把普通引用綁定到字面值上
reset(ctr); //× ctr是無符號類型,類型不匹配
}
盡量使用常量引用
把函數不會改變的形參定義成普通引用,往往會給函數調用者一誤導:函數可以修改它實參的值
同時還會極大地限制函數所能接受的實參類型(比如上面的reset不能接受const)
數組形參
數組的兩個特性:1.不允許拷貝數組 2.使用數組時會將其轉換為指針
因此,我們不能以值傳遞的方式使用數組,並且傳遞數組時,實際上傳遞的是指向首元素的指針
//等價
void print(const int*);
void print(const int[]);
void print(const int[10]);
上面三個函數是等價的,每個函數的唯一形參都是const int*類型
void print(const int*);
int main()
{
int i = 0;
int j[2] = { 0,1 };
print(&i); //√ &i的類型是int*
print(j); //√ j轉換成int*指向j[0]
}
管理數組實參
使用標記指定數組長度
(C風格字符串存儲在字符數組,最后一個字符后面跟着一個空字符)
//輸出C風格字符串
void print(const char* cp) {
if (cp) //若cp不是一個空指針
while (*cp) //只要指針所指的字符不是空字符
cout << *cp++; //輸出當前字符,並向后移動一個位置
}
這種方法適用於有明顯結束標記的情況
使用標准庫規范
傳遞指向數組首元素和尾后元素的指針
//輸出beg到end之間(不含end)所有元素
void print(const int* beg, const int* end) {
while (beg != end)
cout << *beg++ << endl; //輸出當前元素,並向后移動一個位置
}
顯示傳遞一個表示數組大小的實參
C程序和古老的C++程序員常常使用這種技法
void print(const int* ia, size_t size) {
for (size_t i = 0; i != size; ++i)
cout << ia[i] << endl;
}
P.S 關於數組形參和const:當函數不需要對數組元素執行寫操作時,數組形參應該是指向const的指針
數組引用形參
C++允許將變量定義成數組的引用,所以形參也可以是數組的引用
void print(int(&arr)[10]) {
for (auto elem : arr)
cout << elem << endl;
}
注意(&arr)兩端括號必不可少
f(int &arr[10]) //arr是有10個整形引用的數組
f(int (&arr)[10]) //arr是有10個整數的整形數組的引用
傳遞多維數組
多維數組其實就是數組的數組,所以首元素本身就是一個數組,陣陣就是一個指向數組的指針
//martrix指向數組的首元素,該數組的元素是由10個整數構成的
void print(int(*martrix)[10], int rowSize) {
}
(*martrix)兩端括號必不可少
//等價定義
void print(int martrix[][10], int rowSize) {
}