【C++】含有可变形参的函数

背景与意义

有的时候我们无法提前预知应该向函数传递几个实参,为了编写能处理不同数量实参的函数,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_list
  • va_copy 拷贝va_list的内容 [C99]

参考

[1] 含有可变形参的函数 -《C++ Primer(第5版)中文版》 p197

[2] stdarg.h https://baike.baidu.com/item/stdarg.h/10239382

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值