实验3 模板

模版的特点

  • 模版不可以直接使用,只是作为一个框架
  • 模版的通用并不是万能的

函数模版

建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表
语法:template <typename T>
声明一个模版,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用数据类型

template<typename T>
void mySwap(T &a, T &b)
{
	T temp = a;
	a = b;
	b = temp;
}

void test01()
{
	int a = 10;
	int b = 20;
	//利用函数模版交换
	//1、自动类型推导
	mySwap(a, b);
	//2、显示指定类型
	mySwap<int>(a, b);
}

一般模板函数

template<typename T>
int compare(const T& left, const T& right) 
{
    if (left < right) 
    {
        return -1; 
    }
    if (right < left) 
    {
        return 1; 
    }
    return 0;
}

compare<int>(10, 20); 

特化函数模版

  • 使用模板特化时,必须要先有基础的模板函数(就是上面第一个模板函数)

  • 使用特化模板函数时格式有要求:
    1.template 后直接跟<> 里面不用写类型
    2.函数名<特化类型>(特化类型 参数1, 特化类型 参数2 , …) 在函数名后跟<>其中写要特化的类型

  • 特化的函数的函数名,参数列表要和原基础的模板函数相同,避免不必要的错误

template<>
void compare() {
    cout << "特化函数模板调用 " << endl; 
}

compare();

函数模板的注意事项:

  • 自动类型推导,必须推导出一致的数据类型T才可以使用
  • 模板必须要确定出T的数据类型,才可以使用

普通函数与函数模板的区别:

  • 普通函数调用时可以发生自动类型转换(隐式类型转换)
  • 函数模版调用时,如果利用自动类型推导,不会发生隐式类型转换
  • 如果利用显示指定类型的方式,可以发生隐式类型转换

普通函数和函数模版调用的规则:

  • 如果普通函数和函数模版都可以实现,优先调用普通函数
  • 可以通过空模版参数列表来强调调用函数模版myPrint<>(a, b);
  • 函数模版也可以发生重载
  • 如果函数模版可以产生更好的匹配,优先调用函数模版

模版的局限性

模版并不是万能的,有些特定的数据类型,需要用具体化的方法去做特殊实现

函数模版总结

既然选择使用了函数模版,就不要写普通函数,避免二义性

类模版

类模版实例

#include <iostream>
#include <string.h>
using namespace std;
//类模版
template<class NameType, class AgeType>
class Person
{
public:
	Person(NameType name, AgeType age)
	{
		this->m_name = name;
		this->m_age = age;
	}
	NameType m_name;
	AgeType m_age;
}
//调用
void test01()
{
	Person<string,int>p1("ljw",20);
}

void main()
{
	test01();
	system("pause");
	return 0;
}

类模版中的成员函数并不是一开始就创建的,而是在调用的时候才创建

类模版对象做函数参数

//1、指定传入类型
void PrintPerson1(Person<string,int>&p)
{
	p.showPerson();
}

void test01()
{
	Person<string,int>p1("ljw",20);
	PrintPerson1(p1);
}

//2、参数模版化
template<class T1, class T2>
void PrintPerson2(Person<T1, T2>&p);
{
	p.showPerson();
}

void test02()
{
	Person<string,int>p2("Jeffrey",19);
	PrintPerson2(p2);

//3、整个类模版化
template<class T>
void PrintPerson3(T &p)
{
	p.showPerson();
)

void test03()
{
	Person<string,int>p3("Jeffrey",19);
	PrintPerson3(p3);
}

类模版与继承

当类模版碰到继承时,应该注意以下几点:

  • 当子类继承父类的一个类模版时,子类在声明的时候,要指定出父类中T的类型
  • 如果不指定,编译器无法给子类分配内存
  • 如果想灵活指定出父类T的类型,子类也需要变为类模版
template<class T>
class Base
{
	T m;
};
class Son:public Base<int>//必须指定一个类型
{};
template<class T1, class T2>
class Son2:public Base<T2>
{
};

void test02()
{
	Son2<int, char> child1;
}

构造函数的类外实现

template<class T1, class T2>
class Person
{
public:
	Person(T1 name, T2 age);
	T1 name;
	T2 age;
}

template<class T1, class T2>
Person<T1,T2>::Person(T1 name, T2 age)
{
	this->name=name;
	this->age=age;
}

模板类(AutoPtr)

构造函数

template<class T>
AutoPtr<T>::AutoPtr(T* pData)
{
	m_pData = pData;
	m_nUser = new int(1);
}

析构函数

template<class T>
void AutoPtr<T>::decrUser()
{
	‐‐(*m_nUser);
	if ((*m_nUser) == 0) {
		delete m_pData;
		m_pData = 0;
		delete m_nUser;
		m_nUser = 0;
	}
}

拷贝构造函数

template<class T>
AutoPtr<T>& AutoPtr<T>::operator=(const AutoPtr<T>& h)
{
     decrUser();
     m_pData = h.m_pData;
     m_nUser = h.m_nUser;
     (*m_nUser)++;
}

等号、->、*等运算符重载

template<class T>
AutoPtr<T>& AutoPtr<T>::operator=(const AutoPtr<T>& h)
{
     decrUser();
     m_pData = h.m_pData;
     m_nUser = h.m_nUser;
     (*m_nUser)++;
}

类模版自写案例

实现功能

  • 可以对内置数组类型以及自定义数据类型的数据进行存储
  • 将数据中的数据类型存储到堆区
  • 构造函数中可以传入数组的容量
  • 提供对应的拷贝构造函数以及operator=防止浅拷贝问题(有堆区数据就要考虑浅拷贝)
  • 提供尾插法和尾删法对数组中的数据进行增加和删除
  • 可以通过下标的方式访问数组中的元素
  • 可以获取数组中当前元素个数和数组的容量
	//有参构造 参数是数组的容量
	MyArray(int capacity)
	{
		//cout << "MyArray有参构造" << endl;
		this->m_Capacity = capacity;
		this->m_Size = 0;
		this->pAddress = new T[this->m_Capacity];
	}
	MyArray(const MyArray& arr)
	{
		//cout << "MyArray拷贝构造" << endl;
		this->m_Capacity = arr.m_Capacity;
		this->m_Size = arr.m_Size;
		//深拷贝,防止系统自动生成拷贝构造函数中的this->pAddress = arr.pAddress;
		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)
	{
		//cout << "MyArray的operator=" << endl;
		//先判断原来堆区是否有数据,如果有先释放
		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];
		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)
		{
			return;
		}
		this->pAddress[this->m_Size] = val;//在数组末尾插入数据
		this->m_Size++;//更新数组大小
	}
	//尾删法
	void Pop_Back()
	{
		if (this->m_Size == 0)
		{
			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()
	{
		//cout << "MyArray析构函数" << endl;
		if (this->pAddress != NULL)
		{
			delete[] this->pAddress;
			this->pAddress = NULL;//指向空,防止野指针
		}
	}
//MyArray元素
private:
	T* pAddress;//指针指向堆区开辟的真实数组
	int m_Capacity;//数组总容量
	int m_Size;//数组实时大小
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值