Loading... # 一、类模板的基本范例和模板参数的推断 1. 类模板:是产生类的模具,通过给定的模板参数,生成具体的类,也就是实例化一个特定的类。 vector<int>,引入类模板的目的,减少代码冗余 2. 基本范例 myvector可以称为类名或者类模板 , myvector<T>可以称为类型名(myvector后面带了尖括号,表示的就是 一个具体类型了) 类模板中,只有被调用的成员函数,编译器才会产生出这些函数的实例化代码 ```cpp #include <iostream> using namespace std; //类模板定义 template <typename T> //T是类型模板参数,表示myvector这个容器所保存的元素类型 class myvector //myvector可以称为类名或者类模板 , myvector<T>可以称为类型名(myvector后面带了尖括号,表示的就是 一个具体类型了) { public: typedef T* myiterator; //迭代器 public: myvector(); //构造函数 myvector(T tmpt)//增加一个构造函数 用于测试 { } //myvector& operator=(const myvector&); //赋值运算符重载 在类内,类型名可以使用类名来替换 myvector<T>& operator=(const myvector&); //赋值运算符重载 写成上面那种形式也可以 在类里面 类名可以用类型名来提,不用加上T public: void myfunc() { cout << "myfunc()被调用" << endl; } static void mystaticfunc()// 也是只有当调用的时候 才会被实例化 { cout << "mystaticfunc()被调用" << endl; } public: //迭代器接口 myiterator mybegin(); //迭代器起始位置 myiterator myend(); //迭代器结束位置 }; //类模板实现 template <typename T> //在外面实现就要把这个抓出来 myvector<T>::myvector() //类外构造函数的实现 { } int main() { myvector<int> tmpvec; //T被替换成了int 但是这里的T就不能被省略 tmpvec.myfunc(); //调用普通成员函数 不去调用的话 编译器根本不会去实例化这个函数 myvector<string>::mystaticfunc();// 只有被调用了 才会在二进制文件.o文件中出现 return 0; } ``` 在类内的话,声明可以不用写成这种形式 myvector<T>& operator=(const myvector&); 写成myvector& operator=(const myvector&); 这种就行 符号表里都会有这两个函数的定义 ```cpp peter@iZbp1aq6c9kbbder4q405mZ:~/template_class/template_demo/2part$ nm 2_2.o U __cxa_atexit U __dso_handle U _GLOBAL_OFFSET_TABLE_ 00000000000000a0 t _GLOBAL__sub_I_main 0000000000000000 T main U __stack_chk_fail 0000000000000053 t _Z41__static_initialization_and_destruction_0ii 0000000000000000 W _ZN8myvectorIiE6myfuncEv 0000000000000000 W _ZN8myvectorIiEC1Ev 0000000000000000 W _ZN8myvectorIiEC2Ev 0000000000000000 n _ZN8myvectorIiEC5Ev 0000000000000000 W _ZN8myvectorINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEE12mystaticfuncEv U _ZNSolsEPFRSoS_E U _ZNSt8ios_base4InitC1Ev U _ZNSt8ios_base4InitD1Ev U _ZSt4cout U _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_ 0000000000000000 r _ZStL19piecewise_construct 0000000000000000 b _ZStL8__ioinit U _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc ``` 1. 模板参数的推断 **在C++17中**,类模板的类型模板参数也能推断了 ```cpp //类模板定义 template <typename T> //T是类型模板参数,表示myvector这个容器所保存的元素类型 class myvector //myvector可以称为类名或者类模板 , myvector<T>可以称为类型名(myvector后面带了尖括号,表示的就是 一个具体类型了) { public: typedef T* myiterator; //迭代器 public: myvector(); //构造函数 myvector(T tmpt)//增加一个构造函数 用于测试 { } //myvector& operator=(const myvector&); //赋值运算符重载 在类内,类型名可以使用类名来替换 myvector<T>& operator=(const myvector&); //赋值运算符重载 写成上面那种形式也可以 在类里面 类名可以用类型名来提,不用加上T public: void myfunc() { cout << "myfunc()被调用" << endl; } public: //迭代器接口 myiterator mybegin(); //迭代器起始位置 myiterator myend(); //迭代器结束位置 }; int main() { //c++17才支持 g++ 2_2.cpp -std=c++17 myvector tmpvec2(12); //无需指定模板参数了 tmpvec2.myfunc(); //调用类模板中的普通成员函数 return 0; } ``` [为什么这上面的int可以进行隐式推断](https://www.notion.so/int-21e233500dc280c38ea0c7c89faada60?pvs=21) 1. 推断指南(deduction guide)概要了解 4.1 隐式的推断指南 针对类模板A的每个构造函数,都有一个隐式的模板参数推断机制存在,这个机制,被称为隐式的推断指南 template<typename T>· A(T,T)->A<T>; 表达出现->左侧部分内容或者形式时,请推断成->右侧的类型。右侧类型也被称为“指南类型” ->左侧部分:该推断指南所对应的构造函数的函数声明,多个参数之间用,分隔。 ->右侧部分:类模板名,接着一个尖括号,尖括号中是模板参数名。 整个推断指南的含义:当用调用带两个参数的构造函数通过类模板A创建相关对象时,请用所提供的构造函数的实参来推断类模板A的模//板参数类型, **一句话:推断指南的存在意义就是让编译器 能够把模板参数的类型推断出来**。 template<typename T> A(T,T)->A<double>; 那么:A aobj1(15, 16);代码行 相当于A<double> aobj1(15, 16); ```cpp template<typename T> struct A { A(T val1, T val2) { cout << "A::A(T val1,T val2)执行了!" << endl; } A(T val) { cout << "A::A(T val)执行了!" << endl; } }; int main() { //c++17才支持 g++ 2_2.cpp -std=c++17 A<int> aobj1(15, 16); A aobj2(15, 16);//A<int> A aobj3(12.8);//A<double> return 0; } ``` 4.2 自定义的推断指南 ```cpp template<typename T> struct A { A(T val1, T val2) { cout << "A::A(T val1,T val2)执行了!" << endl; } A(T val) { cout << "A::A(T val)执行了!" << endl; } }; //C++17 自定义推断指南: 可以和类定义写在一起 template<typename T> A(T)->A<T>; int main() { A<int> aobj1(15, 16); A aobj2(15, 16); //A<int> A aobj3(12.8); //A<double> return 0; } ``` 4.3 没有构造函数的自动推导后B bobj3会编译报错 ```cpp template<typename T> struct B { T m_b; // 成员变量类型依赖于模板参数 T }; // 定义对象时必须显式指定 T 的类型(如 int) B<int> bobj1; // 正确:显式指定 T=int B<int> bobj2[15]; // 正确:数组中每个元素都是 B<int> B bobj3;//编译器无法推断 T 的具体类型(C++17 之前的类模板不支持参数推导),导致实例化失败。 ``` 修复办法: ```cpp template<typename T> struct B { T m_b; // 成员变量类型依赖于模板参数 T }; template<typename T> B(T)->B<T>;//添加自定义推断指南 //加上上面这句之后就能解决 bobj3;//编译器无法推断 T 的具体类型(C++17 之前的类模板不支持参数推导),导致实例化失败。 ``` # 二:类模板的各种特化 1. 全特化:就是把TC这个泛化版本中的所有模板参数都用具体的类型来代替构成一个特殊的版本(全特化版本)。 在理解上:泛化版本的类模板与全特化版本的类模板,只是名字相同(都叫TC),在其他方面,可以把实例化后的他们理解成是两个完全不同的类。 ```cpp //泛化:大众化,常规 template <typename T,typename U> struct TC { TC() { cout << "TC泛化版本构造函数" << endl; } void functest1(); }; template <typename T,typename U> //泛化版本类里面的一个全特化函数 void TC<T,U>::functest1() //functest1成员函数的全特化版本 { cout << "成员函数TC<double,int>::functest1的泛化" << endl; } template <> //全特化:所有类型模板参数都有具体类型代表,所以<>里就空了 因为类型参数都要被替换 struct TC<int, int> //类名后面有<的话 就是特化版本 { TC() { cout << "TC<int,int>特化版本构造函数" << endl; } void functest1();//如果要移出去的话 /*{ cout << "functest1特化版本" << endl;//特化版本可以写和泛化版本完全不一样的函数 }*/ //全特化可以有自己的成员函数 void functest2(); }; void TC<int, int>::functest1()//特化版本在两个都是Int的时候才有效 { cout << "functest1特化版本" << endl; } void TC<int, int>::functest2() { cout << "functest2特化版本" << endl; } int main() { TC<int, float> mytc; mytc.functest1();//普通成员函数TC<double,int>::functest1的泛化 TC<int, int> mytc2;// 全特化 mytc2.functest1();//functest1特化版本 mytc2.functest2();//functest2特化版本 调用时 才会实例化 return 0; } ``` **全特化可以有自己的成员函数**: **void functest2(); 泛化版本没有functest2这个函数**,但是特化版本中有functest2这个函数。 在理解上:泛化版本的类模板与全特化版本的类模板,只是名字相同(都叫TC),在其他方面,可以把实例化后的他们理解成是两个完全不同的类。 2. **普通成员函数的全特化** {在泛化版本中普通成员的全特化} ```cpp //泛化:大众化,常规 template <typename T,typename U> struct TC { TC() { cout << "TC泛化版本构造函数" << endl; } void functest1(); // static int m_stc; //声明一个静态成员变量 }; template <typename T,typename U> //泛化版本类里面的一个全特化函数 void TC<T,U>::functest1() //functest1成员函数的全特化版本 { cout << "成员函数TC<double,int>::functest1的泛化" << endl; } template <> //泛化版本类里面的一个全特化函数 void TC<double, int>::functest1() //functest1成员函数的全特化版本 { cout << "普通成员函数TC<double,int>::functest1的全特化" << endl; } int main() { TC<int, float> mytc; mytc.functest1();//普通成员函数TC<double,int>::functest1的泛化 TC<double, int> mytc3; mytc3.functest1();//普通成员函数TC<double,int>::functest1的全特化 return 0; } ``` **void TC<double, int>::functest1() 调用的是全特化版本** 1. 静态成员变量的全特化 特别值得一提的是:**如果进行了普通成员函数的全特化,或者是静态成员变量的全特化**,那么,就无法用这些全特化时指定的类型来对整个类模板进行全特化了。 {已经全特化过 所以可以理解为已经实例化过这个类了 , 想单独再实例化这个类的话,就不行} ```cpp //泛化:大众化,常规 template <typename T,typename U> struct TC { TC() { cout << "TC泛化版本构造函数" << endl; } void functest1(); static int m_stc; //声明一个静态成员变量 }; template <typename T,typename U> //泛化版本类里面的一个全特化函数 void TC<T,U>::functest1() //functest1成员函数的全特化版本 { cout << "成员函数TC<double,int>::functest1的泛化" << endl; } template <> //泛化版本类里面的一个全特化函数 void TC<double, int>::functest1() //functest1成员函数的全特化版本 { cout << "普通成员函数TC<double,int>::functest1的全特化" << endl; } template <typename T, typename U> int TC<T,U>::m_stc = 50; //定义静态成员变量 //全特化静态成员变量 template <> int TC<double, int>::m_stc = 100; //已经无法再全特化 /*template <> struct TC<double, int> { };*/ int main() { //如果进行了普通成员函数的全特化,或者是静态成员变量的全特化,那么,就无法用这些全特化时指定的类型来对整个类模板进行全特化了 TC<double, int> mytc3; mytc3.functest1();//普通成员函数TC<double,int>::functest1的全特化 cout << "mytc3.m_stc = " << mytc3.m_stc << endl; return 0; } ``` 因为已经实例化了TC<double, int>这个类,再去struct TC<double, int>就会造成实例化的重复,所以会失败。 case1: template <> //泛化版本类里面的一个全特化函数 void TC<double, int>::functest1() //functest1成员函数的全特化版本 { cout << "普通成员函数TC<double,int>::functest1的全特化" << endl; } case2: template <> int TC<double, int>::m\_stc = 100; 1. 类模板的偏特化(局部特化) > 一方面:模板参数数量上的偏特化,另一方面是模板参数范围上的偏特化 4.1 模板参数数量上的偏特化 ```cpp //偏特化版本 模板参数数量上的偏特化 template <typename U> struct TC<float, U> { TC() { cout << "TC<float,U>偏特化版本构造函数" << endl; } void functest1(); }; template <typename U> void TC<float, U>::functest1() { cout << "TC<float,U>::functest1偏特化版本" << endl; } int main() { TC<float, int> mytc4; mytc4.functest1(); return 0; } ``` 4.2 模板参数范围上的偏特化 {特化的话相当于范围变小了 } int ->const int. T->T\* , T->T& ,T->T&& 特化: 范围往小的变 ```cpp //偏特化版本 模板参数范围上的偏特化 template <typename T, typename U> struct TC<const T, U*> { TC() { cout << "TC<const T,U*>偏特化版本构造函数" << endl; } void functest1(); }; template <typename T, typename U> void TC<const T, U*>::functest1() { cout << "TC<const T,U*>::functest1偏特化版本" << endl; } int main() { //模板参数范围上的偏特化 TC<const float, int *> mytc5; mytc5.functest1(); return 0; } ``` # 三、类型模板参数缺省值的规矩 //TC的特化版本 //类模板片特化版本中的类型模板参数不可以有缺省值。 //template <typename T = char> 这种写法不行 只能写成下面的这种类型 常规缺省参数: 类型模板参数缺省值的规矩:**如果某个模板参数有缺省值,那么从这个有缺省值的模板参数开始,后面的所有模板参数都得有缺省值**。【函数模板没有这个限制】 **类模板偏特化版本中的类型模板参数不可以有缺省值**。 后面的模板参数依赖前面的模板参数 在模板声明中指定缺省参数 ```cpp namespace _nmsp1 { //TC的泛化版本 //template <typename T,typename U> template <typename T=int, typename U=int>// 这边T给了之后,后面的U就也要给 struct TC { TC() { cout << "TC泛化版本的构造函数" << endl; } void functest1() { cout << "functest1泛化版本" << endl; } }; //(3.1)类型模板参数缺省值的规矩 //TC的特化版本 //类模板偏特化版本中的类型模板参数不可以有缺省值。 //template <typename T = char> 这种写法不行 只能写成下面的这种类型 template <typename T> struct TC<T, int> { }; } namespace _nmsp2 { //(3.2)后面的模板参数依赖前面的模板参数 template <typename T,typename U = T*> struct TC { //.... }; } namespace _nmsp3 { //(3.3)在模板声明中指定缺省参数 //声明1,指定了V和W的缺省参数 template <typename T,typename U, typename V = int,typename W = char> struct TC; //声明2,指定U 因为上面已经有V和W的缺省值了 会编译报错 template <typename T, typename U = char, typename V, typename W> struct TC; //定义泛化版本的TC 定义的时候可以不写后面三个参数的缺省值 默认typename U= char, typename V = int,typename W = char template <typename T, typename U, typename V, typename W> struct TC { //... }; ////考虑到类型名比较长,所以 一般都用typedef ,或者using给这些类型名起一个额外的别名来简化书写。 typedef TC<int, float> IF_TC; using IF_TCU = TC<int, float>;//左边才是类型别名 很像赋值语句 } int main() { //_nmsp1::TC<> mytc5; //使用了缺省模板参数,所以<>中啥也不用提供 //_nmsp1::TC<double> mytc6; //<>中第一个类型不使用缺省参数,第二个类型使用缺省参数 /*_nmsp2::TC<double> mytc5; _nmsp2::TC<double,int> mytc6;*/ //_nmsp3::TC<int> mytc; //第二、三、四个模板参数采用缺省值,所以<>里只提供了一个类型模板实参 //_nmsp3::IF_TC mytc10; //IF_TC等价于TC<int, float> ,所以整个代码行等价于:_nmsp3::TC<int, float> mytc10; //_nmsp3::IF_TCU mytc11; //IF_TCU等价于TC<int, float> } ``` # 四:类型别名 考虑到类型名比较长,所以 一般都用typedef ,或者using给这些类型名起一个额外的别名来简化书写。 ```cpp //考虑到类型名比较长,所以 一般都用typedef ,或者using给这些类型名起一个额外的别名来简化书写。 typedef TC<int, float> IF_TC; using IF_TCU = TC<int, float>;//左边才是类型别名 很像赋值语句 ``` # 五、非类型模板参数 `前面讲的都是模板类型参数,也可以参数不是类型参数` 数字是常量.类型一般也限制在整型、指针类型等等\*。 最后修改:2025 年 06 月 30 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 如果觉得我的文章对你有用,请随意赞赏