C++ 函数的形参初始化


前言

记录一下函数形参初始化的一些知识。

一、函数声明

1.1 固定长度形参

形参:就是函数声明语句里声明的变量。
实参:实参是形参的初始值,也就是传入函数的数据。

	int fact(int val)	//	此处的val就是形参
	{
		return val;
	}
	int main(){
		fact(5);	//	正确:此处的5用于初始化形参val,为实参
		fact("hello");	//	错误:实参类型需要与形参类型匹配,或能隐式转换
		fact();	//	错误:参数个数不对应
		fact(3, 4);	//	错误:参数个数不对应
		fact(3.14);	//	正确:自动进行类型的默认转换
		return 0;
	}

1.2 可变长度形参(initializer_list)

initializer_list是一种模板类型,如果函数的实参数量未知但是全部实参的类型都相同,可以使用该方法,需加入initializer_list头文件。
initializer_list类型于vector容器一样,拥有一系列操作函数。

	initializer_list<T> lst;	// 默认初始化
	initializer_list<T> lst{a,b,c...};	//	初始化,lst的元素是对应初始值的副本
	lst2(lst);	//	拷贝或赋值一个initializer_list对象不会拷贝列表中的元素,而是原始列表于副本共享元素
	lst2 = lst;	//	同上
	lst.size();	//	列表中元素数量
	lst.begin();	//	返回指向lst中首元素的指针
	lst.end();	//	返回指向lst中为元素下一位置的指针

使用时如下形式:

	// 其中的initializer_list的T类型必须指定
	void foo(parm_list, initializer_list<T> i1);
	void foo(initializer_list<T> i1);

示例:

	//	形式2
	void error_msg(initializer_list<string> i1){
		for( auto beg = i1.begin(); beg != i1.end(); ++beg)
			cout << *beg << endl;
	}
	//	形式1
	void error_msg(int e, initializer_list<string> i1){
		cout << e.msg() << ": ";
		for (const auto &elem : i1)
			cout << elem << " ";
		cout << endl;
	}

1.3 省略符形参(…)

该方法主要用于访问某些特殊的C代码,省略符形参应该仅仅用于C和C++通用的类型
形式如下:

	void foo(parm_list, ...);
	void foo(...);

第一种形式指定了foo函数的部分形参类型,指定的形参类型会执行正常的类型检查,省略符对应的实参无须类型检查
在实际使用中,需要用到以下方法来读取省略符形参中的数据,需要包含头文件cstdarg

	// va_list是一种数据类型,args用于持有可变参数。
	// 定义typedef char* va_list;
	va_list args;
	
	// 调用va_start并传入两个参数:第一个参数为va_list类型的变量
	// 第二个参数为"..."前最后一个参数名,函数实参按从右到左的顺序压入堆栈,当要获取省略符里的实参时,可以通过其前一个实参存储的位置往后读取。
	// 将args初始化为指向第一个参数(可变参数列表)
	va_start(args, paramN);
	
	// 检索参数,va_arg的第一个参数是va_list变量,第二个参数指定返回值的类型
	// 每一次调用va_arg会获取当前的参数,并自动更新指向下一个可变参数。
	va_arg(args, type);
	
	// 释放va_list变量
	va_end(args);

以下为一个示例:

	void test(int num, ...)
	{
		va_list ap;
	
		va_start(ap, num); // 注意!这里第二个参数是本函数的第一个形参 
	
		auto a = va_arg(ap, int);
	
		auto b = va_arg(ap, char *);
	
		auto c = va_arg(ap, double); // 浮点最好用double类型,而不要用float类型;否则数据会有问题 
	
		va_end(ap);
	
		printf("%d %s %f", a, b, c);
	}
	
	void main()
	{
		test(5,6, "Hello", 788.234);
	}

1.4 空形参

形式如下:

	void f1(){} // 隐式地定义空形参列表
	void f2(void){}	//	显示地定义空形参列表

示例:

	void qwe(void) {
		cout << 5 << endl;
	}
	
	void main()
	{
		qwe();
	}

1.5 默认实参

形参中,在函数的很多次调用中它们都被赋予一个相同的值,该值就是默认实参。调用含有默认实参的函数时,可以包含该实参,也可以省略该实参。

	string screen(int ht = 24, int wid = 80, char backgrnd = ' ');	//	函数声明中添加了默认实参
	
	string window;
	window = screen();	//	等价于screen(24, 80, ' ')
	window = screen(66);	//	screen(66, 80, ' ')
	window = screen(66, 256);	//	screen(66, 256, ' ')
	window = screen(66, 256, '#');	//	screen(66, 256, '#')

默认实参需注意:

  • 函数声明中某个形参被赋予了默认值,它后面的所有形参都必须有默认值(也就是说,设定默认值的形参需要在函数形参后面部分)。
   char * init(int ht = 24, int wd, char bckgrnd);	//	错误:ht被赋予了默认值,但是wd和bckgrnd未赋予默认值
  • 默认实参负责填补函数调用缺少的尾部实参,传入实参按顺序去初始化形参,后面未传入实参的部分使用默认实参。
   window = screen(, , '?')	//	错误:只能省略尾部的实参
   window = screen('?')	//	调用screen('?', 80, ' ')
  • 函数可以多次声明同一个函数,但是给定的作用域中一个形参只能被赋予一次默认形参。函数的后续声明只能为之前那些没有默认值的形参添加默认实参。
	string screen(int, int ,char = ' ' );	//	前两个形参没有默认实参
	string screen(int, int, char = '*' );	//	错误:重复声明
	string screen(int = 24, int = 80 , char);	//	正确:添加默认实参
  • 只要表达式的类型能转换成形参所需的类型,该表达式就能作为默认实参。默认实参的名字在函数声明所在的作用域内解析而这些名字的求值过程发生在函数调用时。
	int wd = 80;
	char def = ' ';
	int ht();
	string screen(int = ht(), int = wd, char = def);	//	解析后绑定的是与声明所在作用域里的变量

	void f2()
	{
		def = '*';	//	改变模型实参的值
		int wd = 100;	//	隐藏了外层定义的wd,但是函数screen()跟该wd不在同一作用域,没有改变默认值
		window = screen();	//	调用 screen(ht(), 80, '*')
	}

二、参数传递

2.1 值传递

值传递是当初始化一个非引用类型的变量时,初始值被拷贝给变量。
其形式与正常拷贝一样:

	int n = 0;	//	int类型的初始变量
	int i = n;	//	i是n的值的副本,在实参初始化形参的过程中也是如此,形参创建了一个新的副本i
	i = 42;	//	修改i的值不改变n的值 

指针形参与其他非引用类型一样,赋值形参时,进行指针值的拷贝,两个指针是不同的指针,但是指向相同的地址,通过解引用访问同一个对象的值。

	void reset(int* ip) {
		*ip = 0;	//	改变指针ip所指对象的值
		ip = 0;	//	改变局部指针ip指向的地址
	}
	int main() {
		int i = 42;
		int* p1 = &i;
		reset(p1);	//	改变了p1指向的i的值,p1地址未改变
		cout << *p1 << endl;
	}

2.2 引用传递

引用传递是如果形参是引用类型,它将绑定到对应的实参上,可以对实参进行修改操作,和其他引用一样,引用形参也是它所绑定对象的别名。

	void reset(int &i) {
		i = 0;	//	改变了i所引对象的值
	}
	int main() {
		int j = 42;
		reset(j);
	}

使用引用可以避免拷贝,拷贝大的类类型对象或者容器对象比较低效,甚至有的类类型根本不支持拷贝操作。
使用引用可以返回额外信息,一个函数智能返回一个值,然后有时需要同时返回多个值,就可以利用引用形参来实现。

2.3 const形参和实参

const形参和实参遵循的规则同其他初始化过程一样
可以查看const常量限定符

2.4 数组形参

数组的特殊性质:不允许拷贝数组、使用数组时通常会将其转换成指针。因此无法以值传递的方式使用数组参数,实际上传递的数组首元素的指针
虽然不能以值传递的方式传递数组,但是可以把形参写成类似数组的形式,一维数组形参形式:

	void print(int*);	
	void print(int[]);	
	void print(int[10]);	//	这里的维度表示我们期望数组含有多少元素,实际不一定

数组引用形参,引用形参绑定在数组上:

	void print(int (&arr)[10]){
		for(auto elem : arr)
			cout << elem << endl;
	}

传递多维数组,真正传递的是指向数组首元素的指针:

	void print(int (*matric)[10], int rowSize) {}
	void print(int matric[][10], int rowSize){}	//	与上式等价
  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值