
🎉作者简介:👓 博主在读机器人研究生,目前研一。对计算机后端感兴趣,喜欢 c + + , g o , p y t h o n , 目前熟悉 c + + , g o 语言,数据库,网络编程,了解分布式等相关内容 \textcolor{orange}{博主在读机器人研究生,目前研一。对计算机后端感兴趣,喜欢c++,go,python,目前熟悉c++,go语言,数据库,网络编程,了解分布式等相关内容} 博主在读机器人研究生,目前研一。对计算机后端感兴趣,喜欢c++,go,python,目前熟悉c++,go语言,数据库,网络编程,了解分布式等相关内容
📃 个人主页: \textcolor{gray}{个人主页:} 个人主页: 小呆鸟_coding
🔎 支持 : \textcolor{gray}{支持:} 支持: 如果觉得博主的文章还不错或者您用得到的话,可以免费的关注一下博主,如果三连收藏支持就更好啦 \textcolor{green}{如果觉得博主的文章还不错或者您用得到的话,可以免费的关注一下博主,如果三连收藏支持就更好啦} 如果觉得博主的文章还不错或者您用得到的话,可以免费的关注一下博主,如果三连收藏支持就更好啦👍 就是给予我最大的支持! \textcolor{green}{就是给予我最大的支持!} 就是给予我最大的支持!🎁
💛本文摘要💛
本专栏主要是对c++ primer这本圣经的总结,以及每章的相关笔记。目前正在复习这本书。同时希望能够帮助大家一起,学完这本书。 本文主要讲解第16章 模板与泛型编程
c++ primer 第五版 系列文章:可面试可复习第2章 变量和基本类型
第3章 字符串、向量和数组
第4章 表达式
第5章 语句
第6章 函数
第8章 IO库
第9章 顺序容器
第10章 泛型算法
第11章 关联容器
第12章 动态内存
第13章 拷贝控制
第 14章 重载运算符
第15章 面向对象程序设计
第 16章 模板与泛型编程
面向对象编程和泛型编程都能处理在编写程序时不知道类型的情况
在编译时就能知道类型了。容器、迭代器、泛型算法都是泛型编程的例子。模板是泛型编程的基础,一个模板就是一个创建类或函数的蓝图。模板定义以关键字 template 开始,后跟一个用 <> 包围,用逗号分隔的模板参数列表。
在模板定义中,模板参数列表不能为空
模板参数列表类似函数列表,需要在类或函数定义中用到的类型和值。使用模板时,需要指定模板实参,将其绑定到模板参数上。
'普通函数'
int compare(const string &v1, const string &v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
'模板'
template <typename T>
bool compare(const T &v1, const T &v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
实例化函数模板
编译器用函数实参来推断模板实参,然后实例化出一个特定版本的函数。//实例化出int compare(const int &, const int &)
cout << compare(1, 0) << endl; //T为int
//实例化int compare(const vector &, const vector &)
vector<int> vec1{1, 2, 3}, vec{4, 5, 6};
模板类型参数
类型参数可以用来指定返回类型或函数的参数类型模板参数前必须使用关键字 class 或 typename,两个含义相同。'正确'
template <typename T, class U> calc(const T&, const U&);
'错误'
template <typename T, U> calc(const T&, const U&);
//T参数返回类型, T* p参数类型
template <typename T> T foo(T* p)
{
T tmp = *p
return *p;
}
'实例'
int a = 0;
int foo(int *p)
{
int tmp = *p;
return tmp;
}
cout << foo(& a) << endl;
非类型模板参数
(如int, double等)而非关键字class或typename来指定非类型参数当一个模板实例化时,非类型参数被用户提供的值所代替,这些值必须是常量表达式,以允许编译器在编译时实例化模板。整型或指针或引用。绑定到指针或引用非类型参数的实参必须具有静态的生存期。非类型模板参数的模板实参必须是常量表达式(例如指定数组大小)template <unsigned N, unsigned M>
int compare(const char (&p1)[N], const char (&p2)[M])
{
return strcmp(p1, p2);
}//定义了一个函数模板。
compare("hi", "mom");//实例化
'实例化的版本'
int compare(const char (&p1)[3], const char (&p2)[M]);
//编译器会在一个字符串字面值常量的末尾插入一个空字符作为终结符
一般对于非类型模板参数,限制const此时可以传递const和非constinline 和 constexpr 的函数模板
与普通函数不同,inline和constexpr说明符放在模板参数列表之后,返回类型之前template<typename T> inline T main(const T&, const T&);
编写类型无关的代码
模板中的函数参数应该是 const 的引用。引用保证了函数可以用于不能拷贝的类型,如unique_ptr, IO 类型。函数体中的条件判断仅使用 < 比较运算。//缺点:对类型要求比较高,如果调用它比较俩个指针,且指针未指向相同的数组,则会出错
if (v1 < v2) return -1;
if (v1 > v1) return 1;
return ;
//传递指针也正确
template <typename T>
int compare(const T &v1, const T &v2)
{
if (less<T>()(v1, v2)) return -1;
if (less<T>()(v2, v1)) return 1;
return 0;
}
模板编译
编译器遇到模板定义时不生成代码,当实例化出模板的一个特定版本时才生成代码。这会影响错误何时被检测到。但是模板不同,模板的头文件通常既包括声明也包括定义。因为编译器需要知道函数模板或类模板成员函数的完整定义才能进行实例化。大多数编译错误在实例化期间报告
编译器不能为类模板推断模板参数类型。通过在模板名后的尖括号中提供额外信息,来代替模板参数的模板实参列表定义类模板
template class Blob {}
实例化类模板
显示模板实参列表,他们被绑定到模板参数Blob<int> ia; //使用类模板
template <> class Blob<int> {}; // 当向上一行那样使用类模板时,编译器实例化出的类就是这样的。
int类型注意
Blob与Blob 类型没有关联,也不会有特殊的访问权限,这完全就是俩个独立的Blob类//定义的实例化出俩个不同的Blob类型
Blob<string> names;
Blob<double> prices;
类模板的成员函数
可以在类模板内部,也可以在外部定义成员函数。定义在类模板内部的函数隐式声明为内联函数。定义在类模板之外的成员函数必须以关键字 template 开始,后接类模板参数列表。'格式'
template <typename T>
ret_type Blob<T>::member_name(parm-list)
template <typename T> void Blob<T>::check(){}
templte <typename T> T& Blob<T>::operator[]{}
//Blob::在外部定义成员函数是,必须说明成员属于哪一个类
//模板实参与模板形参必须相同
类模板成员函数的实例化
默认情况下,一个类模板的成员函数只有当用到它时才进行实例化。在类代码内简化模板类名的使用
使用一个类模板类型时必须提供模板实参,在一个类模板的作用域内,可以直接使用模板而不败指定模板实参在类模板外使用类模板名必须提供模板实参。
template <typename T> class BlobPtr{
BlobPtr<T>& operator++();
BlobPtr<T>& operator--();
};
'在类的作用域(内部)可以省略'
template <typename T> class BlobPtr{
BlobPtr& operator++();
BlobPtr& operator--();
};
在类模板外使用类模板名
我们并不在类的作用域中,直到遇到类名才表示进入类的作用域
类模板和友元
如果一个类模板包含一个非模板友元,则该友元可以访问该模板的所有实例。如果友元也是模板,类可以授权给所有友元模板实例,也可以只授权给特定实例。一对一友好关系
//每个Blob实例将访问权限授予用相同类型实例化的BlobPtr和相等运算符
template <typename T> class Blob{
'友元的T和上面的T类型必须一致'
friend class BlobPtr<T>; // 每个 Blob 实例将访问权限授予了同类型实例化的 BlobPtr。
friend bool operator==<T> (const Blob<T>&,const Blob<T>&); // 将访问权限授予同类型实例化的 ==。
}
'友元的声明用Blob的模板形参作为他们自己的模板实参'
'因此友好关系被限制在相同类型实例化的Blob和BlobPtr之间'
Blob<char> ca; // BlobPtr和operator== 都是本对象的友元
Blob<int> ia; // BlobPtr和operator== 都是本对象的友元
'注意:'
BlobPtr<char> 的成员可以访问ca的非public部分,但是ca对ia或其他Blob的任何实例没有特殊访问权限
通用和特定的模板友好关系

template <typename T> class Blob{
template <typename X> friend class Pal; // Pal 的所有实例都是 Blob 的每个实例的友元。
}
令模板自己的类型参数成为友元
template <typename T> class Blob{
friend T; // 将访问权限授予用来实例化 Blob 的类型
}
模板类型别名
可以定义一个 typedef 来引用实例化的类,但不能引用模板typedef Blob<string> StrBlob;//正确
可以用 using 为类模板定义类型别名template <typename T> using twins = pair<T, T>; // 为 pair 定义了一个类型别名 twins
twins<string> authors; // authors 是一个 pair。
'可以固定一个或多个模板参数,上面只是固定一个'
template <typename T> using partNo = pair<T, unsigned>;
partNo<string>book; //pair
定义模板类型别名时,可以固定其中的部分模板参数template <typename T> using twins = pair<T, unsigned>; // 为 pair 定义了一个类型别名 twins
twins<string> authors; // authors 是一个 pair。
类模板的 static 成员
static 数据成员定义时也要定义为模板
template <typename T> int Blob<T>::num = 0; // 在类模板外定义 static 数据成员的方式。

模板参数与作用域
模板参数会隐藏外层作用域中的相同名字,但是注意在模板内不能重用模板参数名。一个模板参数名在一个特定模板参数列表中只能出现一次typedef double A;
template <typename A, typename B> void f(A a, B b)
{
A tmp = a; //tmp的类型为模板参数A的类型,而非double
double B; //错误:重声明模板参数B
}
模板声明
模板声明必须包含模板参数,声明中的模板参数的名字不必与定义中相同(与函数形参类似)。'3个calc都指向相同的函数模板'
template <typename T> T calc (const T &, const T&); //声明
template <typename U> U calc (const U&, const T&) //声明
template <typename Type> Type calc(const Type& a, const Type& b){....} //定义
使用类的类型成员
如果希望使用一个模板类型参数的类型成员,必须使用关键字 typename 显式地告诉编译器该名字是一个类型,而不能使用classtemplate <typename T>
typename T::value_type top (const T&c)
{
....
}
默认模板实参
可以为函数模板和类模板的模板参数提供默认模板实参,就像可以为函数参数提供默认实参一样。//compare有一个默认模板实参less<>和一个默认函数实参F()
template <typename T, typename F = less<T>>
int compare(const T &v1, const T &v2, F f = F())
{......}
模板默认实参与类模板

普通(非模板)类的成员模板
注意:成员模板不能是虚函数
类模板的成员模板
普通函数成员不同的是, 成员模板是函模板。当在类模板外定义一个成员模板时,必须同时为类模板和成员模板提供模板参数列表。template<typename T> class Blob{
template<typename it> Blob(lt b, lt e);
....
};
template<typename T> //类的类型参数
template<typename ll> //构造函数的类型参数
Blob<T>::Blob(lt b, lt e);
data(std:make_shared<std::vector<T>>(b, e)){}
实例化与成员模板

第一步:定义a1时,显示的指出编译器应该实例化一个int版本Blob
第二步:构造函数自己的类型参数通过begin(a1)和end(a1)类型推断
产生控制实例化原因:
显示实例化:
extern template declaration; // 实例化声明
template declaration; // 实例化定义
extern template class Blob<string>; //声明
template int compare(const int &, const int &); //定义

编译器通常不是对实参进行类型转换,他不知道到怎么转换,而是生成一个新的模板实例const转换:可以将一个非const对象的引用(指针)传递给一个const的引用(或指针)形参数组或函数指针转换:如果函数形参不是引用类型,则可以将数组或函数转换为相应的指针。(数组实参转换为一个指向其首元素的指针,函数实参转换为指向一个该函数类型的指针)
使用相同 模板参数类型的函数形参
long lng;
compare(lng, 1024); //错误:不能实例化compare(lng, 1024);类型不匹配且不能转换
template<typename A, typename B>
int flexibleCompare(const A& v1, const B& v2){....};
long lng;
flexibleCompare(lng, 1024); //正确但是有一个条件,这里面俩个类型是兼容的可以进行比较。
正常类型转换应用于普通函数实参
(对于模板类型,只允许几种情况进行自动转换,intL类型不可以转换,但是对于普通类型来说,可以进行转换)
指定显示模板实参
'只有实例化后编译器才能推导出类型'
template <typename T1, typename T2, typename T3> T1 sum(T2, T3);
'显示模板实参'
auto val3 = sum<long long>(i, lng); // T1是显式指定,T2和T3都是从函数实参类型推断而来
当需要给定T3类型时,T1和T2都要提前给,否则会出错,所以对于这种糟糕的设计,三个类型都要显示给出
正常类型转换应用于显示指定的实参
显示指定了的函数实参,也可以正常的类型转换

decltype(*lt)``lt既不是迭代器对象,也不是指针对象,只是一个类型,因此不可以这样使用decltype(*beg),可以这样使用,但是在编译器遇到函数的列表之前,beg都是不存在的尾置返回类型,由于尾置返回出现在参数列表之后,他可以使用函数的参数当使用一个函数模板初始化一个函数指针或为一个函数指针赋值时,编译器使用指针的类型来推断模板实参。
从左值引用函数参数推断类型

从右值引用函数参数推断类型
template<typename T> void f3(T&&)
f3(42); //实参是一个int类型的右值;模板参数T是int
引用折叠和右值引用参数
只有俩个都是右值引用才能折叠成右值引用
编写接收右值引用参数的模板函数
template<typename T> void f3(T && val)
{
T t = val; //拷贝还是绑定一个引用
t = fcn(t); //赋值只改变t还是既改变t又改变val;
if(val == t); //若T是引用类型,则一直是true
}
'对一个右值调用f3时,例如字面值常量42,T为int'
'对一个左值i调用f3时,T为int &,此时t和val绑定在一起','结果一直为true'
模板转发其实参模板被重载'目前使用右值引用的函数模板通常使用481页的方式重载'
template <typename T> void f(T&&); //绑定到非const右值
template <typename T> void f(const T&); //左值和const 右值
move函数可以将左值引用变为右值引用(虽然不能直接将一个右值引用绑定到一个左值,但是用move获得绑定到左值上的右值引用)std::move是如何定义的

std::move是如何工作的

const的以及实参是左值还是右值forward的新标准库设施来传递参数,它能够保持原始实参的类型。utility中。forward返回显式实参类型的右值引用。即,forward的返回类型是T&&。编写重载模板

int main()
{
string s("hi");
'模板一更精确'
cout << debug_rep(s) << endl; //调用第一个模板,T类型为string
'模板二更精确'
cout << debug_rep(&s) << endl; //地址可以被封装成指针,
//debug_rep(cosnt string*&),由第一个版本的debug_rep实例化而来,T被绑定到string *
//debug_rep( string*),由第二个版本的debug_rep实例化而来,T被绑定到string
'无法确定,但是根据规定,选择最特化版本'
const string *sp = &s;
cout << debug_rep(sp) << endl; //都是精确匹配,但是第二个更特化
//debug_rep(cosnt string*&),由第一个版本的debug_rep实例化而来,T被绑定到string *
//debug_rep(cosnt string*),由第二个版本的debug_rep实例化而来,T被绑定到const string
}
非模板和模板重载
对于一个调用,如果一个非函数模板与一个函数模板提供同样好的匹配,则选择非模板版本。可变参数模板就是一个接受可变数目参数的模板函数或模板类。
template ,Args第一个模板参数包。void foo(const T &t, const Args& ... rest);,rest是一个函数参数包。sizeof...运算符,返回参数的数目。forward机制,实现将实参不变地传递给其他函数。template后面跟一个空尖括号对(<>)。