C++模板初阶

目录

一.泛型编程

二.函数模版

三.类模版


一.泛型编程

首先请思考一个问题:如何实现a和b变量值的交换的函数?

其实很简单,如果a和b是int类型的变量这样写就行了:

void Swap(int& a,int&b)
{
	int tmp = a;
	a = b;
	b = tmp;
}

如果是double类型,再写一个就行了,进行函数重载,可是如果频繁遇到这样需要交换值的时候而且变量的类型很多,此时一个一个写出来就太麻烦了,而且我们也不知道哪些是会用的,如果没用到写出来就浪费了。

这样的方式有两个问题:

1.重载的函数仅仅是类型不同而已,代码复用率太低了,每次有新类型出现的时候,我们就需要自己增加对应的函数。

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

那么有什么解决方式呢?

C++中有这样一种语言特性叫做模版,这是一种用于实现通用程序的设计。模版可以让程序员只写出一般化的代码,由编译器针对特定的不同的类型进行生成相应的代码。

比如紧接着上面的问题:如何实现一种可以交换不同类型值的交换函数呢?

template<class T>
void Swap(T& a,T& b)
{
	T tmp = a;
	a = b;
	b = tmp;
}
int main()
{

	int x = 10, y = 20;
	Swap(x,y);
	cout << x << ' ' << y << endl;
	double n = 1.1, m = 2.2;
	Swap(n, m);
	cout << n << ' ' << m << endl;
}

template就是模版的意思,这里的class可以换成typename二者没有区别都是类型的意思不是类。

然后根据需要写出函数就可以,这样的函数就是函数模版。

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

模版有两种:

二.函数模版

1.概念

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

2.函数模版的格式

template<typename T1,typename T2......//可以写很多个>
返回值 函数名(参数)
{
    //函数体
}

需要注意的是一个template只管他下面的一个函数,如果还需要写函数模版就要再写。

typename是用来定义模版参数的关键字,也可以使用class(切记不能使用struct因为这个class不是类的意思)。通常情况下都是用class,可能因为需要打的字更少。

3.函数模版的原理

函数模版就好像一个模具或者蓝图,他本身并不是函数,是编译器使用特定方式产生的具体类型函数的模具,所以其实模版就是为了将我们需要做的重复性过高的工作交给编译器来帮我们完成。

 

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

这里思考两个问题:

1.如果我们自己写了一个专门处理char类型的交换函数,此时又有函数模版会不会编译报错?

2.如果不会报错,那么调用这个函数处理char类型的变量的时候,编译器是会用我们自己写的,还是重新推演用其生成的?

//}
template<class T>
void Swap(T& a, T& b)
{
	T tmp = a;
	a = b;
	b = tmp;
	cout << "void Swap(T & a, T & b)" << endl;
}
void Swap(char& a, char& b)
{
	char tmp = a;
	a = b;
	b = tmp;
	cout << "void Swap(char& a, char& b)" << endl;
}
int main()
{
	char a = 'a', b = 'b';
	Swap(a, b);
	cout << a << ' ' << b << endl;
}

 运行上面的代码就可以解决这个问题,不会报错,而且会优先使用我们自己写的。(编译器是懒

4.函数模版的实例化

用不同的参数使用函数模版的时候,称之为函数模版实例化。模版参数实例化分为:隐式实例化和显示实例化。

(1).隐式实例化:让编译器根据实参类型来推导T的类型

上面我们写的所有代码都是隐式实例化。

template<class T>
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,d2);

	return 0;
}

如果遇到上面这样的情况怎么办呢?

第一个和第二个add函数都可以推出其类型,而第三个就无法判断T到底是什么类型的因为a1是int,d1是double类型的,此时编译器就会报错,因为无法确定。有时候我们给int类型的变量传double类型的参数也可以因为它可以转化,但是此时并不会,因为一旦编译器可以转化,我们对其所需要的推导功能就没有用了,而且达不到我们要的效果编译器要背黑锅。

所以,在模版中,一般情况下是不会进行类型转化操作的。

此时的处理方式有两种,其中一个很简单,我们自己将其转化为int类型

Add(a1,int(d2));

第二种方式就是显示实例化。

(2).显示实例化:在使用函数时候在函数名后面加上<>用于指定模版参数中的类型

	Add<int>(a1, d2);

这样不会报错,但是会警告 “参数”: 从“double”转换到“const T”,可能丢失数据.

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

 5.模版参数的匹配原则

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

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

// 通用加法函数
template<class T>
T Add(T left, T right)
{
	return left + right;
}

void Test()
{
	Add(1, 2);       // 与非模板函数匹配,编译器不需要特化
	Add<int>(1, 2);  // 调用编译器特化的Add版本
}

2.对于非模版函数和同名函数模版,如果其他条件都相同吗,在调动时会优先调用非模版函数而不会从该函数模版中产生一个实例化。如果模版可以产生一个具有更好匹配的函数就会悬着模版。

 

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

// 通用加法函数
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{
	return left + right;
}

void Test()
{
	Add(1, 2);     // 与非函数模板类型完全匹配,不需要函数模板实例化
	Add(1, 2.0);   // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的
	Add函数
}

3.模版函数不允许自动类型转换,但是普通的函数可以进行自动类型转化。

三.类模版

 1.类模板的格式

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

 //模板不建议声明和定义分离到两个文件.h和.cpp会出现链接错误。

2.类模板的实例化

类模板的实例化与函数模板的实例化不同,类模板实例化需要再类模板名字后跟<>,然后实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结构才是真正的类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值