C++——模板初阶

泛型编程

C语言中交换两个变量数据的内容一般是这样实现的

#include<iostream>

using namespace std;

void swap(int* x, int* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}
int main()
{
	int x = 5;
	int y = 7;

    swap(&x,&y);
   cout << "x=" << x << " ";
   cout << "y=" << y << " ";

}

但自从学了C++引用以后 就不用再传地址和用指针接受实参地址了,直接使用引用 既高效又不开辟栈帧空间。

#include<iostream>

using namespace std;

void swap(int &xx, int&yy)
{
	int tmp = xx;
	xx = yy;
	yy = tmp;
}
int main()
{
	int x = 5;
	int y = 7;

    swap(x,y);
   cout << "x=" << x << " ";
   cout << "y=" << y << " ";

}

结果是一样的。 

但如果要实现多种不同类型数据之间的交换呢?

C++可以使用函数重载

void Swap(int& left, int& right)
{
 int temp = left;
 left = right;
 right = temp;
}
void Swap(double& left, double& right)
{
 double temp = left;
 left = right;
 right = temp;
}
void Swap(char& left, char& right)
{
 char temp = left;
 left = right;
 right = temp;
}

虽然函数重载可以实现 但有几个不好的地方

1.重载的函数仅仅是 类型不同,代码复用率比较低,如果产生了新的类型,就需要自己添加对应类型函数重载。
2.代码的维护性比较低,一个出错可能所有重载均可出错。
那有什么好的解决办法呢?
我们是否可以告诉编译器一个模子,让编译器自己根据不同的类型利用模子生成所对应代码呢?
事实上是可以的,因为C++的祖师爷已经考虑到了这点,并且已经实现了出来。
正所谓前人栽树 后人乘凉 
一般是 泛型编程
泛型编程编写与类型无关的通用代码,是代码复用的一种手段,模板是泛型编程的基础。

模板

模板分为 函数模板类模板

函数模板

函数模板是怎么实现两个数据之间的交换呢?
#include<iostream>

using namespace std;

template<class T>
void swap(const T&x, const T&y)
{
	T tmp = x;
	x = y;
	y = x;
}
int main()
{
	int x = 5;
	int y = 7;

    swap(x,y);
   cout << "x=" << x << " ";
   cout << "y=" << y << " ";

}

函数模板的格式

template<typename T1,typename T2,.......typename Tn>

返回值类型 函数名(参数)

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

#include<iostream>

using namespace std;

template<class T>
void swap(const T&x, const T&y)
{
	T tmp = x;
	x = y;
	y = x;
}
int main()
{
	int x = 5;
	int y = 7;
	double a = 1.34;
	double b = 2.34;
    swap(x,y);
	swap(a, b);
   cout << "x=" << x << endl;
   cout << "y=" << y << endl;
   cout << "a=" << a << endl;
   cout << "b=" << b << endl;

}

模板会根据你传的实参类型自动推演生成对应数据类型的函数。 

但它们俩调用的却不是同一个函数 通过反汇编就可以看出来

编译器自动完成的事情。

函数模板的原理

要清楚模板是在编译阶段就调用完成的。

当数据类型使用函数模板时,编译器通过对实参类型的推演,将T确定为与之对应的类型,然后专门生成处理该数据类型的代码。
当然模板里面的定义的关键字类型也可以 有多种类型
#include<iostream>

using namespace std;

template<class T,class Y>
T add(const T& x, const Y& y)
{
	return x + y;
}


int main()
{
	int a = 4;
	double b = 3.14;
	int ret=add(a, b);
	cout << ret << endl;
}

函数模板的实例化

模板参数语法其实很类似函数参数,函数参数定义的形参对象,模板参数定义的是类型 

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

而实例化又分为隐式实例化显示实例化

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

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

#include<iostream>

using namespace std;

template<class T>
T Add(const T& x, const T& y)
{
	return x + y;
}


int main()
{
	int a = 5;
	int b = 11;
	double c = 3.14;
	double d = 4.14;
	int ret = Add(a, b);
	double ret1 = Add(c, d);

	int ret2 =Add(a, d);error
	该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型
	通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有一个T,
	编译器无法确定此处到底该将T确定为int 或者 double类型而报错
	注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅
    所以为了解决这个问题 1.要么用户自己强转 2.要么显式实例化
	int ret3 = Add(a, (int)d);

	cout << ret << endl;
	cout << ret1 << endl;
	cout << ret3 << endl;
    显式实例化
	cout << Add<int>(a, c) << endl;
	cout << Add<double>(a, b) << endl;
}

 一般这种时候就要显式实例化

否则编译器也不知道f是返回值是什么?

 模板参数的匹配原则

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

#include<iostream>

using namespace std;

普通函数
int Add(const int x, const int y)
{
	cout << "(const int x, const int y)" << endl;
	return x + y;
}

函数模板
template<class T>
T Add(const T& x, const T& y)
{
	cout << "(const T& x, const T& y)" << endl;
	return x + y;
}

int main()
{
	Add(1, 2);
	Add(1.1,1.2);
}

当我把函数模板屏蔽后再运行

double到int可以进行隐式类型转换 所以去调用了普通函数。

由此我们可以发现模板参数匹配调用原则:

1.有现成的吃现成的   (第一个int类型去调用普通函数 第二个double类型去调用函数模板)

2.有适合的吃适合的,哪怕自己要现做 

3.没有就将就吃 (函数模板屏蔽后,去调用普通函数)

类模板

类模板的格式

template<class T1,class T2class Tn>

class 类模板名

{
// 类内成员定义

}

#include<iostream>

using namespace std;

template<class T>

class stack
{
public:n
	stack(int n = 4)
	{
		cout << "stack(int n = 4)" << endl;
		_a = new T[n];
		_top = 0;
		_capacity = n;
	}
	~stack()
	{
		cout << "~stack()" << endl;
		delete _a;
		_a = nullptr;
		_top = _capacity = 0;
	}

private:
	T* _a;
	int _top;
	int _capacity;
};


如果想声明和定义分离这样写
但模板一般声明和定义都是在一个文件写的 不会分开写 因为会产生链接错误
暂且先不讲 我在模板进阶再详细说
template<class T>
Stack<T>::Stack(int n)
{
	cout << "Stack(int n = 4)" << endl;

	_a = new T[n];
	_top = 0;
	_capacity = n;
}

int main()
{
	显式实例化
	stack<int>st;
	stack<double>st;
}

拿栈来做例子,C语言里面如果栈里面要存int和double类型数据 还要typedef 成intdatatype 或者doubledatatype 还要把类型名改成stackint 或者stackdouble

而有了类模板以后就避免了这种低效用法,直接显式实例化要存的类型数据,类模板参数直接推演出对应存储数据类型。

类模板的实例化

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

#include<iostream>
#include<vector>
#include<stack>
using namespace std;

int main()
{
   vector是类名  vector<int>才是类型
   vector<int>s;

   同理stack是类名  stack<double>才是类型
   stack<double>st;
 
     

    return 0;
}

总结:普通类----类名是类型   类模板----类名<数据类型>才是整个类的类型。

  • 9
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值