C++模板

目录

一、泛型编程

二、函数模板

1、概念

2、格式

3、原理

4、函数模板的实例化

5、函数模板的匹配原则

三、类模板

目录

一、泛型编程

二、函数模板

1、概念

2、格式

3、原理

4、函数模板的实例化

5、函数模板的匹配原则

三、类模板

1、格式

2、类模板的实例化



一、泛型编程

以swap为例:

swap函数面对多种类型,需要实现不同函数参数的swap,也就是写不同的swap函数

当我们要想实现一个多种类型通用的swap函数,可以通过函数重载

void Swap(int& left, int& right)
{
 int tmp = left;
 left = right;
 right = tmp;
}
void Swap(double& left, double& right)
{
 double tmp = left;
 left = right;
 right = tmp;
}

函数重载虽然是一个解决方案,但是有两大缺陷

首先重载函数时改变的只是类型,有大量的重复片段导致代码冗余

(注意:不能用auto,auto不能作参数) 

其次,代码的可维护性比较低,一个出错可能导致所有重载都出错

在C++中,存在一个模板,通过给这个模板中提供不同类型,来生成具体类型的代码
泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础

模板分为函数模板和类模板

二、函数模板

1、概念

函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。

2、格式

template
返回值类型 函数名(参数列表){}
例子
template
void Swap(T& left, T& right)
{
     T temp = left;
     left = right;
     right = temp;
}

注意:typename是用来定义模板参数关键字也可以使用class(不能使用struct代替class)  

template

3、原理

函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。
所以其实模板就是将本来应该我们做的重复的事情交给了编译器
C++模板_第1张图片

在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。
比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此

4、函数模板的实例化

用不同类型的参数使用函数模板时,称为函数模板的实例化
模板参数实例化分为:隐式实例化和显式实例化
1. 隐式实例化:让编译器根据实参推演模板参数的实际类型
template
T Add(const T& left, const T& right)
{
	return left + right;
}
int main()
{
	int a1 = 10, a2 = 20;
	double d1 = 10.0, d2 = 20.0;
	Add(a1, a2);
	Add(d1, d2);


	//Add(a1, d1);
	//该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型
	//通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有一个T,
	//编译器无法确定此处到底该将T确定为int 或者 double类型而报错
	//注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅


	// 此时有两种处理方式:
	//1. 用户自己来强制转化 2. 使用显式实例化
	Add(a1, (int)d1);
	return 0;
}
2. 显式实例化:在函数名后的<>中指定模板参数的实际类型
template
T Add(const T& left, const T& right)
{
	return left + right;
}

int main()
{
	int a = 10;
	double b = 20.0;

	// 显式实例化
	Add(a, b);
	return 0;
}

如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。

5、函数模板的匹配原则

匹配调用原则:

1、合适匹配情况下有现成的就用现成的(普通函数)

2、没有就将就用

3、有合适的就用更合适的,哪怕自己做

// 专门处理int的加法函数
int Add(int left, int right)
{
	return left + right;
}
// 通用加法函数
template
T1 Add(T1 left, T2 right)
{
	return left + right;
}
void Test()
{
	Add(1, 2); // 与非函数模板类型完全匹配,就用处理int的那个加法函数
	Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数
}

普通函数和函数模板同时存在 

// 专门处理int的加法函数
int Add(int left, int right)
{
 return left + right;
}

// 通用的加法函数
template
T Add(T left, T right)
{
 return left + right;
}
void Test()
{
 Add(1, 2); // 与非模板函数匹配,编译器不需要特化
 Add(1, 2); // 调用编译器特化的Add版本
}
模板函数不允许自动类型转换,但普通函数可以进行自动类型转换

三、类模板

1、格式

template
class 类模板名
{
// 类内成员定义
};

2、类模板的实例化

类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<> 中即可

 普通类:类名->类型

类模板实例化的类:类名不是类型,类<数据类型>才是整个类的类型

//Stack是类名 Stack是类型
Stack st1;
Stack st2; 

再插入一个问题,为什么要用类模板?

没用类模板时我们没办法实例化多个对象(每个对象都存不同类型的数据)

typedef int STDateType;
class Stack
{
public:
	Stack(int capacity = 1)
	{
		_a = new STDateType[capacity];
		_top = 0;
		_capacity = capacity;
	}
	~Stack()
	{
		delete[] _a;
		_a = nullptr;
		_top = 0;
		_capacity = 0;
	}
	void Push(const T& x)
	{
		//...
	}
private:
	STDateType* _a;
	int _top;
	int _capacity;

};


int main()
{
	
	Stack st1;
	Stack st2;
	return 0;
}

​

假设st1是要存int,st2是要存double,那么就寄了

如果用类模板就很爽

template
class Stack
{
public:
	Stack(int capacity = 4)
	{
		_a = new T[n];
		_top = 0;
		_capacity = capacity;
	}
	~Stack()
	{
		delete[] _a;
		_a = nullptr;
		_top = 0;
		_capacity = 0;
	}
	void Push(const T& x)
	{
		//...
	}
private:
	T* _a;
	int _top;
	int _capacity;

};


int main()
{
	//显式实例化
	Stack st1;
    st1.Push(1);
	Stack st2;//st1和st2不是同一个类型,是同一个模板
    st2.Push(2.0);
	return 0;
}

你可能感兴趣的:(c++,开发语言)