笔记会持续更新,有错误的地方欢迎指正,谢谢!
这一章特别实用,神器—>模板
泛型编程能处理在编译之前类型不知道的情况,在编译时获知类型,比如我们学过的容器、迭代器和算法都是泛型编程。
模板是C++中泛型编程的基础,记住:一个模板就是一个创建类或函数的蓝图。
定义模板
函数模板
要实现一个功能:比较两个值的大小。这两个值可能是int,, double, string等等,如果我们写出这些函数就会发现,它们除了参数类型不同,其他都一样。这样很麻烦,我们就可用模板实现泛型编程(泛型:类型很泛,不针对某一种类型)。
//这是一个函数模板
template <typename T> //以关键字template开始,
//后跟的是模板参数列表(不能为空),用逗号分割的一个或多个模板参数
int compare(const T &v1, const T &v2)
{
if(v1<v2){return -1;}
if(v2<v1){return 1;}
return 0;
}
//这里写了一个函数蓝图,当实际去调用它时,编译器会根据实参来生成相应的函数:
compare(1, 0); //实例化出int compare(const int&, const int&)
compare(2.5, 3.8); //实例化出int compare(const double&, const double&)
compare("abc", "def"); //实例化出int compare(const string&, const string&)
模板类型参数
也就是T,T在后面编译时就被实参类型替代了,它在模板中可被用在任何地方:返回类型、函数参数类型、声明变量等,就跟变量类型一样用:
template <typename T>
T foo(T* p)
{
T temp= *p;
return temp;
}
非类型模板参数
模板参数列表不一定非要放类型模板参数,还可以放非类型模板参数,只不过这个非类型模板参数是用来表示具体的一个值而不是一个类型:
//当非类型参数在编译被替代时,一定是常量表达式,毕竟它是值,不是类型。
template <unsigned N, unsigned M>
int compare(const char (&p1)[N], const char (&p2)[M])
{
return strcmp(p1, p2);
}
模板非类型参数是一个常量,所以有:
- 如果它是整型参数,必须是常量表达式
- 如果是指针或者引用,它所绑定的对象必须有静态生存期
inline和constexpr的函数模板
这两个还是可以使用函数模板的
constexpr知识补充:
被constexpr修饰的一定是常量,必须用常量表达式(字面值类型,包括算术类型、引用、指针)或constexpr函数(constexpr函数是足够简单以使得编译时就可以计算其结果)初始化。
例子:
constexpr int a = 20;//对
constexpr int b = a + 1;//对
constexpr int sz = size();//可对可错,只有当size是一个constexpr函数时才对。
constexpr是编译时检查它修饰的是不是常量,所以,若你认定一个变量是一个常量表达式,那就把它声明为constexpr类型。
模板编译
当编译器遇到一个模板定义时,它并不生成代码,只有当我们实例化出模板的一个特定版本时,编译器才会生成代码。
由于实例化时需要掌握函数模版和类模版成员函数的定义,其定义通常也放在头文件中,与普通函数和普通类成员函数不同。
个人感觉:模板就像Unity里的Prefab预制体,需要用的时候拿来根据自己的需要进行实例化。
类模板
与函数模板不同,编译器不能为类模板推断参数的类型,必须显示提供类型。
定义类模板
作为例子,我们将实现Blob,它不再只针对string,而是作为一个类模板,可以用于更多类型的元素:
//有些成员函数只写了声明,后面会再去实现定义的
template <typename T>
class Blob
{
public:
typedef T value_type;
typedef typename vector<T>::size_type size_type;
//构造函数
Blob();
Blob(initializer_list<T> il);
//Blob中的元素数目
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 pop_back();
//元素访问
T& back();
T& operator[](size_type i);
private:
shared_ptr<vector<T>> data;
//检查错误,若i违法,抛出错误msg
void check(size_type i, const string &msg) const
};
我就知道你typedef typename vector<T>::size_type size_type;
看不懂,所以暖心提供链接:http://blog.csdn.net/yyxyong/article/details/78632340
实例化类模板
必须提供显式模板实参,每种元素类型生成的各个实例类之间无关联,拥有各自的static成员。
Blob<int> ia; //空Blob<int>
类中的T由int代替。你给什么类型,编译器就会实例化出对应类型的类。
上面这句代码包含了两个实例化过程:
- 类模板实例化为类
- 类实例化为对象
模板嵌套
比如:在自定义的一个模板类中使用了vector。vector也是一个模板类。
Blob构造函数
//默认构造函数
template <typename T>
Bolb<T>::Blob() : data(make_shared<vector<T>>()){}
//接受参数的转换构造函数
template <typename T>
Blob<T>::Blob(initializer_list<T> il) : data(make_shared<vector<T>>(il)) {}
类模板成员函数的实例化
实例化了的类模板,其成员函数只有在用到时才被实例化。
模板参数
我们通常将类型参数命名为T,其实我们可以使用任何名字。
模板参数作用域:
在模板内不能重用模板参数的名字,正常的名字隐藏规则依旧适用。比如:template<typename V,typename V>
是错误的!
默认模板实参:
旧标准只允许类模板有默认实参,新标准允许函数模板也有默认实参。
就像我们能为函数参数提供默认实参一样,我们也可以提供默认模板实参,如下方的less。
1.我们先来给函数模板提供默认实参:
//默认使用标准库的less函数对象模板
//此时,有一个默认模板实参less<T>和一个默认函数实参F()
template <typename T, typename F = less<T>>
int compare(const T &v1, const T &v2, F f = F())//定义了一个新函数参数f
{
if(f(v1, v2)){return -1;}
if(f(v2, v1)){return 1;}
return 0;
}
2.我们再来给类模板提供默认实参:
调用类模板时必须加上尖括号,即使所有参数都有默认值。
template <class T = int>
class Numbers
{
public:
Numbers(T v = 0) : val(v) {}//构造函数
};
//因为有了默认实参int,我们就可以这么干了
Numbers<> a;
成员模板
类里面的 本身是模板的成员函数 是模板函数,这个类可以是普通类也可以是类模板,下面就分别来讲:
普通(非类模板)类的成员模板
class Debugdel
{
public:
template <typename T>
void operator()(T *p) const //虽然是这个类的对象,但可以删除任何类型的指针
{
delete p;
}
};
类模板的成员模板
我们要为Blob类定义一个构造函数,接受俩迭代器,表示要拷贝的元素范围,由于我们希望支持不同类型的迭代器,因此要将这个构造函数定义为模板:
template <typename T>
class Blob
{
template <typename It>
Blob(It b, It e);//构造函数模板
};