C++模板
模板概论
| c++提供了函数模板(function template.)所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体制定,用一个虚拟的类型来代表。这个通用函数就成为函数模板。凡是函数体相同的函数都可以用这个模板代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现不同函数的功能。 |
- c++提供两种模板机制:函数模板和类模板
- 类属 - 类型参数化,又称参数模板
总结:
- 模板把函数或类要处理的数据类型参数化,表现为参数的多态性,成为类属。
- 模板用于表达逻辑结构相同,但具体数据元素类型不同的数据对象的通用行为。
函数模板
函数模板基本使用:
- template 或者 template,告诉编译器,紧跟的代码里出现T不要报错
- mySwap(T &a,T &B)类型也需要传入,类型参数化
- mySwap(a,b) 自动类型推导,按照a,b的类型来推导、替换T,此时,要确保,a,b的类型是一致的,不会出现二义性。
- mySwap(a,b) 显式指定的类型
函数模板和普通函数区别
| 函数模板不允许自动类型转化,普通函数能够自动进行类型转化 |
普通函数和函数模板的调用规则
| 1. 如果出现普通函数与模板重载,优先使用普通函数调用,如果没有实现,就会报错 |
| 2. 如果像强制调用模板,那么可以使用空参列表myPrint<>(a, b); |
| 3. 函数模板也可以发生重载 |
| 4. 如果函数模板可以产生更好的匹配,那么优先使用函数模板 |
点击查看代码
| #define _CRT_SECURE_NO_WARNINGS |
| #include<iostream> |
| #include<string> |
| using namespace std; |
| |
| |
| template<class T> |
| T myPlus(T a, T b) |
| { |
| return a + b; |
| } |
| |
| int myPlus2(int a, int b) |
| { |
| return a + b; |
| } |
| |
| |
| |
| template<class T> |
| void myPrint(T a, T b) |
| { |
| cout << "模板函数调用的myPrint(a,b)" << endl; |
| } |
| |
| |
| template<class T> |
| void myPrint(T a, T b,T c) |
| { |
| cout << "模板函数调用的myPrint(a,b,c)" << endl; |
| } |
| |
| void myPrint(int a, int b) |
| { |
| cout << "普通函数调用的myPrint" << endl; |
| } |
| |
| |
| void test01() |
| { |
| int a = 10; |
| int b = 20; |
| char c = 'c'; |
| |
| |
| cout << myPlus2(a, c) << endl; |
| } |
| |
| void test02() |
| { |
| int a = 10; |
| int b = 20; |
| |
| myPrint(a, b); |
| |
| |
| myPrint<>(a, b); |
| |
| |
| myPrint<>(a, b); |
| |
| |
| char c = 'c'; |
| char d = 'd'; |
| myPrint(c, d); |
| } |
| |
| |
| int main() |
| { |
| |
| test02(); |
| |
| system("pause"); |
| return 0; |
| } |

模板机制剖析
思考:
| 1. 为什么函数模板可以和普通函数放在一起? |
| 2. c++编译器是如何实现函数模板机制的? |
C/C++语言程序的编译过程:
| hello.cpp程序是高级c语言程序,这种程序易于被人读懂。为了在系统上运行hello.c程序,每一条c语句都必须转化为低级的机器指令。 |
| 然后将这些机器指令打包成可执行目标文件格式,并以二进制形式存储于磁盘中。 |
| 预处理(Pre-processing) -> 编译(Compiling) ->汇编(Assembling) -> 链接(Linking) |

函数模板机制结论:
- 编译器并不是把函数模板处理成能够处理任何类型的函数
- 函数模板通过具体类型产生不同的函数
- 编译器会对函数模板进行两次编译,在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。
- 第一次编译:检查有没有语法错误
- 第二次编译:生成类型替换后的代码,检查有没有错误
函数模板的局限性
- 模板不能解决所有类型
- 如果出现不能解决的类型,可以通过第三代具体化来解决问题
- 第三代具体化语法:
- template<> 返回值 函数名<具体类型>(参数)
- 返回值和函数名要与具体化之前的一致,不然会报错
假设有如下模板函数:
| template<class T> |
| void f(T a, T b) |
| { |
| … |
| } |
- 如果代码实现时定义了赋值操作 a = b,但是T为数组,这种假设就不成立了
- 同样,如果里面的语句为判断语句 if (a>b), 但T如果是结构体,该假设也不成立,另外如果是传入的数组,数组名为地址,因此它比较的是地址,而这也不是我们所希望的操作。
- 总之,编写的模板函数很可能无法处理某些类型,另一方面,有时候通用化是有意义的,但C++语法不允许这样做。为了解决这种问题,可以提供模板的重载,为这些特定的类型提供具体化的模板。
| class Person |
| { |
| public: |
| Person(string name, int age) |
| { |
| this->mName = name; |
| this->mAge = age; |
| } |
| string mName; |
| int mAge; |
| }; |
| |
| |
| template <class T> |
| void mySwap(T &a, T &b) |
| { |
| T temp = a; |
| a = b; |
| b = temp; |
| } |
| |
| |
| template<>void mySwap<Person>(Person &p1, Person &p2) |
| { |
| string nameTemp; |
| int ageTemp; |
| |
| nameTemp = p1.mName; |
| p1.mName = p2.mName; |
| p2.mName = nameTemp; |
| |
| ageTemp = p1.mAge; |
| p1.mAge = p2.mAge; |
| p2.mAge = ageTemp; |
| |
| } |
| |
| void test() |
| { |
| Person P1("Tom", 10); |
| Person P2("Jerry", 20); |
| |
| cout << "P1 Name = " << P1.mName << " P1 Age = " << P1.mAge << endl; |
| cout << "P2 Name = " << P2.mName << " P2 Age = " << P2.mAge << endl; |
| mySwap(P1, P2); |
| cout << "P1 Name = " << P1.mName << " P1 Age = " << P1.mAge << endl; |
| cout << "P2 Name = " << P2.mName << " P2 Age = " << P2.mAge << endl; |
| } |
点击查看代码
| #define _CRT_SECURE_NO_WARNINGS |
| #include<iostream> |
| #include<string> |
| using namespace std; |
| |
| |
| class Person |
| { |
| public: |
| Person(string name, int age) |
| { |
| this->m_Name = name; |
| this->m_Age = age; |
| } |
| string m_Name; |
| int m_Age; |
| }; |
| |
| template<class T> |
| bool myCompare(T &a, T &b) |
| { |
| if (a == b) |
| { |
| return true; |
| } |
| return false; |
| } |
| |
| |
| |
| |
| |
| template<> bool myCompare<Person>(Person &a, Person &b) |
| { |
| if (a.m_Age == b.m_Age) |
| { |
| return true; |
| } |
| return false; |
| } |
| |
| void test01() |
| { |
| int a = 10; |
| int b = 20; |
| |
| int ret = myCompare(a, b); |
| |
| cout << "ret = " << ret << endl; |
| |
| Person p1("tom",10); |
| Person p2("jerry",10); |
| |
| |
| |
| |
| ret = myCompare(p1, p2); |
| cout << "ret = " << ret << endl; |
| } |
| |
| int main() |
| { |
| test01(); |
| |
| system("pause"); |
| return 0; |
| } |
类模板
类模板基本概念
| 类模板和函数模板的定义和使用类似,我们已经进行了介绍。有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同。 |
类模板用于实现类所需数据的类型参数化
类模板的基本使用
写法:template<T...>紧接着是类
函数模板可以进行自动类型推导,而类模板不可以
函数模板不可以有默认数据类型,而类模板可以
| template<class NameType, class AgeType> |
| class Person |
| { |
| public: |
| Person(NameType name, AgeType age) |
| { |
| this->mName = name; |
| this->mAge = age; |
| } |
| void showPerson() |
| { |
| cout << "name: " << this->mName << " age: " << this->mAge << endl; |
| } |
| public: |
| NameType mName; |
| AgeType mAge; |
| }; |
| |
| void test01() |
| { |
| |
| Person<string, int>P1("德玛西亚", 18); |
| P1.showPerson(); |
| } |
点击查看代码
| #define _CRT_SECURE_NO_WARNINGS |
| #include<string> |
| #include<iostream> |
| using namespace std; |
| |
| |
| |
| template<class NameType, class AgeType> |
| class Person |
| { |
| public: |
| Person(NameType name, AgeType age) |
| { |
| this->m_Name = name; |
| this->m_Age = age; |
| } |
| |
| void showPerson() |
| { |
| cout << "name:" << this->m_Name << " age:" << this->m_Age << endl; |
| } |
| |
| NameType m_Name; |
| AgeType m_Age; |
| }; |
| |
| void test01() |
| { |
| |
| |
| |
| |
| Person<string, int> p("monkey", 100); |
| p.showPerson(); |
| |
| } |
| |
| |
| int main() |
| { |
| test01(); |
| |
| system("pause"); |
| return 0; |
| } |
成员函数的创建时机
点击查看代码
| #define _CRT_SECURE_NO_WARNINGS |
| #include<string> |
| #include<iostream> |
| using namespace std; |
| |
| |
| |
| template<class NameType, class AgeType> |
| class Person |
| { |
| public: |
| Person(NameType name, AgeType age) |
| { |
| this->m_Name = name; |
| this->m_Age = age; |
| } |
| |
| void showPerson() |
| { |
| cout << "name:" << this->m_Name << " age:" << this->m_Age << endl; |
| } |
| |
| NameType m_Name; |
| AgeType m_Age; |
| }; |
| |
| class Person1 |
| { |
| public: |
| void showPerson1() |
| { |
| cout << "showPerson1调用" << endl; |
| } |
| }; |
| |
| class Person2 |
| { |
| public: |
| void showPerson2() |
| { |
| cout << "showPerson2调用" << endl; |
| } |
| }; |
| |
| template<class T> |
| class myClass |
| { |
| public: |
| T obj; |
| void func1() |
| { |
| obj.showPerson1(); |
| } |
| |
| void func2() |
| { |
| obj.showPerson2(); |
| } |
| }; |
| |
| |
| |
| |
| void test01() |
| { |
| |
| |
| |
| |
| Person<string, int> p("monkey", 100); |
| p.showPerson(); |
| |
| } |
| |
| |
| void test02() |
| { |
| myClass<Person1> p1; |
| p1.func1(); |
| p1.func2(); |
| |
| } |
| |
| int main() |
| { |
| |
| test02(); |
| |
| system("pause"); |
| return 0; |
| } |
类模板做函数的参数:
1.显示指定类型
2.参数模板化
3.整体模板化
| |
| cout << "方式三:" << typeid(T).name() << endl; |
| |
| template<class NameType, class AgeType> |
| class Person{ |
| public: |
| Person(NameType name, AgeType age){ |
| this->mName = name; |
| this->mAge = age; |
| } |
| void PrintPerson(){ |
| cout << "Name:" << this->mName << " Age:" << this->mAge << endl; |
| } |
| public: |
| NameType mName; |
| AgeType mAge; |
| }; |
| |
| |
| void DoBussiness(Person<string,int>& p){ |
| p.mAge += 20; |
| p.mName += "_vip"; |
| p.PrintPerson(); |
| } |
| |
| int main(){ |
| |
| Person<string, int> p("John", 30); |
| DoBussiness(p); |
| |
| system("pause"); |
| return EXIT_SUCCESS; |
| } |
| |
点击查看代码
| #define _CRT_SECURE_NO_WARNINGS |
| #include<string> |
| #include<iostream> |
| using namespace std; |
| |
| template<class NameType, class AgeType> |
| class Person |
| { |
| public: |
| Person(NameType name, AgeType age) |
| { |
| this->m_Name = name; |
| this->m_Age = age; |
| } |
| |
| void showPerson() |
| { |
| cout << "name:" << this->m_Name << " age:" << this->m_Age << endl; |
| } |
| |
| NameType m_Name; |
| AgeType m_Age; |
| }; |
| |
| |
| |
| void doWork(Person<string, int> &p) |
| { |
| p.showPerson(); |
| } |
| |
| void test01() |
| { |
| Person <string, int>p("tom", 10); |
| doWork(p); |
| } |
| |
| |
| template<class T1,class T2> |
| void doWork2(Person<T1, T2> &p) |
| { |
| |
| cout << "方式二:" << typeid(T1).name() << endl; |
| cout << "方式二:" << typeid(T2).name() << endl; |
| p.showPerson(); |
| } |
| |
| void test02() |
| { |
| Person <string, int>p("jerry", 10); |
| doWork2(p); |
| } |
| |
| |
| template<class T> |
| void doWork3(T &p) |
| { |
| |
| cout << "方式三:" << typeid(T).name() << endl; |
| p.showPerson(); |
| } |
| |
| void test03() |
| { |
| Person<string, int> p("dog", 12); |
| doWork3(p); |
| } |
| |
| |
| |
| int main() |
| { |
| test01(); |
| test02(); |
| test03(); |
| |
| system("pause"); |
| return 0; |
| } |
| |

类模板碰到的继承问题
当类模板碰到继承:
- 基类如果是模板类,必须让子类告诉编译器,基类中的 T 到底是什么类型,
- 如果不告诉,那么无法分配内存,编译不过
- 利用参数列表 class Child : public Base
点击查看代码
| #define _CRT_SECURE_NO_WARNINGS |
| #include<string> |
| #include<iostream> |
| using namespace std; |
| |
| template<class T> |
| class Base |
| { |
| public: |
| |
| |
| T m_A; |
| }; |
| |
| |
| |
| |
| |
| |
| class Child : public Base<int> |
| { |
| |
| }; |
| |
| |
| template<class T1,class T2> |
| class Child2 : public Base<T2> |
| { |
| public: |
| Child2() |
| { |
| cout << typeid(T1).name() << endl; |
| cout << typeid(T2).name() << endl; |
| } |
| public: |
| T1 m_B; |
| }; |
| |
| |
| |
| void test01() |
| { |
| |
| Child2<int, double>child; |
| } |
| |
| |
| int main() |
| { |
| test01(); |
| system("pause"); |
| return 0; |
| } |
类模板类外实现成员函数
点击查看代码
| #define _CRT_SECURE_NO_WARNINGS |
| #include<string> |
| #include<iostream> |
| using namespace std; |
| |
| template<class T1,class T2> |
| class Person |
| { |
| public: |
| Person(T1 name, T2 age); |
| |
| |
| |
| |
| |
| void showPerson(); |
| |
| |
| |
| T1 m_Name; |
| T2 m_Age; |
| }; |
| |
| |
| template<class T1,class T2> |
| Person<T1, T2>::Person(T1 name,T2 age) |
| { |
| this->m_Name = name; |
| this->m_Age = age; |
| } |
| |
| template<class T1, class T2> |
| void Person<T1, T2>::showPerson() |
| { |
| cout << "姓名:" << this->m_Name << "年龄" << this->m_Age << endl; |
| } |
| |
| |
| void test01() |
| { |
| Person<string,int> p1("Mt", 100); |
| p1.showPerson(); |
| } |
| |
| |
| int main() |
| { |
| test01(); |
| system("pause"); |
| return 0; |
| } |

类模板头文件和源文件分离问题
问题:
- .h 、.cpp 分别声明和实现
- 但是由于类模板的成员函数在运行阶段才去创建,导致包含 .h 头文件不会创建函数的实现,就无法解析外部命令
解决方案:
- 方案一:包含.cpp文件(不推荐)
- 方案二:不要分文件编写,将声明和实现写到同一个文件中,后缀名改为.hpp(约定俗成)
Person.hpp
| #pragma once |
| |
| template<class T1,class T2> |
| class Person{ |
| public: |
| Person(T1 name,T2 age); |
| void ShowPerson(); |
| public: |
| T1 mName; |
| T2 mAge; |
| }; |
| |
| template<class T1, class T2> |
| Person<T1, T2>::Person(T1 name, T2 age){ |
| this->mName = name; |
| this->mAge = age; |
| } |
| |
| template<class T1, class T2> |
| void Person<T1, T2>::ShowPerson(){ |
| cout << "Name:" << this->mName << " Age:" << this->mAge << endl; |
| } |
main.cpp
| #define _CRT_SECURE_NO_WARNINGS |
| #include<iostream> |
| using namespace std; |
| #include<string> |
| #include"Person.hpp" |
| |
| |
| |
| |
| int main(){ |
| |
| Person<string, int> p("Obama", 20); |
| p.ShowPerson(); |
| |
| |
| system("pause"); |
| return EXIT_SUCCESS; |
| } |
**结论: ** 案例代码在qt编译器顺利通过编译并执行,但是在Linux和vs编辑器下如果只包含头文件,那么会报错链接错误,需要包含cpp文件,但是如果类模板中有友元类,那么编译失败!
**解决方案: **类模板的声明和实现放到一个文件中,我们把这个文件命名为.hpp(这个是个约定的规则,并不是标准,必须这么写).
原因:
类模板需要二次编译,在出现模板的地方编译一次,在调用模板的地方再次编译。
C++编译规则为独立编译。
Person.hpp
点击查看代码
| #pragma once |
| #include<iostream> |
| using namespace std; |
| |
| template<class T1,class T2> |
| class Person |
| { |
| public: |
| Person(T1 name,T2 age); |
| void showPerson(); |
| |
| T1 m_Name; |
| T2 m_Age; |
| }; |
| |
| template<class T1, class T2> |
| Person<T1, T2>::Person(T1 name, T2 age) |
| { |
| this->m_Name = name; |
| this->m_Age = age; |
| } |
| |
| template<class T1, class T2> |
| void Person<T1, T2>::showPerson() |
| { |
| cout << "姓名:" << this->m_Name << " 年龄 " << this->m_Age << endl; |
| } |
| |
main
| #include<iostream> |
| #include<string> |
| #include"Person.hpp" |
| |
| |
| using namespace std; |
| |
| |
| |
| |
| int main() |
| { |
| Person<string, int> p("pig", 18); |
| p.showPerson(); |
| |
| |
| |
| |
| system("pause"); |
| return 0; |
| } |
| |
当模板类碰到友元函数
点击查看代码
| #define _CRT_SECURE_NO_WARNINGS |
| #include<iostream> |
| using namespace std; |
| #include <string> |
| |
| template<class T1, class T2> class Person; |
| |
| template<class T1, class T2> void PrintPerson2(Person<T1, T2>& p); |
| |
| |
| template<class T1, class T2> |
| class Person{ |
| |
| friend void PrintPerson(Person<T1, T2>& p){ |
| cout << "Name:" << p.mName << " Age:" << p.mAge << endl; |
| } |
| |
| |
| |
| friend void PrintPerson2<>(Person<T1, T2>& p); |
| |
| |
| template<class U1, class U2> |
| friend void PrintPerson(Person<U1, U2>& p); |
| |
| public: |
| Person(T1 name, T2 age){ |
| this->mName = name; |
| this->mAge = age; |
| } |
| void showPerson(){ |
| cout << "Name:" << this->mName << " Age:" << this->mAge << endl; |
| } |
| private: |
| T1 mName; |
| T2 mAge; |
| }; |
| |
| void test01() |
| { |
| Person <string, int>p("Jerry", 20); |
| PrintPerson(p); |
| } |
| |
| |
| |
| |
| template<class T1, class T2> |
| void PrintPerson2(Person<T1, T2>& p) |
| { |
| cout << "Name2:" << p.mName << " Age2:" << p.mAge << endl; |
| } |
| |
| void test02() |
| { |
| Person <string, int>p("Jerry", 20); |
| PrintPerson2(p); |
| } |
| |
| int main(){ |
| |
| |
| test02(); |
| system("pause"); |
| return EXIT_SUCCESS; |
| } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!