C++:模板核心知识

1.泛型编程和模板的基本介绍

所谓泛型编程就是以独立于任何特定类型的方式编写代码。而模板是泛型编程的基础。模板分为函数模板类模板,模板是创建类或函数的蓝图或公式。

我们来看一个需求:编写一个函数对两个数的大小进行比较?

int compare(const int& a, const int& b)
{
	if (a > b) return 1;
	if (b > a) return -1;
	return 0;
}

int compare(const double& a, const double& b)
{
	if (a > b) return 1;
	if (b > a) return -1;
	return 0;
}

int main()
{
	cout << compare(1, 2) << endl;
	cout << compare(1.1, 2.2) << endl;

	return 0;
}

输出结果

-1
-1

如果这样实现两个数的比较大小,虽然C++的函数重载支持这样的操作,但是这样会有很大问题,如果我要比较不是int,double这样类型的数据,那么我就要补充更多这样类似的函数。但是我们发现这些函数几乎相同,它们之间唯一的区别是形参的类型不同。

我们可以通过函数模板来优化代码!

2.函数模板

函数模板格式:

template<typename T1, typename T2,......,typename Tn>
返回值类型函数名(参数列表){}

模板定义以关键字 template 开始,后接模板形参表,模板形参表是用尖括号括住的一个或多个模板形参的列表,形参之间以逗号分隔。模板形参表不能为空。

//注意函数模板将typename换成class也是可以的但是不能用struct
template<class T> 
int compare(const T& a,const T& b)
{
	if (a > b) return 1;
	if (b > a) return -1;
	return 0;
}

int main()
{
	cout << compare(1, 2) << endl;
	cout << compare(1.1, 2.2) << endl;
	return 0;
}

输出结果

-1
-1
-1

使用函数模板时,编译器会推断哪个(或哪些)模板实参绑定到模板形参。一旦编译器确定了实际的模板实参,就称它实例化了函数模板的一个实例。比如:调用compare函数,编译器会根据传入的类型(这里两个调用int,double)实例化成int版本,和double版本。编译器通过函数模板,为我们承担了编写重复类型函数的单调工作。

其实,在人类发展的过程中,使用机器来替代人工是一种很大的进步,使用模板编写泛型编程,让编译器做重复的操作,又何尝不是一种进步呢!

3.函数模板的实例化

一旦编译器确定了实际的模板实参,就称它实例化了函数模板的一个实例。函数的实例化分为:隐式实例化和显式实例化

隐式实例化:就是让编译器根据实参推演模板参数的实际类型;显式实例化:手动在函数名后的<>中指定模板参数的实际类型。

对于隐式的实例化要保证传入的实参类型正确:比如:两个数相加求和?

template<class T>
T add(const T& a, const T& b)
{
	return a + b;
}

int main()
{
	int a = 1;
	double b = 2.2;
	cout << add(a,b) << endl;
	return 0;
}

使用隐式实例化编译器无法推导,T是什么类型,编译会报错。我们可以通过显示的实例化来告诉编译器,我们想要的类型,编译器会尝试去进行隐式类型转换

int main()
{
	int a = 1;
	double b = 2.2;
	cout << add<int>(a,b) << endl;
	return 0;
}

输出结果:

3
4.类模板

和函数模板一样,类也可以定义为模板。

我们使用一个更为实用的例子queue的模拟实现来使用类模板。队列是先进先出的一种数据结构,在尾部插入数据,在头部删除数据,链表的头删尾插的效率是O(1),可以使用list来实现,但是STL默认使用deque来作为基础数据结构实现队列(适配器模式实现队列),关于deque的底层实现是一个双端队列,实现起来是比较复杂的。

1.queue.h头文件:

#pragma once
#include<deque>	

namespace xiYan
{
	template<class T,class continer = deque<T>>
	class queue
	{
	public:
		void push(const T& val) { _con.push_back(val); }
		T& front() { return _con.front(); }
		void pop() { _con.pop_front(); }
		bool empty() { return _con.empty(); }
	private:
		continer _con;
	};
}

2.test.cpp使用自定义的queue

#include<iostream>
#inlcude<list>
using namespace std;
#include"queue.h"//注意queue.h头文件中使用deque没有展开<deque>,注意要展开std!
using namespace xiYan;


int main()
{
    //指定一个list容器也是可以的,但是vector不行,模拟实现使用的有些接口vector是没有的,比如头删:由于效率问题vector直接没有支持。
    //queue<int, list<int>>q;
	queue<int> q;
	q.push(1);
	q.push(2);

	while (!q.empty())
	{
		cout << q.front() << " ";
		q.pop();
	}
    
    return 0;
}

类模板和函数模板一样,也是template关键词,后接模板形参表。不同于函数模板的是,类模板必须显示的实例化。

当然在模板中还可以再去定义模板:比如,在类模板中在定义函数模板。

template<class T>
class A
{
public:
	template<class T>
	T sum(T a, T b)
	{
		return a + b;
	}

	T minus(T a, T b)
	{
		return a - b;
	}
};

int main()
{
	A<int> a;
	
	cout << a.sum(2.2, 1.1) << endl;
	cout << a.minus(2, 1) << endl;
	return 0;
}

输出结果:

3.3
1

调用sum函数时会匹配double类型,在不同的模板中命名可以重复,不过这样在函数模板,参数列表中在参数名再命名为T是不规范的,要避免命名的重复。

但是在同一个模板中命名是不能重复的。

template<class T,class T>//错误
class A{};
5.在模板定义内部指定类型

如果我们要定义一个通用的打印函数,传入容器对象,使用迭代器怎么打印呢?

template<class Continer>
void Print(Continer& v)
{
	Continer::const_iterator it = v.begin();

	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
}

要接受一个返回类型v.begin(),就要在模板内部定义一个类型,但是这里会有歧义,就是说const_iterator可以是静态的全局变量,也可以是一个类型(如:typedef一个类型),那么默认情况下编译器认为const_iterator是一个数据成员。我们完全可以定义这样的代码:

class Continer
{
public:
	static int const_iterator;
};
int Continer::const_iterator = 1;

int main()
{
    Continer::const_iterator  = 5;
    return 0;
}

所以我们要使用typename显示的指定一个类型。

template<class Continer>
void Print(Continer& v)
{
	typename Continer::const_iterator it = v.begin();
	……
}
6.非类型模板形参

模板形参不必都是类型,也可以是非类型形参。前面我们所说的由typename,class引起的是类型形参,而非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。

下面是C++11增加的array类的模拟实现。

//array.h
#pragma once
namespace xiYan
{
	template<class T,size_t n>
	class array
	{
	public:
		T& operator[](size_t pos) { return _arr[pos]; };
		const T& operator[](size_t pos) const { return _arr[pos]; }
		size_t size() { return _size; };
		bool empty() { return _size == 0; }

	protected:
		T _arr[n];
		size_t _size;
	};
}

//test.cpp
#include"array.h"

using namespace xiYan;
int main()
{
	array<int, 10> arr;
	return 0;
}
7.回顾compare函数
template<class T> 
int compare(const T& a,const T& b)
{
	if (a > b) return 1;
	if (b > a) return -1;
	return 0;
}

我们回到一个开始的例子,这里一个细节,只用了一个>号来,对于自定义类型需要重载一下>号来比较,这里给一个参考的例子:

class Date
{
public:
	friend bool operator >(const Date& d1, const Date& d2);
	Date(int year = 2023, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{}

protected:
	int _year;
	int _month;
	int _day;
};

bool operator >(const Date& d1, const Date& d2)
{
	if (d1._year > d2._year)
		return true;
	if (d1._year == d2._year && d1._month > d2._month)
		return true;
	if (d1._year == d2._year && d1._month == d2._month && d1._day > d2._day)
		return true;
	return false;
}

int main()
{
    Date d1(2023, 2, 1);
	Date d2(2023, 3, 1);
	cout << compare(d1, d2) << endl;

    return 0;
}

如果在特殊的情况下,传入两个指针,也是不能任务的

template<class T>
int compare(const T& a,const T& b)
{
	if (a > b) return 1;
	if (b > a) return -1;
	return 0;
}

int main()
{
	int a = 10,b = 5;
	cout << compare(&a, &b) << endl;
	return 0;
}

输出结果

-1
7.模板的特化

想要解决指针传入比较不正确的情况,要使用模板的特化。所谓的模板特化,就是说一个或多个模板形参的实际类型或实际值是指定的。特化的形式如下:

  • 必须要先有一个基础的函数模板
  • 关键字template后面接一对空的尖括号<>
  • 函数名后跟一对尖括号,尖括号中指定需要特化的类型
  • 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。

我们使用函数特化解决一下,传入指针比较的问题:

template<class T>
int compare(const T& a,const T& b)
{
	if (a > b) return 1;
	if (b > a) return -1;
	return 0;
}

/*
原函数模板的形参是const&,所以特化保持一致,至于是用const int*还是int*都是可以的但是类型要一致都是int*或const int*;
int compare<int*>(const int* const &a, int* const &b)是不行的
*/
template<>
int compare<int*>(int* const &a, int* const &b)
{
	if (*a > *b)return 1;
	if (*b > *a) return -1;
	return 0;
}

int main()
{
	int a = 10,b = 5;
	cout << compare(&a, &b) << endl;
	return 0;
}

输出结果:

1
8.类的全特化

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

template<class T1,class T2>
class Data
{
public:
	Data() { cout << "Data<T1,T2>" << endl; }
private:
	T1 _val1 = 0;
	T2 _avl2 = 0;
};

template<>
class Data<int, int>
{
public:
	Data() { cout << "Data<int,int>" << endl; }
private:
	double val1 = 0;
	double val2 = 0;
};
9.类的偏特化

偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。

template<class T1,class T2>
class Data
{
public:
	Data() { cout << "Data<T1,T2>" << endl; }
private:
	T1 _val1 = 0;
	T2 _avl2 = 0;
};

//变化1:
template <class T1>
class Data<T1, int>
{
public:
	Data() { cout << "Data<T1, int>" << endl; }
private:
	T1 _d1 ;
	int _d2 ;
};

//变化2:
template <typename T1, typename T2>
class Data <T1*, T2*>
{
public:
	Data() { cout << "Data<T1*, T2*>" << endl; }
private:
	T1 _d1;
	T2 _d2;
};

int main()
{
    Data<double, int> d1;
	Data<int, int> d2;
	Data<int*, int*> d3;
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值