背景与意义
有的时候我们无法提前预知应该向函数传递几个实参,为了编写能处理不同数量实参的函数,C++11 新标准提供了两种主要方法:
initializer_list
类型...
省略符
initializer_list 形参
Reference: initializer_list,提供的操作:
initializer_list<T> lst;
默认初始化:T类型元素的空列表initializer_list<T> lst{a, b, c...};
列表初始化:元素数量与列表中的一样多lst2(lst)
拷贝构造,但元素不会被拷贝,原始列表与副本共享元素lst2 = lst
赋值,与上面一样lst.size()
返回列表中的元素数量lst.begin()
返回的是常量指针,也就意味着不能改变所指对象的值lst.end()
回的是常量指针,也就意味着不能改变所指对象的值
这里先举个例子:
void ErrorMsg(initializer_list<string> il) {
for (auto p = il.begin(); p != il.end(); p++) {
cout << *p << " ";
}
cout << endl;
}
int main() {
ErrorMsg({"first", "second", "third"});
}
需要注意的是,initializer_list 只是引用数组中的元素(花括号中的元素),所以前面提到的拷贝构造和赋值(这与 vector 不同),最后也是指向同样的底层元素。另外,这个临时数组的生命周期与 initializer_list 一致。
int main() {
string str("str");
initializer_list<string> il{str, "bb", "cc", "dd"}; //str在这里被复制
initializer_list<string> il2 = il;
cout << il.begin() << endl;
cout << il2.begin() << endl;
cout << &str << endl;
cout << *il.begin() << endl;
str = "new str";
cout << *il.begin() << endl;
}
// 运行结果
// 0x62fcb0
// 0x62fcb0
// 0x62fc20
// str
// str
这个运行结果印证了前面的两个说法。最后,再提一个 initializer-list constructor
这样的构造函数,当使用列表初始化的语法的时候,这个构造函数优先级比普通的构造函数优先级高,相关描述与简单的例子如下:
Constructors taking only one argument of this type are a special kind of constructor, called initializer-list constructor. Initializer-list constructors take precedence over other constructors when the initializer-list constructor syntax is used.
class MyClass {
public:
MyClass(initializer_list<int> il){ cout << "initializer_list\n";}
MyClass(int a, int b){ cout << "constructor\n" ;}
};
int main() {
MyClass mc = {1, 2, 3};
MyClass mc2{1, 2};
MyClass mc3(1, 2);
}
// 运行结果
// initializer_list
// initializer_list
// constructor
再婆婆妈妈地提一下,并不是说不用这个 initializer-list constructor
你就不能使用列表初始化的语法来进行初始化了(使用花括号初始化),但是列表的元素数量要和构造函数的对应上。
class MyClass {
public:
MyClass(int a, int b){ cout << "constructor\n" ;}
};
int main() {
MyClass mc{1, 2};
MyClass mc2 = {1, 2};
//MyClass mc3 = {1}; //错误,没有与参数列表匹配的构造函数
//MyClass mc4 = {1, 2, 3}; //错误,没有与参数列表匹配的构造函数
}
省略符形参
省略符形参是为了便于 C++ 程序访问某些特殊的 C 代码而设置的,这些代码使用了名为 varargs 的 C 标准库功能(头文件 stdarg.h
)。所以可以认为这是为了兼容 C 语言而留下的,因此如果要写纯 C++ 风格的建议还是不要这么写了。形式有如下两种:
void foo(parm_list, ...);
void foo(...);
第一种形式指定了 foo 函数的部分形参的类型,对应于这些形参的实参将会执行正常的类型检查。而省略符对应的实参无须类型检查。举个例子来看下:
#include <cstdarg>
#include <cstdio>
void printargs(int arg1, ...) {
va_list ap;
int i;
va_start(ap, arg1);
for (i = arg1; i != -1; i = va_arg(ap, int))
printf("%d ", i);
va_end(ap);
putchar('\n');
}
int main() {
printargs(5, 2, 14, 84, 97, 15, 24, 48, -1);
printargs(84, 51, -1);
printargs(-1);
printargs(1, -1);
}
stdarg.h 数据成员:
va_list
用来保存宏 va_arg 与宏 va_end 所需信息
stdarg.h 宏:
va_start
使va_list指向起始的参数va_arg
检索参数va_end
释放va_listva_copy
拷贝va_list的内容 [C99]
参考
[1] 含有可变形参的函数 -《C++ Primer(第5版)中文版》 p197
[2] stdarg.h https://baike.baidu.com/item/stdarg.h/10239382