本篇分享一些在函数声明中,形参列表需要注意的一些问题。
一、常量引用形参
在函数调用时,普通引用类型的形参无法传入const对象、字面值以及需要类型转换的对象进行初始化,此时必须使用常量引用作为形参类型。
struct A{
int count;
};
void test1(A &a);
void test2(const A &a);
A a{10};
test1(a); // 正确,普通引用绑定对象实参
test1(A{10}); // 不正确,普通引用形参无法绑定字面值
test2(A{10}); // 正确,常量引用形参可以绑定字面值实参
二、数组形参
- 数组指针形参
将数组用作函数形参时需要考虑它的两种性质,其一是数组无法拷贝,其二是数组名在相当多的情形下会被自动转化为数组首元素的指针。因此,**请谨记,函数形参列表中的函数类型将会被编译器直接转化为指针类型。
第三种函数声明,尽管指示出了该数组应当具有10个元素,但是并不起实际作用,仍旧会被编译器视作// 以下三种函数声明等价,形参类型均为const int* void print(const int*); void print(const int[]); void print(const int[10]);
const int*
来处理。
由于传入的是数组元素指针,因此该形参不具备数组维度信息,在这种情况下想要完成数组遍历或元素访问,可借助以下三种形式:- 其一,是在数组中设置明显的结束标志。类似于C风格字符串以空字符结尾。
- 其二,是以数组的起始和结尾迭代器作为形参类型进行传入,以迭代器的姿态进行遍历。
- 其三,添加数组维度形参,显式传入数组大小。
- 数组引用形参
区别于指针形参,数组引用类型的形参可以明确绑定一个完整的数组类型,并且在该函数中,还可以使用范围for循环对该数组形参进行遍历。// 该形参类型是一个有10个整型元素数组的引用 void print(int(&arr)[10]){ for(auto &elm: arr){ // 对完整数组类型可使用范围for进行遍历 // 元素处理逻辑 } }
- 多维数组形参
将多维数组作为实参,此时数组第二维及之后的维度不可忽略。这与普通数组指针形参不同,普通数组指针形参的维度是被忽略的,而在多维数组中只有第一维会被忽略。例如,在下面的函数中,实参类型其第二维必须为10,而第一维是多少无所谓。//以下三种形式等价 void print(int array[][10]); void print(int (*array)[10]); void print(int array[5][10]);
三、main函数形参
在运行程序时,可以为main函数传入任意数量的参数,这需要将我们将main函数声明称以下任意一种形式:
int main(int argc, char *argv[]) { ... }
int main(int argc, char **argv) { ... }
其中,argv
是一个C风格的字符串数组,argv[0]
默认为程序路径名,argc
是除argv[0]
外剩余参数的数量,可以借助argc
访问该参数数组。
// main.exe arg1 arg2
#include <iostream>
int main(int argc, char *argv[]) {
std::cout << argv[1] << "\n" << argv[2];
}
// stdout:
// arg1
// arg2
四、可变形参
C++实现长度可变的形参列表有三种形式(C++ 11):
initializer_list
initializer_list
是一种标准库模版,其使用方式与vector
容器比较类似,将该模版实例化类型作为形参,可以传入任意数量相同类型的参数。该类型支持列表初始化、size
获取参数包大小以及通过begin()
与end()
获取首尾迭代器。
使用时需要注意几点:void func(initializer_list<int> args){ for(int arg: args){ // 元素操作(注意无法使用int &, // 因为args中的元素均为const int类型) } }
- initializer_list所有参数均为常数类型,函数中无法对参数包中的参数进行修改。
- 使用拷贝或赋值操作时,该参数包中的元素不会发生拷贝,原参数包与目标参数包将共享同一元素副本。
- C兼容的
...
形参
不熟悉这个,以后填坑,详情可参照cpprefrence链接。 - 变参模版
不熟悉这个,以后填坑,详情可参照cpprefrence链接。
五、默认实参
-
默认实参的值不属于函数原型的一部分,因此无法通过修改默认实参来实现函数重载。如果相同的函数原型声明中修改了默认实参的值,则会报“默认实参重定义”的错误。
int test(int arg1, int arg2=10); int test(int arg2, int arg2=12); // error: redefinition of default argument
-
默认实参的名字需要在其作用域内可见,可以是符合类型要求的任意表达式,该表达式的求值在函数调用时发生。(这也就是说,我们有机会在函数调用前更改一个函数的默认实参)
int todayT = 10; // 以函数作为默认实参名称,将在调用时进行求值 int test(int tempreture = todayT){ return tempreture; } cout << test(); // 10 todayT = 15; // 改变默认实参的返回值 cout << test(); // 15
六、形参会忽略实参的顶层const
void test(int arg){
arg = 10;
}
const int i = 15;
test(i);
上面的函数将成功调用。此时发生的是对常数变量值得拷贝,因此顶层const属性将被忽略,无论形参类型是否具有const均可。但若实参具有底层const,则形参必须为const。
七、C风格字符串形参
void test(char *a); // 无法传入字面值
void test(const char *a); // 可传入字面值
void test(const char a[10]); // 与第二种等价
在编写程序时,经常会有需要向函数中传递一个字面值C风格字符串,例如"abcde"
,像这个有五个有效字符的字面值,其类型为const char[6]
(维度考虑结尾空字符)。因此第一种函数并不适用。
考虑之前所提,对于数组指针形参而言,维度并无实际意义,因此第三种与第二种函数声明登记,"abcde"
可以合法传入第三个函数中。