## 一.參數
1. **形參** 函數定義時,參數的名稱。
2. **實參** 形參的初始值。第一個實參初始化第一個形參,第二個實參初始化第二個形參,以此類推。盡管實參與形參存在對應關系,但是并沒有規定實參的求值順序。編譯器能以任意可行的順序對實參求值。
## 二.參數傳遞
1. 傳值調用
+ C++中參數傳遞的缺省初始化方法是把實參的值拷貝到參數的存儲區中,此時函數不會訪問當前調用的實參。
+ 數組作參數時,傳遞的是數組第一個元素的地址。
> 數組形參會被編譯器替換成指向數組第一個元素的指針形參。
+ 此時在函數內改變形參的值對實參沒有影響。
2. 傳指針調用
+ 指針使我們可以間接地訪問它所指的對象,通過指針可以修改它所指對象的值。
+ 修改指針的指向對實參無影響。
3. 傳引用調用
+ 當參數是引用時,函數接收的是實參的左值。此時修改參數可以影響實參。
+ 傳引用調用通常用于以下情況:
+ 需要直接修改實參
+ 傳遞一個較大的對象
+ 從函數返回多個值
4. 引用和指針參數的關系
>引用必須被初始化為指向一個對象,一旦初始化了,它就不能再指向其他對象。指針可以指向一系列不同的對象也可以什么都不指向。
>[info]如果一個參數可能在函數中指向不同的對象,或者這個參數可能不指向任何對象,則必
須使用指針參數。
## 三.默認參數
1. 函數調用的實參按位置解析,缺省實參只能用來替換函數調用缺少的尾部實參。
2. 定義/聲明時,默認參數應 **從右向左** 依次定義。
>[danger] [錯了] `int fun(int a = 0,double b,char c)`
>[success] [對的] `int fun(int a,double b,char c = '\n')`
3. 調用函數時, 參數 **從左向右** 匹配。
```c++
#include <iostream>
using namespace std;
int fun(int a = 0,int b = 1,int c = 2,int d = 3)
{
cout<<"a="<<a<<";b="<<b<<";c="<<c<<";d="<<d<<";"<<endl;
return 0;
}
int main()
{
fun(2019);//a=2019
fun(22,33);//a=22 b=33
fun(2019,2020,2021);//a=2019 b=2020 c=2021
fun(2017,2018,2019,2020);//a=2017 b=2018 c=2019 d=2020
}
```
>[test]
>a=2019;b=1;c=2;d=3;
>a=22;b=33;c=2;d=3;
>a=2019;b=2020;c=2021;d=3;
>a=2017;b=2018;c=2019;d=2020;
>Press any key to continue
3. 既有聲明又有定義時,定義中可省略默認參數;只有定義時,默認參數須出現在定義中。
4. 含有普通局部變量的表達式不能作為默認實參。除此之外,只要表達式的類型能轉換成形參所需的類型,該表達式就能作為默認實參。
>[success]默認參數的函數調用在編譯期時就需要確定其值,或者其地址。
>
>+ 常量表達式能夠確定其值。
>+ 靜態變量能確定其地址。
>+ 含有靜態變量的表達式能夠確定其值。
>+ 普通局部變量本身在編譯期值不確定,地址也不確定。
>+ 含有普通局部變量的表達式的值在編譯期無法確定。
>[warning]默認實參表達式的值的計算發生在函數調用的時候。
>[success] [14+] 允許不被求值的局部變量作為函數形參,如 `sizeof(局部變量)`
## [$]四.省略符形參
### 1.省略符形參實現可變參數
1. 定義一個可變參數的函數:在形參列表末尾加上 `...`
```c++
void one_more_args(int size,...)
{
//your code
}
void zero_more_args(...)
{
//your code
}
```
接下來便可以這么調用:
```c++
one_more_args(5,1,2,3,4,5);
zero_more_args(1,2,3);
zero_more_args(1,2,3,4);
```
需要注意的是,如果省略符形參前面沒有固定的參數,那么這些省略號參數是不能正常接收的。
2. 接收可變參數
需要引入 `cstdarg` 或 `stdarg.h` 頭文件。并確保函數至少有一個固定的形參。
```c++
long add(int num,...)
{
long result = 0;
va_list args;//定義一個形參列表
va_start(args, num); //初始化 args 變量,其中 size 是最后一個已知形參
for (int i = 0; i < num; i++)
{
result += va_arg(args, int);//讀取傳入的參數
}
va_end(args); //參數讀取完畢
return result;
}
```
接下來就可以實現無限的數字相加了:
```c++
cout<<add(4,1,5,8,7);
```
>[test]
>21
### 2.省略符形參實現可變參數的淺層原理
函數調用時,參數(除了引用形參)會按照形參表的順序依次入棧,每個實參彼此相鄰(由于編譯器的內存對齊策略,這些實參之間可能會有少量空隙)。這樣,我們就可以通過指針來依次訪問實參。
+ `va_list` 存儲有函數參數列表的信息,理論上是一個字符指針。
+ `va_start` 是一個宏函數。第一個參數為 `va_list` ,第二個參數為最后一個固定參數。根據固定參數的存儲位置和大小,可以算出第一個不定參數的位置,并賦值給第一個參數。
+ `va_arg` 是一個宏函數。第一個參數為 `va_list` ,第二個參數為讀取的類型名稱。根據第一個參數和類型名稱的大小,可以算出下一個參數的內存位置。
+ `va_end` 是一個宏函數。將第一個參數置為空。
### 3.省略符形參的應用
+ `scanf`
+ `printf`
### 4.省略符形參的局限性
1. 對類類型的支持不友好,有時構造函數無法正常調用。
```c++
class MyInt
{
private:
int num_;
public:
MyInt(int num) : num_(num)
{
cout << "構造MyInt,值為 " << num << endl;
}
friend MyInt &operator+=(MyInt &m1, const MyInt &m2);
friend ostream &operator<<(ostream &os, const MyInt &m);
};
MyInt &operator+=(MyInt &m1, const MyInt &m2)
{
m1.num_ = m1.num_ + m2.num_;
return m1;
}
ostream &operator<<(ostream &os, const MyInt &m)
{
return os << m.num_;
}
MyInt add(int num, ...)
{
MyInt result = 0;
va_list args; //定義一個形參列表
va_start(args, num); //初始化 args 變量,其中 size 是最后一個已知形參
for (int i = 0; i < num; i++)
{
result += va_arg(args, MyInt); //讀取傳入的參數
}
va_end(args); //參數讀取完畢
return result;
}
```
相加:
```c++
cout << add(4, 1, 5, 8, 7);
```
>[test]
>構造MyInt,值為 0
>21
從輸出結果來看,這里的四個省略符參數并沒有正常構造。
>[warning] 一般省略符形參應該僅僅用于C和C++通用的類型。
2. 不支持引用形參。因為引用形參不會占用空間。
3. 編譯器不會對省略符形參進行類型檢查。
## \[11+\][$]五.initializer list 形參
`initializer_list` 是一種標準庫模板類,用于表示某種特定類型的值的數組。
它定義在 `initializer_list` 頭文件中。
### 1.用途
+ 實參數量未知
+ 全部實參的類型相同
### 2.基本操作
+ `initializer_list<T> list;` 定義一個`initializer_list`。
+ `initializer_list<T> list{a,b,c,...};` 定義并使用大括號初始化一個`initializer_list`。
+ 和其他對象一樣的拷貝操作
+ `list.size()` 元素的數目。
+ `list.begin()` 返回一個迭代器,指向首元素。
+ `list.end()` 返回一個迭代器,指向末尾元素的下一個元素。
### 3.initializer_list 形參實現不定參數
```c++
long add(initializer_list<int> list)
{
long result = 0;
//通過迭代器訪問元素
for (auto iter = list.begin(); iter != list.end(); iter++)
result += *iter;
return result;
}
```
調用函數:
```c++
//調用時,這些參數需要加上大括號
cout << add({1, 3, 5, 7, 9});
```
>[test]
>25
## \[11+\][$]六.轉發
### 1.概念
+ 把實參傳遞給其他函數
### 2.不完整的轉發
#### 例1
```c++
//用于調用其他函數,其中 f 是一個可調用對象,包括函數指針、函數對象
//這種轉發會丟掉 頂層的const 和引用
template <typename F, typename T1, typename T2>
void call(F f,T1 t1,T2 t2)
{
//調用前的處理
f(t1,t2);
//調用后的處理
}
void f(int v1, int &v2) //注意v2是一個引用
{
v2 += v1;
}
```
再定義下面兩個變量:
```c++
int i = 66;
int j = 600;
```
當我們直接調用 `f`時:
```c++
f(i,j);
cout<<i<<","<<j<<endl;
```
>[test]
>66,666
當我們通過 `call` 間接調用 `f`時:
```c++
call(f,i,j);
cout<<i<<","<<j<<endl;
```
>[test]
>66,600
很顯然,`call` 和直接調用 `f` 的結果是不一樣的。原因在于這兩種方式調用 `f` 時,`v1` 引用的是 `i`;而后者在調用 `call` 時, `i` 會被復制一份到 `t1`, `v1` 引用的是 `t1` 。
#### 例2
為了解決上面的問題,我們調整 `call` 函數:
```c++
//用于調用其他函數,其中 f 是一個可調用對象,包括函數指針、函數對象
//傳入左值時,t1,t2為左值引用;傳入右值時,t1,t2為右值引用。具體請參見 引用
template <typename F, typename T1, typename T2>
void call(F f,T1 &&t1,T2 &&t2)
{
//調用前的處理
f(t1,t2);
//調用后的處理
}
void f(int v1, int &v2) //注意v2是一個左值引用
{
v2 += v1;
}
void f2(int &&v1, int &v2) //注意v2是一個左值引用,v1是一個右值引用
{
v2 += v1;
}
```
這樣,通過 `call` 調用 `f` 和直接調用 `f` 就沒有什么差別了。
但是,`call` 不能調用 `f2` 。因為具名的右值引用是一個左值,而左值是不能作為右值引用的參數的。
### 3.std::forward 實現完美轉發
+ **頭文件** `utility`
+ **功能** 保持給定實參的引用屬性以及頂層的 `const` 屬性。
+ **格式** `std::forward<參數的類型>(參數名)`
#### 例3
```c++
template <typename F, typename T1, typename T2>
void call(F f, T1 t1, T2 t2)
{
//調用前的處理
f(forward<T1>(t1), forward<T2>(t2));//這里已經使用了 std 命名空間
//調用后的處理
}
```
- 閱讀說明
- 1.1 概述
- C++基礎
- 1.2 變量與常量
- 1.2.1 變量
- 1.2.2 字面值常量
- 字符型常量
- 數值型常量
- 1.2.3 cv限定符
- 1.3 作用域
- 1.3.1 標識符
- 1.3.2 *命名空間
- 1.3.3 作用域
- 1.3.4 可見性
- 1.4 數據類型
- 1.4.1 概述
- 1.4.2 處理類型
- 類型別名
- * auto說明符
- * decltype說明符
- 1.4.3 數組
- 1.4.4 指針
- 1.4.5 引用
- 1.5 表達式
- 1.5.1 概述
- 1.5.2 值的類別
- 1.5.3 *初始化
- 1.5.4 運算符
- 算術運算符
- 邏輯和關系運算符
- 賦值運算符
- 遞增遞減運算符
- 成員訪問運算符
- 位運算符
- 其他運算符
- 1.5.5 *常量表達式
- 1.5.6 類型轉換
- 第2章 面向過程編程
- 2.1 流程語句
- 2.1.1 條件語句
- 2.1.2 循環語句
- 2.1.3 跳轉語句
- 2.1.4 *異常處理
- 2.2 函數
- 2.2.1 概述
- 2.2.2 函數參數
- 2.2.3 內置函數
- 2.2.4 函數重載
- 2.2.5 * 匿名函數
- 2.3 存儲和生命期
- 2.3.1 生命周期與存儲區域
- 2.3.2 動態內存
- 2.4 *預處理命令
- 第3章 面向對象編程
- 3.1 概述
- 3.2 類和對象
- 3.3 成員
- 3.3.1 訪問限制
- 3.3.2 常成員
- 3.3.3 靜態成員
- 3.3.4 成員指針
- 3.3.5 this指針
- 3.4 特殊的成員函數
- 3.4.1 概述
- 3.4.2 構造函數
- 3.4.3 析構函數
- 3.4.4 拷貝語義
- 3.4.5 * 移動語義
- 3.5 友元
- 3.6 運算符重載與類型轉換
- 3.6.1 概述
- 3.6.2 重載方法
- 3.6.3 類型轉換
- 3.7 繼承與多態性
- 3.7.1 概述
- 3.7.2 派生類
- 3.7.3 子類型
- 3.7.4 虛基類
- 3.7.5 虛函數
- 3.7.6 抽象類
- 3.8 模板與泛型
- 3.8.1 概述
- 3.8.2 模板類型
- 3.8.3 *模板參數
- 3.8.4 *模板編譯
- 3.8.5 *模板推斷
- 3.8.6 *實例化與特例化
- 第4章 C++標準庫
- 4.1 概述
- 4.2 輸入輸出流
- 4.2.1 概述
- 4.2.2 *流的狀態
- 4.2.3 *常用流
- 4.2.4 *格式化I/O
- 4.2.5 *低級I/O
- 4.2.6 *隨機訪問
- 4.3 *C輸入輸出
- 4.3.1 *字符輸入輸出
- 4.3.2 *格式化輸入輸出
- 4.4 * 容器
- 4.4.1 * 概述
- 4.4.2 * 基本操作
- 4.4.3 * 順序容器
- 4.4.4 * 迭代器
- 4.4.5 * 容器適配器
- 4.5 * 泛型算法
- 4.6 * 內存管理
- 4.6.1 * 自動指針
- 4.7 * 其他設施