【C++】模板

目录

一、泛型编程

二、函数模板

(一)概念

(二)原理

(三)模板实例化

  1、隐式实例化

2、显式实例化

(四)匹配原则

三、类模板

(一)概念

(二)实例化

四、非类型模板参数

五、模板特化

(一)概念

(二)函数模板特化

(三)类模板特化

1、全特化

2、偏特化

六、模板的优缺点


一、泛型编程

        C++ 泛型编程是一种编程范式,它通过使用 模板 允许编写与类型无关的代码。模板使得程序能够处理不同类型的数据,而无需重复编写相同的逻辑。

二、函数模板

(一)概念

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

template<class T1, class T2>
int Add(const T1& a, const T2& b)
{
	return a + b;
}

(二)原理

        函数模板类似于一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。模板针对不同类型产生不同的具体函数,但都是同一个逻辑效果,模板其实就是将本来应该我们做的重复的事情交给了编译器。

(三)模板实例化

        用不同类型的参数使用函数模板时,称为函数模板的实例化。

        模板参数实例化分为:隐式实例化和显式实例化

1、隐式实例化

        隐式实例化:让编译器根据实参推演模板参数的实际类型。

template<class T>
T Add(const T& a,const T& b)
{
	return a + b;
}
int main()
{
	Add(10, 20);
	Add(10.10, 20.20);
	return 0;
}

        这里根据传入类型的不同,编译器将根据传入参数类型进行不同的实例化。

        针对上述代码只有一个函数模板参数,如果在函数调用时两个参数类型不同的话,编译器便会报错。那么这种情况应该如何解决呢?

2、显式实例化

        针对上述情况,解决方法有很多:

        (1)增加一个函数模板参数;

        (2)在调用时对传入参数进行类型转换;

        (3)显示实例化

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

template<class T>
T Add(const T& a,const T& b)
{
	return a + b;
}
int main()
{
	Add<int>(10, 20.20);
	return 0;
}

(四)匹配原则

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

int Add(int& a, int& b)
{
	return a + b;
}
template<class T>
T Add(const T& a,const T& b)
{
	return a + b;
}
int main()
{
	Add(10, 20);		//调用普通函数
	Add<int>(10, 20);	//调用函数模板生成的函数
	return 0;
}

        当模板函数产生的实例更契合传入的参数时,那么在调用时将会选择模板产生的实例。

int Add(int& a, int& b)
{
	return a + b;
}
template<class T1, class T2>
T1 Add(const T1& a,const T2& b)
{
	return a + b;
}
int main()
{
	Add(10, 20.0);	//调用函数模板生成的函数
	return 0;
}

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

三、类模板

(一)概念

        C++类模板允许编写与数据类型无关的代码。

        使用类模板,你可以定义一个类,这个类可以接受任何数据类型作为参数,从而使得代码更加灵活和可重用。

#include <iostream>
#include <typeinfo>
template<class T>
class A {
private:
	T _a;
public:
	A()
	{
		std::cout << typeid(_a).name() << std::endl;	//输出_a的类型
	}
};
int main()
{
	A<int> a;		//输出 int
	A<char> b;		//输出 char
	return 0;
}

(二)实例化

        类模板实例化与函数模板实例化不同,类模板在使用时必须显示实例化:需要在类模板名字后跟<>,然后将实例化的类型放在<> 中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。

stack<int> v;
queue<char> q;

四、非类型模板参数

        模板除了可以传类型模板参数外,还可以传入非类型参数,但该参数必须是整形且必须传入常量。

#include <iostream>
#include<assert.h>
namespace mh {
	template<class T, size_t N = 10>
	class array {
	private:
		T _array[N];
		size_t _size;
	public:
		array() : _size(N) {}
		T& operator[](size_t index)
		{
			assert(index < _size);
			return _array[index];
		}
		const T& operator[](size_t index) const
		{
			assert(index < _size);
			return _array[index];
		}
		size_t size() 
		{
			return _size;
		}
		bool empty()
		{
			return _size;
		}
	};
}
int main()
{
	mh::array<int> a;
	mh::array<int, 20> b;
	return 0;
}

五、模板特化

(一)概念

        通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结 果,需要特殊处理。

//函数模板
template<class T>
bool Less(T left, T right)
{
	return left < right;
}
int main()
{	
	int a = 10;
	int b = 20;
	cout << Less(a, b) << endl;

	int* pa = &a;
	int* pb = &b;
	cout << Less(pa, pb) << endl;
	return 0;
}

        比如上述代码,在传入指针参数时,进行比较的实际是地址大小的比较,并不能达到进行数值比较的目的。针对上述情况,我们就可以使用特化解决。

(二)函数模板特化

        针对上述情况,我们可以进行函数特化:

        1、必须要先有一个基础的函数模板;

        2、关键字template后面接一对空的尖括号<>;

        3、函数名后跟一对尖括号,尖括号中指定需要特化的类型;

        4、 函数形参表: 必须要和模板函数的基础参数类型完全相同

//函数模板
template<class T>
bool Less(T left, T right)
{
	return left < right;
}
//模板特化
template<>
bool Less<int*>(int* left, int* right)
{
	return *left < *right;
}
int main()
{	
	int a = 10;
	int b = 20;
	cout << Less(a, b) << endl;

	int* pa = &a;
	int* pb = &b;
	cout << Less(pa, pb) << endl;
	return 0;
}

(三)类模板特化

1、全特化

        全特化即是将模板参数列表中所有的参数都确定化。

#include <iostream>
using namespace std;
//类模板
template<class T1, class T2>
class Date
{
public:
	Date()
	{
		cout << "Date<T1, T2>" << endl;
	}
private:
	T1 _a;
	T2 _b;
};
//类模板特化
template<>
class Date<int, char>
{
public:
	Date()
	{
		cout << "Date<int, char>" << endl;
	}
private:
	char _a;
	char _b;
};
int main()
{	
	Date<int, int> d1;        //调用类模板
	Date<int, char> d2;       //调用类模板特化
	return 0;
}
2、偏特化

(1)偏特化:部分特化

#include <iostream>
using namespace std;
//类模板
template<class T1, class T2>
class Date
{
public:
	Date()
	{
		cout << "Date<T1, T2>" << endl;
	}
private:
	T1 _a;
	T2 _b;
};
//类模板特化
template<class T1>
class Date<T1, char>
{
public:
	Date()
	{
		cout << "Date<T1, char>" << endl;
	}
private:
	char _a;
	char _b;
};
int main()
{	
	Date<int, int> d1;		//调用类模板
	Date<int, char> d2;		//调用类模板特化
	Date<char, char> d3;	//调用类模板特化
	return 0;
}

(2)偏特化:参数更进一步的限制

#include <iostream>
using namespace std;
//类模板
template<class T1, class T2>
class Date
{
public:
	Date()
	{
		cout << "Date<T1, T2>" << endl;
	}
private:
	T1 _a;
	T2 _b;
};
//类模板特化
template<class T1>
class Date<T1, char>
{
public:
	Date()
	{
		cout << "Date<T1, char>" << endl;
	}
private:
	char _a;
	char _b;
};
template<class T1, class T2>
class Date<T1*, T2*>
{
public:
	Date()
	{
		cout << "Date<T1*, T2*>" << endl;
	}
private:
	char _a;
	char _b;
};
template<class T1, class T2>
class Date<T1&, T2&>
{
public:
	Date()
	{
		cout << "Date<T1&, T2&>" << endl;
	}
private:
	char _a;
	char _b;
};
int main()
{	
	Date<int, int> d1;		//调用类模板
	Date<int, char> d2;		//调用类模板特化
	Date<int*, char*> d3;	//调用类模板特化
	Date<int&, char&> d4;	//调用类模板特化
	return 0;
}

六、模板的优缺点

        优点在上述内容中已有介绍。缺点是模板会导致代码膨胀,编译时间变长。出现模板编译错误时,报错信息凌乱,不易定位错误。

        分离编译:一个项目由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。

        例如:

// a.h
template<class T>
T Add(const T& left, const T& right);

// a.cpp
template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}

// main.cpp
#include"a.h"
int main()
{
	Add(1, 2);
	Add(1.0, 2.0);

	return 0;
}

        无论是函数模板还是类模板都不支持分离编译,因为分离编译以后,在声明部分编译器并没有看到函数模板的实现部分,而模板在实例化时才会推演类型,因为该声明并不会进入符号表。因此在最后链接时,模板并没有实例化并且没有生成具体代码,但编译器并不能找到调用函数模板的地址,因此会连接报错。

        其实只要将模板的声明和实现放在同一个头文件即可,或者是在模板定义位置进行显式实例化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值