ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

c++——模版

c++——模版 目录引子模版是什么模版格式函数模版函数模版的隐式实例化函数模版的显式实例化原理一些使用的例子帮助理解模版函数的匹配原则类模板简单使用类模板中的成员函数函数放在类外进行定义原理非类型模版参数typename指定类型模版特化函数模板特化函数模板特化的坑类模板特化之全特化类模板特化之偏特化调用优先级为什么模板函数声明和定义不要分离 引子我们经常用到Swap函数交换两个值。由于需要交换的数据的类型不同我们就需要写不同参数列表的同名函数也就是函数重载void Swap(int a,int b) { int temp a; a b; b temp; } void Swap(float a, float b) { int temp a; a b; b temp; } void Swap(char a, char b) { int temp a; a b; b temp; }同样的下面的A类和B类的逻辑完全一样但由于我们需要的是能处理float和int两个类型的类所以我们写了两个类。class A { public: A() {} void Set(int x) { a x; } private: int a; }; class B { public: B() {} void Set(float x) { b x; } private: float b; };然而这三个函数的逻辑是一样的这两个类的逻辑也是一样的他们只是类型不一样写这么多有些多此一举通过泛型编程-模版就能减少相同逻辑代码的书写提高开发效率。模版是什么模版属于泛型编程是一种代码复用的手段每个模版都代表着一个函数/类家族根据实参类型或者直接指定类型来实例化出特定的版本。模版格式templatetypename T1,typename T2,......typename Tntypename是关键字也可以用class代替用来定义一个模版类型。T1-Tn是函数/类的模版类型名T1-Tn都是类型名该语句放到函数/类的上方函数模版注意使用template定义了函数模版类型之后在函数的实现中就可以使用相应的模版类型定义形参变量返回值等总之就是把模版类型当作实际类型使用。函数模版类型的实际类型由调用函数时传入的实参类型推导而来此时你必须在函数形参的定义中使用模版类型或是在调用时直接指定也就是下面的“模版参数的显示实例化”二者必居其一否则编译器无法推导。函数模版的隐式实例化templateclass T T add(T x,T y) { return x y; } int main() { cout add(1, 2) endl; cout add(2.0,5.3) endl; }函数模版的显式实例化templateclass T T add(T x,T y) { return x y; } int main() { cout add(1, 2.5) endl; }编译器不确定T是int还是double类型为了解决这个问题你可以这样templateclass T,class S T add(T x,S y) { return x y; } int main() { cout add(1, 2.5) endl; }但是你也可以这样templateclass T T add(T x,T y) { return x y; } int main() { cout addint(1, 2.5) endl; }这样直接指定了T类型而不用编译器推演叫做函数模板的显式实例化这种情况下b会转换为int类型原理检测你传入的参数类型隐式的帮你生成这一类型的函数重载这个过程叫模版的隐式实例化或者根据指定的类型显示的帮你生成这一类型的函数重载这个过程叫模版的显式实例化。所以实际上仍然生成了多个函数调用的时候也不是调用同一个函数而是调用对应的函数重载只不过编译器替你写了这些函数不用自己写了提高了开发效率。一些使用的例子帮助理解在这种情况下模版函数中只有返回值是模版类型所以根据传入的实参类型无法推导出模版类型的实际类型所以需要显示实例化直接指定模版类型的实际类型templateclass T T add(int x,double y) { return x y; } int main() { cout addint(1, 2.5) endl; }在这种情况下可以通过传入的实参类型推导出所有模版类型的实际类型也可以直接显示实例化templateclass T,class S T add(T x,S y) { return x y; } int main() { cout add(1, 2.5) endl; cout addint,double(1, 2.5) endl; }模版函数的匹配原则一个非模板函数可以和一个同名的函数模板同时存在对于非模板函数和同名函数模板如果其他条件都相同在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数 那么将选择模板模板函数如果隐式实例化传参推导则不允许自动类型转换因为有歧义但普通函数可以进行自动类型转换举个例子吧类模板注意使用template定义了类模版类型之后在类的实现中就可以使用相应的模版类型定义函数可以认为类的模版类型就是每个成员函数的模版类型成员变量等总之就是把模版类型当作实际类型使用。类模版类型的实际类型只能通过模版的显示实例化来实现也就是在创建对象时指定模版类型。一定要注意的是使用了模版类型的类的类型也要带上模版类型的实际类型比如下面“简单使用”的例子中A类的类型就变成了Aint。这比较好理解因为模版类会根据指定的类型实例化出不同的类如果这些类都叫做A的话该怎么区分呢?简单使用templateclass T class A { public: static T Set(T x) { _x x; } private: T _x; }; int main() { Aint x; x.Set(5); }类模板中的成员函数函数放在类外进行定义templateclass T class A { public: T Set(T x); private: T _x; }; templateclass T T AT::Set(T x) { _x x; } int main() { Aint x; x.Set(5); }必须加上模板参数列表例如templateclass T类型也要写成AT原理根据指定的类型隐式的帮你生成这一类型的类这一过程叫做模版的显示实例化。所以实际上仍然生成了多个类只不过编译器替你写了这些类不用自己写了提高了开发效率。非类型模版参数函数和类模板参数除了可以是类型还可以是常量这个常量的值在显示实例化中指定。举个例子使用时需要注意的几点在c20标准之前n的类型只能是整形。在c20及之后n可以是所有内置类型。但是哪个标准中n都不可以是自定义类型n是右值也就是说它不是变量而是常量所以你不能再类中修改它在标准库中的 arry 数组类的设计就引用了非类型模版参数用于指定数组所开空间的大小如下在栈上开辟了10个int大小的空间。typename指定类型先看个代码这样的代码会报错原因是模版定义的时候还不知道vectorT::iterator是什么这取决于vector是否对某类型有特化有可能取出来的是迭代器类型也有可能取出来的是可访问变量编译器默认把他当做变量这时只要在前面加上typename 说明这是一个类型名即可模版特化特化就是针对特殊类型使模版实例化之后的逻辑特殊化特化有两种函数模板特化和类模板特化。函数模板特化这是一个比较类的模板函数看起来不错但如果我想要这样即使给compare传地址也能正常比较数字大小原来的逻辑显然不行因为地址需要解引用然后比较地址指向的数据我们可以像下面这样。这时下面的compare就是上面的compare模版函数的特化只要给compare传入的参数类型是int*就会走下面的compare从上面这个例子我们也可以看到函数模板特化的形式必须要先有一个基础的函数模板关键字template后面接一对空的尖括号函数名后跟一对尖括号尖括号中指定需要特化的类型函数形参表: 必须要和模板函数的参数类型完全相同如果不同编译器可能会报一些奇怪的错误。函数模板特化的坑为什么会报错呢原函数模板中const修饰的是形参而特化出来的函数中const修饰的是形参指向的内容所以可以认为两者参数不一致那么下面的compare也就不算上面的compare的特化了。解决方法这样编译器就认为参数一致了const修饰的都是形参本身。实际上函数模版特化一般不会使用因为其实可以直接编写非模版函数由于非模版函数的优先性调用时会优先调用非模版函数效果一样还更简单。类模板特化之全特化先看一下代码从这个例子也可以看出类模板全特化的形式必须要先有一个基础的类模板关键字template后面接一对空的尖括号类名后跟一对尖括号尖括号中指定需要特化的类型类模板特化之偏特化偏特化是指只特化指明类模版参数中所有模版类型的一部分。从这个例子也可以看出类模板偏特化的形式必须要先有一个基础的类模板。关键字template后面接一对空的尖括号尖括号里面定义剩下的没有指明的模版类型。类名后跟一对尖括号尖括号中指定需要特化的类型以及剩下的没有指明的模版类型。上面只是偏特化的其中一种形式下面是偏特化的另一种体现上面的特化不指明模板参数的具体类型而是限制了只要传入的两个模板参数都是指针类型(也可以限制成引用甚至一个限制引用另一个限制成指针)那么就走特化。特别注意在上面例子的特化中T和S的类型不是指针类型而是指针指向的数据类型。比如Aint*, int* x1;走的是特化但是实例化后T和S是int类型而不是int *。类模版之可变参数模版的部分特化这一块我们直接看代码C新特性中的tuple就可以使用这种方式实现一个对象存储任意个不同类型的值// 主模板任意数量的参数 templateclass... Args class tuple { }; // 部分特化表示如果至少有一个参数就会走这里 templateclass T, class... Args class tupleT, Args... { };关于tuple具体实现方式可以在我主页搜索tuple调用优先级非模版函数特化函数模版函数为什么模板函数声明和定义不要分离 先看看分离之后会怎么样显然报错原因是找不到Add函数的定义。让我们分析一下这是编译器生成可执行文件的整个过程把ww.h中的代码放到ww.cpp中生成ww.o把ww.h中的代码放到test.cpp中生成test.o将ww.o和test.o链接起来生成可执行文件在这个过程中通过函数名找到函数地址那我们进一步分析链接之前是这样的在test.o中有声明无定义而在ww.o中有定义.所以连接时test.o通过Add这个函数名在ww.o中找到Add函数的定义感觉没问题呀。然而我们忽视了一个重要的问题对于模板来说没有调用是不会被编译出相应函数的在ww.o中没有调用模板函数所以模板不会被编译所以实际上ww.o中没有Add函数的定义因此test.o调用了Add却找不到相应的函数定义而报错。这样就解释了为什么模板函数声明和定义不要分离 。除了把声明和定义写到一起外还有别的解决方法吗有可以在ww.cpp中显示实例化这个模板template int addint(int, int); template double adddouble(double, double);这样的话ww.o就会编译出一个int类型和double类型的Add函数ww.cpp中也就有了定义。但是如果你传入的值是其他类型的话还是会找不到定义这样还得写其他类型的显式实例化等等等等很多类型的实例化这样模版还有什么意义。总之还是把模板的声明和定义写到一起为好
返回列表