前言
C++知识点全系列文章汇总,欢迎读者阅览:
【C++知识点总结全系列 (01)】:数据类型、数据类型转换和变量
【C++知识点总结全系列 (02)】:C++中的语句、运算符和表达式详细总结
【C++知识点总结全系列 (03)】:函数(函数参数传递、可变参数、函数返回值等详细介绍)
【C++知识点总结全系列 (04)】:C++类的详细总结与分析
【C++知识点总结全系列 (05)】:IO 类的详细总结和分析
【C++知识点总结全系列 (06)】:STL六大组件总结- 配置器、容器、迭代器、适配器、算法和仿函数
【C++知识点总结全系列 (07)】:模板与泛型编程详细总结与分析 ==》 当前位置
【C++知识点总结全系列 (08)】:面向对象编程OOP
================================================================================
1、概述
(1)What(什么是模板、泛型编程)
模板:
模板分为函数模板和类模板,其类内部的类型和函数的形参类型不具体指定,用一个 虚拟的类型来代表,在具体使用的时候在具体化
泛型编程:
以一种独立于任何特定类型的方式编写代码,模板是泛型编程的基础
(2)Why
实现代码的重用
(3)Which
- 模板函数
- 模板类
(4)模板参数
A.What
模板参数是在 C++ 模板中使用的类型或非类型实体的占位符,分为类型模板参数和非 类型模板参数
B.How
template <typename T> T calc(const T&, const T&); //模板的声明
注意:通常一个文件所有模板的声明放在文件的开始位置
C.模板参数的类型成员
- T::value_type():必须显式地告诉编译器该名字是一个类型,且只能使用关键字 typename(而非 class)
D.默认模板参数
与函数默认实参一样,对于一个模板参数,只有当它的右侧都有默认参时,它才可以有默认参数
2、模板函数
#include <sstream>
using namespace std;
template <class T>//T 是类型模板参数
string tTostring(T t)
{
std::ostringstream osstream;
osstream << t;
return osstream.str();
}
template <typename T, typename U>//模板参数列表
auto add(T t, U u){
if constexpr (std::is_same<T, std::string>::value)
return t + tTostring(u);
if constexpr (std::is_same<U, std::string>::value)
return tTostring(t) + u;
if constexpr (std::is_arithmetic<T>::value && std::is_arithmetic<U>::value)
return t + u;
}
3、模板类
(1)How(如何定义和使用模板类)
template <class T>
class Blob {
public:
typedef typename std::vector<T>::size_type size_type;
private:
std::vector<T> *data_; void check(size_type i, const std::string &msg) const; public:
Blob(){ data_ = new std::vector<T>();}
Blob(std::initializer_list<T> il)
{
data_ = new std::vector<T>(il);
}
Blob(const Blob &blob) {//在一个类模板作用域内,可直接使用模板名,而不必指定模板参数
data_ = new std::vector<T>(*blob.getData());
// 也可 blob.data_,在类的成员函数内部,可直接访问同类的其他对象私有成员 }
~Blob()
{
delete data_;
}
std::vector<T> *getData() const
{
return data_;
}
size_type size() const
{
return data_->size();
}
bool empty() const
{
return data_->empty();
}
void push_back(const T &t)
{
data_->push_back(t);
}
void push_back(T &&t)
{
data_->push_back(std::move(t));
}
};
void main()
{
Blob<double> blob; //实例化一个blob对象,用域处理double类型的数据
...
}
(2)成员模板
本身是模板函数的成员函数,成员模板不能是虚函数
4、模板实参和完美转发
(1)模板实参推断
A.What(什么是模板实参推断)
在实例化模板函数或模板类的时候,进行模板实参推断
B.Which(有哪些常见的实参推断)
-
(a) 无法推断时必须显式地指定模板实参类型
template <typename T1, typename T2, typename T3> T1 sum(T2, T3); auto res = sum<long>(i, lng); //实例化:long sum(int, long)
-
(b) 尾置返回类型
template <typename It> ??? &func(It begin, It end) { //TODO:业务逻辑处理 return *begin; //返回序列中的引用 } //为了避免不必要的歧义,一般定义成如下形式 auto &func(It begin, It end) -> decltype(*begin) { //TODO:业务逻辑 return *begin; } auto &str = func(vecStr.begin(),vecStr.end()); auto &i = func(vecInt.begin(), vecInt.end());
-
(c ) STL中的标准类型转换
如下图所示,展示了如何将引用传递改为值传递:
常见的类型转换模板有如下:
-
(d) 实参推断和引用
如果一个函数参数的类型是 constT&,正常绑定规则告诉我们可以传递给它任 意类型的实参—对象、临时变量、字面值常量
从左值引用推断模板参数:
从右值引用推断模板参数:
-
(e) 引用折叠和右值引用参数
- 将左值传递给右值参数引用时(且函数模板参数类型为 T&&),编译器推断模 板类型参数为实参的左值引用类型
下面程序中:T 的类型为 string&,参数类型为 string& &&,根据折叠原则,为 string &
template <class T> void show(T &&t){ cout << t << endl; } string zs = "张三同学"; show(zs);//将左值引用传递给右值引用参数
- 我们不能直接定义一个引用的引用,但是,通过类型别名或模板类型参数可以 间接定义引用的引用;此时会进行引用折叠
X& &、X& &&和X&& &都会折叠成类型X&
类型X&& &&折叠成X&&- 总结:当模板函数的参数为T&&,则可以传递任意类型的实参给它
传递左值时:结果为int& && -> int &
传递右值时:结果为int&& && -> int &&
(3)完美转发
A.What(什么是完美转发)
某些函数将其一个或多个实参连同其类型不变地转发给其它函数,我 们需要保证被转发实参的所有性质,这种保证转发实参所有性质的事情,我们称之为“完美转发”
B.How(如何进行完美转发)
常见的模板函数定义形式如下:
template <typename F, typename T1, typename T2>
auto flip(F f, T1 &&t1, T2 &&t2)
{
return f(t1, t2);
}
假如存在函数func()如下:
int func(int& iVal01, int iVal02)
{
return iVal01 + iVal02;
}
int main()
{
int iVal = 10;
auto res = flip(func, iVal, 30); //res为40
return 1;
}
在上述例子中,T1为int& &&,折叠为int& t1,从而实现了完美转发
C. forward()和move()函数介绍
- forward 可以保持给定实参的左值/右值属性
forward<T>(t); //使用方式,保持t的左值或右值属性
- move函数将左值转换为右值
std::string str = "Hello, ThreeBody!";
std::string &&ref = std::move(str); //将左值强转为右值
(4)可变参数模板
A.What(什么是可变参数模板)
void show() // 终止条件
{
cout << endl;
}
template <typename T, typename... Args> // Args 为模板参数包,表示 0 个或多个模板参数
void show(const T &t, const Args &...args) // args 为函数参数包,表示 0 个或多个函数参数
{
cout << t << " " << endl;
cout << sizeof...(Args) << endl; // 2 1 0 ;sizeof...运算符,返回包中参数的个数 cout << sizeof...(args) << endl; // 2 1 0
show(args...);
}
string zs = "张三";
double defict = 128.1;
int year = 1981;
show(zs, defict, year);
B.包拓展
包拓展就是将它分解成构成元素
C.转发参数包
void show() // 终止条件
{
cout << endl;
}
template <typename T, typename... Args> // Args 为模板参数包,表示 0 个或多个模板参数
void show(T &&t, Args &&...args) // args 为函数参数包,表示 0 个或多个函数参数
{
cout << t << " " << endl;
cout << sizeof...(Args) << endl; // 2 1 0 ;sizeof...运算符,返回包中参数的个数
cout << sizeof...(args) << endl; // 2 1 0
show(std::forward<Args>(args)...);
}
(5)模板特例化
A.What
对特定的模板参数提供定制实现;分为完全特化和偏特化
- 完全特化:所有模板参数都提供定制
- 偏特化:部分模板参数提供定制-只允许类模板偏特化,函数模板不允许
B.Why
为了针对特定类型或条件提供特殊的实现或行为。模板特例化可以在需要时覆盖通用 模板,以针对某些特定情况做出额外的处理或提供定制化的实现
C.How
template <class T1, class T2, class T3>
void display(T1 t1, T2 t2, T3 t3)
{
cout << "通用模板版本 display" << endl;
cout << t1 << "," << t2 << "," << t3 << endl;
}
template <> //完全特例化模板函数 display,函数模板不允许偏特例化 void display(double d, string str, int a)
{
cout << "特化版本 display" << endl;
cout << a << "," << d << "," << str << endl;
}
int main(){
string zs = "张三";
double defict = 128.1;
int year = 1981;
display(defict, zs, year); // 调用特例化版本的
display display(128.1, "李四", 1981); // 调用通用版本的 display
return 0;
}
5、动态内存分配
6、智能指针模板
================================================================================
C++知识点总结全系列文章索引:
【C++知识点总结全系列 (01)】:数据类型、数据类型转换和变量
【C++知识点总结全系列 (02)】:C++中的语句、运算符和表达式详细总结
【C++知识点总结全系列 (03)】:函数(函数参数传递、可变参数、函数返回值等详细介绍)
【C++知识点总结全系列 (04)】:C++类的详细总结与分析
【C++知识点总结全系列 (05)】:IO 类的详细总结和分析
【C++知识点总结全系列 (06)】:STL六大组件总结- 配置器、容器、迭代器、适配器、算法和仿函数
【C++知识点总结全系列 (07)】:模板与泛型编程详细总结与分析 ==》 当前位置
【C++知识点总结全系列 (08)】:面向对象编程OOP