Przeglądaj źródła

feat: 添加模板的一些示例

刘聪 3 lat temu
rodzic
commit
610ae24ddf
1 zmienionych plików z 322 dodań i 2 usunięć
  1. 322 2
      cpp/泛型与模板.md

+ 322 - 2
cpp/泛型与模板.md

@@ -768,7 +768,7 @@ template<typename T>
 void fill(Array<T>*, T cosnt & = T())
 ```
 
-不过使用`T()`去初始化缺省调用实参也可能存在问题
+不过使用`T()`去初始化缺省调用实参也可能存在问题,比如下面的代码
 
 ```cpp
 class Value{
@@ -780,4 +780,324 @@ void init(Array<Value> *arr){
     fill(arr, zero);    // 正确的
     fill(arr);          // 错误的 因为不存在Value()的无参构造函数
 }
-```
+```
+
+除了上述两种基本类型的模板之外,还可以使用相似的符号来参数化其他的3种声明
+
+1. 类模板的成员函数的定义
+2. 类模板的嵌套类成员的定义
+3. 类模板的静态数据成员的定义
+
+```cpp
+template <int I>
+class CupBoard {
+    void open();                //译注:隶属于 CupBoard class template
+    class Shelf;                //译注:隶属于 CupBoard class template
+    static double total_weight; //译注:隶属于 CupBoard class template
+    // ...
+};
+
+template <int I>
+void CupBoard<I>::open()
+{
+    // ...
+}
+
+template <int I>
+class CupBoard<I>::Shelf {
+    // ...
+}; 
+
+template <int I>
+double CupBoard<I>::total_weight = 0.0;
+```
+
+**成员函数不能被声明为虚函数**,因为虚函数调用机制的普遍实现都使用了一个大小固定的表,每个虚函数都对对应表的一个入口
+
+但是,成员函数模板的实例化个数,要等到整个程序都翻译完毕才能确定,这就和表的大小(固定的)发生了冲突
+
+不过,类模板的普通成员函数可以是虚函数,因为当类被实例化之后,他们的个数是固定的
+
+```cpp
+template<typename T>
+class TestClass
+{
+public:
+	virtual ~TestClass();               // Success
+
+	template<typename T2>
+	virtual void Copy(const T2&);       // Error : 成员函数模板不能是虚拟的
+};
+```
+
+### 模板参数
+
+3种模板参数
+1. 类型参数
+2. 非类型参数
+3. 模板的模板参数
+
+**类型参数**是通过关键字typename或者class引入的,关键字后面必须是一个简单的标识符,使用逗号隔开多个标识符,等号(=)表示缺省模板实参
+
+在模板声明内部,类型参数的作用类似于typedef。如果T是一个模板参数,就不能使用注入`class T`等形式的修饰名称,即使T是一个要被class类型替换的参数也不可以
+
+```cpp
+template<typename Allocator>
+class List{
+    class Allocator* allocator;     // Error
+    friend class Allocator;         // Error
+};
+```
+
+**非类型参数**表示的是 在编译器或链接期可以确定的常值
+
+整个常值必须是
+1. 整数或者枚举类型
+2. 指针类型
+3. 引用类型
+
+所有其他的类型都不允许作为非类型参数使用
+
+```cpp
+template<typename T,                            // 类型参数
+            typename T::Allocator* Allocator>   // 非类型参数
+
+template<int buf[5]>
+class Lexer;
+// 上面等价于下面
+template<int *buf>
+class Lexer;
+
+template<int const length>
+class Buffer;
+// 上面等同于下面 const是无效的
+template<int length>
+class Buffer;
+```
+
+非类型模板参数只能是右值,不能被取地址,不能被赋值
+
+**模板的模板参数**是代表类模板的占位符,不能使用关键字class或union
+
+```cpp
+template<template<typename X> class C>  // Success
+void f(C<int>* p);
+
+template<template<typename X> struct C> // Error
+void f(C<int>* p);
+
+template<template<typename X> union C>  // Error
+void f(C<int>* p);
+```
+
+模板的模板参数的参数可以具有缺省模板实参
+
+```cpp
+template<template<typename T,
+                    typename A = MyLoocator> class Container>
+class Adaptation{
+    Container<int> storage; // 隐式等同于 Container<int, MyLoocator>
+    // ...
+}
+```
+
+对于模板的模板参数而言,它的参数名称只能被自身其他参数的声明使用
+
+```cpp
+template<template<typename T, T*> class Buf>
+class Lexer{
+    static char storage[5];
+    Buf<char, &Lexer<Buf>::storage[0]> buf;
+    // ...
+};
+
+template<template<typename T, T*> class List>
+class Node{
+    static T* storage;  // Error 模板的模板参数的参数不能被用在这里
+    // ...
+}
+```
+
+任何类型的模板参数都可以拥有一个**缺省实参**,只要该缺省实参能够匹配整个参数就可以。显然缺省实参不能依赖于自身的参数,但可以依赖于前面的参数
+
+```cpp
+template <typename T, typename Allocator = allocator<T>>
+class List;
+```
+
+与缺省的函数调用实参的约束一样,对于任意一个模板参数,只有在之后的模板参数都提供了缺省实参的情况下,才能具有缺省模板实参
+
+```cpp
+template <typename T1, typename T2, typename T3, typename T4 = char, typename T5 = char>    // Success
+class MyClass1;
+
+template <typename T1, typename T2, typename T3, typename T4 = char, typename T5>           // Error
+class MyClass1;
+
+template <typename T1 = char, typename T2, typename T3, typename T4, typename T5>           // Error
+class MyClass1;
+```
+
+除此之外,缺省实参不能重复声明
+
+```cpp
+template<typename T = void>
+class Value;
+
+template<typenaem T = void>
+class Value;            // 重复出现的缺省实参
+```
+
+### 模板实参
+
+模板实参是指:在实例化模板时,用来替换模板参数的值
+
+1. 显式模板实参:紧跟在模板名称后面,尖括号内部的显式模板实参值。所组成的整个实体称为template-id
+2. 注入式类名称:对于具有模板参数P1、P2...的类模板X,在它的作用域中,模板名称X等同于template-id
+3. 缺省模板实参
+4. 实参演绎:对于不是显式指定的函数模板实参,可以在函数的调用语句中,根据函数调用实参的类型来演绎出函数模板实参
+
+**函数模板实参**
+
+对于函数模板的模板实参,可以显式的指定它们,也或者借助模板的使用方式对他们进行实参演绎
+
+```cpp
+template<typename T>
+inline T const& max(const T& a, const T& b)
+{
+    return a < b ? b : a;
+}
+
+int main(){
+    max<double> (1.0, 2.0); // 显式指定模板实参
+    max(1.0, 2.0);          // 实参演绎
+    max<int>(1.0, 2.0);     // 显式的
+    return 0;
+}
+```
+
+但是某些模板实参永远也得不到演绎的机会,所以最好时把实参所对应的参数放在模板参数列表的开始处,从而可以显示的指定这些参数,而其他的参数仍然可以进行实参演绎
+
+```cpp
+template<typename DstT, typename SrcT>
+inline DstT implicit_cast(SrcT const& x){   // SrcT可以被演绎 但是DstT不可以
+    return x;
+}
+
+double val = implicit_cast<double>(1);
+```
+
+> 比如把`implicit_cast`改为`template<typename SrcT, typename DstT>`就必须显式的指定两个模板实参
+
+由于函数模板可以被重载,所以显式提供所有的实参并不足以表示每一个函数
+
+```cpp
+template <typename Func, typename T>
+void apply(Func func_ptr, T x)
+{
+	func_ptr(x);
+}
+template <typename T> void single(T);
+template <typename T> void multi(T);
+template <typename T> void multi(T*);
+
+void foo() {
+	apply(single<int>, 1);  // success
+	apply(multi<int>, 1);   // error T 或者 T* 都可以被匹配
+}
+```
+
+另外,在函数模板种,显式的指定模板实参可能会试图构造一个无效的C++类型
+
+```cpp
+template<typename T>
+RT1 test(typename T::X const *);
+
+template<typename T>
+RT2 test(...);
+```
+
+表达式`test<int>`会让第一个函数模板毫无意义,因为int中没有名为X的类型,但是它可以匹配第二个函数模板。因此,表达式`&test<int>`能够表示一个唯一函数地址(第二个函数地址)
+所以不能用int来替换第一个模板的参数,并不意味着错误,**替换失败并非错误**(substitution-failure-is-not-an-error, SFINAE)
+
+```cpp
+typedef char RT1;
+typedef struct { char a[2]; } RT2;
+
+#define type_has_memeber_type_X(T) (sizeof(test<T>(0)) == 1)
+
+template<typename T>
+RT1 test(typename T::X const*) {
+	return 1;
+}
+
+template<typename T>
+RT2 test(...) {
+	return 0;
+}
+
+void foo_temp() {
+	std::cout << type_has_memeber_type_X(int) << std::endl;
+	std::cout << type_has_memeber_type_X(TestClass) << std::endl;
+}
+```
+
+于是,就可以在编译器检查给定类型T是否具备成员类型X
+
+**SFINAE**原则保护的只是防止创建无效的类型,但不能保护试图计算无效的表达式
+
+```cpp
+template<int I> void f(int (&)[24/(4-I)]);
+template<int I> void f(int (&)[24/(4+I)]);
+int main()
+{
+    &f<4>; // ERROR:除数为 0
+}
+```
+
+**非类型实参**
+
+非类型模板实参是那些提花你非类型参数的值,必须是下面的某一种
+
+1. 某一个觉有正确类型的非类型模板参数
+2. 一个编译器整形常值或枚举值。这只有在参数类型和值类型能够进行匹配,或值的类型隐式地转换为参数类型的前提下,才是合法的
+3. 漆面有单目运算符&(取地址)的外部变量或者函数的名称。对于函数或者数组变量,&运算符可以省略
+4. 对于引用类型的非类型模板参数,前面没有&运算符的外部变量和外部函数也是可取的
+5. 一个指向成员的指针常量
+
+```cpp
+template <typename T, T nontype_param>
+class C;
+
+C<int, 33>* c1;                         // 整数型(integer type)
+
+int a;
+C<int*, &a>* c2;                        // 外部变量的地址
+
+void f();
+void f(int);
+C<void (*)(int), f>* c3;                // 一个函数名称。重载解析规则在此选用 f(int)。& 会被隐寓加入。
+
+class X {
+    public:
+        int n;
+        static bool b;
+};
+C<bool&, X::b>* c4;                     // static class members 都可接受
+
+C<int X::*, &X::n>* c5;                 // pointer-to-member 常数亦可接受
+
+template<typename T>
+void templ_func();
+
+C<void (), &templ_func<double> >* c6;   // function template 具现体也是一种函数
+```
+
+模板实参的一个普遍约束是在程序创建的时候编译器或者链接器要能够确定实参的值
+
+另外,有一些常值不能作为有效的非类型实参
+
+1. 空指针常量
+2. 浮点值
+3. 字符串
+