C++ Primer 5th 第16章 模板与泛型编程
模板是C++中泛型编程的基础,一个模板就是创建一个类或者函数的蓝图或者说公式。
C++模板分为函数模板和类模板。C++根据调用模板时传入的具体类型来生成相应类型的具体函数或者类。
类模板则可以是整个类是个模板,类的某个成员函数是个模板,以及类本身和成员函数分别是不同的模板。
1.函数模板
函数模板以关键字template开始,后接尖括号括起来的模板参数列表,模板参数列表不允许是空的,也即模板参数至少有一个或多个,多个之间使用逗号分割。
模板参数表示的是函数中用到的类型或者是一个值。当我们使用模板时,根据提供的实参推断出实参的类型,该类型即被用于绑定到模板参数,这个过程被叫做模板的实例化,相应的,生成的版本叫做模板的实例。
模板参数表示的是类型,该类型可以用于制定函数的返回类型或者函数的参数类型,也可以用于函数体内变量声明定义等。
对于每个模板参数列表中的每个类型参数,其前面必须加上关键字typename或class。关键字typename和class之间没有区别。
除了定义模板的类型参数,还可以定义一种非类型的参数。非类型参数不表示一种类型,而是表示一个值。非类型参数也不使用关键字typename和class,而是使用具体的类型来指定。
非类型参数在模板实例化时,被用户或者编译器推断出的值所代替,该值必须是常量表达式。(常量表达式是指值不会改变,并且编译期间就能计算得出结果的表达式,字面值属于常量表达式,常量表达式初始化的const对象也是常量表达式)。
例如:
template <unsigned N, unsigned M>
int fun(const char (&p1)[N], const char (&p2)[M])
{
return strcmp(p1, p2);
}
这里的N,M将会被我们调用fun时传入的实参的值替代:
fun("hi", "mom");
编译器会使用字面常量的大小来替代N,M,并在字符串字面值末尾插入一个空字符作为终止标记,最终实例化为:
int fun(const char (&p1)[], const char (&p2)[]);
非类型参数可以是整形,也可以是指针或者引用。条件是整形是常量表达式,而指针和引用必须是关联static对象。
模板可以声明为inline和constexpr的。
当编译器对代码进行编译时,在源码模板定义部分,编译器并没有实际去生成相应的模板代码,只有在使用模板实例化一个具体的版本时,编译器才生成相应的代码。
2.类模板
与函数模板不同,类模板不能为其模板类型参数进行推断。为什么不能为类模板推断类型参数?因为定义类对象的时候,有可能无法提供足够的类型来让编译器进行推断,比如 vector v;这里仅仅定义了一个vector对象,没有提供任何额外的信息让编译器来推断模板参数,所以类模板不为其模板类型参数进行推断,必须我们在实例化时显式指明。
类模板的名字不是一个类,也即不是自定义的一个class,而是一个生成class的说明模板。因此使用模板生成的类,必定是带模板类型实参的class,所以一个实例化的class必然有<>来指明类型参数。
类模板的成员函数可以在内部定义,这样是隐式内联。也可以在外部定义。类模板的成员函数是一个普通的成员,而不是模板。虽然在外部定义时,成员函数需要以关键字template开始,并且后接模板参数列表,但这不表示该成员函数是个模板。之所以需要关键字template和模板参数列表,是因为成员函数所属的类在实例化时,会具体绑定到一个特定的类型上,成员函数也需要相应被动地绑定到该类型。
类模板的成员函数只有被用到的时候才会进行实例化,如果没有被用到,就不会实例化,如同类中定义的成员函数一样,如果不会被用到,那么可以只声明而不定义它。
当我们在类模板作用域内进行模板成员的定义时,可以省略模板实参。
template <typename T>
class BlobPtr
{
public:
BlobPtr& operator++(); //无需说明具体类名BlobPtr<T>
BlobPtr& operator--(); //无需说明具体类名BlobPtr<T>
BlobPtr operator--(int); //无需说明具体类名BlobPtr<T> bool empty()
{
return data->empty();
} size_t size(); private:
std::shared_ptr<std::vector<T>> data;
}; template <typename T>
size_t BlobPtr<T>::size() //类作用域外,需说明具体类名BlobPtr<T>
{
return data->size();
}
当我们在类模板的外面定义成员函数时,必须以关键字template开始,后接类模板参数列表。
这里为什么类成员函数size( )前要加模板参数列表<T>?template<typyname T>不已经指明了类型了吗?
前面已经说过:类模板的成员函数是一个普通的成员,而不是模板。这里的template <typename T>是说明size( ) 所属的BlobPtr是个模板类,并且BlobPtr
本身也不是一个类名,是类模板名,真正的类名是BlobPtr<T>,说白了,其实是在具体指明类作用域。所以在类外定义成员函数时,成员函数的参数列表对模板参数是可以省略的。
因此在类模板外面定义普通非模板成员函数时,首先用template <typename T>说明是一个类模板,然后再具体说明该成员函数是哪个类中的成员。后面会看到,如果模板类的成员函数也是一个模板,那么就需要分别各自说明类的模板和函数的模板。
3.类模板和友元
如果一个类模板包含一个非模板友元,则友元可以访问该类模板的所有实例。
如果类和友元都是模板,则类实例可以对友元所有实例授权,也可以只授权给特定实例。
为了让所有实例成为友元,友元声明中必须使用与类模板本身不同的模板参数。
在C++11中,我们也可以模板类型参数声明成友元。例如:
template <typename Type>
class Bar
{
friend Type;
// ...
};
新标准也允许我们为类模板定义一个类型别名:
template <typename T> using twin = pair<T, T>;
twin<string, string> authors; //authors是一个pair<string, string>
模板参数遵循普通的作用域规则。与任何其他名字一样,模板参数会隐藏外层作用域中声明的相同名字,需要注意的是,在模板内不能重用模板参数名。例如:
typedef double A;
template <typename A, typename B> void f(A a, B b)
{
A tmp = a; //类型A隐藏外部typedef double A
double B; //错误,不允许重用模板类型参数
}
模板声明必须包含模板参数。与函数参数相同,声明中的模板参数的名字不必与定义中相同。
对于一个给定的模板的声明和定义必须有相同数量和种类的参数。
关于typename可以用做模板参数的关键字,也可以用来指示类型还是变量名。具体可以参考这篇文章:http://feihu.me/blog/2014/the-origin-and-usage-of-typename/
4.类模板的static成员
与普通类一样,模板类也可以拥有static成员,如下:
template <typename T>
class Foo
{
public:
static std::size_t count() { return ctr; } //声明并定义
private:
static std::size_t ctr; //声明,尚未定义
};
上面这段代码,Foo是一个类模板,它实例化后的类有一个count的静态成员函数和一个静态ctr数据成员。
类的static数据成员有且只有一个定义,类模板也是如此。因此,我们将需要在类模板外定义ctr数据成员,类模板外定义静态数据成员的格式是template关键字开始,后跟模板参数列表,如下:
template <typename T>
size_t Foo<T>::ctr = ;
上面代码中类名是带模板参数的,因为实例化后的静态数据成员是具体属于某一个类的,而一个具体类则是带模板实参的,仅有类名则只是一个类模板的名字。
在新标准中,我们还可以为函数模板和类模板提供默认模板实参,对于类模板,当使用默认实参时,只需使用空的尖括号<>来表示即可。
5.成员模板
一个普通类或者类模板可以包含一个模板的成员函数。这种成员被称为成员模板。成员模板不允许是虚函数。
对于类模板,其类和成员有各自独立的模板参数。
与类模板的普通成员函数不同,成员模板是函数模板。当我们在类模板外面定义实现成员模板时,要同时提供类模板和成员模板的参数列表。其中,类模板的参数列表在前,成员模板的在后。例如:
template <typename T>
class Blob
{
template <typename It> Blob(It b, It e);
// ...
}; template <typename T> //类模板的参数列表
template <typename It> //成员模板参数列表
Blob<T>::Blob(It b, It e):data(std::make_shared<std::vector<T>>(b, e))
{
//constructor
}
对于含成员模板的类模板,在实例化时,需要同时提供类模板实参和成员模板实参,对于类模板实参,则是显式提供,对于成员模板的实参,则是自动推断。
6.控制实例化
模板只有被用到时,编译器才会根据实参进行实例化实参相应的代码,在不同源文件中提供相同实参实例化同一模板时,将会在不同文件中重复生成相同的实例,每个源文件中都会有一个实例。大系统中,这会导致严重的额外开销。
C++11新标准中,可以使用显式实例化来避免这种额外开销。方法是使用实例化声明和实例化定义。
形如:
extern template declaration; //实例化声明
template declaration; //实例化定义 下面是实际的例子
extern template class Blob<string>; // 声明
template int compare(const int&, const int&); // 定义
上面的代码中,第一行是声明,第二行是定义。需要注意的是,模板的实例化控制的声明和定义是配套使用的。
当编译器遇到extern模板声明时,编译器就不再实例化,它会去程序的其他地方寻找实例,可以多次声明,但只能一次定义。
由于编译器会在使用模板时自动实例化,因此extern声明必须在任何使用当前模板前面声明。
当编译器遇到一个模板的实例化控制的定义时,编译器将会进行实例化,以生成代码。
类模板的实例化定义会实例化模板的所有成员,而不是用到哪个成员才实例化哪个成员。
7.
C++ Primer 5th 第16章 模板与泛型编程的更多相关文章
- [C++ Primer] : 第16章: 模板与泛型编程
面向对象编程(OOP)和泛型编程都能处理在编写程序时不知道类型的情况, 不同之处在于: OOP能处理类型在程序运行之前都未知的情况, 而在泛型编程中, 在编译时就能获知类型了. 函数模板 模板是C++ ...
- 【c++ Prime 学习笔记】第16章 模板与泛型编程
面向对象编程(OOP)和泛型编程(GP)都能处理在编写程序时类型未知的情况 OOP能处理运行时获取类型的情况 GP能处理编译期可获取类型的情况 标准库的容器.迭代器.算法都是泛型编程 编写泛型程序时独 ...
- C++ Primer 5th 第12章 动态内存
练习12.1:在此代码的结尾,b1 和 b2 各包含多少个元素? StrBlob b1; { StrBlob b2 = {"a", "an", "th ...
- C++ Primer 5th 第6章 函数
正如第一章所说:C++的函数是一个能够完成一个功能的模块或者说是一段命名了的代码块. 如下图所示,函数可以重载,是一段实现某些功能命名了的代码. 一个完整的函数的构成有四部分: 1.返回类型 2.函数 ...
- C++ Primer 5th 第3章 字符串、向量和数组
*****代码在Debian g++ 5.40 / clang++ 3.8(C++11)下编写调试***** 本章主要是关于字符串.数组的内容,以及一些简单的容器知识. 1.using的声明 usin ...
- C++ Primer 5th 第1章 开始
*****代码在Ubuntu g++ 5.31 / clang++ 3.8(C++11)下编写调试***** 每个C++程序必须有一个main( )函数,main( )函数的返回值也必须是int类型, ...
- C++ Primer 5th 第9章 顺序容器
练习9.1:对于下面的程序任务,vector.deque和list哪种容器最为适合?解释你的选择的理由.如果没有哪一种容器优于其他容器,也请解释理由.(a) 读取固定数量的单词,将它们按字典序插入到容 ...
- C++ Primer 5th 第7章 类
类的基本思想是数据抽象和封装,定义类就是定义一个抽象数据类型. 类中的所有成员必须在类中声明,也即默认定义在类中的成员全部为声明,除非显式的定义成员函数的函数体.成员函数是在类中声明的,定义可以在类内 ...
- C++ Primer 5th 第2章 变量和基本类型
*****代码在Debian g++ 5.3.1 / clang++ 3.8(C++11)下编写调试***** 由于部分编译器对标准遵循的不同以及自身额外的扩展,本章书中的少数知识点与实际实现存在偏差 ...
随机推荐
- sublime每次打开时都提示升级,怎么取消这个弹出框?
答案其实很简单,设置如下: 进入Preferences -> Settings-User ,添加 "update_check": false 重启Sublime. 发现了什么 ...
- Linux&shell 之Linux文件权限
写在前面:案例.常用.归类.解释说明.(By Jim) Linux文件权限用户useradd test (添加用户test)userdel test (删除用户test)passwd test(修改用 ...
- ZOJ-2587-Unique Attack(最小割的唯一性)
题意: 求无向图最小割是否唯一 分析: 1.我们先对原图求一次最大流 2.对残留网络,我们从S开始,找到所有所有S能到达的点:再从T开始,找出所有能到达T的点. 3.判断原网络中是否还有没有访问到的点 ...
- 【转】Android JNI编程—JNI基础
原文网址:http://www.jianshu.com/p/aba734d5b5cd 最近看到了很多关于热补的开源项目——Depoxed(阿里).AnFix(阿里).DynamicAPK(携程)等,它 ...
- HDOJ 2073 无限的路
Problem Description 甜甜从小就喜欢画图画,最近他买了一支智能画笔,由于刚刚接触,所以甜甜只会用它来画直线,于是他就在平面直角坐标系中画出如下的图形: 甜甜的好朋友蜜蜜发现上面的图还 ...
- javaweb开发过程中的地址写法
凡是要表示web资源的地址,比如浏览器地址栏中,都是 /凡是要表示硬盘地址, 都是 \ public class ServletDemo1 extends HttpServlet { //实际开发过 ...
- bootstrap 仿实例
bootstrap实现一个网页 html文件 <!DOCTYPE html> <html> <head lang="en"> <meta ...
- 【转】JavaScript对Json节点的增删改
var json = { "age":24, "name":"cst" }; //修改Json中的age值,因为Json中存在age属性 j ...
- motan源码分析九:开关
在前面的文章中,我们已经发现了开关的踪影,例如cluster,motan支持多个cluster,当前的cluster因为开关关闭的情况下,就会使用下一个cluster. 1.开关相关的类和接口主要都在 ...
- Unity3D NGUI制作进度条
利用GUI可以制作进度条,但是NGUI更加方便 我是用的NGUI3.5.3, 先找到NGUI Slider的预制体,利用自带的UISlider来制作. 主要是利用UISlider的Value来控制进 ...