C++模板——读书总结篇
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
C++模板——读书总结篇
⽬录
函数模板
定义
例⼦⼀
template<typename T>
void Func() {
cout << "hello, world" << endl;
}
例⼦⼆
template <typename T, template<typename,typename> class A, size_t N>
void Func(T input) {
T a = input;
A<int , double> a;
int array[N] = {};
}
实例化
隐式实例化
1. 编译器使⽤函数实参来推断模板实参进⾏实例化。
2. 对于返回值类型为模板参数的情况,编译器⽆法推断,必须显⽰指定。
3. 当我们使⽤⼀个函数模板初始化⼀个函数指针时,编译器会使⽤函数指针的类型来推断模板实参的。
如果不能唯⼀实例化(编译
器推断不出来了),该操作就会失败。
显⽰实例化
当隐式⽆法实例化时,可以显⽰实例化。
例如:
// 函数模板的声明与定义
template<typename RetType, typename T>
RetType Func(T input) {
RetType a = 1;
return a;
}
// 函数模板的实例化
double a = 100.4;
Func<int>(a);
类模板
模板的定义
1. 举例⼀:普通的定义
template<typename T>
class Base {
};
2. 举例⼆:使⽤模板类作为类型参数
template<typename T>
class Base {
};
// 模板类作为类型形参数。
template<typename T, template<typename M> class Base>
class Other {
}
3. 模板类的成员函数:对于类模板的成员函数,即可以在类内定义(默认为inline),也可以在类外定义(需要使⽤template关键字开
始)。
// 类内定义
template<typename T>
class A {
public:
void Print() {
cout << "hello, world" << endl;
}
};
// 类外定义
template<typename T>
class A {
public:
void Print();
}
template<typename T>
void A<T>::Print() {
cout << "hello, owrld" << endl;
}
4. 类模板的类型名的简写: 在类模板⾃⼰的作⽤域时,可以直接使⽤类模板名,⽽不需要类模板名<类型参数> ,就是相当于简化了。
例
如:
template<typename T>
class A {
public:
A<T>& GetInstance() {
A* instance = new A; // A<T> 可以简写为A.
return *instance;
}
};
类模板的实例化
1. 默认情况下,⼀个类模板的成员函数只有在使⽤时,它才被实例化。
所以呢,有⼀些成员函数⽆法实例化时,只要不使⽤它,就没有
问题。
2. 使⽤类模板时,必须使⽤在<>中加上类型参数进⾏显式实例化。
类模板与友元
1. 当⼀个类包含⼀个友元时,类与友元各⾃是否为模板是相互⽆关的。
如果⼀个类包含了⼀个⾮模板的友元,则友元被授权可以访问所
有模板实例。
如果友元⾃⼰是模板,类可以授权给所有友元模板实例。
2. 为了引⽤⼀个模板的特定实例,必须先声明模板⾃⾝。
3. 在新标准中,可以将模板类型参数声明为友元。
template<typename type>
class Bar { friend Type;};
模板类的别名
1. typedef 不允许为模板定义⼀个类型别名,因为模板不是⼀种类型。
2. 使⽤using 可以为模板定义⼀个别名,例如:
template<typename T, typename M>
using twin = pair<T, M>;
template<typename T>
using ttt = pair<T, int>;
twin<int, char> a;
ttt<double> b;
模板类内的类型成员
1. 在模板内部可以定义⼀种类型,但是在使⽤的时候存在问题,编译器⽆法区别使⽤的类型还是类内的static 变量?例如 : T::new_type,
这个new_type到底是变量还是类型呢?
2. 默认情况下, c++语⾔假定通过作⽤域运算符访问的名字不是类型
3. 当我们使⽤类型时,使⽤typename 来标识它,例如:typename T::new_type a(定义变量a)。
成员函数模板
1. ⼀个类(⽆论是否为模板类),都只可以是模板的成员函数,这种成员叫作成员模板。
2. 成员模板不可以是虚函数。
3. 普通类的成员模板,即普通类的成员函数为模板函数,性质与函数模板相同。
4. 类模板的成员模板:
a. 类与成员模板,它们有各⾃的独⽴的模板参数。
b. 当我们在类模板之外定义⼀个成员模板时,必须同时为类模板与成员模板提供参数列表。
c. 类模板的参数列表在前,成员模板的模板参数在后。
// 类模板声明
template<typename T>
class A {
public:
template<typename M>
void Print();
};
// 成员函数的定义
template<typename T>
template<typename M>
void A<T>::Print() {
cout << "hello" << endl;
}
// 使⽤
A<int> a;
a.Print<double>();
模板的参数
1. 对于模板参数,即可以为类型,也可以为⾮类型参数,也可以为模板类。
2. 当模板的参数为⾮类型参数时,
a. 它为⼀个值,⽽⾮类型,编译期可以确认的值。
b. 它只可以为整型、可以为对象或函数类型的指针或左值引⽤。
(对象必须有静态⽣命周期,就是全局变量了。
如果是static 变量,
需要是const的,原因是⾮const的static变量的地址编译器可能⽆法确定,因为的是⽂件作⽤域,我猜的)
// 类型参数
template<typename T>
void Print();
// ⾮类型参数
template<size_t M>
void Print();
// 模板类参数
template <template<typename> class A>
void Print();
// 指针或引⽤
template<int* p>
void Print() {
cout << 指针 << endl;
}
template<int& v>
void Print() {
cout << 引⽤ << endl;
}
int a = 10;
int& b = a;
Print<&a>();
Print<b>();
3. 默认参数
a. 例如:template<typename T = int, typename Y = double>
b. 对于模板参数,只有当它右侧的所有参数都有默认实参时,它才可以有默认实参。
4.
模板的实参的推断
1. 指的是函数模板中根据实参类型进⾏推断形参。
2. 对于函数模板来说,只有两种类型转换(隐式转换).除此之外,算术类型的转换、派⽣类到基类的转换、⽤户⾃定义的转换等都是不
允许的。
a). const 转换,⼀个⾮const 对象的引⽤或指针的实参可以传递给⼀个const的引⽤或指针的形参。
b). 数组与函数指针转换:如果函数不是引⽤类型(当是引⽤类型时,数组的⼤⼩不⼀样,类型不同)时,可以对数组或函数类型的实
参应⽤正常的指针转换。
即数组转换为指针,函数转换为函数指针。
3. 在函数模板中,如果使⽤到普通类型定义的参数,它们是可以正常转换的。
只有模板的类型参数不可以正常转换。
4. 正常的类型转换也可以应⽤于显⽰指定的实参的。
5. 当我们使⽤⼀个函数模板初始化⼀个函数指针时,编译器会使⽤函数指针的类型来推断模板实参的。
如果不能唯⼀实例化(编译器推
断不出来了),该操作就会失败。
6. 重量级知识点:引⽤折叠与右值引⽤参数:
a): 当模板参数为左值引⽤时,只能传递给它的⼀个左值的实参。
b): 当模板参数为const 左值引⽤时,传递给它的⼀个左值的实参/临时对象、字⾯值等
c): 神奇到来:当模板函数参数是⼀个右值引⽤时,传递给它的实参可以是任意类型(也就是说左值也可以),原因就是因为引⽤折
叠。
d): 例外规则⼀: 当我们将⼀个左值传给模板函数的右值引⽤参数(T&&)时, 编译器推断模板类型参数为的左值引⽤类型,例如为int类型
时,推断T为int&.
e): 例外规则⼆:如果我们间接创建了⼀个引⽤的引⽤,则这些引⽤形成了引⽤折叠。
正常情况下,不能直接创建引⽤的引⽤,但是可
以间接创建。
⼤部分情况下,引⽤的引⽤会折叠为普通的左值引⽤(T& &、T& &&、 T&& &),右值引⽤的右值引⽤,分折叠成右值引⽤。
f): 通过两个规则,得出⼀个结论:如果⼀个函数模板参数为右值引⽤,则可以传递给它任意类型的实参
7. 引⽤折叠,引出的std::move的实现:
template <typename T>
typename remove_reference<T>::type&& move(T&& t) {
return static_cast<typename remove_reference<T>::type&&>(t);
}
控制模板的实例化
1. 编译器对模板的处理:当编译器遇到模板定义时,并不⽣成代码,⽽是当实例化模板时,才会⽣成代码。
也就是说,使⽤的时候才⽣
成代码。
2. 通常情况下,当模板被使⽤时,才会进⾏实例化。
这意味着,相同的实例可能出现在多个对象⽂件中。
在⼤的系统中,多个⽂件实例
化相同模板的开销会很⼤,后处理时还要相办法只保留⼀份。
3. 在新的标准中,可以通过显⽰实例化来控制实例化的过程。
4. 实例化的声明,格式为:extern template declaration, 其中declaration 是类或函数声明,并且其中模板参数都已经被替换为模板的实参。
在
实例化的声明与定义之前,都应该已经定义好了模板类或者模板函数,否则编译报错。
// 实例化的声明
extern template class Blob<int>;
extern template Func(int a);
// 模板类或模板函数的声明,注意区分它们。
template< typname T>
class A;
template<typename T>
void Func();
5. 实例化定义, 格式为:template declaration
// 实例化的定义
template class Blob<int>;
template Func(int a);
6. 额外知识点:
a). 当编译器遇到extern时,不会在本⽂件中⽣成实例化的代码。
b). 将⼀个实例化的声明为extern就表⽰了在程序的其它位置有该实例化(不包含extern)
c). ⼀个类模板的实例化定义会实例化所有的成员函数。
这与处理类模板的普通实例化是不相同的(通常情况下,使⽤到的时候才进⾏
实例化)
模板的特例化
1. 特例化,就是我们⼈⼯地为编译器针对模板做了推导⼯作,遇到相同参数类型时,请使⽤⼈⼯给的模板样例,编译器别⾃⼰推断了。
2. 特例化与实例化是不同的。
实例化,编译器会⽣成实例代码的,⽐如⼀个函数模板,实例化时,会⽣成对应的函数。
特例化只是给了
⼀个⼈⼯的模板,编译器实例化时,请优先使⽤我给的特例化模板。
3. 全特例化:把所有的形参都进⾏特例化.
template <typename T1, typename T2>
void Func() {
cout << "hello" << endl;
}
template<>
void Func<int ,double>() {
cout << "wow" << endl;
}
4. 部分特例化:
a). 类模板可以部分特例化,函数模板不可以部分特例化。
b). 部分特例化分两种:特例化⼀部分参数和特例化参数的⼀部分。
// 特例化⼀部分参数
template <typename T1, typename T2>
class A {
public:
A() { cout << "general" << endl; }
};
template<typename T>
class A<int, T> {
public:
A() { cout << "first int" << endl; }
};
template<typename T>
class A<T, double> {
public:
A() { cout << "first double" << endl; }
};
A<char, char> a;
A<int ,char> b;
A<char, double> c;
// 特列化参数的⼀部分
template<typename T>
class A {
public:
A() { cout << "general " << endl;}
};
template<typename T>
class A<T&> {
public:
A() { cout << "reference " << endl;}
};
template<typename T>
class A<T*> {
public:
A() { cout << "pointer " << endl;}
};
A<int> a;
A<int&> b;
A<int*> c;。