C++模板

目录

1、泛型编程

2、函数模板

概念

格式

函数模板原理

函数模板的实例化

隐式实例化

显式实例化

函数模板的匹配原则

3、类模板

类模板的格式

类模板的实例化

4、非类型模板参数

5、模板的特化

类模板的特化

函数模板特化

模板的偏特化

模板特化的匹配规则

类模板的匹配方式

函数模板的匹配方式


1、泛型编程

泛型编程是一种编程风格,它可以让你编写完全一般化并可重复使用的算法,其效率与针对某特定数据类型而设计的算法相同。泛型编程的代表作就是STL。

int Add1(int a, int b)
{
	return a + b;
}

double Add2(double a, double b)
{
	return a + b;
}

int main()
{
	double a = 12.5, b = 13.5;
	cout << Add2(a, b) << endl;

	return 0;
}

上面的Add函数只有当参数匹配能得出正确结果,那么如果有多种类型都要使用Add函数时就必须多写几个Add函数,这样的代码复用率低,可维护性低,所以就需要用到泛型编程思想。

泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。

模板就相当于一个模具,这个模具可以做出很多类似的产品,C++中通过函数模板可以解决上面Add函数要写多个的问题。

2、函数模板

概念

函数模板代表相似功能的函数,这个函数模板与类型无关;在使用时会转换成特定类型版本,类似于之前提到过的auto。

格式

template <typename T1,typename T2...>

//参数类型相同
template<class T>
T Add3(T a, T b)
{
	return a + b;
}

//参数类型不同
//要求返回值按T2参数化后的类型返回
template<class T1,class T2>
T2 Add4(T1 a, T2 b)
{
	return a + b;
}

int main()
{
	double a = 12.5, b = 13.5;
	cout << Add3(a, b) << endl;
	int c = 10;
	cout << Add4(a, c) << endl;

	return 0;
}

注意:typename是用来定义模板参数的关键字,也可以用class(不能用struct),通常我们都是用class

函数模板原理

函数模板本质就是一个模具,本身并不是函数,当我们传给它数据后,它会根据传入的实参类型推导出对应类型的函数;例如上面的Add4,我们传了一个double和int类型的参数,它会自动推导出T1就是double,T2是int,返回值为int,然后运行函数。

 如上就是通过函数模板实例化出的两个不同函数,两个char类型或者short等都可以使用该模板。

函数模板的实例化

对于不同类型的参数使用函数模板时,这个过程就叫做函数模板的实例化函数模板的实例化分为显示实例化和隐式实例化。

隐式实例化

template<class T>
T Add(T a, T b)
{
	return a + b;
}

int main()
{
	double a = 12.5, b = 13.5;
	cout << Add(a, b) << endl;

	return 0;
}

上面就是函数模板的隐式实例化,我们不指明要实例化成什么类型,让系统根据实参类型自己去推导。

显式实例化

template<class T>
T Add3(T a, T b)
{
	return a + b;
}

int main()
{
	double a = 12.5;
	int b = 13;
    //指定实例化成int类型
	cout << Add<int>(a, b) << endl;

	return 0;
}

在函数名后<>内指定参数模板的实际类型。

如果参数不匹配,编译器会进行隐式类型转换,如果无法转换的话就会报错。

函数模板的匹配原则

函数模板可以和同名的非函数模板函数同时存在。

template<class T>
void Add(T a, T b)
{
	cout << "void Add1(T a, T b):" << a + b << endl;
}

void Add(int a, int b)
{
	cout << "void Add2(int a, int b):" << a + b << endl;
}

int main()
{
	int a = 10, b = 10;
	Add(a, b);

	double c = 10.5, d = 10.5;
	Add(c, d);

	return 0;
}

 可以看到,如果有现成的int类型的Add函数,会去优先选择现成的,而不会去调用函数模板实例化。当没有现成的函数使用时,就回去调用函数模板实例化一个。

3、类模板

类模板的格式

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

使用实例:

template<class T>
class A
{
public:
	A(size_t capacity = 4)
		: _p(new T[capacity])
		, _size(0)
		, _capacity(capacity)
	{}

	// 析构函数类外定义示例
	~A();

private:
	T* _p;
	int _size;
	int _capacity;
};
// 类模板中函数放在类外进行定义时,需要加模板参数列表
template <class T>
A<T>::~A()
{
	if (_p)
		delete[] _p;
	_size = _capacity = 0;
}

类模板的实例化

类模板的实例化需要在类模板名字后加上<>,在<>里写上实例化的类型,类模板的名字并不是真正的类,实例化出来的结果才算是真正的类。

// A是类名,A<int>才是类型
A<int> a1;
A<double> a2;

4、非类型模板参数

模板参数有两种:类型形参和非类型形参

类型形参:就是在class和typename后加参数类型名,就像上面例子中的 class T 一样。

非类型模板参数:用常数作为模板参数,在类模板和函数模板中可以把非类型模板参数当做常量使用。

template<class T,size_t N = 4>
class A
{

private:
	//定义一个静态数组,大小为N
	T _p[N];
	size_t _size;
};

说明:

1、非类型模板参数只能是常量整型值(包括枚举),指向对象、函数、成员的指针,指向对象或者函数的左值引用。

2、浮点数和类类型对象不能作为非类型模板参数。

3、非类型的模板参数必须在编译期就能确认结果。

5、模板的特化

类模板的特化

在有些情况下,针对特殊的类型,需要对模板进行特化,例如当我们需要一个bool型的时候,实际上bool类型用1个bit位就能标识了,这是如果用模板就会浪费空间。

//正常
template <class T>
class A {};
//特化
template < >
class A<bool> {};

template<>就相当于告诉了编译器这是一个模板的特化。

函数模板特化

template<class T>
void Add(T x, T y)
{
	cout << x + y << endl;
}

template<>
void Add<int*>(int* p, int* q)
{
	cout << *p + *q << endl;
}

int main()
{
	int a = 5;
	int b = 5;
	Add(a, b);
	double c = 5.5;
	double d = 5.5;
	Add(c, d);

	//当两个指针相加时,会报错,无法直接相加
	//针对这种情况,就需要写一个模板的特化
	int* p1 = &a;
	int* p2 = &b;
	Add(p1, p2);//这里走的就是第二个的Add

	return 0;
}

说明:

1、必须在template后加上一个空的<>

2、函数名后加<>,里面写上指定的类型

3、形参列表里的参数类型必须和特化的类型一致

模板的偏特化

部分特化:将模板参数一部分进行特化

template <class T1, class T2>
class A {};
template <class T>
//下面两种都可以,不分先后
//class A<T, bool> {};
class A<bool,T> {};

将模板参数偏特化成指针类型

//不能没有主模板,只有偏特化。
//所以必须写上主模板声明/定义,不写下面这句就会报错
template <class T1, class T2> class Data;
//将两个参数特化成指针类型
template<class T1, class T2>
class Data <T1*,T2*>
{
public:
	Data() { cout << "Data<T1*, T2*>" << endl; }

private:
	T1 _d1;
	T2 _d2;
};

int main()
{
	//创建两个int*的类对象
	Data<int*, int*> d;

	return 0;
}

将模板参数偏特化成引用类型

template <class T1, class T2> class Data;
//将两个参数特化成指针类型
template<class T1, class T2>
class Data <T1&, T2&>
{
public:
	Data() { cout << "Data<T1&, T2&>" << endl; }

private:
	T1 _d1;
	T2 _d2;
};

int main()
{
	Data<int&, int&> d1;
	Data<int&, double&> d2;

	return 0;
}

模板特化的匹配规则

类模板的匹配方式

template <class T> class A {};	// 普通类型都能匹配
template <class T> class A<T*> {};  //指针类型优先选T*
template <class T> class A <T&> {}; //引用类型优先选T&
template <> class A <int*> {}; //int*类型优先选

函数模板的匹配方式

template <class T> void Func(T);  //普通类型都可以
template <class T> void Func(int, T); //当第一个参数int时匹配
template <class T> void Func(T*);  //优先指针类型
template <> void Func<int>(int); //参数只有一个int时匹配
void Func(double);  //参数只有一个double类型时匹配

总结:当类型匹配时,优先选择已经存在的,尽量不去使用特化版本。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值