欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 焦点 > C++11 可变参数模板

C++11 可变参数模板

2025/5/18 3:06:57 来源:https://blog.csdn.net/weixin_73757824/article/details/141855909  浏览:    关键词:C++11 可变参数模板

序言

 不知道大家有没有细细研究过在 C 语言 中的 printf 函数,也许我们经常使用他,但是我们可能并不是那么了解他。先看一下调用格式:int printf ( const char * format, ... );,在这里的 format 代表我们的输出格式,后面的 ... 省略号这又是什么呢,这代表 可变参数,你可以传递任意数量的参数。这是怎么实现的呢?


1. C 中的可变参数

1.1 可变参数的概念

 可变参数是指在函数定义中,允许传入不定数量的参数的一种机制。在编程语言中,可变参数使得函数能够更加灵活地处理不同数量的输入。

1.2 实现可变参数

 在实现可变参数的函数之前,我们先认识几个函数:

stdarg.h 头文件

 为了处理可变参数,C 语言 标准库提供了 stdarg.h 头文件,它定义了一组宏来访问这些参数。这些宏包括:

  • va_list:一个类型,用于声明一个变量,该变量可以用来遍历函数的参数列表。
  • va_start(ap, last_arg):初始化 va_list 变量 aplast_arg 是最后一个固定参数的名字,ap 将用来遍历所有后续的可变参数。
  • va_arg(ap, type):返回 ap 指向的下一个参数,并将 ap 更新为指向下一个参数的指针。type 参数指定了期望的参数类型。
  • va_end(ap):清理 va_list 变量 ap,结束对可变参数列表的遍历。

现在我们实现一个简单的打印数字的可变参数函数:

void PrintNums(int cnt, ...)
{va_list ap;// 初始化va_start(ap, cnt);// 遍历for (int i = 0; i < cnt; i++){// 遍历参数包中的所有参数int num = va_arg(ap, int);std::cout << num << ' ';}std::cout << std::endl;// 释放va_end(ap);
}int main()
{// 第一个参数代表可变参数的个数PrintNums(5, 1, 2, 3, 4, 5);return 0;
}

最后的输出结果也和我们的预期一致:

1 2 3 4 5


1.3 可变参数的原理

 首先我们传递我们的参数时,是 从右到左依次入栈,如下图:
在这里插入图片描述
注意:在这里不要被图像误导,参数占的空间其实很小,只是为了美观画的大一点

通过查看 va_list 的定义 — typedef char* va_list; ,我们发现其实他就是一个 char* 指针。现在该指针需要指向可变参数的起始部分,所以我们需要传递 cnt 过去,对该指针进行初始化后自然就指向了可变参数的起始地址:
在这里插入图片描述
之后我们取数据的时候告诉指针,这是一个 int 类型,你一次性要取 4 / 8 个字节才是完整的数据。之后该指针一直重复取数据的参数,直至遇到结束条件(在这里是 i == cnt)。

 原理似乎也没有那么复杂,但是当可变参数遇到模板时…


2. C++ 中的可变参数模板

2.1 可变参数模板

 在 C++ 中,可变参数模板允许你定义可以 接受任意数量模板参数的模板函数或模板类。这是通过使用模板参数包(template parameter pack)和函数参数包(function parameter pack)来实现的。

2.2 实现可变参数模板

 可变参数模板实际使用起来是比较别扭的,比如这里我就简单实现一个打印多个类型的函数:

void MyPrint()
{std::cout << std::endl;
}template <class T, class... Args>
void _MyPrint(const T &val, Args... args)
{std::cout << val << ' ';_MyPrint(args...);
}template <class... Args>
void MyPrint(Args... args)
{_MyPrint(args...);
}int main()
{MyPrint(1, 1.2, 'A', "ABCD");return 0;
}

输出结果也没有任何的问题:

1 1.2 A ABCD

这个方案的逻辑是递归的去解析参数包,每次取出一个参数直至参数取为空。

 接下来还有一个方案,实现的方式稍微简单一些:

template <class T>
int _MyPrint(const T& val)
{std::cout << val << " ";return 0;
}template <class... Args>
void MyPrint(Args... args)
{int arr[] = {_MyPrint(args)...};std::cout << std::endl;
}int main()
{MyPrint(1, 1.2, 'A', "ABCD");return 0;
}

当然结果肯定和方案一是一致的,但是我们又该怎么理解呢:

  1. 当我们编译程序时需要为这个数组申请指定大小的空间
  2. 但是怎么获取数组中有多少元素呢
  3. 数组中的函数执行一次就有一个返回值,所以执行多少次就有多少元素
  4. 那么函数具体执行多少次呢
  5. 该函数需要一个参数,所以参数包里的参数数量决定执行次数
  6. 执行该函数时我们就会处理一个参数直至参数被使用完

但是很少有让我们实现可变参数模板的场景,大家当作了解一下。


3. 可变参数模板的应用

 就拿容器 vector 举例子,它常使用 insert,push_back 这两个方法添加元素,为了提高效率通过可变参数模板,新的插入元素的方法 emplace, emplace_back 由此而生。

 他的怎么高效的呢?举个栗子来比较一下:

// 方式一
std::string s = "ABC";
vec.push_back(s);// 方式二
vec.push_back(std::string("ABC"));// 方式三
vec.emplace_back("ABC");

我们在这里来比较三者的效率:

  • 方案一:构造函数 + 拷贝构造
  • 方案二:构造函数 + 移动构造
  • 方案三:构造函数

emplace_back直接在容器内构造对象可以避免多余的复制或移动操作,提高性能。
并且 emplace_back 是兼容 push_back 的使用的,所以在使用时大家尽量使用前者。


4. 总结

 在这篇文中我们首先介绍了在 C 语言 中的可变参数,之后简单讲解了 C++ 中的可变参数模板的使用以及应用。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词