Loading... 在开发中,我们可能需要定义可以处理不同数量参数的函数。如果我们使用传统的方式定义多个函数,每个函数接受不同数量的参数,那么这将会导致代码冗余,不易维护。因此,使用可变参数函数可以避免这种情况的发生,提高代码的可维护性和重用性。 ## 方法一 cstdarg头文件 我们查看`printf`函数的实现,就通过这样的方式实现了可变参数,即使用了`...`: ```C _CRT_STDIO_INLINE int __CRTDECL printf( _In_z_ _Printf_format_string_ char const* const _Format, ...) ``` > 提示:在C++中`cstdarg`和`stdarg.h`功能一致,但是`stdarg.h`定义在全局命名空间中(global namespace),而`cstdarg`定义在标准命名空间(std namespace)中,也就是说,需要使用`using namespace std;`或者加上`std::`。 下面是使用`cstdarg`库的一个例子: ```c++ #include <stdarg.h> #include <iostream> int get_sum(int num, ...) { int result = 0; //第一步:初始化 va_list ap; //第二步:va_start 宏允许访问后随具名参数 parm_n 的可变参数。 //void va_start( std::va_list ap, parm_n ); va_start(ap, num); for (int i = 0; i < num;i++) { //第三步:va_arg 宏展开成对应来自 std::va_list ap 的下个参数的 T 类型表达式。 result += va_arg(ap, int); } //最后:va_end 宏进行对为 va_start 或 va_copy 所初始化的 ap 对象的清理。 va_end 可以修改 ap 并令它不再可用。 va_end(ap); return result; } int main() { std::cout << get_sum(3,2,3,4) << std::endl; // result=9 return 0; } ``` `cstdarg`中定义了如下几个宏(C++11中还增加了va_copy),他们的功能分别如下: 1. `va_start()`:初始化一个可变参数列表,并使其指向第一个可变参数。 2. `va_arg()`:获取可变参数列表中的下一个参数。 3. `va_end()`:结束一个可变参数列表的访问。 查看这几个宏的具体定义,可见这样的可变参数列表是通过指针来实现的,`sizeof(t)`意味着`t`需要有确定的大小。也就是说,其支持的数据类型仅包括 int、double、float、char、指针。 ```C #define va_start __crt_va_start #define va_arg __crt_va_arg #define va_end __crt_va_end #define __crt_va_start_a(ap, x) ((void)(__va_start(&ap, x))) #define __crt_va_arg(ap, t) \ ((sizeof(t) > sizeof(__int64) || (sizeof(t) & (sizeof(t) - 1)) != 0) \ ? **(t**)((ap += sizeof(__int64)) - sizeof(__int64)) \ : *(t* )((ap += sizeof(__int64)) - sizeof(__int64))) #define __crt_va_end(ap) ((void)(ap = (va_list)0)) ``` 由此可见,想要使用省略符来实现可变参数,就必须要指定后续参数的个数。因此,通过`cstdarg`库实现可变参数是非常危险的,当传入的参数数量与指定的参数数量不一致时,程序可能会访问到错误的地址,尤其当传入的参数是指针时,程序可能会读取或写入未分配的内存,造成崩溃和错误。 ## 方法二 可变参数模板参数包 在C++11标准中引入了一种新的语法特性——可变参数模板参数包(variadic template parameter pack),这使得我们可以定义任意数量参数的模板函数或类。 在之前的C++标准中,函数或类模板只能使用确定数量的模板参数,但是这样做会限制它们的灵活性。比如,如果我们想要定义一个模板函数,它可以接受任意数量的参数并对它们进行求和,我们必须使用重载或者是函数模板嵌套的方式来实现。这会使得代码变得冗长且难以维护。而可变参数模板参数包的引入,可以大大简化这个过程。 使用可变参数模板参数包,我们可以定义一个函数模板,它接受任意数量的参数,并将这些参数打包成一个参数包(parameter pack)。我们可以使用类似于展开操作符(unpacking operator)的语法,将参数包中的每一个参数解包出来,这样就可以方便地对这些参数进行处理。 下面使用**例子1**展示一些参数包展开的常用情景 ```cpp //可变参数模板参数包展开 例子1 #include <iostream> template<typename... Args> auto sum(Args... args){ return (args + ...); } template<typename... Args> void print_A(Args... args){ (std::cout << ... << args) << std::endl; } int main(){ std::cout<<sum(1,2,3,4)<<std::endl; print_A("Hello world ",2023,"-",1,"-",1); return 0; } //输出 //10 //Hello world 2023-1-1 ``` 除了**参数包展开**之外,我们也可以使用**递归调用**来实现可变参数。对于下面**例子2****递归调用**的情况,当传入多个参数时,`print_B`函数会递归调用`void print_B(const T& arg, const Args&... args)`,直至只剩一个参数时,得益于C++函数重载的特性,会调用`void print_B(const T& arg)`,这样就实现了打印函数。 ```cpp //例子2 #include <iostream> // 基本情况 template<typename T> void print_B(const T& arg) { std::cout << arg << std::endl; } // 递归情况 template<typename T, typename... Args> void print_B(const T& arg, const Args&... args) { std::cout << arg ; print_B(args...); } int main(){ // 递归调用 print_B("Hello world ",2023,"-",1,"-",1); return 0; } //输出: //Hello world 2023-1-1 ``` ## 方法三 初始化列表(initializer_list) 在C++11之后,还引入了initializer_list,它也可以用于实现可变参数函数。与可变参数模板参数包不同,initializer_list是一个包含任意数量元素的列表,但是所有元素的类型必须相同。 ```cpp #include <iostream> double get_avg(std::initializer_list<int> li){ int sum=0; int count=0; for(auto i:li){ sum+=i; count++; } return (double)sum/count; } int main(){ std::cout<<get_avg({4,5,6,7})<<std::endl; return 0; } ``` 最后修改:2023 年 11 月 23 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 13 如果觉得我的文章对你有用,请随意赞赏
1 条评论
博主太厉害了!