[读书笔记] C++Primer (第5版) 第16章 模板与泛型编程

1.函数模板:

一个公式。template关键字开始,后跟一个模板参数列表,以逗号分割的一个或多个模板参数。 template
编译器用推断出的模板参数来为我们实例化。
这些编译器生成的函数版本通常被称为模板的实例(函数的形参为int,这个函数就是实例)。
typename:模板类型参数。可以用来指定返回类型或函数的参数类型。就像内置类型或类类型说明符一样使用。也可以使用class代替。
非模板参数:一个非类型参数表示一个值而非一个类型。
当一个模板被实例化时,非类型参数被一个用户提供的或编译器推断出的值所代替,这些值必须是常量表达式。
一个非类型参数可以是一个整形,或是一个指向对象或函数类型的指针或左支野用。绑定到非类型整型参数的实参必须是一个常量表达式,绑定到指针或引用非类型参数的实参,必须具有静态的生存期。
模板程序应尽量减少对实参类型的要求。
编写泛型代码的两个重要原则:函数参数是cost的引用;函数体中的条件判断仅使用一种运算。
当编译器遇到一个模板定义时,他并不生成代码,只有当我们实例化出模板的一个特定版本时,它才会生成代码。
通常调用一个函数或使用一个类类型对象时,编译器只需掌握函数的声明即可。
对于模板,为了生成一个实例化版本,编译器需要掌握还是函数模板或类模板成员函数的定义。因此模板的头文件通常包括声明,也包括定义。

2.类模板:

类模板是用来生成类的蓝图。编译器不能为类模板推断模板参数类型。
template <typename T> class A { };
将模板参数当做替身,代替使用模板时,用户需要提供的值或类型。
一个类模板的每一个实例都形成一个独立的类,互相没有访问权。
类模板的名字不是一个类型名。 类模板用来实例化类型,而一个实例化的类型总是包含模板参数的。
定义在类模板之外的成员函数必须与关键字template开始,后接类模板参数列表。
template <typename T> Ret_type A ::Func(paramlist) { }; //函数定义
已经实例化的类模板, 其成员只有在被使用时才被实例化。
当我们使用一个类模板类型时,必须提供模板参数,但是这一规定有个例外,在类模板自己的作用域中,我们可以直接使用模板名,而不提供实参。
在内模板外定义及成员时,并不在类的作用域中,直到遇到类名才表示进入类的作用域。
当一个类包涵一个友元声明时,类与友元各自是否是模板是互相无关的。类模板与另一个(类或函数)模板间的友好关系,最常见的形式是建立对应实例及其友好关系。
当用其中一个的模板形参作为另一个的模板实参时,友好关系被限定到在相同的实例化。
一个类也可以将另一个模板的每个实例都声明为自己的友元,但必须使用与本类模板不同的模板参数。
新标准可以将模板类型参数声明为友元:
template <typename T > class Bar{
friend Type: // 将访问权限授予用来实例化Bar的类型
}
Bar <Foo> 表示Foo为Bar<<>Foo>的友元。
模板类型别名:可以定义一个typedef来引用实例化的类。不能定义一个typedef引用一个模板。
新标准允许我们为类模板定义一个类型别名。
类模板也可以声明static成员。 对任意给定类型x都有一个静态成员。
通过类直接访问static成员。必须也用一个特定的实例。

3.模板参数:

一个模板参数名的可用范围是在其声明之后,至模版声明或定义结束之前。
模板参数会隐藏外层作用域中声明的相同名字。在模板内不能重用模板参数名。
由于参数名不能重用,所以一个模板参数名在一个特定的参数列表中只能出现一次。
所有模板的声明通常一起放置在文件开始位置,出现在任何使用这些模板的代码之前。
C++语言假定通过运算符访问的名字不是类型。
希望通知编译器一个名字表示类型时,必须使用关键字typename,而不能使用class。
可以提供默认模板实参。在新标准中可以为函数和类模板提供默认实惨,更早的版本只允许为类模板提供默认实惨参。
使用默认实参的模板时,必须在模板名之后跟一个空尖括号对。

4.成员模板:

一个类可以包含本身是模板的成员函数,这种成员被称为成员模板,成员模板不能是虚函数。
与类模板的普通函数不同,成员模板是函数模板。在类外定义一个成员模板时,必须同时为类模板和函数模板提供参数列表。类模板的参数在前。

template<typename T>
template<typename It>
Blob<T>::Blob(It a, It b){};

实例化一个类模板的成员模板,必须提供类和函数模板的实参。
extern关键字修饰一个实例化声明,该声明只有一个定义。只有一个实例化的模板。可能有多个extern,但只有一个定义。
有extern声明的文件,编译成.o或.obj的时候,不包含该实例化,在其定义的类的.o或.obj中才有该实例。必须将这两个文件链接到一起。
实例化定义会实例化所有成员,即使不使用这些成员。
shared_ptr是间接保存删除器的,删除器的类型运行时才知道,中途也可更改删除器类型。
unique_ptr的删除器是成员,所以在创建对象时就要确定删除器的类型,且不能更改。

5.模板实参推断:

如果一个函数形参使用模板类型实参,则一般是生成一个新的模板实例。能够自动应用的类型转换(不生成新的)只有const转换(非const转const)以及数组(一个数组可转换为指向首元素的指针)或函数到指针的转换。
显式模板参数:在模板名后使用尖括号指定模板类型。从左到右的顺序匹配模板参数列表。
当希望确定返回类型时,可以使用尾置返回类型。
可以使用remove_reference获得元素类型,实现类型转换。
remove_reference<<decltype(*begA)>>::type;
remove_reference模板有一个成员变量type,可以脱去引用,返回元素类型。
函数指针或一个函数指针赋值时。编译器使用指针的类型来判断模板参数。
用函数模板初始化一个当参数是一个函数模板实例的地址时,程序上下文必须满足:每个模板参数能唯一确定其类型或值。
规则一:将一个左值传递给函数的右值引用参数。且次左值引用指向模板类型参数(T&&)时,编译器推断模板类型参数为实参的左值引用类型。
规则二:间接创建一个也用的引用,则这些引用形成了折叠在所有情况下。(除一个例外),引用会折叠成一个普通的左值引用。也用折叠,只能应用于间接创建的,引用的引用如类型别名或模板参数。
这两个规则造成的结果:
如果一个函数参数是一个指向模板类型参数的右值引用。则它可以被绑定到一个左值。
如果实参是一个左值,则推断出的模板实参类型将是一个左值饮用,且函数参数将被实例化为一个普通左值引用。
如果一个函数参数是指像模板参数类型的右值引用,则可以传递给他任意类型的实参。
若代码中涉及的类型可能是非引用和引用,则编写正确的代码就比较困难了。可以使用remove_reference来转换成特定的类型。
从一个左值static_cast到一个右值引用是允许的。
转发使用std::forward(arg) 能保证原始实参的类型。

6.重载与模板:

函数模板可以被另一个模板或一个普通非模板函数重载。
候选的函数模板总是可行的,有非模板函数,先匹配非模板函数。多个重载模板对一个调用提供同样好的匹配时,应选择最特例化的版本。
通常忘记声明一个函数代码会编译失败,但是对于重载函数模板的函数来说,可以从模板实例化出与调用匹配的版本。为了避免匹配到我们不希望的版本,最好都写出函数声明。

7.可变参数模板:

可变参数模板就是一个可以接受可变数目参数的模板函数或模板类。
可变数目的参数被称为参数包。
分为模板参数包(表示零个或多个模板参数)和函数参数包(表示零个或多个函数参数。)。
class…或typename…指出接下来的参数表示零个或多个类型的列表。
template<typename T, typename… Arags> // arags是一个模板参数包
void Foo(const T &t, const Arags& … Rest); // Rest是一个函数参数包。
sizeof…(args)需要知道包中多少元素时,可以使用sizeof运算符。
可以使用一个initializer_list来定义一个可接受可变数目实参的函数。但所有实参必须具有相同类型。
可变参函数通常是递归的。

template<typename T>
ostream &print(ostream& os, const T& t){	// 递归的终止函数
	return os<<t;
}
template<typename T, typename... Args>
ostream &print(ostream& os, const T& t, const Args... rest){
	os<<t<<", ";
	return print(os, rest...);
}

当定义可变参数版本的print时,非可变参数版本(即递归终止函数)的声明必须在作用域中。否则无限递归。
包扩展就是将它分解为构成的元素。我们要提供用于每个扩展元素的模式。
省略号(…)来触发扩展操作。
扩展中的模式会独立地应用于包中的每个元素。

8.模板特例化:

当我们不能或不需要使用模板版本时,可以定义类或函数模板的一个特例化版本。
template <> // compare的特殊版本
int compare(const char* const &p1, const char* const &p2)
{ return strcmp (p1, p2); }
特例化的本质是实例化一个模板,而非重载它。因此特例化不影响函数匹配。
模板及其特例化版本应该声明在同一个文件中。所有同名版本的声明应该放在前面,然后是这些版本的特例化版本。
只能部分特例化类模板,不能部分特例化函数模板。
可以只特例化特定成员函数,而不是特例化整个模板。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值