[cpp primer随笔] 09. 形参列表细则

本篇分享一些在函数声明中,形参列表需要注意的一些问题。

一、常量引用形参

在函数调用时,普通引用类型的形参无法传入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}); // 正确,常量引用形参可以绑定字面值实参

二、数组形参

  1. 数组指针形参
    将数组用作函数形参时需要考虑它的两种性质,其一是数组无法拷贝,其二是数组名在相当多的情形下会被自动转化为数组首元素的指针。因此,**请谨记,函数形参列表中的函数类型将会被编译器直接转化为指针类型。
    // 以下三种函数声明等价,形参类型均为const int*
    void print(const int*);
    void print(const int[]);
    void print(const int[10]);
    
    第三种函数声明,尽管指示出了该数组应当具有10个元素,但是并不起实际作用,仍旧会被编译器视作const int*来处理。
    由于传入的是数组元素指针,因此该形参不具备数组维度信息,在这种情况下想要完成数组遍历或元素访问,可借助以下三种形式:
    • 其一,是在数组中设置明显的结束标志。类似于C风格字符串以空字符结尾。
    • 其二,是以数组的起始和结尾迭代器作为形参类型进行传入,以迭代器的姿态进行遍历。
    • 其三,添加数组维度形参,显式传入数组大小。
  2. 数组引用形参
    区别于指针形参,数组引用类型的形参可以明确绑定一个完整的数组类型,并且在该函数中,还可以使用范围for循环对该数组形参进行遍历。
    // 该形参类型是一个有10个整型元素数组的引用
    void print(int(&arr)[10]){
    	for(auto &elm: arr){ // 对完整数组类型可使用范围for进行遍历
    	 // 元素处理逻辑
    	}
    }
    
  3. 多维数组形参
    将多维数组作为实参,此时数组第二维及之后的维度不可忽略。这与普通数组指针形参不同,普通数组指针形参的维度是被忽略的,而在多维数组中只有第一维会被忽略。例如,在下面的函数中,实参类型其第二维必须为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 
int main(int argc, char *argv[]) { 
	std::cout << argv[1] << "\n" << argv[2];
}
// stdout:
// arg1
// arg2

四、可变形参

C++实现长度可变的形参列表有三种形式(C++ 11):

  1. initializer_list
    initializer_list是一种标准库模版,其使用方式与vector容器比较类似,将该模版实例化类型作为形参,可以传入任意数量相同类型的参数。该类型支持列表初始化、size获取参数包大小以及通过begin()end()获取首尾迭代器。
    void func(initializer_list<int> args){
    	for(int arg: args){
    		// 元素操作(注意无法使用int &,
    		// 因为args中的元素均为const int类型)
    	}
    }
    
    使用时需要注意几点:
    • initializer_list所有参数均为常数类型,函数中无法对参数包中的参数进行修改。
    • 使用拷贝或赋值操作时,该参数包中的元素不会发生拷贝,原参数包与目标参数包将共享同一元素副本。
  2. C兼容的...形参
    不熟悉这个,以后填坑,详情可参照cpprefrence链接。
  3. 变参模版
    不熟悉这个,以后填坑,详情可参照cpprefrence链接。

五、默认实参

  1. 默认实参的值不属于函数原型的一部分,因此无法通过修改默认实参来实现函数重载。如果相同的函数原型声明中修改了默认实参的值,则会报“默认实参重定义”的错误。

    int test(int arg1, int arg2=10);
    int test(int arg2, int arg2=12);
    // error: redefinition of default argument
    
  2. 默认实参的名字需要在其作用域内可见,可以是符合类型要求的任意表达式,该表达式的求值在函数调用时发生。(这也就是说,我们有机会在函数调用前更改一个函数的默认实参

    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"可以合法传入第三个函数中。

你可能感兴趣的:(C++,c++)