文章目录
To-Do:
-
typename与class的异同
-
类模板和模板类
以及函数模板和模板函数 -
非类型模板参数的完善
P580 与 https://blog.csdn.net/lanchunhui/article/details/49634077 -
inline & constexpr 函数模板
与类型无关的代码
P581-583 的所有内容 -
在模板作用域中引用模板类型
P585 -
类模板与友元开始
P588-591 -
模板参数
P592-597 -
控制实例化
P597-600 -
从这里往后的基本都没有
到时候回顾拓展的, 如果熟悉就直接跳过吧
不过基本是需要遍历, 即便是JB老师讲过的内容, 其中也有很多与C++ Primer中不相符的地方需要进行补充
泛型编程概述:
学习模板库首先应该理解的是泛型编程的概念
其可以总结下面的一句话:
它是把数据类型作为一种参数传递进来
泛型编程让你编写完全一般化并可重复使用的算法,其效率与针对某特定数据类型而设计的算法相同
泛型编程的代表作品STL, 它包含很多计算机基本算法和数据结构,而且将算法与数据结构完全分离,其中算法是泛型的,不与任何特定数据结构或对象类型系在一起
C++模板技术:
通常有两种形式:函数模板和类模板;
函数模板
将函数参数类型提取出来作为“类型参数”,通过创建通用的函数模板,避免函数体的重复定义
类模板
将类中所处理的数据类型提取出来作为“类型参数”,创建通用的类模板
函数模板的定义:
//函数模板:
template <typename T>
T max(T a, T b){
return a<b?b:a;
}
template <typename T1, typename T2, typename T3>
T3 min(T1 a, T2 b)
{
....
}
几个关键字:
-
template:
表明开始进行一次模板定义 -
<>中括号中的内容
称之为模板参数列表
而其中的内容为逗号分隔的一个或多个模板参数 -
typename:
用于申明泛指类型
其中后头的T为模板标志, 相当于形参, 支持用户自定义注意每个模板参数前头都要有一个typename, 和函数的形参声明类似
这里的typename可以替换为class
二者具体的区别将在下头说明
注意:
每次进行定义或声明一个函数模板时, 都要有个头:
template <typename T1, ...>
由于template关键字没有明显的作用范围, 所以, 其只是作用下一条语句
非类型模板参数:
模板参数列表中还可以是非类型模板参数:
template <typename T, int x, unsigned y>
T max(T a, T b){
return a<b?b:a;
}
即其中可以加入非typename或class关键字的特定数据类型
而后, 模板实例化时需要提供足够多的模板参数 (与函数的参数相似):
template <typename T, int x, unsigned y>
class MyClass{
public:
int xx=x;
unsigned yy=y;
};
//实例化时:
class MyClass<int> obj1; //报错, 需要提供3个参数
但是, 非类型模板参数是有类型限制的
浮点数和类对象(class-type)不允许作为非类型模板参数
如:
template <typename T, int x, double y> //报错
T max(T a, T b){
return a<b?b:a;
}
函数模板的实例化:
直接当成普通的函数进行调用即可
//其中max为上头的函数模板:
cout<< max(3,6) <<endl;
char c1=‘a’;
char c2=‘b’;
cout<< max(c1,c2) <<endl;
编译器由此生成的函数实例:
//编译器推断出所有的T都是int类型
int max<int>(int a, int b){
return a<b?b:a;
}
//编译器推断出所有的T都是char类型
char max<char>(char a, char b){
return a<b?b:a;
}
函数生成的二义性:
当编译器无法推导出合适类型时, 报错(二义性)
max(4, 4.2); //二义性报错
解决方法就是显式制定编译器生成什么类型的函数:
函数生成的后置类型声明:
max <int> (4,7);
max <double> (4, 4.2);
显示的指定模板参数的具体类型, 解决上头的二义性问题
注意这是一个良好的编程习惯, 方式ERROR且提高可读性, 需严格遵守
自动类型推导:
在定义模板时使用auto & decltype
但是这里的使用方式上与传统用法有一点区别
//auto的使用:
template <typename _Tx,typename _Ty>
void Multiply(_Tx x, _Ty y){
auto v = x*y;
std::cout << v;
}
//decltype的使用:
template <typename _Tx, typename _Ty>
auto multiply(_Tx x, _Ty y)->decltype(x*y){
return x*y;
}
-
auto的使用和之前相同
-
decltype的使用需要用auto进行占位
按理来说, 原本的代码应该是这个样子的:
decltype(x*y) multiply(_Tx x, _Ty y)
但是在编译时, x & y的静态类型不确定, 类型推断报错
所以C++11引入了全新的语法 返回值类型后置语法, 用来解决这个问题 (所以是template模板特供):
template <typename T, typename U> auto add(T t, U u) -> decltype(t + u) { return t + u; }
使用一个auto占位, 而后用->指向真正的返回值类型
并且这个返回值类型并不一定是decltype推出的:template <typename T1, typename T2> auto sum(T1 a, T2 b) -> T1 //这样也可以 { return a+b; }
类模板的定义:
注意类模板和模板类是有区别的, 这个等到之后在进行深究
template <typename T>
class Exam
{
public:
void setValue (T const& value);
T getValue() const;
private:
T elems;
};
几个关键字:
都和函数模板一样, 这里就不做重复的表述了
这里同样也可以将typename换成class
//来个大号的定义, 可以看的更清楚一点
template <typename T>
class MyVector{
public:
MyVector(int maxSize=1);
MyVector(const MyVector<T>& a);
MyVector(MyVector<T>&& a);
MyVector& operator=(const MyVector<T>& a);
MyVector& operator=(MyVector<T>&& a);
~MyVector();
int getSize()const {return size;}
T getValue(int index)const;
void setValue(int index,T value);
T& operator[](int index);
const T& operator[](int index)const;
void pushData(T data);
void removeData();
void removeData(int index);
};
类模板成员的定义:
-
数据成员的定义与普通类相同
-
成员函数的定义:
-
当成员函数定义在类内部时, 和普通类相同
-
当其定义在外部时, 每一个成员函数都需要在原有的定义上加上一个模板标志:
//需要添加额外的模板标志 template <typename T> //而后就是正常的定义 //注意这里Exam<T> 和常规的定义不相同 void Exam<T> ::setValue(const T& value) { elems = vlaue; } //由于每一个成员函数的类外定义都需要Exam<T> //所以每一个都要来一句template <typename T> template <typename T> //注意ClassName后也要加一个<T> T Exam<T> ::getValue() const { return elems; }
-
注意, 类模板的成员都需要定义在同一个文件中( 通常是头文件)
如果将成员函数定义在.cpp文件中, 由于每个.cpp文件是分开独立编译的, 使得编译器便无法确定要根据什么类型来产生相应的类,也就造成了错误
允许不带模板参数列表的情况:
注意:
MyVector(const MyVector<T>& a);
MyVector(MyVector<T>&& a);
虽然之前说了, 使用类模板时必须提供其模板参数列表, 但是上头的这几个函数形参中有MyVector对象, 其后头跟的<T>
可有可无
因为在类模板中, 这个硬性要求被简化了:
MyVector(const MyVector& a);
MyVector(MyVector&& a);
//以上代码等价为上头的代码
当在类模板的作用域中使用不带模板参数列表的类模板时, 默认为使用定义中的参数
但是, 显然加上<T>
更能凸显出程序的意图:
明确需要的是MyVector<T>
的形参, 提高了程序的可读性
并且, 在需要特定类型时, 就必须要指明:
MyVector(const MyVector<double> &a){
this->size=a.size;
return ;
}
类模板的实例化:
int main(){
Exam<int> e1;
e1.setValue(5);
cout<<e1.getValue()<<endl;
Exam<double> e2;
e2.setValue(5.5);
cout<<e2.getValue()<<endl;
return 0;
}
类模板和函数模板不同, 需要显式指明模板参数的类型:
类模板名<真实类型参数表> 对象名(构造函数实际参数表);
注意:
类模板成员函数只有在被使用到时才会实例化
指明:
MyVector(const MyVector<double> &a){
this->size=a.size;
return ;
}
类模板的实例化:
int main(){
Exam<int> e1;
e1.setValue(5);
cout<<e1.getValue()<<endl;
Exam<double> e2;
e2.setValue(5.5);
cout<<e2.getValue()<<endl;
return 0;
}
类模板和函数模板不同, 需要显式指明模板参数的类型:
类模板名<真实类型参数表> 对象名(构造函数实际参数表);
注意:
类模板成员函数只有在被使用到时才会实例化