类模板

类模板

定义

所谓类模板,实际是建立一个通用类,其数据成员成员函数的返回类型形参类型不具体指定,用一个虚拟的类型来代表。使用类模板定义对象时,系统会根据实参的类型来取代类模板中虚拟类型从而实现了不同类的功能
定义一个类模板与定义函数模板的格式类似,必须以关键字template开始,其定义的一般格式如下:
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 }
View Code

       MyClass <Person1> p;
       p.showPerson1(); //正确

       p.showPerson2(); //报错

解释:可以看到,只有实例化类模板MyClass并且指定准确的T类型为Person1后,才会去创建类模板中的成员函数showPerson1()。由于已经指定T的类型了,所以 p.showPerson2()会报错,因为Person1中根本没有成员函数 showPerson2(), 只有showPerson1()

 

1.3.4 类模板对象做函数参数

一共有三种传入方式:

  1. 指定传入的类型  --  直接显式对象的数据类型
  2. 参数模板化 -- 将对象中的参数变为模板进行传递
  3. 整个类模板化 -- 将这个对象类型  模板化进行传递
#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;
}
View Code

总结:

  • 通过类模板创建的对象,可以有三种方式向函数中进行传参
  • 使用比较广泛是第一种类型:指定传入的类型,因为其他两种都是函数模板和类模板相结合的 来使用了,比较麻烦。

 

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;
}
View Code

运行截图:

总结:如果父类是类模板,子类必须要指出父类中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;
}
View Code

 

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 };
View Code

测试类:

 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 }
View Code

 

posted @ 2023-07-23 16:01  C++杀我  阅读(110)  评论(0编辑  收藏  举报