您的位置:首页 > 财经 > 金融 > 中国服装设计网站_河南省十大互联网企业_seo深圳网络推广_百度客服人工

中国服装设计网站_河南省十大互联网企业_seo深圳网络推广_百度客服人工

2025/4/29 7:41:53 来源:https://blog.csdn.net/tan_run/article/details/147550353  浏览:    关键词:中国服装设计网站_河南省十大互联网企业_seo深圳网络推广_百度客服人工
中国服装设计网站_河南省十大互联网企业_seo深圳网络推广_百度客服人工

📝前言:
这篇文章我们来讲讲C++11——可变参数模板

🎬个人简介:努力学习ing
📋个人专栏:C++学习笔记
🎀CSDN主页 愚润求学
🌄其他专栏:C语言入门基础,python入门基础,python刷题专栏,Linux


文章目录

  • 一,什么是可变参数模板
  • 二,基本语法
  • 三,可变参数模板的使用
    • 参数包作为整体
    • 包展开
      • 错误示范1(不能args[i])
      • 普通一次展开
      • 递归展开
      • 错误示范2(if运行时判断)
          • if constexpr 编译时判断解决问题
      • 非递归展开

一,什么是可变参数模板

什么是可变?
可变就是可以不同。可变参数,即:参数类型可变,参数个数可变。

  • C++11之前,类模板和函数模板中只能包含固定数量的模板参数
  • C++11新增了可变参数模板,让我们可以对模板的参数高度泛化,即:可以直接传一个参数包给模板参数,这个参数包可以接受任意数量和任意类型的参数。

二,基本语法

使用 typename...class... 声明模板参数包,...就代表是一个参数包:

template<typename T, typename... Args>
void myFunc(T first, Args... rest) { /*...*/ } // 使用模板的参数包
  • Args 是模板的类型参数包,表示接收一组类型
  • Args... rest 是函数参数包,表示使用Args类型,接收一组参数(后面要跟...)。它将模板参数包展开为一组函数参数
  • Args只是参数包的名字,可以取其他名字
  • 参数包代表可接收0个或多个参数

三,可变参数模板的使用

参数包作为整体

我们在使用时,可以往myFunc里传入不同个数和不同类型的参数。
编译器在编译的过程中,根据myFunc传入的不同参数,实例化出带有不同参数的myFunc函数
示例:

template<class ...Args>
void Myfunc(Args...args)
{cout << sizeof...(args) << endl;
}int main()
{int x1 = 1;double x2 = 2.2;std::string s1 = "hello world";Myfunc(x1);  // 输出:1Myfunc(x1, x2);  // 输出:2Myfunc(x1, x2, s1);  // 输出:3return 0;
}

说明:

  • 可以理解为,在编译的时候,编译器根据传入的不同参数的Myfunc实例化出了三个:Myfunc(int x1)Myfunc(int x1, int x2)Myfunc(int x1, int x2, string s1)
  • sizeof...(args)sizeof...是一个整体运算符,用来求函数参数包中参数的个数,这里的args就是一个整体,即:一组参数。

包展开

对于一个参数包,我们可以把他当做一个整体进行使用,如上面的sizeof...(args),我们就是直接把args当一个整体进行使用

如果我们想要拿到里面的每一个参数就需要用到包展开。直接将参数包依次展开依次作为实参给⼀个函数去处理。
【注意,参数包可不能args[i]这样下标访问使用。因为:参数包是在编译时就确定的一组参数。在编译时,编译器会根据具体的模板实例化来处理参数包,而不像数组那样在运行时存在于内存中】


错误示范1(不能args[i])

假设你想打印出函数参数包里面的所有参数。

template<class ...Args>
void Print(Args...args)
{cout << args... << endl;
}

你这样肯定不行,会报错:“args”: 未声明的标识符。因为参数列表里面args在编译时早就被具体实例化成了有具体类型的函数了,如Print(1.1, 2)就被实例化成:Print(double x1, int x2);


普通一次展开

在使用递归展开的时候,我们先来看一个简单的展开:

template<class T1, class T2, class...Args>
void ShowList(T1 x1, T2 x2, Args...args)
{cout << x1 << endl;cout << x2 << endl;cout << sizeof...(args) << endl;
}int main()
{ShowList(1, 2.2, 3.3, "hello world");return 0;
}

输出结果:
在这里插入图片描述

在这里,我们传入一组参数,其中12.2分别给了x1x2,后面的3.3"hello world"被打包给了args


递归展开

写法三大要点:

  • 递归终止函数
  • 递归的包展开函数,参数列表为:(单个参数,参数包)【这样原来传入的参数包,第一个参数会被提出来,第2 - n个参数会被作为新参数包】。然后在这个函数内,就可以对这个提出来的参数进行操作
  • 外壳函数,即:在递归函数外面封一层,用于把参数包传给递归包展开函数

示例:
Print 打印出函数参数包里面的所有参数

// 递归终止函数:当参数列表为空时匹配这个函数
void ShowList()
{cout << endl;
}// 递归包展开函数
template<class T, class...Args>
void ShowList(T x, Args...args)
{cout << x << " "; // 打印被展开的单个参数ShowList(args...);
}template<class...Args>
void Print(Args... args)
{ShowList(args...);
}int main()
{Print(1, string("xxxxx"), 2.2);return 0;
}
  • args......是包展开操作符
  • 在调用到ShowList的时候:最少传入一个参数(因为有个 T x
  • 当没有参数传入的时候,就会匹配到ShowList()结束

运行结果:

1 xxxxx 2.2

具体编译时实例化理解图:
在这里插入图片描述
当然也可以编写有参的结束函数:

//递归终止函数
template<class T>
void ShowList(T x)
{cout << x << endl;
}

ShowList参数个数为1的时候编译器会找最匹配的,也就是调到这个ShowList(T x),然后执行完以后,ShowList(T x)里面没有再调用ShowList于是完成终止


错误示范2(if运行时判断)

那在递归函数里面,利用sizeof...(args) == 0来终止可以吗?
答案是不可以!!!

template<class T, class ...Args>
void ShowList(T value, Args... args)
{cout << value << " "; //打印传入的若干参数中的第一个参数if (sizeof...(args) == 0){return;}ShowList(args...);    //将剩下参数继续向下传
}int main()
{ShowList(1, 2.2, 3.3, "hello world");return 0;
}

报错:“ShowList”: 未找到匹配的重载函数

为什么 if (sizeof...(args) == 0) 无法终止递归(以下内容由AI生成):

1. sizeof...(args) 是编译期常量,但 if 是运行时判断

  • sizeof...(args) 的值在编译时就已经确定(比如 args 有 3 个参数时,sizeof...(args) 就是 3)。
  • if (sizeof...(args) == 0) 这个 if 语句本身是运行时执行的,编译器仍然会 生成所有分支的代码,包括 ShowList(args...) 的调用。

2. 编译器必须实例化 ShowList(args...)
即使 if 条件在运行时是 false,编译器仍然要 确保 ShowList(args...) 的调用是合法的,因为:

  • C++ 的模板实例化规则要求 所有可能调用的模板函数都必须有定义
  • 即使 if 条件保证 ShowList(args...) 不会执行,编译器仍然会 尝试实例化它

3. 当 args... 为空时,ShowList() 无匹配版本

  • 当递归到最后一步,args... 为空时,ShowList(args...) 就变成了 ShowList()
  • 但代码 没有定义 ShowList() 这个无参数版本,我们的ShowList(T value, Args... args)args可以为空,但是value必须给。所以编译器报错: error: no matching function for call to 'ShowList()'

解决方法:

方法1 : 像之前介绍的一样,额外写第一个终止函数。

if constexpr 编译时判断解决问题

方法 2:用 if constexpr(C++17 编译时判断)【已验证有效】

template<class T, class... Args>
void ShowList(T value, Args... args) {cout << value << " ";if constexpr (sizeof...(args) > 0) {  // 编译时判断ShowList(args...);               // 仅在 args 非空时实例化}// 否则自动终止
}
  • if constexpr 是编译期判断,不会生成无效的 ShowList() 调用。

非递归展开

示例:

template <class T>
const T& GetArg(const T& x) {cout << x << " ";  // 打印参数return x;          // 返回原参数(保证类型不变)
}template <class ...Args>
void Arguments(Args... args) {}  // 空函数,仅用于接收展开后的参数包template <class ...Args>
void Print(Args... args) {Arguments(GetArg(args)...);  // 关键:展开参数包并调用 GetArg 处理每个参数
}
int main()
{Print(1, 2.2, 3.3, "hello world");return 0;
}

  • GetArg参数包展开及使用函数

    • GetArg(args)......让编译器遍历args的每一个参数,并对 每一个参数 调用 GetArg,相当于: Arguments(GetArg(arg1), GetArg(arg2), ..., GetArg(argN));
    • GetArg必须要有返回值,用于让Arguments 接收到参数
  • Arguments 空函数

    • 仅用于 接收展开后的参数包,确保 GetArg(args)... 能正确展开。
    • 在 C++ 中,参数包展开GetArg(args)...必须发生在合法的上下文环境中,Arguments(GetArg(args)...) 提供的就是这样一个上下文。【可以理解为,就需要一个能接受参数包的容器来接收GetArg(args)...的返回值】

🌈我的分享也就到此结束啦🌈
要是我的分享也能对你的学习起到帮助,那简直是太酷啦!
若有不足,还请大家多多指正,我们一起学习交流!
📢公主,王子:点赞👍→收藏⭐→关注🔍
感谢大家的观看和支持!祝大家都能得偿所愿,天天开心!!!

版权声明:

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

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