这一章介绍了面向对象编程中最复杂的部分:模板与模板编程,读起来很吃力,总结也写了很久。其中16.2的类型转换部分会有点绕,16.4的可变参数模板则很实用,可以有效提高我们的开发效率。这篇内容较多较难,可以的话应该仔细看书慢慢看。
16.1.1 函数模板
// 类型模板参数,模板函数
// 此处的T是作为一个待定类型使用的
template<typename T>
int typeTemp(T inp) {
return static_cast<int>inp;
}
// 非类型模板参数,模板函数
// 此处的N是作为一个待定常量表达式使用的
template<unsigned N>
int typeTemp(int inp[N]) {
return inp[0];
}
16.1.2 类模板
// 需要保证目标友元在作用域中可见
template<typename T> class friendTemp1;
template<typename T> class friendTemp2;
// 类模板
template<typename T> class classTemp {
// 一对多的友元,template用不到的时候可以不用写,注意要有不同的标识符
template<typename X> friend class friendTemp1;
// 一对一的友元,模板实参需要写明白
friend class friendTemp2<int>;
};
// 类模板的全参数别名
template<typename A, typename B> using shortTemp = classTemp<A,B>;
// 固定一些参数的类模板别名
template<typename A, typename B> using ssTemp = classTemp<int, double>;
16.1.3-16.1.4 模板参数&成员模板
// 类模板的默认实参
template<typename A = int, typename B = double> class defaultTemp {
};
// 实例化时若想要使用全部默认实参则也不要忘了空尖括号对
defaultTemp<> test;
// 模板类
template<typename A> class ATemp {
// 成员模板的内部声明
template<typename B> void BFunc(B inp);
};
// 成员模板的外部定义
template<typename A>
template<typename B>
void ATemp<A>::BFunc(B inp){
return;
}
16.1.5-16.1.6 控制实例化&效率与灵活性
16.2 模板实参推断
// 使用尾置返回来返回待定的类型,此时它可以使用函数的参数
template<typename T>
auto fun(T inp) -> decltype(*inp) {
return *inp;
}
// move的定义,目标是对任意形式的输入都进行类型推断并返回推断的类型T的右值引用
// 根据实参推断出T的类型,左值则推断出左值引用t&,右值则是去掉右值引用的t
// 按照推断出来的类型T实例化emove_reference<T>::type
// 得到去掉引用引用的类型t,这样便确定下返回值是t&&
// 如果是左值,则直接加上右值引用并正确返回
// 再回去看模板函数的参数,发现此时的实参T&&则由两种情况,t&&或(t&)&&,发生引用折叠得t&
// 所以对于左值,实例化得 t&& move(t& inp); 对于右值,实例化得 t&& move(t&& inp)
template<typename T>
typename remove_reference<T>::type&& move(T&& inp) {
return static_cast<typename remove_reference<T>::type&&>(inp);
}
16.3 重载与模板
16.4 可变参数模板
// 首先需要写模板参数包
template<typename T, typename... Q>
// 然后函数参数中对应模板参数包写函数参数包
int test(T t, Q... q) {
// 用sizeof...()可以返回参数包中参数的数量
return sizeof...(q);
}
// 递归终止函数,一般是处理参数包的最后一个函数用的
template<typename T>
void print(T inp) {
cout << inp;
}
// 包扩展函数,通常递归调用自己,参数是传入的包但是第一个参数是固定的
// 通过固定的第一个参数从包中提取出一个参数输出,然后继续递归
// 通过省略号对参数进行包扩展,会将包中的内容展开为一个重载函数调用
template<typename T, typename... bag>
void print(T inp, bag... b) {
cout << inp;
print(b...);
}
int main() {
print(1, 2, 3, 4, 5); // 会输出12345
return 0;
}
// 和前面的print一致
template<typename T>
void print(T inp);
template<typename T, typename... bag>
void print(T inp, bag... b);
// 测试改变的元素
int add(int i) {
return i + 1;
}
// 包装函数
template<typename... bag>
void func(bag... b) {
// 包扩展在这里,通过对包调用函数后用省略号扩展
// 相当于让整个包的每个元素都进行了一次函数处理然后才传入
print(add(b)...);
}
int main() {
func(1, 2, 3, 4, 5); // 会输出23456
return 0;
}
16.5 模板特例化