本篇要學習的內容和知識結構概覽 函數的參數及其傳遞方式 1. 函數參數傳遞方式 傳值: 傳變數值: 將實參記憶體中的內容拷貝一份給形參, 兩者是不同的兩塊記憶體 傳地址值: 將實參所對應的記憶體空間的地址值給形參, 形參是一個指針, 指向實參所對應的記憶體空間 傳引用: 形參是對實參的引用, 形參和實參是同 ...
本篇要學習的內容和知識結構概覽
函數的參數及其傳遞方式
1. 函數參數傳遞方式
傳值:
傳變數值: 將實參記憶體中的內容拷貝一份給形參, 兩者是不同的兩塊記憶體
傳地址值: 將實參所對應的記憶體空間的地址值給形參, 形參是一個指針, 指向實參所對應的記憶體空間
傳引用:
形參是對實參的引用, 形參和實參是同一塊記憶體空間
2. 對象作為函數參數, 也就是傳變數值
將實參對象的值傳遞給形參對象, 形參是實參的備份, 當在函數中改變形參的值時, 改變的是這個備份中的值, 不影響原來的值
像這樣:
void fakeSwapAB(int x , int y) { int temp = x; x = y; y = temp; } int a = 5; int b = 8; cout << "交換前: " << a << ", " << b << endl; // 傳變數值 fakeSwapAB(a, b); cout << "交換後: " << a << ", " << b << endl;
3. 對象指針作為函數參數, 也就是傳地址值
形參是對象指針, 實參是對象的地址值, 雖然參數傳遞方式仍然是傳值方式, 因為形參和實參的地址值一樣, 所以它們都指向同一塊記憶體, 我們通過指針更改所指向的記憶體中的內容, 所以當在函數中通過形參改變記憶體中的值時, 改變的就是原來實參的值
像這樣:
void realSwapAB(int * p, int * q) { int temp = *p; *p = *q; *q = temp; } int a = 5; int b = 8; cout << "交換前: " << a << ", " << b << endl; // 傳地址值 realSwapAB(&a, &b); cout << "交換後: " << a << ", " << b << endl;
對於數組, 因數組名就是代表的數組首地址, 所以數組也能用傳數組地址值的方式
void swapArrFirstAndSecond(int a[]) { int temp = a[0]; a[0] = a[1]; a[1] = temp; } int main(int argc, const char * argv[]) { int a[] = {2, 3}; cout << "交換前: " << a[0] << ", " << a[1] << endl; swapArrFirstAndSecond(a); cout << "交換後: " << a[0] << ", " << a[1] << endl; return 0; }
4. 引用作為函數參數, 也就是傳地址(註意: 這裡不是地址值)
在函數調用時, 實參對象名傳給形參對象名, 形參對象名就成為實參對象名的別名. 實參對象和形參對象代表同一個對象, 所以改變形參對象的值就是改變實參對象的值
像這樣:
void citeSwapAB(int & x, int & y) { int temp = x; x = y; y = temp; } int a = 5; int b = 8; cout << "交換前: " << a << ", " << b << endl; // 傳引用 citeSwapAB(a, b); cout << "交換後: " << a << ", " << b << endl;
優點: 引用對象不是一個獨立的對象,不單獨占記憶體單元, 而對象指針要另外開闢記憶體單元(記憶體中放實參傳過來的地址),所以傳引用比傳指針更好用。
5. 預設參數
不要求程式在調用時必須設定該參數, 而由編譯器在需要時給該參數賦預設值.
規則1:當程式需要傳遞特定值時需要顯式的指明. 預設參數必須在函數原型中說明.
如果函數在main函數後面定義, 而在聲明中設置預設參數, 在定義中不需要設置預設參數
像這樣:
// 在main函數前聲明函數, 並設置預設參數 void PrintValue(int a, int b = 0, int c = 0); int main(int argc, const char * argv[]) { // 調用函數 PrintValue(5); return 0; } // 在main函數後定義函數, 不需要設置預設參數 void PrintValue(int a, int b, int c) { cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "c = " << c << endl; }
如果函數在main函數前面定義, 則在定義中設置預設參數
像這樣:
// 在main前定義函數, 需要設置預設參數 void PrintValue(int a, int b = 0, int c = 0) { cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "c = " << c << endl; } int main(int argc, const char * argv[]) { // 調用函數 PrintValue(5); return 0; }
規則2:預設參數可以多於一個,但必須放在參數序列的後部。
像這樣:
可以有一個預設參數:void PrintValue(int a, int b, int c = 0);
可以是有多個預設參數:void PrintValue(int a, int b = 0, int c = 0);
不可以在中間設置預設參數:void PrintValue(int a, int b = 0, int c);
規則3:如果一個預設參數需要指定一個特定值時,則在此之前的所有參數都必須賦值
// 調用函數 第一種: 三個參數全部有特定值 PrintValue(5, 8, 9); // 調用函數 第二種: 我們給第二個參數設特定值, 它前面所有參數必須賦值, 所以可以 PrintValue(5, 8); /* 調用函數 第三種: 當一個預設參數有特定值時, 它前面所有的參數都必須賦值, 我們給第三個預設參數設特定值 也就是說第一, 二個參數也必須賦值 所以不可以 */ // PrintValue(5, , 9);
6. 使用const保護數據
用const修飾要傳遞的參數, 該函數只能使用參數, 而無權修改參數, 以提高系統的自身安全.
像這樣:
// 拼接字元串的函數 void catStr(const string str) { string str2 = str + " Ray!"; // 函數內部不能修改const修飾的形參, 所以不能這麼使用 // str = "Hi"; cout << str2 << endl; } int main(int argc, const char * argv[]) { // 實例化一個字元串 string str = "Hello"; // 調用函數 catStr(str); return 0; }
函數返回值
C++函數返回值類型可以是除數組和函數以外的任何類型
當返回值是指針或引用對象時, 需要註意函數返回值所指的對象必須存在, 因此不能將函數內部的局部對象作為函數返回值, 因為函數內, 局部變數或者對象在函數運行完畢後記憶體就釋放啦
1. 返回引用的函數
函數可以返回一個引用, 目的是為了讓該函數位於賦值運算符的左邊
格式: 數據類型 & 函數名(參數列表);
像這樣:
// 全局數組 int arr[] = {2, 4, 6, 8}; // 獲得數組下標元素 int & getValueAtIndex(int i) { return arr[i]; } int main(int argc, const char * argv[]) { cout << "更改前: " << arr[2] << endl; // 調用函數, 並且用於計算或者重新賦值 getValueAtIndex(2) = 10; cout << "更改後: " << arr[2] << endl; return 0; }
2. 返回指針的函數
返回值是存儲某種數據類型數據的記憶體地址, 這種函數稱為指針函數
格式: 數據類型 * 函數名(參數列表);
像這樣:
// 返回指針的函數 int * getData(int n) { // 根據形參, 申請記憶體空間 int * p = new int[n]; // 給申請下來的記憶體空間賦值 for (int i = 0; i < n; i++) { p[i] = i + 10; } // 返回這段記憶體空間的首地址 return p; } int main(int argc, const char * argv[]) { // 調用函數, 並接收返回值, 不要忘記釋放函數中分配的記憶體 int * p = getData(5); // 列印指針所指向的記憶體中的內容 for (int i = 0; i < 5; i++) { cout << p[i] << endl; } return 0; }
3. 返回對象的函數
格式: 數據類型 函數名(參數列表);
像這樣:
// 返回對象的函數 string sayHello(string s) { // 我們拼接好一個字元串, 給str string str = "Hello " + s; // 並把str這個對象返回 return str; } int main(int argc, const char * argv[]) { // 調用函數, 接收函數返回的對象 string str = sayHello("Ray"); cout << str << endl; return 0; }
4. 函數返回值作為函數參數
如果函數返回值作為另一個函數的參數, 那麼這個返回值必須與另一個函數的參數類型一致
像這樣:
// 求最大值的函數 int getMax(int x, int y) { return x > y ? x : y; } int main(int argc, const char * argv[]) { // 先求8, 9返回最大值; 返回值再跟5比較, 返回最大值 int maxValue = getMax(5, getMax(8, 9)); cout << maxValue << endl; return 0; }
內聯函數
1. 內聯函數的概念
使用關鍵字inline聲明的函數稱為內聯函數, 內聯函數必須在程式中第一次調用此函數的語句出現之前定義, 這樣編譯器才知道內聯函數的函數休, 然後進行替換
像這樣:
// 判斷輸入的字元是否為數字 inline bool isNumber(char c) { if (c >= '0' && c <= '9') { return true; } else { return false; } } int main(int argc, const char * argv[]) { // 聲明字元c char c; // 從鍵盤輸入字元 cin >> c; // 進行判斷, 這裡的isNumber(c), 在程式編程期間就會被isNumber()函數體所替換, 跟巨集一樣一樣的 // 如果函數體特別大, 替換的地方特別多, 就增加了代碼量 if (isNumber(c)) { cout << "輸入了一個數字" << endl; } else { cout << "輸入的不是一個數字" << endl; } return 0; }
2. 註意
在C++中, 除具有迴圈語句, switch語句的函數不能說明為內聯函數外, 其它函數都可以說明為內聯函數.
3. 作用
使用內聯函數可以提高程式執行速度, 但如果函數體語句多, 則會增加程式代碼量.
函數重載和預設參數
1. 函數重載
一個函數名具有多種功能, 具有多種形態, 稱這種我為多態性, 一個名字, 多個函數
函數重載要滿足的條件:
參數類型不同或者參數個數不同
像這樣:
// 求和的函數 2兩個整型參數 int sumWithValue(int x, int y) { return x + y; } // 求和的函數 3兩個整型參數 int sumWithValue(int x, int y, int z) { return x + y + z; } // 求和的函數 2個浮點型參數 double sumWithValue(double x, double y) { return x + y; } // 求和的函數 3個浮點型參數 double sumWithValue(double x, double y, double z) { return x + y + z; } int main(int argc, const char * argv[]) { // 兩個整型變數求和 int sumValue1 = sumWithValue(8, 9); // 三個整型變數求和 int sumValue2 = sumWithValue(8, 9, 10); // 兩個浮點型變數求和 double sumValue3 = sumWithValue(1.2, 2.3); // 三個浮點型變數求和 double sumValue4 = sumWithValue(1.2, 2.3, 3.4); cout << sumValue1 << endl; cout << sumValue2 << endl; cout << sumValue3 << endl; cout << sumValue4 << endl; return 0; }
2. 函數重載與預設參數
當函數重載與預設參數相結合時, 能夠有效減少函數個數及形態, 縮減代碼規模.
這樣我們每種數據類型只保留一個函數即可完成我們的功能, 直接少了兩個函數.
像這樣:
// 整型參數求和 int sumWithValue(int x = 0, int y = 0, int z = 0) { return x + y + z; } // 浮點型參數求和 double sumWithValue(double x = 0, double y = 0, double z = 0) { return x + y + z; } int main(int argc, const char * argv[]) { // 兩個整型變數求和 int sumValue1 = sumWithValue(8, 9); // 三個整型變數求和 int sumValue2 = sumWithValue(8, 9, 10); // 兩個浮點型變數求和 double sumValue3 = sumWithValue(1.2, 2.3); // 三個浮點型變數求和 double sumValue4 = sumWithValue(1.2, 2.3, 3.4); cout << sumValue1 << endl; cout << sumValue2 << endl; cout << sumValue3 << endl; cout << sumValue4 << endl; return 0; }
如果使用預設參數, 就不能對參數個數少於預設個數的函數形態進行重載, 只能對於多於預設參數個數的函數形態進行重載.
像這樣:
// 求和的參數, 並且使用預設參數, 最多三個整型參數求和 int sumWithValue(int x = 0, int y = 0, int z = 0) { return x + y + z; } // 像這樣是不行的, 不能對參數個數少於預設個數的函數形態進行重載 //int sumWithValue(int x, int y) { // return x + y; //} // 像這樣是可以的, 當調用時傳入4個整型參數時就會調用該參數 int sumWithValue(int x, int y, int z, int t) { return x + y + z + t; } int main(int argc, const char * argv[]) { // 求和, 只給兩個特定值 int sumValue1 = sumWithValue(8, 9); // 求和, 給三個特定值 int sumValue2 = sumWithValue(8, 9, 10); // 求和, 有4個整型參數 int sumValue3 = sumWithValue(8, 9, 10, 11); cout << sumValue1 << endl; cout << sumValue2 << endl; cout << sumValue3 << endl; return 0; }
函數模板
從而上面可以看出, 它們是邏輯功能完全一樣的函數, 所提供的函數體也一樣, 區別僅僅是數據類型不同, 為了統一的處理它們, 引入了函數模板.
現在我們的函數從4個縮減成一個, 但是我們的功能沒有減少, 反而增加了. 比如我們可以計算char, float類型
1. 什麼是函數模板
在程式設計時沒有使用實際存在的類型, 而是使用虛擬的參數參數, 故其靈活性得到加強.
當用實際的類型來實例化這種函數時, 就好像按照模板來製造新的函數一樣, 所以稱為函數模板
格式: 一般用T來標識類型參數, 也可以用其它的
Template <class T>
像這樣:
// 定義模板 template <class T> // 定義函數模板 T sumWithValue(T x, T y) { return x + y; } int main(int argc, const char * argv[]) { // 調用模板函數 int sumValue1 = sumWithValue(3, 5); // 調用模板函數 double sumValue2 = sumWithValue(3.2, 5.1); cout << sumValue1 << endl; cout << sumValue2 << endl; return 0; }
當用用函數模板與具體的數據類型連用時, 就產生了模板函數, 又稱為函數模板實例化
2. 函數模板的參數
函數模板名<模板參數>(參數列表);
我們可以將參數列表的數據強制轉換為指定的數據類型
像這樣:int sumValue2 = sumWithValue<int>(3.2, 5.1);
我們將參數列表裡的數據強制轉換為int類型, 再參與計算
也可以樣:double sumValue2 = sumWithValue(3.2, (double)5);
我們也可以將參數列表裡的單個參數進行強制類型轉換, 再參與計算
不過我們一般不會加上模板參數.
3. 使用關鍵字typename
用途就是代替template參數列表中的關鍵字class
像這樣
template <typename T>
只是將class替換為typename, 其它一樣使用.
強烈建議大家使用typename, 因為它就是為模板服務的, 而class是在typename出現之前使用的, 它還有定義類的作用, 不直觀, 也會在一些其它地方編譯時報錯.
總結:
可能對於初學者來說, 函數有點不是很好理解, 包括我當初也是, 不要想得過於複雜, 其實它就是一段有特定功能的代碼, 只不過我們給這段代碼起了個名字而已, 這樣就會提高代碼的可讀性和易維護性。
自學C/C++編程難度很大,不妨和一些志同道合的小伙伴一起學習成長!
C語言C++編程學習交流圈子,【點擊進入】微信公眾號:C語言編程學習基地
有一些源碼和資料分享,歡迎轉行也學習編程的伙伴,和大家一起交流成長會比自己琢磨更快哦!