C++:模板(1)

目录

实现泛型的交换函数

函数模板

1.概念

2.格式

3.原理

4.函数模板实例化

5.函数模板参数的匹配原则

类模板

1.定义格式

2.实例化

3.声明与定义问题


实现泛型的交换函数

我们实现一个对所有类型都通用的交换函数,可以用函数重载来实现。

cpp 复制代码
void Swap(int& x, int& y)
{
	int temp = x;
	x = y;
	y = temp;
}
void Swap(double& x, double& y)
{
	double temp = x;
	x = y;
	y = temp;
}
void Swap(char& x, char& y)
{
	char temp = x;
	x = y;
	y = temp;
}

//......

使用函数重载可以实现,但有几个缺点:

1.重载函数仅仅是类型不同,代码复用率低,当有新类型出现的时候,就又需要增加对应的函数。

2.代码的可维护性低,一个出错就可能导致所有的重载出错。

那我们能不能自己实现一个交换函数的模具,我们传递不同类型的值,就根据这个类型生成对应的交换函数?

就如同下面的浇筑过程。

答案是可以的,这就是C++中的模板模板分为函数模板和类模板,模板又是泛型编程的基础。

泛型编程:编写与类型无关的通用代码,实现代码复用。

函数模板

1.概念

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

2.格式

模板都需要用一个关键字template,使用格式如下:

cpp 复制代码
​
//template<typename T1, typename T2, ...... , typename Tn>
template<class T1, class T2, ......, class Tn>
返回值类型 函数名 (参数列表哦){ }

​

**typename是用来定义模板参数的关键字,也可以使用class,**但是不能用struct代替。

用模板实现交换函数

cpp 复制代码
template<class T>
void Swap(T& x, T& y)
{
	T temp = x;
	x = y;
	y = temp;
}

具体例子

cpp 复制代码
template<class T>
void Swap(T& x, T& y)
{
	T temp = x;
	x = y;
	y = temp;
}


int main()
{
	int i1 = 10;
	int i2 = 50;
	cout << "交换前i1、i2为:" << i1 << " " << i2 << endl;
	Swap(i1, i2);
	cout << "交换后i1、i2为:" << i1 << " " << i2 << endl;
	cout << endl;

	double d1 = 28.8;
	double d2 = 67.6;
	cout << "交换前d1、d2为:" << d1 << " " << d2 << endl;
	Swap(d1, d2);
	cout << "交换后d1、d2为:" << d1 << " " << d2 << endl;
	cout << endl;

	char ch1 = 'c';
	char ch2 = 'x';
	cout << "交换前ch1、ch2为:" << ch1 << " " << ch2 << endl;
	Swap(ch1, ch2);
	cout << "交换后ch1、ch2为:" << ch1 << " " << ch2 << endl;
	cout << endl;
	return 0;
}

3.原理

函数模板是一个蓝图,他本身并不是函数,是编译器产生特定具体类型函数的模具。

模板就是将本来我们做的重复的事情交给了编译器。

在编译器编译阶段 ,对于函数模板的使用,编译器根据传入的实参类型来生成对应类型的函数以供调用。比如:当double模板使用函数模板时,编译器通过实参的类型确定T为double,然后生成一个double类型的函数,对于其他类型也是如此。

4.函数模板实例化

不同类型的参数使用函数模板时,称为函数模板的实例化函数模板实例化分为:隐式实例化和显式实例化

实例化出来的函数就是模板函数,模板函数的生成就是将函数模板的类型形参实例化的过程。

1.隐式实例化:编译器根据传入实参推算模板参数的实际类型

cpp 复制代码
template<class T>
T Add(const T& x, const T& y)
{
	return x + y;
}

int main()
{
	int i1 = 20, i2 = 30;
	cout << "i1 + i2 =" << Add(i1, i2) << endl;
	cout << endl;

	double d1 = 15.0, d2 = 20.0;
	cout << "d1 + d2 =" << Add(d1, d2) << endl;
	cout << endl;
	return 0;
}

注意以下这种方式时不能通过编译的:

cpp 复制代码
Add(i1, d1);

因为在编译阶段,编译器已经根据i1的类型推断T为int类型 ,而模板参数列表T只有一个 ,当编译器接收到d1时,发现时double类型,那编译器就会因为无法确定将T确定为int还是double而报错

在模板中,编译器一般不会进行类型转换操作,如果转换出问题,那编译器就需要背黑锅。

这种报错有两种解决方式:1.我们自己强制转化 2. 显式实例化

cpp 复制代码
//用户自己强制转化
Add(i1, (int)d1);

//显式实例化
Add<int> (i1, d1);

2.显式实例化:在函数名<>中指定模板参数的实际类型

cpp 复制代码
int main()
{
	int i1 = 10;
	double d1 = 10.0;

	//显式实例化
	Add<int>(i1, d1);

	return 0;
}

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

5.函数模板参数的匹配原则

1.一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板可以实例化为这个非模板函数。

cpp 复制代码
// 专门处理int的加法函数
int Add(int x, int y)
{
	return x + y;
}
// 通用加法函数
template<class T>
T Add(T x, T y)
{
	return x + y;
}
void Test()
{
	Add(1, 2); // 与非模板函数匹配,编译器不需要生成对应类型的模板函数
	Add<int>(1, 2); // 调用编译器特化的Add版本
}

2.对于非模板函数与同名的函数模板,在条件相同的情况下,在调用时会优先调用非模板函数而不会使用函数模板产生实例。如果函数模板可以实例化产生更加匹配的模板函数,那么会选择模板函数。

cpp 复制代码
// 专门处理int的加法函数
int Add(int x, int y)
{
	return x + y;
}
// 通用加法函数
template<class T1, class T2>
T1 Add(T1 x, T2 y)
{
	return x + y;
}
void Test()
{
	Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化
	Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数
}

3.模板函数不允许自动类型转换,但普通函数可以。

类模板

1.定义格式

cpp 复制代码
template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};

例子

cpp 复制代码
// 类模版
template<typename T>
class Stack
{
public:
	Stack(size_t capacity = 4)
	{
		_array = new T[capacity];
		_capacity = capacity;
		_size = 0;
	}
	void Push(const T& data);
private:
	T* _array;
	size_t _capacity;
	size_t _size;
};

// 模版不建议声明和定义分离到两个文件.h 和.cpp会出现链接错误。
template<class T>
void Stack<T>::Push(const T& data)
{
	// 扩容
	_array[_size] = data;
	++_size;
}
int main()
{
	Stack<int> st1; // int
	Stack<double> st2; // double
	return 0;
}

2.实例化

类模板的实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,并且将实例化的类型放中间。

类模板名字不是真正的类,实例化的结果才是真正的类。

cpp 复制代码
// Stack是类名,Stack<int>才是类型
Stack<int> st1; // int
Stack<double> st2; // double

3.声明与定义问题

为什么在c++中,模板(函数)的声明和定义不建议分离到两个文件?

func.h 函数声明

func.cpp 函数定义

test.cpp 调用函数

报链接错误的直接原因就是链接时,符号表没有对应函数的地址。

1.代码开始编译的时候,首先就预处理,把头文件展开、宏替换、条件编译、去掉注释,.h和对应对的.cpp文件合在一起生成.i文件

2.然后就到编译,根据语法树,检查语法,生成对应对的汇编代码,模板这时候问题就出在这,函数的.i文件,有声明有定义,没有具体类型,test.i中有函数的声明,有类型,但是没有定义,所以就不能生成具体的函数符号表也就没有对应的地址,函数.i文件普通函数有声明有定义有类型,可以生成,这时test.i还是转换成汇编 call func(?),等着链接时把地址连接上,也没有报错,由.i文件生成.s文件;

3.编译完就到了汇编,汇编代码转换成二进制机械码,生成.obj文件;

4.链接时把目标文件合并在一起生成可执行程序,并把需要的函数地址等连接上。

解决方案:声明定义不分离;显式实例化模板。


拜拜,下期再见😏

摸鱼ing😴✨🎞

相关推荐
励志成为嵌入式工程师7 分钟前
c语言简单编程练习9
c语言·开发语言·算法·vim
捕鲸叉37 分钟前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer42 分钟前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
Peter_chq44 分钟前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
wheeldown1 小时前
【数据结构】选择排序
数据结构·算法·排序算法
青花瓷2 小时前
C++__XCode工程中Debug版本库向Release版本库的切换
c++·xcode
观音山保我别报错2 小时前
C语言扫雷小游戏
c语言·开发语言·算法
幺零九零零3 小时前
【C++】socket套接字编程
linux·服务器·网络·c++
TangKenny3 小时前
计算网络信号
java·算法·华为
景鹤3 小时前
【算法】递归+深搜:814.二叉树剪枝
算法