参数传递
形参初始化的机理和变量初始化一样。形参的类型决定了形参和实参交互的方式。如形参是引用类型,它将绑定到对应的实参上;否则,将实参的值拷贝后赋给形参。
当形参是引用类型时,我们说它对应的实参被引用传递,或者函数被传引用调用。和其他引用一样,引用形参也是它绑定的对象的别名;也就是说,引用形参是它对应的实参的别名。
当实参的值被拷贝给形参时,形参和实参是两个相互独立的对象。我们说这样的实参被值传递或者函数被传值调用。
因为C中没有引用,所以C中传递参数的方式只有值传递,而C++中还有引用,不仅有值传递,还有引用传递。
值传参和引用传参
值传参
当初始化一个非引用类型的变量时,初始值被拷贝给变量。此时,对变量的改动不会影响初始值。值传参的原理和这个一样,函数对形参做的所有操作都不会影响实参。
指针形参
指针的行为和其他非引用类型一样。当执行指针拷贝操作时,拷贝的是指针的值。拷贝之后,两个指针是不同的指针。但是因为指针可以让我们间接访问它所指向的对象,所以可以通过指针修改它所指对象的值。
引用传参
函数接受的参数是引用类型的话就是引用传参。通过使用引用形参,函数可以改变一个或者多个实参的值。
引用传参的好处:
- 避免拷贝,可以避免低效的拷贝操作,或者有些类型不支持拷贝,比如IO类型。
- 间接的实现多个返回值(也可以通过值传递指针实现)。
值传参和引用传参的区别
值传参是将原始变量的值拷贝一份给形参,函数对形参的操作不会影响实参(指针可以简介的修改)。
而引用传参是相当于直接把实参的引用给传递了形参,任何对形参的修改都是直接对实参的修改。
const
形参和实参
当形参是const
时,必须注意顶层const
,顶层const
作业于对象本身。当用实参初始化形参时,会忽略掉顶层const
,即形参的顶层const
被忽略掉了。当形参有顶层const
时,传递给它常量或者非常量对象都是可以的。
指针或者引用形参和const
形参的初始化方式和变量的初始化方式是一样的,所以指针或者引用形参和const
结合时,按照const
变量的初始化规则执行就行。
尽量使用常量引用
把函数不会修改的形参定义成普通的引用是一种比较常见的错误,这么做带给函数的调用者一种误导,即函数可以修改它的实参的值。此外,使用引用而非常量引用也会极大地限制函数所能接受的实参类型。(比如,不能把const
对象,字面值或者需要类型转换的对象传递给普通的引用传参)
数组形参
数组有两个特殊的性质:
- 不允许拷贝,因为不能拷贝数组,所以不能以值传递的方式使用数组参数。
- 在使用数组时通常会将其转换成指针。因为数组会被转换成指针,所以为函数传递数组时,实际上传递的是指向数组首元素的指针,这样子可以节约开销。
管理数组转换的指针
当传递给函数一个数组时,实参自动的转成指向数组首元素的指针,数组的大小对于函数的调用没有什么影响。因为数组是以指针的形式传递给函数的,所以函数其实是不知道数组的大小的,调用者应该为此提供一些额外的信息。通常有三种方式:
- 显示传递一个表示数组大小的形参
- 使用标记指定数组长度,要求数组本身包含一个结束标记,典型的例子是C风格字符串。
- 使用标准库规范,传递数组首元素和尾后元素的指针。可以使用
begin
和end
函数获得数组的首元素和尾后元素的指针。
数组形参和const
当函数不需要对数组进行写操作时,数组形参应该是指向const
的指针(底层const)。只有当函数确实需要改变数组元素值的时候,才把形参定义成指向非常量的指针。
数组引用形参
C++允许将变量定义成数组的引用,形参也可以是数组的引用。此时,引用形参绑定到数组上。
传递多维数组
C++其实没有真正的多维数组,多维数组其实就是数组的数组。把多维数组传递给函数时,传递的是指向数组首元素的指针。而多维数组是数组的数组,首元素本身就是一个数组,多维数组转换成指向数组的指针。数组第二维以及后面维度都是数组类型的一部分,不能省略。
函数指针形参
和数组类似,虽然不能定义函数类型的形参,但是形参可以是指向函数的指针。这个时候,形参看起来是函数类型,实际上是当成指针使用。
可以直接把函数作为实参使用,这个时候它会自动的转换成指针:1
2
3
4
5
6//第三个参数是函数类型,它会自动的转换成指向函数的指针
void useBigger(const string &s1, const string &s2, bool pf(const string &, const string &));
//第三个参数是显式声明的指向函数的指针
void useBigger(const string &s1, const string &s2, bool (*pf)(const string &, const string &));
// 函数lengthCompare会被自动的转换成函数指针
useBigger(s1, s2, lengthCompare);
使用typedef
和delctype
简化函数指针
可以使用下列语句定义函数指针:1
2
3
4
5
6
7
8//下面两个是函数类型:
typedef bool Func(const string&, const string&);
typedef delctype(lengthCompare) Func2;
//下面两个是函数指针类型:
typedef bool (*FuncP)(const string&, const string&);
typedef delctype(lengthCompare) *FuncP2;
需要注意的是,decltype
返回函数类型,不会将函数自动转换成指针类型,只有在前面加上*
才能得到指针。
可变形参
当不知道向函数传递多少个参数时,C++ 11提供了两种方法处理不同数量实参,如果所有实参类型相同,传递initializer_list
标准库类型,如果实参类型不同,编写特殊的函数,可变参数模板。
此外,C++ 还有一种特殊的形参,叫做省略符,可以用它传递可变数量的实参。
initializer_list
形参
如果实参数量未知,但是类型相同,可以使用标准库类型initializer_list
类型的形参。该标准库提供的操作如下:
initializer_list<T> lst
,默认初始化,T类型元素的空列表initializer_list<T> lst{a, b, c...}
,list元素是对应初始值的副本,列表中的元素是const
lst2(lst)
和lst2 = lst
,注意,拷贝或者赋值一个initializer_list
对象不会拷贝列表中的元素;拷贝后,原始列表和副本共享元素。lst.size()
lst.begin()
lst.end()
initializer_list
是一个标准库类型,它也是一个模板,它的元素永远都是常量。
有一个疑问,就是std::initializer_list
和std::vector
有什么区别?[2,3,4]
拷贝std::initializer_list
的时候并不会拷贝底层的对象。相当于拷贝了“指针”,或者说std::initializer_list
有reference semantics而std::vector
具有value semantics。
Initializer lists may be implemented as a pair of pointers or pointer and length. Copying a std::initializer_list does not copy the underlying objects.
省略符形参
省略符形参是为了便于C++访问某些特殊的C代码而设置的。省略符形参只能出现在形参列表的最后一个位置:1
2void foo(param_list, ...)
void foo(...);
main
命令行选项
详细可以查看C/C++ main argc argv。
参考文献
1.《C++ Primer》第五版
2.https://stackoverflow.com/questions/27753420/initializer-list-vs-vector
3.https://en.cppreference.com/w/cpp/utility/initializer_list
4.https://stackoverflow.com/questions/14414832/why-use-initializer-list-instead-of-vector-in-parameters