文章目录
- 前言
- 正文
- C语言中的可变参数宏
- C++中的可变参数模板
- 代码刨析
- 终
前言
模板是C++的一个重要特性,其中,可变参数模板更是重中之重,我们学习模板一定会学习可变参数模板。
首先,我们先要了解可变参数模板的目的是什么:
可变参数模板是为了解决函数或类模板无法处理或限制参数数量,类型和形参的问题而引入的一个新特性。它允许模板参数数量不确定,使得我们可以传递任意数量和类型的参数,从而更加灵活和通用。
正文
本文的重点是在C++11的可变参数模板,但是我也会说一些我觉得有必要的内容。
其实,不只是C++提供了可变参数,C语言也有,只是很多人没有见过。
C语言中的可变参数宏
可能有人在学习C语言的时候就见到过它,即:##__VA_ARGS__,这是很多编译器提供的一个宏,它不需要使用头文件就能使用:
#include <stdio.h>
#define LOG(format, ...) printf(format, ##__VA_ARGS__)
int main() {
int num = 42;
LOG("The answer is: %d\n", num);
LOG("Hello, world!\n");
return 0;
}
但是跟C++不一样的是:C语言中,函数的参数一定是确定的,因此##__VA_ARGS__只能在宏定义中使用。它的用法我在先前的文中C++花式预处理指令有说过,本质就是利用宏定义本质是文本替换来定义函数,但是这样写出来的宏定义非常长,并且我觉得可读性不高。
C++中的可变参数模板
对比起C语言来说,C++中的可变参数就显得十分的优美:
#include <iostream>
void func(){
std::cout << std::endl;
}
template<class T, class... Args>
void func(const T& val, Args... args){
std::cout << val << ' ';
func(args);
}
int main(){
func(1, 2.3, "Hello World");
}
这样就实现了一个简单的程序,如果我们不使用可变参数模板的话,或许我们要将这个函数调用三次,而现在,我们只需手动实现一次就能够达到要求。
代码刨析
先说结论:C++的可变参数模板的使用就类似函数递归调用!
现在,我们逐行理解为什么我们的程序需要这么写。
我们将目光放到这段程序的核心内容:
template<class T, class... Args>
void func(const T& val, Args... args){
std::cout << val << ' ';
func(args);
}
- 模板声明:class T这个太常用了,就不多说了,它跟普通的模板声明是一样的,我们将重点放在后面的class… Args。
将它跟class T进行比较,不难发现大致是一样的,不同处在哪?“…”,这个就是可变参数模板的重点,它就表示Args是一个可变参数模板,而我们可以认为Args被抽象为一个“类”。 - 模板使用:在模板的声明中,我们已经知道了有这么一个“类”叫做Args,它是一个可变参数模板,类型随意,数量随意,为了体现这个“类”它是可变的,因此我们再次用上了这个符号:“…”,然后就跟函数声明中声明形参一样,我们给Args这个“类”在函数中的使用对象起个名,就叫做args。
- “递归调用”:在函数的末尾,func(args)的作用又是什么呢?我们将它再次带回函数声明,可以发现:args被分为了两部分,新的const T& val和新的Args… args,以此反复分裂,直到args为空。
这个主体函数的刨析到这里就结束了,但是我要说的内容还没结束,难道到这里就完全讲完了吗?
是否还记得在主体函数的上面有另一个函数重载?我们还没有提到它,难道它一点用都没有吗?我们将其删掉,发现程序会报错,说明它也是程序的一部分:
void func(){
std::cout << std::endl;
}
我们现在都能够立即args会被一直分割了吧,但是它所列的参数的数量毕竟是有限的,那么:当args分裂到最后,args为空了呢?我们还能够再次调用func(const T& val, Args… args)了吗?显然是不行的,因为没法再分成一个const T& val作为参数进行调用了,因此就需要func的无参函数,这个函数也能够被理解为递归调用中的退出函数,当args被分割至空的时候,就调用它,func函数的运行到了末尾。
终
至此,C++11的可变参数模板就讲完了。
希望阅读了本文章的大佬能够私信我或者在评论区指出这篇文章的错误和不足,感激不尽!