• c++提高篇——模板(下)


    一、类模板

    类模板可以建立一个通用类,类中的成员数据类型可以不具体制定,用一个虚拟的类型来代表。
    类模板的语法如下:

    template<typename T>
    • 1
    • 2

    这种语法与函数模板一致,具体的内容请见上一篇博客。
    先举一个小的代码样例:

    //类模板
    template<class NameType, class AgeType>
    
    //人类
    class Person
    {
    public:
    
    	Person(NameType name, AgeType age)
    	{
    		this->m_age = age;
    		this->m_name = name;
    	}
    
    	void ShowPerson()
    	{
    		cout << "姓名为" << this->m_name << endl;
    		cout << "年龄为" << this->m_age << endl;
    	}
    
    	string m_name;
    	int m_age;
    };
    
    void test()
    {
    	Person<string, int> p1("张三", 12);
    	p1.ShowPerson();
    }
    
    • 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

    二、类模板与函数模板区别

    类模板与函数模板区别主要有两点:
    1.类模板没有自动类型推导的使用方式:

    //类模板与函数模板的区别
    template<class NameType, class AgeType>
    class Person
    {
    public:
    
    	Person(NameType name, AgeType age)
    	{
    		this->m_name = name;
    		this->m_age = age;
    	}
    
    	void ShowPerson()
    	{
    		cout << "姓名为" << this->m_name << endl;
    		cout << "年龄为" << this->m_age << endl;
    	}
    	
    	NameType m_name;
    	AgeType m_age;
    
    };
    
    void test08()
    {
    	string name = "张三";
    	int age = 12;
    	Person p1(name, age);
    }
    
    • 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

    样例代码如上,当使用如上代码时,会报如下错误:
    在这里插入图片描述
    之所以报这么错误就是因为类模板不像函数模板一样,它并不能自动推导出传入数据的类型,因此会报如上错误。
    将调用类模板的代码改为:

    Person<string, int>(name, age);
    
    • 1

    即可解决错误。
    2.类模板在模板参数列表中可以有默认参数:

    template<class NameType, class AgeType = int>
    
    • 1

    其样例如上,在定义类模板时可以指定参数列表中的默认参数类型,当我们在调用这个类时就不需要再指定默认参数的参数类型了:

    Person<string>p1(name, age);
    
    • 1

    这是类模板的一个特有的功能,在函数模板中是没有这个功能的。

    三、类模板中成员函数创建时机

    类模板中成员函数和普通类中成员函数创建时机是有区别的:
    1、普通类中的成员函数一开始就可以创建
    2、类模板中的成员函数在调用时才创建

    四、类模板对象做函数参数

    类模板实例化出的对象,向函数传参的方式,一共有三种:
    1.指定传入的类型:直接显示对象的数据类型

    //类模板对象做函数参数
    
    template<class T1, class T2>
    
    class Person
    {
    public:
    	Person(T1 name, T2 age)
    	{
    		this->m_age = age;
    		this->m_name = name;	
    	}
    
    	void ShowPerson()
    	{
    		cout << "姓名" << this->m_name << endl;
    		cout << "年龄" << this->m_age << endl;
    	}
    
    	T1 m_name;
    	T2 m_age;
    };
    
    //指定传入的类型
    void printPerson1(Person<string, int>&p)
    {
    	p.ShowPerson();
    }
    
    void test09()
    {
    	Person<string, int>p1("张三", 19);
    	printPerson1(p1);
    }
    
    • 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

    2.参数模板化:将对象中的参数变为模板进行传递,样例如下:

    //参数模板化
    template<class T1, class T2>
    void printPerson2(Person<T1, T2>&p)
    {
    	p.ShowPerson();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    我们也可以查看到类模板推理出来的变量类型:

    	cout << "T1的类型为: " << typeid(T1).name() << endl;
    	cout << "T2的类型为: " << typeid(T2).name() << endl;
    
    • 1
    • 2

    此时显示如下:
    在这里插入图片描述

    3.整个类模板化:将这个对象类型模板化进行传递,代码样例如下:

    //整个类模板化
    template<class T>
    void printPerson3(T &p)
    {
    	p.ShowPerson();
    	cout << "T的类型为: " << typeid(T).name() << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    此时终端会显示:
    在这里插入图片描述

    五、类模板与继承

    当类模板碰到继承时,需要注意一下几点:
    1、当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型,如果不指定,编译器无法给子类分配内存,则此时会报如下错误,之所以发生如下错误是因为当子类继承父类时,并不知道自己要分配多少内存空间。
    在这里插入图片描述
    当发生如上错误时,需要指定一下T的类型,方法如下:

    class son:public base<int>
    
    • 1

    2、如果想灵活指定出父类中T的类型,子类也需变为类模板,样例如下:

    template<class T1, class T2>
    class son:public base<T2>
    {
    	T1 A;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5

    六、类模板成员函数类外实现

    在这里语法就比较简单,直接上例子啦
    重点在这里:person::person(T1 name, T2 age)

    //类模板成员函数的类外实现
    template<class T1, class T2>
    class person
    {
    public:
    
    	person(T1 name, T2 age);
    
    	void ShowPerson()
    	{
    		cout << "姓名: " << this->m_name << "年龄" << this->m_age << endl;	
    	}
    
    	T1 m_name;
    	T2 m_age;
    };
    
    
    //类外实现
    template<class T1, class T2>
    person<T1, T2>::person(T1 name, T2 age)
    {
    	this->m_age = age;
    	this->m_name = name;
    }
    
    • 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

    七、类模板分文件编写

    类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到,这是我们有如下两个解决方式:
    解决方式1:直控包含.cpp源文件,样例如下:

    #include"person.cpp"
    
    void test12()
    {
    	person<string, int> p1("张三", 12);
    	p1.showPeson();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    解决方式2:将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制。(主流方法)
    我们首先创建一个person.hpp的文件,将声明与实现全部写入到.hpp中:

    #pragma once
    #include
    using namespace std;
    #include
    
    //类模板分文件编写问题以及解决
    template<class T1, class T2>
    class person
    {
    public:
    
    	person(T1 name, T2 age);
    
    	void showPeson();
    
    	T1 m_name;
    	T2 m_age;
    };
    
    
    template<class T1, class T2>
    void person<T1, T2>::showPeson()
    {
    	cout << "年龄为: " << this->m_age << "姓名为:  " << this->m_name << endl;
    }
    
    template<class T1, class T2>
    person<T1, T2>::person(T1 name, T2 age)
    {
    	this->m_age = age;
    	this->m_name = name;
    }
    
    • 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

    在调用这个类模板时,我们可以直接调用#include"person.hpp"即可

    八、类模板与友元

    全局函数类内实现:直接在类内声明友元即可,样例如下:

    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;
    };
    
    void test13()
    {
    	person<string, int> p1("张三", 13);
    	printPerson(p1);
    }
    
    • 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

    全局函数类外实现:需要提前让编译器知道全局函数的存在,样例如下:

    //提前让编译器知道类模板的存在
    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;
    };
    
    • 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

    在类外实现全局函数时,我们需要让编译器知道我们全局函数的存在,所以我们需要将函数的实现体写在类模板的上方,但是函数的实现体中又包含了类模板,所以在函数的实现体之前要声明一下类模板的存在。

    九、类模板的应用案例

    案例描述:实现一个通用的数组类,要求如下:
    1、可以对内置数据类型以及自定义数据类型的数据进行存储
    2、将数组中的数据存储到堆区
    3、构造函数中可以传入数组的容量
    4、提供对应的拷贝构造函数以及operator=防止浅拷贝问题
    5、提供尾插法和尾删法对数组中的数据进行增加和删除
    6、可以通过下标的方式访问数组中的元素
    7、可以获取数组中当前元素个数和数组的容量
    针对要求1-4我们可以使用如下的代码进行实现,首先为了实现第一条的功能,很明显我们主要一个数组类的模板来实现该要求;针对问题二,为了将数组存放到堆区,我们需要在堆区内new一块我们需要的内存,同时需要在析构函数中对开辟的内存进行手动释放;针对问题三,我们很容易的实现出来,这里特别注意一点,我们在调用构造函数时,我们需要将类中的属性进行初始化;针对问题四,我们直接使用以前的方法实现即可。

    //通用的数组类
    #pragma once
    #include
    using namespace std;
    
    
    template<class T>
    class MyArray
    {
    
    public:
    
    	//传入数组的初始容量
    	MyArray(int Capacity)
    	{
    		this->m_Capacity = Capacity;
    
    		//初始的数组大小为0
    		this->m_Size = 0;
    
    		//在堆区先开辟出一个指定容量的内存空间
    		this->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[arr.m_Capacity];
    
    		//将arr中的数据都拷贝过来
    		for (int i = 0; i < this->m_Size; i++)
    		{
    			this->pAddress[i] = arr.pAddress[i];
    		}
    	}
    
    	//防止浅拷贝
    	MyArray& operator=(const MyArray& arr)
    	{
    	//判断原来堆区是否有数据,如果有,则先释放
    		if (this->pAddress != NULL)
    		{
    			delete[] this->pAddress;
    			this->pAddress = NULL;
    			this->m_Capacity = 0;
    			this->m_Size = 0;
    		}
    		//深拷贝
    		this->m_Capacity = arr.m_Capacity;
    		this->m_Size = arr.m_Size;
    
    		//深拷贝
    		this->pAddress = new T[arr.m_Capacity];
    
    		//将arr中的数据都拷贝过来
    		for (int i = 0; i < this->m_Size; i++)
    		{
    			this->pAddress[i] = arr.pAddress[i];
    		}
    		return *this;
    	}
    
    	//尾插法
    	void Push_Back(const T & val)
    	{
    		//判断容量是否等于数组大小
    		if (this->m_Capacity == this->m_Size)
    		{
    			cout << "数组容量已经达到上限!" << endl;
    			return;
    		}
    
    		//在数组的末尾插入数据
    		this->pAddress[this->m_Size] = val;
    
    		//更新现在数组的大小
    		this->m_Size++;
    	}
    
    	//尾删法
    	void Pop_Back()
    	{
    
    		//让用户访问不到最后一个元素,即为尾删
    		if (this->m_Size == 0)
    		{
    			cout << "无数据" << endl;
    			return;
    		}
    		//更新现在数组的大小
    		this->m_Size--;
    	}
    
    	//通过下标的方式来访问数组中的元素
    	T& operator[](int index)
    	{
    		return this->pAddress[index];
    	}
    
    	//返回数组的容量
    	int GetCapacity()
    	{
    		return this->m_Capacity;
    	}
    
    	//返回数组的大小
    	int GetSize()
    	{
    		return this->m_Size;
    	}
    
    	//析构函数
    	~MyArray()
    	{
    		if (this->pAddress != NULL)
    		{
    			delete[] this->pAddress;
    			this->pAddress = NULL;
    
    		}
    	}
    
    
    private:
    
    	T * pAddress; //指针,指向堆区开辟的数组
    
    	int m_Capacity;//数组容量
    
    	int m_Size; //数组大小
    };
    
    • 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
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135

    测试代码:

    #include
    using namespace std;
    #include"MyArray.hpp"
    #include
    
    
    void PrintIntArray(MyArray <int>& arr)
    {
    	for (int i = 0; i < arr.GetSize(); i++)
    	{
    		cout << arr[i] << endl;
    	}
    }
    
    
    void test14()
    {
    	MyArray<int> arr1(5);
    
    	for (int i = 0; i < 5; i++)
    	{
    		//利用尾插法向数组中插入数据
    		arr1.Push_Back(i);
    	}
    	cout << "arr1: " << endl;
    	PrintIntArray(arr1);
    	cout << "arr1的大小" << arr1.GetSize() << endl;
    	cout << "arr1的容量" << arr1.GetCapacity() << endl;
    
    	MyArray<int> arr2(arr1);
    	cout << "arr2的打印输出" << endl;
    	PrintIntArray(arr2);
    
    	//尾删
    	arr2.Pop_Back();
    	cout << "arr2的大小" << arr2.GetSize() << endl;
    	cout << "arr2的容量" << arr2.GetCapacity() << endl;
    }
    
    class person
    {
    public:
    
    	person() {};
    	person(string name, int age)
    	{
    		this->m_name = name;
    		this->m_age = age;
    	}
    	string m_name;
    	int m_age;
    };
    
    void printPersonArray(MyArray<person>& arr)
    {
    	for (int i = 0; i < arr.GetSize(); i++)
    	{
    		cout << "姓名: " << arr[i].m_name << "年龄: " << arr[i].m_age << endl;
    	}
    
    }
    
    void test15()
    {
    	MyArray<person> arr(10);
    
    	person p1("张三", 13); 
    	person p2("李四", 14);
    	person p3("王五", 12);
    
    	//将数据插入到数组中
    	arr.Push_Back(p1);
    	arr.Push_Back(p2);
    	arr.Push_Back(p3);
    	printPersonArray(arr);
    	cout << "arr的大小" << arr.GetSize() << endl;
    	cout << "arr的容量" << arr.GetCapacity() << endl;
    }
    
    int main() {
    
    	test15();
    	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
    • 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
  • 相关阅读:
    UE5 官方案例Lyra 全特性详解 8.如何用配置表初始化角色数据
    kernel-4.0 定时器使用问题在kernel-4.19上纠正
    React 入门:使用 create-react-app 创建 react 17 版本的应用
    Scala系列从入门到精通(三)
    [YOLOv8单机多卡GPU问题解决]
    1、web自动化环境安装(selenium安装、卸载和Chromedriver下载、安装)
    五种常见的IO模型
    电子商务商城源码 Spring Cloud、Spring Boot、Mybatis、Redis
    力扣142 - 环形链表||【二重双指针+哈希表】
    在Win11的子系统中安装 Ubuntu ,并安装DotNet
  • 原文地址:https://blog.csdn.net/qq_52302919/article/details/127951101