类和对象之模板

在这里插入图片描述

前言

在我们现代的制造业当中,人们广泛地使用各种模具批量生产各种外形相同的产品,比如说手机的外壳,这些模具就可以看作是各种产品的模板(template)。

1.函数模板

像我们平时写的程序中,如果有定义众多的swap()函数,这个函数只是参数类型不同,但是功能完全相同。类似这种情况,如果能将逻辑功能相同而函数参数和函数值类型不同的多个重载函数用一个函数来描述,将会使代码的可重用性大大提高,从而提高软件的开发效率,此时C++提供的函数模板就是为了解决这个问题。

函数模板的定义形式如下:

template<class 类型名 1,class 类型名 2,...>
返回类型  函数名(形参表)
{
	函数体;
}
  • template关键字表示声明的是模板。
  • < >中是模板的参数表,可以有一项或者多项,其中的类型名称为参数化类型,是一种抽象类型或者可变类型。
  • class 是类型关键字,也可以使用typename作为关键字。
  • 函数返回值类型可以是普通的内置类型,也可以是自定义类型,还可以是模板参数表中的指定的类型。
  • 模板参数表中的参数类型可以是基本数据类型。

例如,下面将swap函数定义成了一个函数模板:

template<class T>
void Swap(T &val1, T &val2)
{
	T temp = val1;
	val1 = val2;
	val2 = temp;
}

下面的一个函数模板的参数表中带有基本数据类型的形式参数:

template<class T,int size>
T sum()
{
	...
}

1.1 函数模板的实例化

函数模板实例化分为显式实例化和隐式实例化。

1.1.1 显式实例化的格式如下:
 函数名  <具体的类型名 1,具体的类型名 2, ... ,常量表达式> (实参表)
  • 根据< >中给出的具体类型,用类似于函数调用实参于形参结合的方式,将模板参数表中的参数化类型一一实例化成具体的类型,将函数中的参数化类型也一一实例化。
  • 如果模板参数表中有形式参数,还需要用常量表达式初始化。
  • 例1,使用Swap< double >(8.1,9.2)
    void Swap(T &val1, T &val2)示例化成
    void Swap(double &val1,double &val2);
  • 例2,使用sum<int,100>将T sum()实例化成int sum(),其中size获得初值100。
1.1.2 隐式实例化的格式如下:
  • 隐式实例化的格式像函数调用一样,实例化过程是在实参与形参结合时用实参的类型实例化形参对应的参数化类型。
  • 例3,使用Swap('A','B')void Swap(T &val1, T &val2)实例化成:
    void Swap(char &val1, char &val2);
★注意
  • 使用隐式实例化无法初始化模板参数表中的普通类型的形参,如果模板参数表中使用普通类型参数,必须使用显示初始化。
  • 函数模板示例化后,函数调用执行的实际上是由函数模板生成的模板函数。

1.2 普通函数与函数模板调用规则

  • 当我们的代码中出现了普通函数并且函数模板同名且同时也满足该函数的调用。此时普通函数与函数模板的调用规则如下:

1.如果函数模板和普通函数都可以调用,优先调用普通函数。
2.可以通过空模板参数列表 强制调用 函数模板。
3.函数模板也可以发生函数重载。
4.如果函数模板可以产生更好的匹配,优先调用函数模板。

具体问题具体分析,请看下面的例4:

void  myPrint(int x, int y)
{
	cout << "调用普通函数" << endl;
}

template<class T>
void  myPrint(T x, T y)
{
	cout << "调用模板函数" << endl;
}

template<class T>
void  myPrint(T x, T y,T z)
{
	cout << "调用重载的模板函数" << endl;
}

void test01()
{
	int a = 10;
	int b = 20;

	//优先调用普通函数
	myPrint(a, b);

	//通过空模板参数列表,强制调用函数模板
	myPrint<>(a, b);

	//函数模板也可以发生重载
	myPrint(a, b, 100);

	//如果函数模板产生更好的匹配,优先调用函数模板
	char c1 = 'a';
	char c2 = 'b';
	myPrint(c1,c2);
}

//总结:既让提供了函数模板,最好就不要提供普通函数,否则容易出现二义性
int main()
{
	test01();
	system("pause");
	return 0;
}

结果如下:
在这里插入图片描述

1.2.1 普通函数与函数模板区别
  • 普通函数调用可以发生隐式类型转换。
  • 函数模板用自动类型推导,不可以发生隐式类型转换。
  • 函数模板用显示指定类型,可以发生隐式类型转换。

2.类模板

和函数模板一样,用户也可以抽象出类的若干共同特性定义为类模板。
类模板就是带有类型参数的类,是能根据不同参数建立不同类型成员的具体类,称之为类的实例化。类模板中的数据成员、成员函数的参数、成员函数的返回值都可以取不同的类型,在实例化成具体对象时根据传入的实际参数类型实例化成具体类型的对象。

  • 类模板定义的语法如下:
template< 模板参数表 >
class 类名
{
	成员名;
};
  • 模板类的成员函数还可以在类外定义,其语法如下:
template< 模板参数表 >
类型  类名  < 模板参数名表 >::函数名(参数表)
{
	函数体;
}
  • 其中:

1.模板参数表与类模板的模板参数表相同。
2.模板参数名表列出的是模板参数表中的参数名,顺序与模板参数表中的顺序一致。

2.1 类模板的实例化

  • 一个类模板是具体类的抽象,在使用类模板建立对象时才根据给定的模板参数值实例化成具体的类,然后由类建立对象。与函数模板不同,类模板实例化只能采用显式方式。
  • 类模板示例化、建立对象的语法具体如下:
    类模板名 <模板参数值表> 对象名称;

其中:

  • 模板参数值表的值为类型名,类型名可以是基本数据类型名,也可以是构造数据类型名,还可以是类类型名。
  • 模板参数值表的值还可以是常数表达式,以初始化模板参数表中的普通参数。
  • 模板参数值表的值按一一对应的顺序实例化类模板的模板参数表。

2.2类模板成员函数和普通类中成员函数区别

类模板中成员函数和普通类中成员函数创建时机是有区别的:

  • 普通类中的成员函数一开始就可以创建
  • 类模板中的成员函数在调用才创建
    总结:类模板中的成员函数并不是一开始就创建的,是在调用时才去创建。

3.模板编程

3.1 动态数组模板

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;

template <class T>
class MyArray
{
public:
	MyArray(int capacity)
	{
		this->m_Capacity = capacity;
		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];
		}
	}

	//重载operator =防止浅拷贝问题
	MyArray& operator =(const MyArray&arr)
	{
		//先判断原来堆区是否有数据,如果有先释放干净
		if (this->pAddress != NULL)
		{
			delete[]this->pAddress;
			this->pAddress = NULL;
			this->m_Size = 0;
			this->m_Capacity = 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)
	{
		//考虑越界
		assert(index < this->m_Size&& index>= 0);
		
		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;		//元素个数
};

3.2 栈类模板

栈是一种先进后出(First In Last Out,FILO)的结构,在程序设计中被广泛的使用。栈的基本操作由压栈、出栈,其他的操作有判空、判满、读栈顶元素等。如若不懂,请点击查看我的往期栈的博客

下面将栈设计成一个类模板,在栈中存放任意类型的数据。

#include<iostream>
template<class T>
class Stack
{
public:
	Stack(int size= 10);

	//析构函数
	~Stack()
	{
		delete[]this->m_space;
	}
	int ma(int);
	bool push(const T& element);

	T pop();

	//判空
	bool IsEmpty() const
	{
		return this->m_top == this->m_size;
	}

	//判满
	bool IsFull() const
	{
		return this->m_top == 0;
	}
private:
	int m_size;		//元素个数
	int m_top;		//栈顶
	T* m_space;		//栈的空间
};

//构造函数
template<class T>
Stack<T>::Stack(int size)
{
	this->m_size = size;
	this->m_space = new T[size];
	this->m_top = size;
}

//入栈
template<class T>
bool Stack<T>::push(const T& element)
{
	if (!IsFull())
	{
		this->m_space[--this->m_top] = element;
		return true;
	}
	else
	{
		return false;
	}
}

//出栈
template<class T>
T Stack<T>::pop()
{
	return this->m_space[this->m_top++];
}

4.模板总结:

【优点】
1.模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生。
2.增强了代码的灵活性。

【缺陷】
1.模板会导致代码膨胀问题,也会导致编译时间变长。
2.出现模板编译错误时,错误信息非常凌乱,不易定位错误。
3.模板不支持分离编译。

在这里插入图片描述

END...

  • 8
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值