C++11 模板机制:
① 函数模板
② 类模板
模板的使用:
① 范围:模板的声明或定义只能在全局或类范围进行,不可以在局部范围(如函数)
② 目的:为了能够编写与类型无关的代码
函数模板:
- 格式:
template <类型形参表或成模板参数列表> //类型函数声明
返回类型 函数名 (形参表) {
函数体
}
- 注意:
模板参数表:定义在类或函数定义中用到的类型或值
类型形参表:可包含基本数据类型和类类型。类型形参需要加class/typename。
其中class和typename等价
T-通用的数据类型,名称可替换,通常为大写
类模板:
- 格式:
template <类型形参表> //类型参数声明
class 类名 {
类模板的代码
}
template <类型参数表> //定义在类模板之外的函数必须以关键字template开始
返回类型 类名 类型名表::成员函数n(形参表)
{
成员函数定义体
}
一、模板的优化
1.模板的右尖括号
/*
1.模板的右尖括号
*/
#include
#include
using namespace std;
// 类模板Base
template
class Base {
public:
// 遍历容器的操作函数traversal()
void travelsal(T& t) {
auto it = t.begin();
for(;it!=t.end();++it) {
cout<<*it<<" ";
}
cout << endl;
}
};
int main() {
vector v{1,2,3,4,5,6,7,8,9};
Base> b;
b.travelsal(v);
return 0;
}
① c++98标准下的编译
g++ -std=c++98 4.模板的优化.cpp -o app
② c++11标准下的编译
g++ -std=c++11 4.模板的优化.cpp -o app
2.默认模板参数
在C++98/03标准中
① 类模板可以有默认的模板参数
② 不支持函数的默认模板参数
① 在C++98/03标准中,类模板可以有默认的模板参数
#include
#include
using namespace std;
// 2.默认模板参数
/*
在C++98/03标准中
① 类模板可以有默认的模板参数
② 不支持函数的默认模板参数
*/
// ① 类模板可以有默认的模板参数
template
class Test{
public:
void printString() {
cout<< "current value: " << t < t;
t.printString();
Test t1;
t1.printString();
return 0;
}
总结: 对于类模板,哪怕所有参数都有默认参数,在使用时也必须在模板名后跟随<>来实例化
② 在C++98/03标准中,不支持函数的默认模板参数
#include
#include
using namespace std;
// 2.默认模板参数
/*
在C++98/03标准中
① 类模板可以有默认的模板参数
② 不支持函数的默认模板参数
*/
// ② 不支持函数的默认模板参数
template // C++98/03不支持这种写法, C++11中支持这种写法
void print(T t) {
cout<< "current value: " << t <
c++11支持对函数模板的默认参数
总结:当所有模板参数都有默认模板,函数模板的调用如同一个普通函数。
③ C++11 结合默认模板参数和模板参数自动推导
#include
#include
using namespace std;
template
R test(N arg) {
return arg;
}
int main() {
auto ret1 = test(666);
cout<<"return value-1: "<(54.321);
cout<<"return value-2: "<(54.321);
cout<<"return value-3: "<(100);
cout<<"return value-4: "<
④ C++11 同时使用默认模板参数和模板参数自动推导
#include
#include
using namespace std;
// 函数的定义
template
void func(T arg1 = 99,U arg2 = 99) {
cout<<"arg1: "<
func();编译出错是必须确定出T的数据类型,才能使用。
总结:
① 若可以推导出参数类型,就使用推导出的类型
② 若函数模板无法推导出参数类型,则编译器会使用默认模板参数
③ 若无法推导出模板参数 且 没有设置默认模板参数,编译器会报错
概括:
① 模板参数类型的自动推导是根据模板函数调用时指定的实参进行推导的,无实参则无法推导
② 模板参数类型的自动推导不会参考函数模板中指定的默认参数
⑥ 函数模板使用时,自动类型推导,必须推导出一直的数据类型T,才可以使用
#include
using namespace std;
template
T add(T a,T b) {
return a + b;
}
// 1.自动类型推导,必须推导出一致的数据类型T,才可以使用
int main() {
int a=1;
int b=2;
char c='c';
add(a,b);//正确,可以推导出一致T
add(a,c);//错误,推导不出一致的T
}
3.类模板
类模板:
类模板允许用户为类定义一种模式,使得类中的某些数据成员、成员函数的参数或成员函数
的返回值能取任意类型。类模板的成员函数被认为是函数模板。
template <类型形参表> //类型参数声明
class 类名 {
类模板的代码
}
template<类型形参表> //定义在类模板之外的函数必须以关键字template开始
返回类型 类名 类型名表::成员函数n(形参表) {
成员函数定义体
}
① 实例化类模板
1.实例化类模板
① 类模板不能直接使用,必须先实例化为相应的模板类。
② 定义类模板之后,创建模板类:
类模板名<类型实参表> 对象表;
类型实参表与类模板中的类型形参表相匹配
#include
using namespace std;
/*
实例化类模板
① 类模板不能直接使用,必须先实例化为相应的模板类。
② 定义类模板之后,创建模板类:
类模板名<类型实参表> 对象表;
类型实参表与类模板中的类型形参表相匹配
*/
template
class Student{
public:
T1 name;//名字
T2 age;//年龄
Student(T1 k,T2 v) : name(k),age(v) {};
// 重载操作符:operator ,对小于号< 进行重载
bool operator < (const Student& p) const;
};
template
bool Student::operator < (const Student& p) const {
// Pair的成员函数 operator< "小"的意思是关键字小
return age < p.age;
}
int main() {
Student stu1("Sakura",8);//实例化出一个类Pair
cout <<"My name is " << stu1.name << ",and age is " << stu1.age < stu2("Luffy",14);//实例化出一个类Pair
cout <<"My name is " << stu2.name << ",and age is " << stu2.age <
// 2.类模板实例
#include
#include
#include
using namespace std;
template
class Array { //类模板
int size;
T* p;
public:
Array();//默认构造函数
Array(int n);//重载构造函数
Array(initializer_list lst);
T& operator[](int) ;//[]重载函数
int getSize();//获取数组大小
};
// 默认构造函数
template
Array::Array() {
size = 10;
p = new T[size];
}
// 重载构造函数
template
Array::Array(int n) {
size = n;
p = new T[size];
}
// 下标运算符重载函数
template
T& Array::operator[](int i) {
if(i>=0 && i
Array::Array(initializer_list lst){
size = lst.end() - lst.begin();
p = new T[size];
//cout<<"size大小:"<
int Array::getSize(){
cout<<"获取size:"< arr1(2);
arr1[0] = 520;
arr1[1] = 1314;
Array arr2({1,2,3,5});
cout<<"==============打印arr1数组=============="< arr3({a,b,c});
size = arr3.getSize();
for (int i = 0; i < size; i++) {
cout<
学习initializer_list,可以看这篇博客
C++ 函数传递多参数处理 可变参数模板_c++ 可变参数传递_Mr.禾的博客-CSDN博客https://blog.csdn.net/qq_24447809/article/details/114793958
#include
#include
#include
using namespace std;
template
class Array { //类模板
int size;
T* p;
public:
Array();//默认构造函数
Array(int n);//重载构造函数
Array(initializer_list lst);
T& operator[](int) ;//[]重载函数
int getSize();//获取数组大小
};
// 默认构造函数
template
Array::Array() {
size = 10;
p = new T[size];
}
// 重载构造函数
template
Array::Array(int n) {
size = n;
p = new T[size];
}
// 下标运算符重载函数
template
T& Array::operator[](int i) {
if(i>=0 && i
int Array::getSize(){
cout<<"获取size:"<
Array::Array(initializer_list lst){
size = lst.end() - lst.begin();
p = new T[size];
//cout<<"size大小:"< arr1(4);
int size = arr1.getSize();
Student x(1, "Tom"), y(2, "Jerry"), z(3, "Sakura");
Student other = x;
arr1[0] = x; arr1[1] = y; arr1[2] = z,arr1[3] = other;
cout<<"================arr1 数组================"< arr2({x,y,z,other,x});
size = arr2.getSize();
for (int i=0;i < size;i++) {
arr2[i].display();
}
return 0;
}
② 类模板作为函数参数
#include
using namespace std;
//类模板作为函数参数
template
class A {
T x;
public:
A(T a) {
x = a;
}
T abs() {
if(x<0) return -x;
else return x;
}
};
//函数模板中模板类作为形参
template
void func(A x) {
cout< a(-5);
A b(-5.8);
func(a);
func(b);
}
③ 函数模板作为类模板的成员
类模板中的成员函数可以是一个函数模板
// 函数模板作为类模板的成员
#include
using namespace std;
template
class A{
public:
template
void print(T2 t) {cout< a;
//成员函数模板func被实例化
a.print('K'); //K
cout<
④ 类模板中也可以使用非类型参数,即值参数
#include
using namespace std;
template
class A{
public:
T sum(T a) {
return a+size;
}
};
int main(){
A a;
cout<
⑤ 类模板的友元函数
template
class A{
public:
T value;
A() {}
A(T v):value(v){}
friend void func1();//与参数类型无关的友元函数
friend void func2(A& a);//与参数类型有关的友元函数
};
template
void func1(){
cout<<"func1"<
void func2(A& a){
cout<<"func2 value:" < a(1.2);
func1();//func1是模板类A的友元函数
func1();//func1是模板类A的友元函数
func2(a);//func2是模板类A的友元函数
}
1.声明一个参数包T...args,这个参数包中可以包含0到任意个模板参数;
2.在模板定义的右边,可以将参数包展开成一个个独立的参数
3.参数args前面有省略号,所以它就是一个可变模板参数
参数包:带省略号的参数,包含了0到N(N>=0)个模板参数
学习难点:
① 无法直接获取参数包中的每个参数
② 只能通过展开参数包的方式来获取参数包中的每个参数
对比可变模板参数和普通的模板参数:
① 参数语义是一致的。可以应用于函数和类,即可变模板参数函数和可变模板参数类
② 模板函数不支持偏特化。所以 可变模板参数函数 和 可变模板参数类 展开
可变模板参数方法不尽相同
4.可变参数模板
可变参数模板:
① C++11增强了模板功能,允许模板定义中包含0到任意个模板参数
② 对参数进行了高度泛化,能表示任意个数,任意类型的参数
③ 是一个接收可变数目参数的模板函数或模板类
④ 可变数目的参数被称为参数包
语法:
template
void f(T... args);
声明一个参数包T... args,这个参数包中可以包含0到任意个模板参数
① 可变参数模板例子
/*
可变参数模板:
① C++11增强了模板功能,允许模板定义中包含0到任意个模板参数
② 对参数进行了高度泛化,能表示任意个数,任意类型的参数
③ 是一个接收可变数目参数的模板函数或模板类
④ 可变数目的参数被称为参数包
语法:
template
void f(T... args);
声明一个参数包T... args,这个参数包中可以包含0到任意个模板参数
*/
#include
#include
② 可变参数模板的展开
>>了解模板函数和函数模板
函数模板:是一个模板,使用通用类型参数,不能直接执行;
模板函数:是一个具体执行的函数,由编译系统在遇到具体函数调用时生成,可执行。
>>函数模板实例化为模板函数
函数模板:
template //T 的名字也为其他
T abs(T x)
{
if (x < 0) return -x;
return x;
}
执行cout << abs(-1) << endl;语句,调用函数模板生成模板函数
模板函数:
int abs(int x)
{
if(x<0) return -x;
return x;
}
>>>>>>>>>>>>进入正题>>>>>>>>>>>>
可变参数模板的展开:通过展开参数包的方式来获取参数包中的每个参数
两种展开方式:
① 通过递归函数来展开参数包
② 通过逗号表达式来展开参数包
① 打印变参数的个数
#include
using namespace std;
// 一个简单的可变模板参数函数
template
void f(T... args) {
cout<
这个例子只是简单的将可变模板参数的个数打印出来,可见可变模板参数的类型和个数是不固定的,所以可以传任意类型和个数的参数给函数f。
>>>>>>>>>>>>接下来介绍可变参数模板的展开的两种方式>>>>>>>>>>>>
② 可变参数模板的展开的两种方式
可变参数模板的展开:通过展开参数包的方式来获取参数包中的每个参数
两种展开方式:
① 通过递归函数来展开参数包
② 通过逗号表达式来展开参数包
递归函数方式展开参数包:这个例子会输出每一个参数,直到为空时输出empty。
展开参数包的函数有两个:
① 递归函数
② 递归终止函数
参数包Args...在展开的过程中递归调用自己,每调用一次参数包中的参数就会少一个,直到所有的参数都展开为止,当没有参数时,则调用非模板函数print终止递归过程。
【注意】必须要一个重载的递归终止函数,即必须要有一个同名的终止函数来终止递归
/*
可变参数模板的展开:通过展开参数包的方式来获取参数包中的每个参数
两种展开方式:
① 通过递归函数来展开参数包
② 通过逗号表达式来展开参数包
*/
#include
using namespace std;
//递归终止函数
void print()
{
cout << "empty!!!" << endl;
}
//展开函数
template
void print(T head,Args... rest) {
cout<<"param:"<
递归调用的过程:
print(1,2,3,4);
print(2,3,4);
print(3,4);
print(4);
print();
上面的递归终止函数还可以写成这样:
template
void print(T t) {
cout << t << endl;
}
当参数包展开到最后一个参数时递归为止
print(1,2,3,4);
print(2,3,4);
print(3,4);
print(4);
③ 可变模板参数求和
#include
using namespace std;
template
T sum(T t){
return t;
}
template
T sum (T first, Types ... rest) {
return first + sum(rest...);
}
int main(void)
{
cout<
【思考】递归函数展开包虽是一种标准做饭,但是有一个缺点,那就是必须要一个重载的递归终止函数,
即必须要有一个同名的终止函数来终止递归,不是很方便,有没有更简单的方式呢?
【回答】有的,下文介绍的方法可以不通过递归方式来展开参数包
④ 逗号表达式展开参数包
逗号表达式展开参数包
① 可以不通过递归方式来展开参数包
② 需要借助逗号表达式和初始化列表
这个例子将分别打印出1,2,3,4四个数字,使用了逗号表达式展开参数包
① 无需通过递归终止函数,是直接在expand函数体中展开。
② printArg不是一个递归终止函数,只是一个处理参数包中每一个参数的函数
#include
using namespace std;
//逗号表达式展开参数包
template
void printArg(T t)
{
cout << t << " ";
}
template
void expand(Args... args)
{
int arr[] = {(printArg(args), 0)...};
}
int main(){
expand(1,2,3,4);
cout<
【关键】这种就地展开参数包的实现方式的关键是 逗号表达式。
【了解】逗号表达式
d = (a = b, c);
这个表示会按顺序执行:b 先赋值给 a,括号中的表达式返回 c 的值,因此 d 等于 c
expand函数中的逗号表达式:(printArg(args,0)),也是按照这个执行顺序。
① 先执行printArg(args),再得到逗号表达式的结果0
② {(printArg(args),0)...},使用了C++11的另一特性----初始化列表,
用来初始化一个变长数组,{(printArg(args),0)...} 将会展开成
((printArg(arg1),0), (printArg(arg2),0), (printArg(arg3),0), etc... ),
最终会创建一个元素值都为0的数组 int arr[sizeof...(Args)]。
③ 由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printArg(args)
打印出参数
④ 也就是说在构造 int 数组的过程就将参数包展开了
但上面这个数组的目的纯粹视为了在数组构造过程中展开参数包。那么改进一下,将函数作为参数,就可以支持lambda表达式了,也可以少些一个递归终止函数了。
#include
#include
using namespace std;
templatevoid expand(const F& f,Args&&... args) {
// 这里用到了完美转发
initializer_list{(f(std::forward(args)),0)...};
}
int main() {
//expand(1,2,3,4);
expand([](int i){
cout<
若使用C++14的新特性泛型lambda表达式,可以写更泛化的lambda表达式
expand([](auto i){
cout<
5.可变模板参数类
可变参数模板类式一个待可变模板参数的模板类,如C++11中的元祖 std::tuple 就是一个可变模板类。定义如下:
template< class... Types >
class tuple;
这个可变参数模板类可以携带任意类型任意个数的模板参数:
std::tuple tp1 = std::make_tuple(1);
std::tuple tp2 = std::make_tuple(1, 3.5);
std::tuple tp3 = std::make_tuple(1, 3.5, "heheda");
其参数个数可以为0个:
std::tuple<> tp;
可变参数模板类的参数包展开方式 和 可变参数模板函数的展开方式的区别:
① 可变参数模板的参数包展开需要通过模板特化 和 继承方式 去展开
② 展开方式比可变参数模板函数更复杂
(一)模板特化展开参数包
① 模板偏特化和递归方式来展开参数包
这个Sum类的作用是在编译期计算出参数包中参数类型的size之和,通过Sum
// 前向声明 作用:声明这个Sum类是一个可变参数模板类
template
struct Sum;
//基本定义 作用:定义了一个部分展开的可变参数模板类别,告诉编译器如何递归展开参数包
template
struct Sum {
enum{value = Sum::value + Sum::value};
};
//递归终止 作用:特化的递归终止类,通过这个特化的类来终止递归
template
struct Sum{
enum{value = sizeof(Last)};
};
// 这个Sum类的作用是在编译期计算出参数包中参数类型的size之和,通过
// Sum::value就可以获取这3个类型的size之和为14
int main() {
int num = Sum::value;
cout<
有时候0个模板参数没有意义,所以对于可变参数模板中的模板参数可以限定模板参数不能为0个,可将前向声明去掉。
这种方式只要一个基本的模板类定义和一个特化的终止函数就行了,而且限定了模板参数至少有一个
//基本定义 作用:定义了一个部分展开的可变参数模板类别,告诉编译器如何递归展开参数包
template struct Sum{
enum{value = Sum::value + Sum::value};
};
//递归终止 作用:特化的递归终止类,通过这个特化的类来终止递归
template
struct Sum{
enum{value = sizeof(Last)};
};
// 这个Sum类的作用是在编译期计算出参数包中参数类型的size之和,通过
// Sum::value就可以获取这3个类型的size之和为14
int main() {
int num = Sum::value;
cout<
(1)在展开到最后两参数时终止
templatestruct Sum;
template
// 在展开到最后两个参数时终止
struct Sum {
enum{ value = sizeof(First) + sizeof(Last)};
};
// 这个Sum类的作用是在编译期计算出参数包中参数类型的size之和,通过
// Sum::value就可以获取这3个类型的size之和为14
int main() {
int num = Sum::value;
cout<
(2)可以在展开到0个参数时终止
templatestruct Sum;
// 还可以在展开到0个参数时终止
template<>struct Sum<> {
enum{ value = 520 };
};
// 这个Sum类的作用是在编译期计算出参数包中参数类型的size之和,通过
// Sum::value就可以获取这3个类型的size之和为14
int main() {
int num = Sum<>::value;
cout<
(二)继承方式展开参数包
待续...
学习和参考一下文章:
模板的优化 | 爱编程的大丙 (subingwen.cn)https://subingwen.cn/cpp/template/C++11 模板用法总结_herryone123的博客-CSDN博客https://blog.csdn.net/kenjianqi1647/article/details/118207899
泛化之美--C++11可变模版参数的妙用 - qicosmos(江南) - 博客园 (cnblogs.com)https://www.cnblogs.com/qicosmos/p/4325949.html