C++11:可变参数模板

简介: C++11:可变参数模板

1、背景

为了解决强类型语言的严格性与灵活性的冲突,将类型抽象

  • 带参数的宏定义(原样替换)
  • 函数重载(函数名字相同,参数不同)
  • 模板(将数据类型作为参数)

语言类型

  • 强类型语言:C++/C/Java,有类型定义,能进行类型安全检查,编译型语言,安全但不灵活
  • 弱类型语言:js/python,没有严格的类型,解释型语言,错误留到运行时,灵活但不安全

2、模板的特征

模板的定义形式

template <class/typename T>

模板的类型

  • 函数模板
  • 类模板

2.1、函数模板

template <模板参数表> //模板参数列表不能为空
 返回类型 函数名(参数列表)
  { 函数体 }
 template <class T>  
 T func(T t)

函数模板与模板函数的关系


函数模板通过模板参数推导(实例化)为模板函数。

// 隐式实例化:编译器自动推导。
 cout << add(i1, i2) << endl; // 由i1,i2都是int,自动生成函数 int add(int,int)
 // 显式实例化
 cout << add<double>(d1, d2) << endl;

函数模板与普通函数间的关系

  • 函数模板可以与普通函数进行重载,而且普通函数优先于函数模板执行
  • 函数模板与函数模板间也是可以进行重载的

模板的特化:统一的函数模板不能适用的类型时,模板在特定类型下的实现

  • 偏特化:特化部分参数。
  • 全特化:特化所有参数。函数模板只有全特化
#include <string.h>
 #include <iostream>
 using std::cout;
 using std::endl;
 template <class T>
 T add(T x, T y)
 {
     cout << "T add(T,T) " << endl;
     return x + y;
 }
 //模板的全特化,是函数模板。也可以函数重载,普通的函数
 template <>
 const char * add<const char *>(const char * px, const char * py)
 {
     char * ptmp = new char[strlen(px) + strlen(py) + 1]();
     strcpy(ptmp, px);
     strcat(ptmp, py);
     return ptmp;
 }
 int main() {
     const char * ps1 = "hello";
     const char * ps2 = "world";
     const char * pstr = add(ps1, ps2);
     cout << pstr << endl;
     delete pstr;
     int i1 = 1, i2 = 2;
     double d1 = 1.1, d2 = 2.2;
     cout << add(i1, i2) << endl;
     cout << add(d1, d2) << endl;
     return 0
 }

模板头文件与实现文件

C++头文件都是使用模板进行编写的,而模板的的特点是必须知道所有实现才能进行正常编译。模板不能将声明与实现分开,模板的类型是inline 函数。模板的运行机制是在编译时,通过实参传递时,进行参数推导。当头文件与实现文件分离时,实现文件中没有函数的调用,所以在编译时并不会进行参数推导,没有函数的产生。

函数声明与实现分离:

#ifndef __ADD_H__
 #define __ADD_H__
 template <class T>
 T add(T x, T y);
 #include "add.tcc" //实现函数声明与实现分离
 #endif

模板的参数类型

  • 类型参数,class T
  • 非类型参数,常量表达式,即整型 bool / char / short / int / long (float, double不行)
#include <iostream>
 using std::cout;
 using std::endl;
 template <class T, int kBase=10> //double报错
 T multiply(T x, T y)
 {
     return x * y * kBase;
 }
 void test0()
 {
     int i1 = 10, i2 = 11;
     //常量的传递是在函数调用时完成的
     cout << multiply<int, 10>(i1, i2) << endl;
     cout << multiply<int, 20>(i1, i2) << endl;
     cout << multiply(i1, i2) << endl; // 若没有给int给默认参数,则无法推导报错
 }
 int main(){
     test0();
     return 0;
 }

成员函数模板

class Point 
 {
 public: 
     template <typename T = int> 
     T func() 
     { 
         return (T)_dx; 
     } 
 private: 
     double _dx; 
     double _dy; 
 };

2.2、类模板

注意类模板的嵌套,一层一层写,成员函数类外定义必须加上模板参数列表。

#include <string>
 #include <iostream>
 using std::cout;
 using std::endl;
 using std::string;
 template <class T, size_t kSize =10>
 class Stack 
 {
 public:
     Stack()
     : _top(-1)
     , _pdata(new T[kSize]())
     {}
     ~Stack();
     T top() const;
     bool empty() const;
     bool full() const;
     void push(const T & t);
     void pop();
 private:
     int _top;
     T * _pdata;
 };
 // 如果是类模板,其成员函数在类外定义时,必须加上模板参数列表
 template <class T, size_t kSize>
 Stack<T,kSize>::~Stack()
 {
     if(_pdata) {
         delete [] _pdata;
     }
 }
 template <class T, size_t kSize>
 T Stack<T, kSize>::top() const
 {
     return _pdata[_top];
 }
 template <class T, size_t kSize>
 bool Stack<T, kSize>::empty() const
 {
     return _top == -1;
 }
 template <class T, size_t kSize>
 bool Stack<T, kSize>::full() const
 {
     return  (kSize - 1) == _top;
 }
 template <class T, size_t kSize>
 void Stack<T, kSize>::push(const T & t)
 {
     if(full()) {
         cout << "stack is full, cannnot push data any more!" << endl;
     } else {
         _pdata[++_top] = t;
     }
 }
 template <class T, size_t kSize>
 void Stack<T, kSize>::pop()
 {
     if(empty()) {
         cout << "stack is empty, no data!" << endl;
     } else {
         --_top;
     }
 }
 void test1()
 {
     Stack<string> stack;
     cout << "此时栈是否为空? " << stack.empty() << endl;
     stack.push(string(2, 'a'));
     cout << "此时栈是否为空? " << stack.empty() << endl;
     cout << stack.top() << endl;
     for(int idx = 1; idx < 11; ++idx) {
         stack.push(string(2, 'a' + idx));
     }
     cout << "此时栈是否已满? " << stack.full() << endl;
     while(!stack.empty()) {
         cout << stack.top() << endl;
         stack.pop();
     }
     cout << "此时栈是否为空? " << stack.empty() << endl;
 }
 int main(void)
 {
     test1();
     return 0;
 }

3、可变模板参数

C++ 11 新特性,对参数进行了高度的泛化,可表示 0 到任意个数、任意类型的参数。

  • 模板参数包 Args:可以接受任意多个参数作为模板参数
template <typename... Args>
  • 函数参数包 args:函数可以接受多个任意类型的参数。要求函数参数包必须唯一,且是函数的最后一个参数。
template<typename... T> 
 void f(T... args)

省略号的作用

  • 位于参数左边:打包... args
  • 位于参数右边:解包args...

获取可变模板参数的个数

sizeof...(Args)
 sizeof...(args)

3.1、可变模板参数的展开

  • 通过递归函数来展开参数包
  • 是通过逗号表达式来展开参数包。

3.1.1、通过递归函数展开参数包

参数包在展开的过程中递归调用自己,每调用一次参数包中的参数就会少一个,直到所有的参数都展开为止,当没有参数时,则调用递归终止函数终止递归过程。

#include <iostream>
 using namespace std;
 // 递归终止函数
 void print() {
     cout << endl;
 }
 // 展开函数
 template <class T, class ...Args>
 void print(T head, Args... rest) {
     cout  << head << " ";
     // 调用过程,对参数包的展开过程
     print(rest...);
 }
 // 递归终止函数
 template<typename T>
 T sum(T t) {
     return t;
 }
 // 展开函数
 template<typename T, typename ... Types>
 T sum(T first, Types ... rest) {
     return first + sum<T>(rest...);
 }
 int main() {
     print(1, 2, 3, "hello", "world");
     cout << sum(1, 2, 3, 4) << endl; //10
     return 0;
 }

3.1.2、通过逗号表达式展开参数包

逗号表达式:表达式1, 表达式2,先执行表达式1,再执行表达式2,返回的结果只与表达式2有关。利用初始化列表来初始化变长数组,在数组构造的过程中展开参数。

#include <iostream>
 using namespace std;
 // 1、处理参数包中每一个参数的函数
 template <class T>
 void printarg(T t) {
     cout << t << endl;
 }
 // 2、展开参数包
 template <class ...Args>
 void expand(Args... args) {
     // 先执行printarg(args)展开参数包并打印参数,再执行逗号表达式得到结果0
     // 1、int arr[] = {(printarg(1), 0), (printarg(2), 0), (printarg(3), 0)} 
     // 2、int arr[] = {0, 0 ,0}
     int arr[] = {(printarg(args), 0)...};
 }
 int main() {
     expand(1, "hello", 3);
     return 0;
 }

将函数作为参数,改写成 lambda 表达式

#include <iostream>
 using namespace std;
 template<class F, class... Args>
 void expand(const F& f, Args&&...args) {
     //std::initializer_list 接收任意长度的初始化列表,这里用到了完美转发
     initializer_list<int>{(f(std::forward< Args>(args)), 0)...};
 }
 int main() {
     // 只能参数列表中类型的参数
     expand([](int i) { cout << i << endl; }, 1, 2, 3);
     return 0;
 }
相关文章
|
19天前
|
编译器 C语言 C++
c++的学习之路:19、模板
c++的学习之路:19、模板
32 0
|
1月前
|
存储 C++ 容器
C++STL(标准模板库)处理学习应用案例
【4月更文挑战第8天】使用C++ STL,通过`std:vector`存储整数数组 `{5, 3, 1, 4, 2}`,然后利用`std::sort`进行排序,输出排序后序列:`std:vector<int> numbers; numbers = {5, 3, 1, 4, 2}; std:sort(numbers.begin(), numbers.end()); for (int number : numbers) { std::cout << number << " "; }`
21 2
|
2天前
|
存储 算法 编译器
C++的模板与泛型编程探秘
C++的模板与泛型编程探秘
8 0
|
3天前
|
编译器 C++
【C++从练气到飞升】08---模板
【C++从练气到飞升】08---模板
|
4天前
|
算法 编译器 C++
【C++入门到精通】新的类功能 | 可变参数模板 C++11 [ C++入门 ]
【C++入门到精通】新的类功能 | 可变参数模板 C++11 [ C++入门 ]
20 1
|
5天前
|
编译器 C语言 C++
【C++】模板进阶
【C++】模板进阶
12 1
|
5天前
|
存储 编译器 C++
【C++】内存管理和模板基础(new、delete、类及函数模板)
【C++】内存管理和模板基础(new、delete、类及函数模板)
20 1
|
10天前
|
C++
【期末不挂科-C++考前速过系列P6】大二C++实验作业-模板(4道代码题)【解析,注释】
【期末不挂科-C++考前速过系列P6】大二C++实验作业-模板(4道代码题)【解析,注释】
【期末不挂科-C++考前速过系列P6】大二C++实验作业-模板(4道代码题)【解析,注释】
|
11天前
|
存储 算法 C++
详解C++中的STL(标准模板库)容器
【4月更文挑战第30天】C++ STL容器包括序列容器(如`vector`、`list`、`deque`、`forward_list`、`array`和`string`)、关联容器(如`set`、`multiset`、`map`和`multimap`)和容器适配器(如`stack`、`queue`和`priority_queue`)。它们为动态数组、链表、栈、队列、集合和映射等数据结构提供了高效实现。选择合适的容器类型可优化性能,满足不同编程需求。
|
13天前
|
运维 Serverless Go
Serverless 应用引擎产品使用之在阿里云函数计算中c++模板,将编译好的C++程序放进去部署如何解决
阿里云Serverless 应用引擎(SAE)提供了完整的微服务应用生命周期管理能力,包括应用部署、服务治理、开发运维、资源管理等功能,并通过扩展功能支持多环境管理、API Gateway、事件驱动等高级应用场景,帮助企业快速构建、部署、运维和扩展微服务架构,实现Serverless化的应用部署与运维模式。以下是对SAE产品使用合集的概述,包括应用管理、服务治理、开发运维、资源管理等方面。
12 1