每次函数调用时都会重新创建它的形参,并用传入的实参对形参初始化。形参的初始化机理与变量初始化一样。
函数参数的传递方式:
- 引用传递,也称为函数被传引用调用
- 值传递,也称为函数被传值调用
传值参数
当初始化一个非引用类型的变量时,实参拷贝给形参,此时形参的改变并不会影响实参,其机理类似:
int n = 1;
int i = n; //使用n初始化i
i = 2; //此时对于i的改变并不会影响n
指针形参
指针的行为和其它引用的类型一样,当执行指针拷贝时,拷贝的只是指针的值,拷贝之后形参指针和实参指针是两个不同的指针。
指针可以间接的访问它所指的对象,所以可以通过指针修改它所指向的值。
传引用参数
对引用的操作就是对引用所绑定对象的操作,通过引用参数可以使得函数修改实参的值。
使用引用避免拷贝
拷贝大的类类型对象或者容器对象是比较低效的,甚至一些类类型(如IO类型)根本就不支持拷贝操作。当某种类型不支持拷贝操作时,函数只能通过引用形参访问该类的对象。
如果函数无须改变引用形参的值,最好将其声明为常量引用。
使用引用形参返回额外的信息
引用形参为函数一次性返回多个结果提供了一种有效途径。
const 形参与实参
当用实参初始化形参时会忽略掉顶层 const
,当形参有顶层 const
时,传给它常量对象或者非常量对象都是可以的。
void func(const int i)
调用 func
时可以传入 const int
也可以传入 int
。
在C++中允许函数重载,但是函数重载的前提是不同函数的形参列表有明显的区别,因为顶层的 const
被忽略掉,因此只有形参是 const
和非 const
并不能构成函数重载。
指针或引用形参与const
可以使用一个非常量初始化一个顶层const
对象,但是反之则不行,同时一个普通的引用必须用同类型的对象初始化。
void reset(int &i);
void reset(int *i);
int i = 0;
const int ci = i;
string::size_type ctr = 0;
reset(&i); //正确,调用形参为int* 的函数
reset(&ci); //错误,不能使用指向const int 对象的指针初始化int*
reset(i); //正确,调用形参为int& 的函数
reset(ci); //错误,不能把普通引用绑定到const对象ci上
reset(30); //错误,不能把普通引用绑定到字面值上
reset(ctr); //错误,类型不匹配,ctr是无符号类型
C++中允许使用字面值初始化常量引用,所以像 reset(30)
的调用方式,函数的定义形式应该是 void reset(const int &i);
。
尽量使用常量引用
使用非常量引用参数的弊端:
- 把函数不会改变的形参定义成普通引用,会给调用者带来误导,即函数可以修改它的实参值。
- 使用普通引用作函数的形参,不能把
const
对象、字面值传递给普通的引用形参。
数组形参
数组的两个特殊性质
- 不允许拷贝数组。
- 数组使用时会将其转换成指针。
因为不能拷贝数组,所以不能以值传递的方式使用数组参数。
因为数组会转变成指针,所以当为函数传递一个数组时,实际上传递的是指向数组元素的首元素的指针。
传递数组形参
尽管不能够以值传递的方式传递数组,但是可以把形参写成数组的形式:
void print(const int*);
void print(const int[]);
void print(const int[10]); //数组的维度只是起到期望值的作用,实际上并不一定。
三面的三种形式是等价的,每个函数的唯一形式都是 const int*
,编译器处理时,只检查参数是否是 const int*
。
管理数组实参的三种技术
使用标记指定数组长度
管理数组实参的第一种方法是要求数组本身有一个结束标记,使用这种方法的典型示例就是C风格字符串,C风格字符串最后一个字符后面跟着一个空字符,函数处理C风格字符串时遇到空字符停止。
void print(const char *cp)
{
if(cp)
{
while(*cp) //只要指针指向的不是空字符
cout<<*cp<<endl;
}
}
该方法适用于那些有明显结束标记且该标记不会与普通数据混淆的情况。
使用标准库规范
管理数组实参的第二种技术是传递指向数组首元素和尾后元素的指针。
void print(const int *beg, const int *end)
{
while (beg != end)
cout << *beg++ << endl;
}
为了调用这个函数,需要传入两个指针,使用标准库的 begin
,end
函数:
int arr[5] = { 1,2,3,4,5 };
print(begin(arr), end(arr));
显示传递一个表示数组大小的形参
第三种管理数组实参的方法是专门定义一个表示数组大小的形参,在C和过去的C++程序中,这种方法很常见:
void print(const int a[], size_t size)
{
for (size_t i = 0; i != size; ++i)
{
cout << a[i] << endl;
}
}
数组形参和 const
上面的 print
函数都把数组形参定义成了指向 const
的指针,当函数不需要对数组元素执行写操作的时候,数组形参应该定义成指向 const
的指针。只有当函数确实要改变元素的值的时候,才把形参定义成指向非常量的指针。
数组引用形参
C++ 允许将变量定义成数组的引用,基于这个道理,形参也可以是数组的引用,此时,引用形参绑定到对应的实参上,也就是绑定到数组上:
void print(int (&arr)[5])
{
for (auto elem : arr)
cout << elem << endl;
}
注意:
f(int &arr[10]); //arr是引用的数组
f(int (&arr)[10]); //arr是一个具有10个整数的整型数组的引用
另外,这里限定了函数的作用对象,在这里函数只能作用于包含5个元素的数组。
传递多维数组
本质上C++语言并不存在真正的多维数组,所谓的多维数组其实就是数组的数组。
和所有的数组一样,当多维数组传递给函数时,真正传递的是指向数组首元素的指针,因为要处理的是数组的数组,所以首元素本身就是一个数组,指针就是一个指向数组的指针。
void print(int (*matrix)[10],int rowSize);
matrix
声明成指向含有10个整型数组的指针。
注意:
int *matrix[10]; //10个指针构成的数组
int (*matrix)[10]; //指向含有10个整数的数组的指针
当然,此处也可以使用数组的语法定义函数,此时编译器会忽略掉第一个维度:
void print(int matrix[][10],int rowSize);
matrix
看起来像一个二维数组,实际上形参是指向含有10个整数的数组指针。
main:处理命令行选项
int main(int argc,char *argv[]){...}
- 第二个参数
argv
是一个数组,它的元素是指向C风格字符串的指针;argv
的第一个元素是指向程序名字或者一个空字符串。 - 第一个参数
argc
表示数组中字符串的数量。
可变形参的函数
为了编写处理不同数量实参的函数,C++11 提供了两种主要方法:
- 如果所有的实参类型是相同的,则可以传递一个
initializer_list
的标准库类型。 - 如果是实参的类型不同,则可以编写所谓的可变参数模板。
C++ 还提供了一种特殊形参类型,即省略符类型,可以使用它传递可变数量的实参,这种功能一般用于C函数交互接口程序。
initializer_list 形参
如果函数的形参数量未知,但是形参的类型相同,可以采用 initializer_list
类型的形参。
initializer_list
是一种标准库类型,用于表示某种特定类型的值的数组。initializer_list
是一种模板类型,定义时需要指明所含元素的类型。initializer_list
中的元素永远都是常量值,无法修改initializer_list
对象中元素的值。
void error_msg(initializer_list<string> il)
{
for (auto beg = il.begin(); beg != il.end(); ++beg)
{
cout << *beg << endl;
}
}
如果想向 initializer_list
形参传递一个值的序列,则必须把序列放在一对花括号内:
error_msg({"function","excepted","actual"});
包含 initializer_list
形参的函数也可以同时拥有其它形参,例如:
void error_msg(int err_code,initializer_list<string> il)
省略符形参
省略符形参是为了C++程序访问某些特殊的C代码设置的,这些代码使用了名为 varargc
的C标准库功能。
省略符形参只能出现在形参列表的最后一个位置。