谭书:谭浩强版《C++程序设计(第3版)》
Cpp:《C++ Primer Plus(第6版)》
EC:《Effective C++(第3版)》
一、模板
函数模板【CppP231】
声明:
template<typename Type>
void fun(Type a,float b);
定义:
template<typename Type>
void fun(Type a,float b){……}
(1)函数模板本身不会创建任何函数,只有在调用函数模板时才会根据实际情况创建相应的实际函数
(2)最终的程序不会包含函数模板,只包含创建的实际函数
(3)函数模板的重载与普通函数的重载相同,需要函数特征标不同,与函数类型无关:
void fun(Type a,float b);
void fun(Type a,char b);
(4)函数模板可能无法处理某些数据类型
比如给fun(Type a,float b)传递实参fun(s,1.0),其中s是结构体变量,fun中的某些语句可能无法应用到结构体变量s上
这时候需要对特定类型的数据单独编写具体化的模板定义,即“显式具体化”
函数模板的具体化【CppP234】
函数模板的具体化:都是使用具体类型的函数定义,而不是通用描述
(1)显式实例化:直接命令编译器以int创建对应的实际函数。借用模板来生成函数定义,并不专门写函数定义。
声明时实例化:template void fun<int>(int,float); //此时template后面不写<typename Type>
使用时实例化:fun<int>(1.0,2.0); //发现第一个参数不是int型,则将1.0强制转换为int型
(2)隐式实例化:借用模板来生成函数定义,并不专门写函数定义
使用时实例化:fun(1,2.0); //编译器发现第一个参数类型为int,则以int创建对应的实际函数
(3)显式具体化:有自己专门的函数定义
声明时具体化:template <> void fun<int>(int,float);
template <> void fun(int,float); //<int>可以省略,两种声明等价,因为函数的参数列表中,第一个参数的类型由模板的Type具体为int
函数定义: template <> void fun<int>(int,float){……}
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————
总结:
(1)调用的优先顺序: 非模板函数 > 显式具体化 > 常规函数模板
void fun(int,float);
template <> void fun<int>(int,float);
template<typename Type> void fun(Type,float);
(2)不要在同一个文件中使用同一种类型的显式实例化和显示具体化:
template void fun<int>(int,float);
template <> void fun<int>(int,float);
调用时会出现歧义
类模板【谭书P293 CppP463 P470】
类模板声明:
template <class Type,int n> //n为非类型参数,可以是:整型、枚举、引用、各种指针
class A{……}
(类内)定义类模板的成员函数:
Type max(Type x1,Type x2){……}
(类外)定义类模板的成员函数:
template <class Type,int n>
Type A<Type,n>::max(Type x1,Type x2){……} //第一个Type指的是max函数的返回值类型为Type
类模板定义对象:
A <int,3> a1(1,2);
————————————————————————————————————————————————————————————————————————————————————————————
(1)类模板不是真正的类,类模板的成员函数体也不是函数的定义。类模板是编译器指令,说明了如何生成类和成员函数定义。
(2)在调用时,即实例化时才生成一个对应的类及其成员函数,如A <int,3> a1(1,2)和A <float,5> a1(1,2)分别生成了两个相互独立的类
(3)应该把类模板的类体、成员函数体(整个类模板的东西)放在同一个头文件中,使用时包涵这个头文件
(4)类模板可以作为基类,派生出派生类模板
(5)在类模板内可以写缩写A,在类外必须使用完整的:A<Type,n>
(6)默认类型模板参数:
template <class Type1=int,int n=5>
class A{……}
使用时:A<> a; // 模板参数均有默认值,使用时,类模板<>不能省略,函数模板<>可以省略
(7)类型参数默认值:类模板可用,函数模板不可用(C++11开始函数模板也可用类型参数默认值)
非类型参数默认值:类模板、函数模板均可用
类模板的具体化【CppP473】
类模板的具体化:模板以泛型的方式描述类,具体化使用具体的类型生成类声明
(1)显式实例化:使用关键字template并指出所需类型来声明类。该声明必须位于模板定义所在的名称空间中。
template class A<int,3>; //此时虽然没有创建对象,但也将生成具体的类声明
(2)隐式实例化:在创建对象时,给出所需的类型,编译器使用模板的描述来生成具体的类
A <int,3> a1(1,2);
在创建对象之前,编译器不会生成具体的类声明:
A<int,3> *p; //不生成具体的类声明
p = new A<int,3>; //生成具体的类声明
(3)显式具体化:特定类型的类声明,用于替换模板生成的版本。因为类模板的某些操作对于特定类型并不合适,需要对这些操作进行修改。
具体化模板:template <> class A<int,3>{…}
创建对象 :A<int,3> a1(1,2); //将使用基于类模板A修改过的具体化模板,而不是通用的模板来生成具体的类声明
当具体化模板和通用模板都与实例化请求匹配时,编译器将使用具体化版本
(4)部分具体化:给类型参数之一指定具体的类型,template后面的<>内声明的是没有被具体化的类型参数
类模板 :template <class Type1,class Type2> class A{…}
显示具体化:template <> class A<int,int>{…} //将Type1、Type2具体化为int
1、部分具体化:template <class Type1> class A<Type1,int>{…} //将Type2具体化为int
2、为指针类型提供类模板的特殊版本:
template <class Type1,class Type2*> class A{…} //将Type2具体化为指针类型
3、将一个类型参数具体化为另一个类型参数:
template <class Type1> class A<Type1,Type1>{…} //将Type2具体化为Type1
template <class Type1> class A<Type1,Type1*>{…} //将Type2具体化为Type1的指针类型
如果有多个模板可供选择,编译器将使用具体化程度最高、最匹配的模板:
A<double,double> a1; //使用A<Type1,Type2>
A<double,int> a1; //使用A<Type1,int>
A<double,double*> a1; //使用A<Type1,Type2*>
模板作为类成员【CppP474】
函数模板作为类模板成员:
类内声明:
template<typename T>
class A{
public:
template<typename U>
void fun(U a,T b);
}
类外定义:
template<typename T> //类模板
template<typename U> //函数模板
void A<T>::fun(U a,T b){…}
————————————————————————————————————————————————————————————————————————————————————————————
类模板作为类模板嵌套类
类内声明:
template<typename T>
class A{
private:
template<typename V>
class B;
}
类外定义:
template<typename T> //类模板
template<typename V> //嵌套的类模板
class A<T>::B{…}
类模板作为模板的类型参数【CppP476】
template<template<typename T> class U,typename V> //template<typename T> class代替typename;U为类型参数,是一个类模板的名
class A{
private:
U<int> u1; //用类模板U创建对象
U<V> u2;
}
A<B> a1; //以类模板B为类型参数,创建对象a1
A内的U<int> u1就替换为B<int> u1,U<V> u2就替换为B<V> u2
类模板与友元函数【CppP477】
【非模板友元函数】
两步:
(1)在类内声明友元函数:
template<typename T>
class A{
public:
friend void fun(T a); //注意,带有T不代表fun就是模板函数
}
(2)对于所有出现的A的具体化类型,fun()都要给出对应的具体化重载版本:
A<int> a1;
A<double> a2;
void fun(int a){…}
void fun(double a){…}
————————————————————————————————————————————————————————————————————————————————————————————
【约束模板友元函数】
类模板和函数模板一一对应,类的每种具体化都会生成对应的函数具体化
三步:
(1)类定义前面声明每个函数模板:
template<typename T> void fun1();
template<typename T> void fun2(T);
(2)类定义内将函数模板声明为友元:
friend void fun1<T>();
friend void fun2<>(T); //如果能从参数推断出fun2的类型,则可以省略<>中的T。也可以写全template<typename T> void fun2<T>(T);
(3)类外定义友元函数模板
template<typename T>
void fun1(){…}
template<typename T>
void fun2(T a){…}
————————————————————————————————————————————————————————————————————————————————————————————
【非约束模板友元函数】
多对多。类的具体化与函数的具体化相互独立
两步:
(1)类定义内声明函数模板和友元
template<typename T>
class A{
public:
template<typename U> friend void fun(U); //实际调用fun()时会根据参数类型自动生成具体化定义
}
(2)类外定义友元函数模板
template<typename U>
void fun(U){…}
类模板提供一系列别名【CppP482】
template<typename T>
using aa = A<T,3>; //将aa作为A<T,3>的别名
aa<int> a1; //a1为A<int,3>的对象
using= 用于非模板时:
using pin = int *; //pin作为int*的别名
二、容器
vector与array类模板【CppP99】
vector<typeName> v(n_elem); //定义一个类型为typeName的,长度为n_elem的vector对象v
(1)vector用new创建,因此存储在堆(自由存储区)中
(2)一般初始长度设置为0,利用vector包中的各种方法插入、删除值时,自动调整长度
————————————————————————————————————————————————————————————————————————————————————————————
array<typeName,n_elem> arr; //定义一个类型为typeName的,长度为n_elem的array对象arr
(1)array对象的长度是固定的,存储在栈中(普通数组作为自动变量,也存储在栈中)
(2)n_elem不能是变量,只能填一个常数或符号常量(const int a=4;),这一点与vector不同
(3)可以用一个arr对象给另一个arr对象赋值(对象的赋值):arr1 = arr2;