c++提高篇——模板(上)

一、模板的概念

模板就是建立通用的模具,大大提高复用性

二、模板语法

C++另一种编程思想称为泛型编程,主要利用的技术就是模板;C++提供两种模板机制:函数模板和类模板
函数模板作用:建立一个通用函数。其函数返回值类型和形参类型可以不具体制定,用一个虚教的类型来代表。
具体语法如下:

template<typename T> 函数声明或定义

上述语法中:
template:代表声明创建模板
typename:表示其后面的符号是一种数据类型。可以用class代替
T:通用数类型,名称可以替换,通常为大写字母
我们在这里举一个例子:
当我们想要实现两个数进行交换我们需要定义我们要传入参数的数据类型,例如:

void swapint(int &a, int &b)
{
	int temp = a;
	a = b;
	b = temp;
}

但是当我们想要交换两个浮点数时,我们需要重新写一个函数:

//交换两个浮点型的函数
void swapDouble(double &a, double &b)
{
	double temp = a;
	a = b;
	b = temp;
}

当我们还想要交换其他类型的变量时,我们仍然需要重新写一个函数,这增加了我们的代码量,这是我们就可以用模板进行函数实现的统一,样例如下:

template<typename T>
void MySwap(T &a, T &b)
{
	T temp = a;
	a = b;
	b = temp;
}

模板其实就是将函数传入的参数类型进行了参数化,方便我们在交换不同不变量时重复套用相同函数,极大减少了我们的代码量。
我们使用模板时,有两个方式:


	int a = 10;
	int b = 20;
	//两种方式使用函数模板
	//1、自动类型推导
	MySwap(a, b);

	//2、显示指定类型
	MySwap<int>(a, b);
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;

一种是当调用该函数时,有模板自动推导其类型,另外一种是我们自己说明我们要传入的函数类型。

三、函数模板的注意事项

1、自动类型推导,必须推导出一致的数据类型T,才可以使用
样例如下:

template<typename T> //typename可以替换为class


void MySwap(T &a, T &b)
{
	T temp = a;
	a = b;
	b = temp;
}

void test02()
{
	int a = 10;
	double b = 20;

	MySwap(a, b);

	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
}

当运行如上代码时则会报错:
在这里插入图片描述
也就是说当一个模板函数传入的两个不一样的变量类型,使用自动推导的方式确定两个变量时,会报错!
2、模板必须要确定出T的数据类型,才可以使用,样例如下:

template<typename T> //typename可以替换为class
void func()
{
	cout << "函数调用" << endl;
}

void test02()
{
	int a = 10;
	int b = 20;

	func();
}

当运行如上代码时,会报错:
在这里插入图片描述
由于func()函数没有传入任何变量,造成了编译器没有办法推导函数类型,则会报如上的错误,此时我们需要指定T的类型:

	func<int>();

这样就不会报错了

四、模板小案例

下面就是一个任意类型的数组进行排序的一个小案例

//通用数组排序器
template<typename T>
void MyEmp(T &a, T &b)
{
	T temp = a;
	a = b;
	b = temp;

}

template<class T>
void Sort(T charArr[], int len)
{
	for (int i = 0; i < len; i++)
	{
		int max = i; //确定最大值的下表
		for (int j = i + 1; j < len; j++)
		{
			if (charArr[max] < charArr[j])
			{
				max = j;
			}
		}
		if (max != i)
		{
			MyEmp(charArr[max], charArr[i]);
		}
	}
}

template<class T>
void printArray(T arr[], int len)
{
	for (int i = 0; i < len; i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
}
void test03()
{
	int charArr[] = { 1, 2, 3, 4 };
	int num = sizeof(charArr) / sizeof(charArr[0]);
	Sort(charArr, num);
	printArray(charArr, num);

}

五、普通函数与函数模板的区别

1、普通函数调用时可以发生自动类型转换(隐式类型转换)
隐式类型转换的表现样例如下:

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

void test04()
{
	int a = 2;
	char c = 'c';
	cout << Add(a, c) << endl;
}

当运行如上代码是,会输出结果为101,这就是函数中的隐式类型转换,具体来说c在ASII码中对应的数字是99,函数将c转换为了十进制数,在与a相加,则会产生101的结果。

2、函数模板调用时,如果利用自动类型推导。不会发生隐式类型转换,具体的表现如下:

template<typename T>
T Add01(T a, T b)
{
	return a + b;
}

int a = 2;
char c = 'c';
cout << Add01(a, c) << endl;

当运行如上代码时,则会报错:
在这里插入图片描述
当用自动类型推导时,编译器是不知道到底要将两个变量统一成什么类型,因此会报错。

3、如果利用显示指定类型的方式。可以发生隐式类型转换
在样例二中,我们将模板函数调用改成:cout << Add01<int>(a, c) << endl;时,编译器就不会报错了,因为我们已经告诉编译器我们到底要将两个变量统一成什么类型了。

六、普通函数与函数模板的调用规则

函数模板和普通函数一样,也可以发生函数重载,当函数模板与函数名称一样时,他们的调用规则如下:
1.如果函数模板和普通函数都可以实现,优先调用普通函效,以下的代码会调用普通函数

int Add02(int a, int b)
{
	cout << "普通函数" << endl;
	return a + b;
}


template<typename T>
T Add02(T a, T b)
{
	cout << "模板函数" << endl;
	return a + b;
}

void test05()
{
	int a = 10;
	int b = 10;
	Add02(a, b);
}

但是当普通函数没有实现体、、只有一个声明时,样例如下:

int Add02(int a, int b);

template<typename T>
T Add02(T a, T b)
{
	cout << "模板函数" << endl;
	return a + b;
}

此时则会报错:在这里插入图片描述
就是因为以上的代码仍然会调用普通函数,即使只是一个声明。
2.可以通过空模板参数列表来强制调用函数模板
那么我们想强制调用函数模板要怎么设定呢?
这是时候我们在调用函数时写一个函数模板的空实现:

Add02<>(a, b);

通过以上的调用方式就可以强制调用函数模板了。
3.函数模板也可以发生重载,具体样例如下:

template<typename T>
T Add02(T a, T b)
{
	cout << "模板函数" << endl;
	return a + b;
}

template<typename T>
T Add02(T a, T b, T c)
{
	cout << "重载模板函数" << endl;
	return a + b;
}

4.如果函数模板可以产生更好的匹配,优先调用函教模板,样例代码如下:
其中函数模板与普通函数的实现方式一样。当编译器遇到如上的代码时,会进行一个匹配操作,如果使用普通函数时,需要将char类型的变量隐式的转换为int这样的转换相较于模板函数直接推导成char要复杂,所以是运行了函数模板

char a = 'qq';
char b = 'ww';

Add02(a, b);

七、模板的局限性

模板的通用性并不是万能的,有如下样例:
1、

template<typename T>
T func(T a, T b)
{
	a = b;
}

当传入的a和b是数组时,以上的函数模板就无法实现了
2、

template<typename T>
T func(T a, T b)
{
	if (a > b)
	{
		return;
	}
}

当传入一个想person的自定义的数据类型时,则无法运行。因此C++为了解决这种问题。提供模板的重载,可以为这些特定的类型提供具体化的模板。具体样例如下:

class Person
{
public:

	Person(string name, int age)
	{
		this->m_name = name;
		this->m_age = age;
	}
	int m_age;
	string m_name;
};

//判断两个数据是否相等
template<class T>
bool func(T &a, T &b);


//利用具体化的Person的版本来实现代码,具体化优先调用
template<> bool func(Person &p1, Person &p2)
{
	if (p1.m_age == p2.m_age && p1.m_name == p2.m_name)
	{
		return true;
	}
	else
	{
		return false;
	}
}

void test06()
{
	Person p1("张三", 16);
	Person p2("lisi", 156);
	bool ret = func(p1, p2);
	if (ret)
	{
		cout << "a = b" << endl;
	}
	else
	{
		cout << "a != b" << endl;
	}
}

至此模板的上半部分已经结束!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值