[C++学习]函数之参数传递
一、传值参数
当初始化一个非引用类型的变量时,初始值被拷贝给变量。此时,对变量的改动不会影响初始值。函数对传值参数的所有操作都不会影响实参。
指针形参
指针的行为和其他非引用类型一样,当执行指针拷贝操作时,拷贝的时指针的值。拷贝之后的两个指针是不同的指针。但因为指针使我们可以间接访问它所指的对象,所以通过指针可以修改它所指的对象的值。
在C++语言中,建议使用引用类型的形参替代指针。
二、传引用参数
使用引用参数,允许函数改变一个或多个实参的值。和其他引用一样,引用形参绑定初始化它的对象。
使用引用避免拷贝
拷贝大的类类型的对象或者容器对象比较抵消,甚至一些类类型根本不支持拷贝操作。所以,如果函数无须改变引用形参的值,最好将其声明为常量引用。
举个例子,比较两个string对象的长度。因为string对象可能会很长,所以应该避免直接拷贝它们,这时使用引用形参比较好。又因为比较长度无需改变其内容,所以把形参第一位常量的引用。
bool isShorter(const string &s1, const string &s2)
{
return s1.size() < s2.size();
}
使用引用形参返回额外信息
一个函数只能返回一个值,然而有时候函数需要同时返回多个值,引用形参就为我们一个返回多个结果提供了有效的途径。
三、const形参和实参
当实参初始化形参时会忽略掉顶层const,传给它常量对象或非常量对象都是可以的。
void function(const int i) { /* function能够读取i,但是不能向i写值 */}
C++语言中允许定义若干具有相同名字的函数,不过前提是不同的函数的形参列表应该有明显的区别。因为顶层const被忽略,不能这样定义第二个函数:
void function(const int i) {}
void function(int i) {}
指针或引用形参与const
形参的初始化方式与变量的初始化方式是一样的。我们可以使用非常量初始化一个底层const对象,但是反过来不行,同时一个普通的引用必须用同类型的对象初始化。
尽量使用常量引用
把函数不会改变的形参定义成引用是一种常见的错误,这么做带给函数的调用者一种误导,即函数可以修改它的实参的值。此外,使用引用而非常量引用也会极大地限制函数所能接受的实参类型。
四、数组形参
数组有两个特殊性质:不允许拷贝数组以及使用数组时通常会将其转换成指针。因为数组会被转换成指针,所以当我们为函数传递一个数组时,实际上传递的是指向数组首元素的指针。
void function(const int*);
void function(const int[]);
void function(const int[10]); // 表示期望的元素个数,实际不一定
因为数组是以指针的形式传递给函数的,所以一开始并不知道数组的大小,调用者应该为此提供一些额外的信息。
使用标记指定数组长度
此方法的典型示例是C语言风格的字符串,其字符串最后一个字符后面跟着一个空字符串。在处理C语言风格字符串时遇到空字符停止。
void print(const char *cp)
{
if (cp)
while (*cp)
cout << *cp++;
}
使用标准库规范
管理数组实参的第二种方法是传递指向数组首元素和尾元素的指针。
void print(const int *beg, const int *end)
{
while (beg != end)
cout << *beg++ << endl;
}
调用方法:
int number[] = {1, 2, 3, 4, 5};
print(begin(number), end(number));
显式传递一个表示数组大小的形参
void print(const int ia[], size_t size)
{
for (size_t i = 0; i != size; ++i)
{
cout << ia[i] << endl;
}
}
调用方法:
int number[] = {1, 2, 3, 4, 5};
print(number, end(number) - begin(number));
数组形参和const
当函数不需要对数组元素执行写操作时,数组形参应该是指向const的指针,只有当函数确实需要改变元素值的时候,才把形参定义为指向非常量的函数。
数组引用形参
C++语言允许将变量定义成数组的引用,基于同样的道理,形参也可以是数组的引用。
void print(int (&arr)[10])
{
for (auto elem : arr)
{
cout << elem << endl;
}
}
传递多维数组
所谓的多维数组其实是数组的数组。
和所有数组一样,当多维数组中传递给函数时,指针传递的时指向数组首元素的指针。
void print(int (*matrix)[10], int rowSize) { /*matrix是指向含有10个整数的数组的指针*/ }
void print(int matrix[][10], int rowSize) { /*等价于上面的写法*/ }
五、处理命令行选项
命令行选项通过两个形参传递给main函数:
int main(int argc, char *argv[]) { /* */ }
int main(int argc, char **argv) { /* */ }
可选实参从argv[1]开始,argv[0]保存着程序的名称。
六、含有可变形参的函数
有时候我们无法预知应该像函数传递几个实参。为了能处理不同数量实参的函数,C++11新标准提供了两种的方法:
initializer_list形参
如果函数的是参数量未知但是全部实参的类型相同,我们可以使用initializer_list类型的形参。initializer_list是一种标准库类型,由于表示某种特定类型的值的数组。
下面是initializer_list提供的操作:
操作 | 说明 |
---|---|
initializer_list |
默认初始化T类型元素的空列表 |
initializer_list |
lst初始化是对应的初始化副本,列表中的元素是const |
lst2(lst) | 拷贝一个initializer_list对象不会拷贝列表中的元素,拷贝后,原始列表和副本共享元素 |
lst2 = lst | 赋值一个initializer_list对象 |
lst.size() | 列表中的元素数量 |
lst.begin() | 返回指向lst首元素的指针 |
lst.end() | 返回指向lst中尾元素下一位置的指针 |
与vector不同的是,initializer_list对象中的元素医院是常量值,无法修改。
void error_msg(initializer_list<string> il)
{
for (auto beg = il.begin(); beg != il.end(); ++beg)
{
cout << *beg << endl;
}
}
省略符形参
省略符形参是为了便于C++程序访问某些特殊的C代码设置的。这些代码使用了名为varargs的C标准库功能。通常,省略符形参不应用于其他目的。
省略符形参应该仅仅用于C和C++的通用的类型。特别应该注意的是,大多数类型的对象在传递给省略符形参时都无法正确的拷贝。
省略符形参只能出现在形参列表的最后一个位置。