类模板
类模板
定义
1 template < 模板形参表 > 2 class 类模板名 { //类体 3 成员列表 4 }
具体形式:
1 template < typename 类型参数 > 或者 template < class 类型参数 > 2 class 类名 { 3 类成员声明 4 };
例如:
template<class NameType, class AgeType> class Person { public: Person(NameType name, AgeType age) { this->name = name; this->age = age; } NameType name; AgeType age; void showPerson(){ std::cout << "姓名为:" << this->name << " 年龄为:" << this->age << std::endl; } }; void test01(){ Person<string,int> p1("孙悟空", 9999); p1.showPerson(); }
1.3.2 类模板与函数模板区别
- 类模板没有自动类型推导的使用方式
- 类模板在模板参数列表中可以有默认参数 (函数模板不能使用!!!)
关于第二点,就是上述代码中改成
template<class NameType, class AgeType = int> Person<string> p1("孙悟空", 9999);
并且由于第二个参数已经默认为int,因此使用显式指定类型的时候,可以不用加int了,可以只写成 Person<string>
1.3.3 类模板中成员函数创建时机
类模板中成员函数和普通类中成员函数创建时机是有区别的:
- 普通类中的成员函数一开始就可以调用
- 类模板中的成员函数在调用时才创建
1 #include <iostream> 2 #include <string> 3 using namespace std; 4 //类模板中成员函数的创建时机 5 //调用的时候才会创建 6 class Person1{ 7 public: 8 void showPerson1 () { 9 std::cout << "showPerson1调用" << std::endl; 10 } 11 }; 12 class Person2{ 13 public: 14 void showPerson2 () { 15 std::cout << "showPerson2调用" << std::endl; 16 } 17 }; 18 template<class T> 19 class MyClass 20 { 21 public: 22 T obj; 23 void showPerson1 () { 24 obj.showPerson1 (); //如果没有确定最终T的类型为Person1 并且调用showPerson1,这行代码就不会报错,因为它根本没有被创建 25 } 26 27 void showPerson2 () { 28 obj.showPerson2 (); //如果没有确定最终T的类型为Person2 并且调用showPerson2,这行代码就不会报错,因为它根本没有被创建 29 } 30 }; 31 void test01(){ 32 MyClass <Person1> p; 33 p.showPerson1(); //正确 34 //p.showPerson2(); //报错 35 36 MyClass <Person2> p2; 37 //p.showPerson1(); //报错 38 p2.showPerson2(); //正确 39 } 40 int main() 41 { 42 test01(); 43 return 0; 44 }
MyClass <Person1> p;
p.showPerson1(); //正确
p.showPerson2(); //报错
解释:可以看到,只有实例化类模板MyClass并且指定准确的T类型为Person1后,才会去创建类模板中的成员函数showPerson1()。由于已经指定T的类型了,所以 p.showPerson2()会报错,因为Person1中根本没有成员函数 showPerson2(), 只有showPerson1()
1.3.4 类模板对象做函数参数
一共有三种传入方式:
- 指定传入的类型 -- 直接显式对象的数据类型
- 参数模板化 -- 将对象中的参数变为模板进行传递
- 整个类模板化 -- 将这个对象类型 模板化进行传递
#include <iostream> #include <string> #include <typeinfo> using namespace std; //类模板对象做函数参数 template<class T1, class T2> class Person { public: Person(T1 name, T2 age) { this->m_Name = name; this->m_Age = age; } void showPerson() { std::cout << "姓名:" << this->m_Name << " 年龄:" << this->m_Age << std::endl; } T1 m_Name; T2 m_Age; }; //1. 指定传入类型 void printPerson1(Person<string, int>& p) { p.showPerson(); } void test01() { Person<string, int> p1("孙悟空", 999); printPerson1(p1); //类模板做函数参数 } //2. 参数模板化 template<class T1, class T2> void printPerson2(Person<T1, T2>& p) { p.showPerson(); std::cout << "T1的类型为:" << typeid(T1).name() << std::endl; std::cout << "T2的类型为:" << typeid(T2).name() << std::endl; } void test02() { Person<string, int> p2("猪八戒", 888); printPerson2(p2); } //3. 整个类都模板化 template<class T> void printPerson3(T& p) { p.showPerson(); std::cout << "T的类型为:" << typeid(T).name() << std::endl; } void test03() { Person<string, int> p3("唐僧", 77); printPerson3(p3); } int main() { test01(); test02(); test03(); return 0; }
总结:
- 通过类模板创建的对象,可以有三种方式向函数中进行传参
- 使用比较广泛是第一种类型:指定传入的类型,因为其他两种都是函数模板和类模板相结合的 来使用了,比较麻烦。
1.3.5 类模板与继承
当类模板碰到继承时,需要注意以下几点:
- 当子类继承的父类是一个类模板时,子类在声明的时候,要指定父类中T的类型
- 如果不指定,编译器无法给子类分配内存
- 如果想灵活指定出父类中T的类型,子类也需要变为类模板
且看代码:
#include <iostream> using namespace std; template <class T> class Base { public: Base() { cout << "调用Base的构造函数,并且此时它的T数据类型为" << typeid(T).name() << endl; } T b; }; //class Son :public Base //这样写报错 -- 缺少类模板"Base"的参数列表 class Son :public Base <int> { //这样写的话,其实就是"定死"了Base中的T类型为 int }; void test01() { //当子类继承的父类是一个类模板时,子类在声明的时候,要指定父类中T的类型,如果不指定就无法为其分配内存 Son p1; //这种方式没有错误,但是不够灵活,所以就有了test02() } template<class T1, class T2> class Dau :public Base <T2> //括号内意思是指定父类中的T类型为T2类型,但是具体的T2类型,还是要看传入的实参 { public: Dau() { cout << "调用Dau的构造函数,并且T1,T2的数据类型分别为\n" << typeid(T1).name() << "\n" << typeid(T2).name() << endl; } }; void test02() { //测试类模板继承的灵活性 Dau<string, int> p2; } int main() { test01(); test02(); return 0; }
运行截图:
总结:如果父类是类模板,子类必须要指出父类中T的数据类型。
当然有两种方式。第一种就比如代码中的Son子类,它不是类模板,它只需要在继承Base父类的时候显式的指定父类T的数据类型(class Son :public Base <int>)
第二种就比较灵活了,就是将整个子类也变成类模板,将子类定义的T1,T2类型"告诉"父类,让父类的T变成T2类型,当然此时我们说的这些类型依然很抽象。所以通过第30行,声明p2实例的时候再显式指出Dau中的T1,T2类型,又间接的指明了Base中的T类型。
1.3.6 类模板的构造函数和成员函数在类外实现
#include <iostream> using namespace std; //类模板的构造函数和成员函数 类外实现 template <class T1, class T2> class Person { public: Person(T1 name, T2 age); /*{ this->m_Name = name; this->m_age = age; }*/ void showPerson(); /*{ std::cout << "姓名为 " << this->m_Name << " 年龄为 " << this->m_age << std::endl; }*/ 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() { std::cout << "姓名为 " << this->m_Name << " 年龄为 " << this->m_age << std::endl; } int main() { Person<string, int> tom("张艺", 20000); tom.showPerson(); return 0; }
1.3.7 类模板分文件编写
问题:
- 类模板中成员函数创建时期是在调用阶段,导致分文件编写时链接不到
解决方法:
- 第一种解决方式,直接包含.cpp头文件 #include "person.cpp"
- 第二种解决方式,将.h和.cpp的内容写到一起,并将后缀名改成 .hpp (hpp是约定的名称,不是强制要求) #include "person.hpp"
主流一般使用第二种方式,将类模板成员函数写到一起,并改名为.hpp
1.3.8 类模板案例 -- 数组类分装
案例描述:实现一个通用的数组类,要求如下:
- 可以对内置数据类型以及自定义数据类型的数据进行存储
- 将数组中的数据存储到堆区
- 构造函数中可以传入数组的容量
- 提供对应的拷贝构造函数 以及 opeartor=防止浅拷贝问题
- 提供尾插法和尾删法堆数组中的数据进行增加和删除
- 可以通过下标的方式访问数组中的元素
- 可以通过数组中当前元素个数和数组的容量
代码如下(下面代码很重要,要多看):
MyArray类:
1 //自己的通用的数组类 2 #pragma once 3 #include <iostream> 4 using namespace std; 5 template <class T> 6 class MyArray { 7 public: 8 //有参构造 参数 容量 9 MyArray(int capacity) { 10 //cout << "有参构造函数调用" << endl; 11 this->m_Capacity = capacity; 12 this->m_Size = 0; 13 this->pAddress = new T[this->m_Capacity]; 14 } 15 16 //拷贝构造 -- 深拷贝 17 MyArray(const MyArray& arr) { 18 //cout << "拷贝构造函数调用" << endl; 19 this->m_Capacity = arr.m_Capacity; 20 this->m_Size = arr.m_Size; 21 //this->pAddress = arr.pAddress; //如果使用编译器默认的拷贝构造函数,它会只会这么赋值,这对指针来说不对的,会导致浅拷贝的问题。 22 //因此我们需要深拷贝 23 this->pAddress = new T[arr.m_Capacity]; 24 25 //将arr中的数据都拷贝过来 26 for (int i = 0; i < this->m_Size; i++) 27 { 28 this->pAddress[i] = arr.pAddress[i]; 29 } 30 } 31 32 //operator = 防止浅拷贝的问题 33 MyArray& operator=(const MyArray& arr) { 34 //cout << "operator构造函数调用" << endl; 35 //先判断原来堆区是否有数据,如果有,先释放 36 if (this->pAddress != NULL) { 37 delete[] this->pAddress; 38 this->pAddress = NULL; 39 this->m_Size = 0; 40 this->m_Capacity = 0; 41 } 42 this->m_Size = arr.m_Size; 43 this->m_Capacity = arr.m_Capacity; 44 this->pAddress = new T[arr.m_Capacity]; 45 //将arr中的数据都拷贝过来 46 for (int i = 0; i < this->m_Size; i++) 47 { 48 this->pAddress[i] = arr.pAddress[i]; 49 } 50 return *this; 51 } 52 53 //尾插法 54 void Push_Back(const T& value) { 55 if (this->m_Capacity == this->m_Size) { 56 return; 57 } 58 this->pAddress[this->m_Size] = value; //数组末尾插入数据 59 this->m_Size++; //更新数组大小 60 } 61 62 //尾删法 63 void Pop_Back() { 64 if (this->m_Capacity == 0) { 65 return; 66 } 67 //this->pAddress[this->m_Size] = NULL; 68 this->m_Size--; 69 } 70 71 //通过下标方式访问数组中的元素 arr[0] 72 T& operator[](int index) { 73 return this->pAddress[index]; 74 } 75 76 //返回数组的容量 77 int getCapacity() { 78 return this->m_Capacity; 79 } 80 81 //返回数组的大小 82 int getSize() { 83 return this->m_Size; 84 } 85 86 //析构函数 87 ~MyArray() 88 { 89 if (this->pAddress != NULL) { 90 //cout << "析构函数调用" << endl; 91 delete[] this->pAddress; 92 this->pAddress = NULL; 93 this->m_Size = 0; 94 this->m_Capacity = 0; 95 } 96 } 97 private: 98 T* pAddress; //指针指向堆区开辟的真实数组 99 int m_Capacity; //数组容量 100 int m_Size; //数组大小 101 };
测试类:
1 #pragma once 2 #include <iostream> 3 #include "MyArray.hpp" 4 #include <string> 5 using namespace std; 6 void PrintArray(MyArray<int> &arr) { 7 cout << "打印输出数组: " << endl; 8 for (int i = 0; i < arr.getSize(); i++) { 9 cout << arr[i] << " "; 10 } 11 cout << endl; 12 } 13 void test01() { 14 //有参构造函数调用 15 MyArray <int> arr1(5); 16 17 for (int i = 0; i < 5; i++) { 18 arr1.Push_Back(i); 19 } 20 PrintArray(arr1); 21 22 cout << "输出arr1容量:" << arr1.getCapacity() << endl; 23 cout << "输出arr1大小:" << arr1.getSize() << endl; 24 25 //拷贝构造调用 26 MyArray <int> arr2(arr1); 27 //尾删一个元素 28 arr2.Pop_Back(); 29 cout << "尾删后输出arr2容量:" << arr2.getCapacity() << endl; 30 cout << "尾删后输出arr2大小:" << arr2.getSize() << endl; 31 32 //operator调用 33 //MyArray <int> arr3(100); 34 //arr3 = arr1; 35 } 36 37 //测试自定义数据类型 38 class Person { 39 public: 40 Person() {}; 41 Person(string name, int age) { 42 this->m_Name = name; 43 this->m_Age = age; 44 } 45 string m_Name; 46 int m_Age; 47 }; 48 49 void PrintPerson(MyArray<Person> &arr) { 50 for (int i = 0; i < arr.getSize(); i++) 51 { 52 cout << "姓名为:" << arr[i].m_Name << " " << "年龄为:" << arr[i].m_Age << endl; 53 } 54 } 55 void test02() { 56 MyArray<Person> arr(10); 57 58 Person p1("猪八戒",88); 59 Person p2("妲己", 61); 60 Person p3("李白", 15); 61 Person p4("关羽",99); 62 Person p5("杨鉴", 10); 63 64 arr.Push_Back(p1); 65 arr.Push_Back(p2); 66 arr.Push_Back(p3); 67 arr.Push_Back(p4); 68 arr.Push_Back(p5); 69 PrintPerson(arr); 70 } 71 int main() { 72 test01(); 73 cout << endl; 74 test02(); 75 system("pause"); 76 }