C++模板 模板就是建立通用的模具,大大提高复用性 模板的特点:
模板不可以直接使用,它只是一个框架
模板的通用并不是万能的
C++另一种编程思想称为 泛型编程 ,主要利用的技术就是模板
C++提供两种模板机制:
1 函数模板 建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表
1.1 语法
template 函数声明或定义
每个函数模板都是上面这种结构嗷
解释:
template — 声明创建模板 typename — 表面其后面的符号是一种数据类型,可以用class代替,有人用typename T表示函数模板,用class T表示类模板,还有人二个模板都用class T T — 通用的数据类型,名称可以替换,通常为大写字母T
1.2 函数模板调用
1.3 函数模板实例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 template <typename T>void my_swap (T &a,T &b) { T temp = a; a = b; b = temp; }int main () { int a = 10 ; int b = 20 ; char c = 'x' ; my_swap (a, b); my_swap <int >(a, b); cout << "a = " << a << endl; cout << "b = " << b << endl; system ("pause" ); return 0 ; }
a = 10
b = 10
如果不能自动推导类型,函数模板必须要确定出T的数据类型才可以使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 template <typename T>void func () { cout << "func调用" << endl; }int main () { func <int >(); system ("pause" ); return 0 ; }
func调用
1.4 普通函数与函数模板区别
普通函数调用时可以发生自动类型转换(隐式类型转换)
函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
如果利用显示指定类型的方式,可以发生类型转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 int myAdd01 (int a,int b) { return a + b; }template <typename T>T myAdd02 (T a,T b) { return a + b; }int main () { int a = 10 ; int b = 20 ; char c = 'c' ; cout << myAdd01 (a, c) << endl; cout << myAdd02 <int >(a, c) << endl; system ("pause" ); return 0 ; }
109
109
总结: 如果是使用模板,建议使用显示强制转换指定数据类型,因为可以自己确定通用类型T
1.5 普通函数与函数模板的调用规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 void myPrint (int a, int b) { cout << "调用的普通函数" << endl; }template <typename T>void myPrint (T a,T b) { cout << "调用的模板函数" << endl; }template <typename T>void myPrint (T a, T b,T c) { cout << "调用重载的模板" << endl; }int main () { int a = 10 ; int b = 20 ; myPrint (a, b); myPrint<>(a, b); int c = 30 ; myPrint (a, b, c); char c1 = 'a' ; char c2 = 'b' ; myPrint (c1, c2); system ("pause" ); return 0 ; }
总结:既然提供了函数模板,最好就不要提供普通函数,否则容易出现二义性
1.6 函数模板的重载 模板的通用性并不是万能的,在下面代码中提供的赋值操作,如果T的数据类型传入的a和b是一个数组或者自定义的类,就无法实现了
1 2 3 4 5 template <class T>void f (T a, T b) { a = b; }
C++为了解决这种问题,提供模板的重载,可以为这些特定的类型在原有普通函数模板的基础上提供具体化的模板,语法:
template<> 函数类型 函数名(参数列表)
具体化:显示原有函数模板的原型和意思,具体化比常规模板优先调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 class People {public : People (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 ; } else { return false ; } }template <> bool myCompare (People& p1, People& p2) { if (p1.m_Name == p2.m_Name && p1.m_Age == p2.m_Age) { return true ; } else { return false ; } }int main () { int a = 10 ; int b = 20 ; bool ret1 = myCompare (a, b); if (ret1) { cout << "a == b " << endl; } else { cout << "a != b " << endl; } People p1 ("Tom" , 10 ) ; People p2 ("Tom" , 10 ) ; bool ret2 = myCompare (p1, p2); if (ret2) { cout << "p1 == p2 " << endl; } else { cout << "p1 != p2 " << endl; } system ("pause" ); return 0 ; }
a != b p1 == p2
总结:
利用具体化的模板,可以解决自定义类型的通用化
学习模板并不是为了写模板,而是在STL能够运用系统提供的模板
2 类模板 类模板作用:建立一个通用类,类中的成员 数据类型可以不具体制定,用一个虚拟的类型 来代表。
2.1 语法 1 2 template <typename T> 类定义/声明
解释:
template — 声明创建模板
typename — 表面其后面的符号是一种数据类型,可以用class代替
T — 通用的数据类型,名称可以替换,通常为大写字母
2.2 类模板调用 类模板调用时必须要显示指定类型,在类后面加<>,里面写上具体的类型
2.3 类模板实例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <iostream> #include <string> using namespace std;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; };int main () { Person<string, int >P1 ("孙悟空" , 999 ); P1.showPerson (); system ("pause" ); return 0 ; }
总结:类模板和函数模板语法相似,在声明模板template后面加类,此类称为类模板
2.4 类模板与函数模板区别 类模板与函数模板区别主要有两点:
类模板没有自动类型推导的使用方式
类模板在模板参数列表中可以有默认参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <iostream> #include <string> using namespace std;template <class NameType , class AgeType =int > 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; };int main () { Person <string ,int >p1 ("孙悟空" , 1000 ); p1.showPerson (); Person <string> p2 ("猪八戒" , 999 ); p2.showPerson (); system ("pause" ); return 0 ; }
2.5 类模板中成员函数创建时机 类模板中成员函数和普通类中成员函数创建时机是有区别的:
普通类 :中的成员函数一开始就可以创建
**类模板:**中的成员函数在调用时才创建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #include <iostream> #include <string> using namespace std;class Person1 {public : void showPerson1 () { cout << "Person1 show" << endl; } };class Person2 {public : void showPerson2 () { cout << "Person2 show" << endl; } };template <class T >class MyClass {public : T obj; void fun1 () { obj.showPerson1 (); } void fun2 () { obj.showPerson2 (); } };int main () { MyClass<Person1> m; m.fun1 (); system ("pause" ); return 0 ; }
2.6 类模板对象做函数参数 类模板实例化出的对象是可以作为函数的参数的,其向函数传参的方式有如下三种
指定传入的类型 — 直接显示对象的数据类型,较常用
参数模板化 — 将对象中的参数变为模板进行传递
整个类模板化 — 将这个对象类型 模板化进行传递
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 #include <iostream> #include <string> using namespace std;template <class NameType , class AgeType = int > 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 printPerson1 (Person<string, int > &p) { p.showPerson (); }void test01 () { Person <string, int >p ("孙悟空" , 100 ); printPerson1 (p); }template <class T1 , class T2 >void printPerson2 (Person<T1, T2>&p) { p.showPerson (); cout << "T1的类型为: " << typeid (T1).name () << endl; cout << "T2的类型为: " << typeid (T2).name () << endl; }void test02 () { Person <string, int >p ("猪八戒" , 90 ); printPerson2 (p); }template <class T>void printPerson3 (T & p) { cout << "T的类型为: " << typeid (T).name () << endl; p.showPerson (); }void test03 () { Person <string, int >p ("唐僧" , 30 ); printPerson3 (p); }int main () { test01 (); test02 (); test03 (); system ("pause" ); return 0 ; }
总结:使用比较广泛是第一种:指定传入的类型。可读性更好一些。
2.7 类模板与继承 类模板也是类,涉及到继承的问题。有以下几点需要注意:
当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型,如果不指定,编译器无法给子类分配内存(不知道类型,不知道分配多大内存)
如果想灵活指定出父类中T的类型,子类也需变为类模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <iostream> #include <string> using namespace std;template <class T >class Base {public : T m; };class Son :public Base<int > { };void test01 () { Son child1; cout << typeid (child1.m).name () << endl; }template <class T1 >class Son2 :public Base<T1>{}; void test02 () { Son2<int > child2; Son2<char > child3; cout << typeid (child2.m).name () << endl; cout << typeid (child3.m).name () << endl; }template <class T1 ,class T2 >class Son3 :public Base<T2> {public : T1 n; }; void test03 () { Son3<char ,int > child4; cout << typeid (child4.n).name () << endl; cout << typeid (child4.m).name () << endl; }
2.8 类模板成员函数类外实现 类模板也是类,类中自然可以定义成员函数,一般成员函数都采用类外实现,这样使得类的结构更加清晰
类外实现成员函数的格式其实就是加一个<>填入模板参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <iostream> #include <string> using namespace std;template <class T1 , class T2 >class Person {public : Person (T1 name, T2 age); void showPerson () ; public : 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 > Person<T1, T2>::showPerson (){ cout << "姓名: " << this ->m_Name << " 年龄:" << this ->m_Age << endl; }int main () { erson<string, int > p ("Tom" , 20 ) ; p.showPerson (); system ("pause" ); return 0 ; }
2.9 类模板分文件编写 问题:
日常的程序编写过程中,类的定义一般放在头文件,类的具体内容放在源文件,但由于类模板具有特殊性,类模板中成员函数创建时机是在调用阶段,这就导致分文件编写时链接不到,只包含头文件是会编译出错的
解决:
解决方式1:直接包含.cpp源文件
解决方式2:将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制,app, bpp均可,但一般用hpp
示例:person.hpp中代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #pragma once #include <iostream> using namespace std;#include <string> template <class T1 , class T2 >class Person {public : Person (T1 name, T2 age); void showPerson () ;public : 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.cpp或者其他cpp文件中代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <iostream> using namespace std;#include "person.cpp" #include "person.hpp" int main () { Person<string, int > p ("Tom" , 10 ) ; p.showPerson (); ystem ("pause" ); return 0 ; }
2.10 类模板与友元 前面知道,全局函数想要访问类中的私有成员,是需要使用友元的。也需要再次提醒的是,尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。
方法一:全局函数类内实现 - 直接在类模板内声明友元即可,比较简单,更推荐使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <iostream> #include <string> using namespace std;template <class T1 , class T2 >class Person { friend void printPerson (Person<T1, T2> & p) { cout << "姓名: " << p.m_Name << " 年龄:" << p.m_Age << endl; } public : Person (T1 name, T2 age){ this ->m_Name = name; this ->m_Age = age; } private : T1 m_Name; T2 m_Age; };int main () { Person<string, int > p ("Tom" , 10 ) ; printPerson (p); system ("pause" ); return 0 ; }
方法二:全局函数类外实现 - 需要提前让编译器知道全局函数的存在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <iostream> #include <string> using namespace std;template <class T1 , class T2 > class Person ;template <class T1, class T2>void printPerson2 (Person<T1, T2> & p) { cout << "类外实现 ---- 姓名: " << p.m_Name << " 年龄:" << p.m_Age << endl; }template <class T1 , class T2 >class Person { friend void printPerson2<>(Person<T1, T2> & p); public : Person (T1 name, T2 age){ this ->m_Name = name; this ->m_Age = age; }private : T1 m_Name; T2 m_Age; };int main () { Person<string, int > p ("Tom" , 10 ) ; printPerson2 (p); system ("pause" ); return 0 ; }
2.11 类模板综合案例 案例描述: 实现一个通用的数组类,要求如下:
可以对内置数据类型以及自定义数据类型的数据进行存储
将数组中的数据存储到堆区
构造函数中可以传入数组的容量
提供对应的拷贝构造函数以及operator=防止浅拷贝问题
提供尾插法和尾删法对数组中的数据进行增加和删除
可以通过下标的方式访问数组中的元素
可以获取数组中当前元素个数和数组的容量
示例:
myArray.hpp中代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 #pragma once #include <iostream> using namespace std;template <class T >class MyArray {public : MyArray (int capacity) { this ->m_Capacity = capacity; this ->m_Size = 0 ; pAddress = new T[this ->m_Capacity]; } MyArray (const MyArray & arr) { this ->m_Capacity = arr.m_Capacity; this ->m_Size = arr.m_Size; this ->pAddress = new T[this ->m_Capacity]; for (int i = 0 ; i < this ->m_Size; i++) { this ->pAddress[i] = arr.pAddress[i]; } } MyArray& operator =(const MyArray& myarray) { if (this ->pAddress != NULL ) { delete [] this ->pAddress; this ->m_Capacity = 0 ; this ->m_Size = 0 ; } this ->m_Capacity = myarray.m_Capacity; this ->m_Size = myarray.m_Size; this ->pAddress = new T[this ->m_Capacity]; for (int i = 0 ; i < this ->m_Size; i++) { this ->pAddress[i] = myarray[i]; } return *this ; } T& operator [](int index) { return this ->pAddress[index]; } void Push_back (const T & val) { if (this ->m_Capacity == this ->m_Size) { return ; } this ->pAddress[this ->m_Size] = val; this ->m_Size++; } void Pop_back () { if (this ->m_Size == 0 ) { return ; } this ->m_Size--; } int getCapacity () { return this ->m_Capacity; } int getSize () { return this ->m_Size; } ~MyArray () { if (this ->pAddress != NULL ) { delete [] this ->pAddress; this ->pAddress = NULL ; this ->m_Capacity = 0 ; this ->m_Size = 0 ; } }private : T * pAddress; int m_Capacity; int m_Size; };
test.cpp中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 #include "myArray.hpp" #include <string> void printIntArray (MyArray<int >& arr) { for (int i = 0 ; i < arr.getSize (); i++) { cout << arr[i] << " " ; } cout << endl; }void test01 () { MyArray<int > array1 (10 ) ; for (int i = 0 ; i < 10 ; i++) { array1.Push_back (i); } cout << "array1打印输出:" << endl; printIntArray (array1); cout << "array1的大小:" << array1.getSize () << endl; cout << "array1的容量:" << array1.getCapacity () << endl; cout << "--------------------------" << endl; MyArray<int > array2 (array1) ; array2.Pop_back (); cout << "array2打印输出:" << endl; printIntArray (array2); cout << "array2的大小:" << array2.getSize () << endl; cout << "array2的容量:" << array2.getCapacity () << endl; }class Person {public : Person () {} Person (string name, int age) { this ->m_Name = name; this ->m_Age = age; }public : string m_Name; int m_Age; };void printPersonArray (MyArray<Person>& personArr) { for (int i = 0 ; i < personArr.getSize (); i++) { cout << "姓名:" << personArr[i].m_Name << " 年龄: " << personArr[i].m_Age << endl; } }void test02 () { MyArray<Person> pArray (10 ) ; Person p1 ("孙悟空" , 30 ) ; Person p2 ("韩信" , 20 ) ; Person p3 ("妲己" , 18 ) ; Person p4 ("王昭君" , 15 ) ; Person p5 ("赵云" , 24 ) ; pArray.Push_back (p1); pArray.Push_back (p2); pArray.Push_back (p3); pArray.Push_back (p4); pArray.Push_back (p5); printPersonArray (pArray); cout << "pArray的大小:" << pArray.getSize () << endl; cout << "pArray的容量:" << pArray.getCapacity () << endl; }int main () { test02 (); system ("pause" ); return 0 ; }