文章目录
前言
记录一下函数形参初始化的一些知识。
一、函数声明
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){} // 与上式等价