C++基本知識 —— 缺省參數·函數重載·引用
- 1. 缺省參數
- 2. 函數重載
- 3. 引用
- 3.1 引用的基礎知識
- 3.2 引用的作用
- 3.3 const 引用
- 3.4 指針與引用的關系
1. 缺省參數
什么是缺省參數?缺省參數是聲明或定義函數時為函數的參數指定一個缺省值。在調用該函數的時候,如果沒有指定實參則采用該形參的缺省值(默認值),否則使用指定的實參。缺省參數也就做默認參數,缺省值一般都是字面量常量,也可以是全局變量;傳參時實參只能從左向右傳,且若傳參時,只傳了一個實參,那么函數的參數列中的第一個形參的值就是這個實參的值;缺省參數分為全缺省和半缺省參數。全缺省就是全部形參使用的都是缺省值。下面用具體的代碼來解釋上述概念:
// 這就是一個全缺省函數
void Func(int x = 10, int y = 20, int z = 30)
{cout << "x = " << x << "\ty = " << y << "\tz = " << z << endl;
}int main()
{//不傳參 x y z 使用的都是缺省值Func(); //只傳一個實參,那么 x 會使用指定的實參,y z 使用的是缺省值Func(1);//傳兩個實參,那么 x y 會使用指定的實參,z 使用的是缺省值Func(1, 2);//全部都傳實參,那么 x y z 都會使用指定的實參Func(1, 2, 3);return 0;
}
運行結果:
C++規定在傳實參時,不能間隔跳躍傳參,只能從左到右連續的傳,如下所示:
// 這樣傳參是錯誤的
Func(1, , 3);
Func(, 2, 3);
半缺省就是一部分的形參使用的是缺省值,且半缺省參數的缺省值得要從右往左給,不能從左往右給,也不能跳躍給缺省值,如下所示:
//半缺省函數
void Func(int x, int y = 20, int z = 30)
{cout << "x = " << x << "\ty = " << y << "\tz = " << z << endl;
}void Func(int x, int y, int z = 30)
{cout << "x = " << x << "\ty = " << y << "\tz = " << z << endl;
}
為什么必須要從右往左給形參缺省值呢?為什么不能從左往右給缺省值呢?具體原因如下所示:
void Func(int x = 10, int y = 20, int z)
{cout << "x = " << x << "\ty = " << y << "\tz = " << z << endl;
}int main()
{Func(1, 2);return 0;
}
當如上圖所示傳實參時:調用Func函數時,會產生歧義,不知道實參1和實參2的值是傳給哪個形參,很明顯實參2傳遞給形參 z 的,而實參1的值是傳遞哪個形參呢?是 x 還是 y ,這是不明確的,而從右往左給缺省值就不會存在這樣的歧義:
void Func(int x, int y = 20, int z = 30)
{cout << "x = " << x << "\ty = " << y << "\tz = " << z << endl;
}int main()
{Func(1, 2);return 0;
}
第一個實參一定是傳給第一個形參,第二個實參一定是傳給第二個形參,不會存在什么歧義問題。若是傳一個實參,那么這個實參一定是傳給函數參數列表中的第一個形參;若是傳兩個實參,那么這兩個實參一定是傳給函數的參數列表中的前兩個形參。
當函數為半缺省函數時,沒有缺省值的形參必須要有與之相對應的實參,也就是說,至少要傳一個實參。
函數的聲明與定義分離時,缺省參數不能在函數聲明和定義中同時出現,規定必須函數聲明給缺省值,缺省值不僅僅只能是具體的值,還可以是表達式。如下所示:
聲明:
定義:
2. 函數重載
什么是函數重載?函數重載指的是:一個函數名可以有多重的意義。C++中支持在同一作用域中出現同名函數,但是要求這些同名函數的形參不同,而C語言不支持。
int Add(int x, int y)
{return x + y;
}double Add(double x, double y)
{return x + y;
}short Add(short x, short y)
{return x + y;
}//……………………
這里的參數不同可以是參數的數據類型不同,參數的個數不同,參數的順序不同,如下所示:
//以下兩個 Add 函數構成函數重載 —— 參數類型不同
int Add(int x, int y)
{return x + y;
}double Add(double x, double y)
{return x + y;
}//以下兩個 Add 函數構成函數重載 —— 參數個數不同
short Add(short x, short y)
{return x + y;
}short Add(short x, short y, short z)
{return x + y + z;
}//以下兩個 Add 函數構成函數重載 —— 參數順序不同
long Add(int x, long y)
{return x + y;
}int Add(long x, int y)
{return x + y;
}
但是有時構成重載的兩個函數在被調用時會產生歧義,如下代碼所示:
//以下兩個 Func 函數構成函數重載 —— 參數的個數不同
void Func()
{cout << "Func( )" << endl;
}void Func(int n = 4)
{cout << "Func( int n )" << endl;
}
若是傳參調用 Func 函數,調用的一定是第二個 Func 函數;若是不傳參調用,那會調用哪個函數?其實這兩個函數都會調用,因為無參的函數既可以調用本就是無參的函數,也可以調用全缺省函數。這時代碼就會報錯,解決方法是不要將這兩個函數同時寫,用第二個函數去替代。
注意:函數的返回值類型不同不能作為函數重載的條件,因為返回值可以不被接收。
3. 引用
3.1 引用的基礎知識
引用的概念:引用不是新定義一個變量,而是給已存在的變量取了一個別名。引用的定義:類型& 引用別名 = 引用對象。如下所示:
// ra 就是 a 的別名
int a = 10;
int& ra = a;
編譯器不會為引用變量開辟內存空間,引用變量和引用對象共用同一塊內存空間:
由此圖就可以得知引用變量與引用對象指向的是同一塊內存空間,ra 和 a 的地址都是一樣的,且變量的值也是一樣的。
對引用變量操作還會改變其引用對象,如下所示:
ra 和 a 的值都被改變了。
此外也可以對同一個變量取多個別名,也可以對別名取別名,如下所示:
它們的值是一樣的,地址也是一樣的。
在引用中尤其要注意以下的代碼:
int x = 20;
int& rx = x;
int y = 30;
rx = y;
請問這里的 rx = y 是在給 y 取別名嗎?而 y 的別名是 rx 嗎?其實并不是,這里只是將 y 的值賦值給了 rx ,修改了 rx 和 x 的值,來看一下調試窗口:
這里就說明了引用不會改變指向,只要它是一個變量的引用,那么它永遠都是那個變量的別名,沒有語法能改變引用的指向。C++為了區分引用與取地址,只有&跟在類型的后面才稱之為引用,單獨一個&是取地址操作符,對變量執行取地址操作。
根據上述的分析,總結出引用的特性:
1. 引用在定義時必須初始化
2. 一個變量可以有多個引用
3. 引用一旦引用一個實體,就不能再引用其它實體了
3.2 引用的作用
在C語言中,若想要交換兩個變量的值,需要將這兩個變量的地址傳給形參,如此才能完成兩個變量值的交換;但是在C++中,可以使用引用,如下所示:
// rx 是 x 的別名,ry 是 y 的別名
void Swap(int& rx, int& ry)
{int tmp = rx;rx = ry;ry = tmp;
}int main()
{int x = 10;int y = 20;cout << "交換前:x = " << x << "\ty = " << y << endl;Swap(x, y);cout << "交換后:x = " << x << "\ty = " << y << endl;return 0;
}
運行結果:
由此可以得出引用的功能:做函數的參數,修改形參影響實參。
在C語言中,使用傳值傳參時,若實參的內存很大,那么往往會改為傳址傳參,因為這里會涉及拷貝問題;但是在C++中,可以使用引用,如下所示:
struct Test
{int arr[100000];int size = 0;int capacity = 0;
};// rtst 是 tst 的別名
void Func(struct Test& rtst)
{}int main()
{Test tst;Func(tst);return 0;
}
由此可以得出引用的功能:做函數的參數,減少拷貝,提高效率。
既然可以給整型變量取別名,那可不可以給指針變量取別名呢?當然可以。在C語言中,若想交換兩個指針的內容,可以將指針的地址傳給形參,也就是使用二級指針;在C++中,可以使用引用來解決這個問題:
void Swap(int*& rpx, int*& rpy)
{int* tmp = rpx;rpx = rpy;rpy = tmp;
}int main()
{int x = 10;int y = 20;int* px = &x;int* py = &y;cout << "交換前:px = " << px << "\tpy = " << py << endl;Swap(px, py);cout << "交換后:px = " << px << "\tpy = " << py << endl;return 0;
}
運行結果:
若想要修改函數的返回值該怎么做呢?可以使用傳引用返回,那么為什么不能使用傳值返回呢?首先得要理解一個概念,只要是傳值返回,返回的都不是原來的值,都會拷貝生成一個臨時變量,返回的是這個臨時變量,下面來介紹具體原因:
由此可以得出引用功能:引用作為函數的返回值,如此就可以修改返回對象;當函數的返回值的內存較大時,用引用作為函數的返回值,可以減少拷貝,提高效率。
雖然引用非常的強大,但是并不是所有的函數都要用引用作為返回值,若所有的函數的返回值都用引用,那么會造成意想不到的后果,如下代碼所示:
跟C語言中學的野指針一樣,這里類似于野指針,由于是引用,可以稱之為野引用。
當返回的對象出作用域后未被銷毀,那么可以使用傳引用返回;若返回的對象出作用域后被銷毀了,則不可以使用傳引用返回,應該使用傳值返回。
從函數棧幀的角度去考慮:
所以并不是所有的函數都要用引用作為返回值,當函數變量為局部變量的時候,出了作用域該變量就銷毀了,此時返回該局部變量的別名是一個危險的行為。
3.3 const 引用
1. 若想要引用一個const 對象,那么就必須要用使用 const 引用
int main()
{int x = 10;int& rx = x;const int y = 20;const int& ry = y;return 0;
}
若這里引用 const 修飾的對象時,并沒有使用 const 引用,那么程序會報錯,這是因為這里涉及到了權限的放大 —— 原本變量 y 被 const 修飾后不能被修改,現在對 y 取別名后,就可以通過修改別名來修改變量 y ,但是 y 本身是不能被修改的,別名卻可以修改 y ,這樣是不可取的。權限是不能放大的。const 引用也可以引用普通對象,因為普通對象的訪問權限在引用過程中可以縮小,但是不能放大。如下所示:
int main()
{int x = 10;int& rx = x;const int& r = x;int& const p = x;return 0;
}
這里就涉及到了權限的縮小,之前 x 的值是可以修改的,但是現在被 const 引用修飾后,x 的值就不可以修改了,這就是權限的縮小。這里 const 寫在類型的左右邊都可以,但是一般寫在類型的左邊。
接下來根據以下代碼來回答下面的問題:
int a = 1;
int& ra = a;
int b = a;
該代碼涉及權限的放大或縮小嗎?都不涉及,只有指針和引用才涉及權限的放大與縮小,這里只是將變量 a 的值賦值給變量 b 。
2. 要注意的是類似int& rb = a * 4;double c = 3.14;int& rc = c;這樣一些場景下a * 4的結果保存在一個臨時對象中;int& rc = c也是類似的情況,在類型轉換時會將中間值用臨時對象來保存。也就是說,rb與rc引用的都是臨時對象,而C++規定臨時對象具有常性。所以在這里就涉及到了權限的放大,必須要用const引用才行
int main()
{int a = 10;int b = 20;double c = 3.14;const int& r1 = a; //給變量取別名const int& r2 = b; //給變量取別名const int& r3 = 30; //給常量取別名 //將double類型的變量c的整型部分給給臨時對象const int& r4 = c; //當表達式運算完畢后會計算出一個結果,這個結果存儲在臨時對象中//r5引用的是表達式運算的結果,也就是臨時對象const int& r5 = a * 4; return 0;
}
之后在寫函數的參數的時候,就使用引用來作為形參,視情況考慮是否加const。當函數的參數使用const引用后,參數就可以是臨時對象了,使用 const 引用的好處是既可以接收普通變量,也可以接收 const 修飾的變量。如下代碼所示:
int main()
{int a = 10;int b = 20;double c = 3.14;const int& r1 = a; const int& r2 = b; Func(r1);Func(r2);Func(10);Func(c);Func(a * 4);return 0;
}
3.4 指針與引用的關系
引用可以完全替代指針嗎?當然是不可以的。指針有一個引用做不到的事情,就是改變指向。在數據結構中是時常要改變指向的,在這方面引用就無法做到了,必須使用引用。在實踐中指針與引用相輔相承,功能有重疊性,但是各有各的特點,互相是不可替代的:
1. 語法概念上,引用是對一個對象的取別名,不會開辟空間;指針是存儲一個對象的地址,需要開辟空間
2. 引用在定義時必須要初始化,指針可以初始化也可以不初始化,但是建議初始
3. 引用在初始化時引用一個對象后,就不能再引用其它對象了,而指針可以不斷的改變其指向的對象
4. 引用可以直接訪問其指向的對象,而指針需要解引用后,訪問的才是其指向的對象
5. 指針很容易出現空指針和野指針問題,引用很少出現,引用使用起來更加的安全
6. sizeof的含義不同,引用結果為引用類型的大小,但是指針始終是地址空間的所占字節個數(32位機器平臺下是4個字節,64位機器平臺下是8個字節)
底層指針與引用之間的區別:
從指令匯編角度來看,引用是由指針實現的。