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

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

  • 一、类模板
  • 二、类模板与函数模板区别
  • 三、类模板中成员函数创建时机
  • 四、类模板对象做函数参数
  • 五、类模板与继承
  • 六、类模板成员函数类外实现
  • 七、类模板分文件编写
  • 八、类模板与友元
  • 九、类模板的应用案例

一、类模板

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

template<typename T>

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

//类模板
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.类模板没有自动类型推导的使用方式:

//类模板与函数模板的区别
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);
}

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

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

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

template<class NameType, class AgeType = int>

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

Person<string>p1(name, age);

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

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

类模板中成员函数和普通类中成员函数创建时机是有区别的:
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);
}

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

//参数模板化
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;

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

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

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

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

五、类模板与继承

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

class son:public base<int>

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

template<class T1, class T2>
class son:public base<T2>
{
	T1 A;
};

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

在这里语法就比较简单,直接上例子啦
重点在这里: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:直控包含.cpp源文件,样例如下:

#include"person.cpp"

void test12()
{
	person<string, int> p1("张三", 12);
	p1.showPeson();
}

解决方式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;
}

在调用这个类模板时,我们可以直接调用#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);
}

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

//提前让编译器知道类模板的存在
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、提供对应的拷贝构造函数以及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; //数组大小
};

测试代码:

#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;
}

你可能感兴趣的:(c++基础,c++,算法)