当前位置: 首页 > news >正文

模板·初阶

在这里插入图片描述

你好,我是安然无虞。

文章目录

  • 自学网站
  • 泛型编程
  • 函数模板
    • 函数模板·格式
    • 函数模板·原理
    • 函数模板·实例化
      • 隐式实例化
      • 显式实例化
    • 模板参数·匹配原则
  • 类模板
    • 类模板·格式
    • 类模板·实例化
  • 练习题

自学网站

推荐给老铁们两款学习网站:
面试利器&算法学习:牛客网
风趣幽默的学人工智能:人工智能学习
首个付费专栏:《C++入门核心技术》

泛型编程

首先,何为泛型编程呢?
泛型编程指不再是针对某种类型,能适应广泛的类型,跟具体类型无关的代码。
所以引入了模板的概念,模板分为函数模板和类模板。

template<class T> //用class/typename都可以

void Swap(T& left, T& right)
{
	T temp = left;
	left = right;
	right = temp;
}

大家想想我们之前是怎么实现Swap函数的:

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;
}

// ……

我们之前就是利用函数重载,但是你有没有想过,重载出来的函数除了类型不同之外,其余均相同,所以这样会导致代码复用率更低,而且代码的可维护性也不好。

那模板就是告诉编译器有一个模子,让编译器根据不同的类型利用该模子来生成代码。

再次提到泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。
在这里插入图片描述

函数模板

函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
函数模板的类型一般是编译器根据实参传递给形参,然后推演出来的。

函数模板·格式

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

比如:

template<class T>

void Swap(T& left, T& right)
{
	T temp = left;
	left = right;
	right = temp;
}

其实模板参数在很多地方跟函数参数是很像的:
模板参数传递的是类型,如:

//类型也可以给缺省值
template<class T = char>

函数参数传递的是对象值,如:

T* func(int n = 10)
{
	//……
}

还有一点需要注意哦,普通的函数是有地址的,但是函数模板没有地址

typename是用来定义模板参数的关键字,也可以使用class,注意一下,咱们的struct 在这里不能代替class 哦。

函数模板·原理

函数模板只是一个蓝图,它本身并不是函数,是编译器使用特定方式产生具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的工作交给了编译器。
在这里插入图片描述
在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如上图:当用int 类型使用函数模板时,编译器通过对实参类型的推演,将T 确定为int 类型,然后产生一份专门处理int 类型的代码,当然了,对于 double类型也如此。

函数模板·实例化

当不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化。

隐式实例化

隐式实例化就是:让编译器根据传入的实参类型推演生成模板参数的实际类型。

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, d1);//编译报错

	return 0;
}

为什么下面这行代码编译报错了呢?

Add(a1, d1);

因为在编译期间,当编译器看到该实例化时,需要推演其实参类型,通过实参a1 将T推演为int ,通过实参d1 将T 推演为double ,但是模板参数列表只有一个T ,所以编译器无法确定将这里的T视为int 还是double 而报错。
注意哦,在模板中,编译器一般不会进行类型转换操作,因为一旦转换出现问题,编译器就需要承担责任。

那怎么处理上述的错误呢?

// 有两种处理方式:1.用户自己来强制转换;2.使用显式实例化
Add(a, (int d));

显式实例化

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

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<int>(a1, d1);

	return 0;
}

函数模板的类型一般是编译器根据实参传递给形参,推演出来的;如果不能自动推演,那么就需要我们显式实例化指定模板参数。

template<class T>
T* func(int n)
{
	return new T[n];
}

int main()
{
	//函数模板显式实例化
	int* p1 = func<int>(10);
	double* p2 = func<double>(10);
	return 0;
}

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

模板参数·匹配原则

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

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

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

int main()
{
	Add(1, 2);//与非模板函数匹配,不需要函数模板实例化
	Add<int>(1, 2);//编译器显式实例化
	
	return 0;
}

原则二:对与非模板函数和同名函数模板,如果其他条件都相同,在调用时会优先调用非模板函数。但是如果函数模板可以产生一个具有更好匹配的函数,那么将选择模板。

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

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

int main()
{
	Add(1, 2);//与非模板函数匹配,不需要函数模板实例化
	Add(1, 2.0);//函数模板可以生成更加匹配的版本
	
	return 0;
}

原则三:函数模板不允许自动类型转换,但是普通函数可以进行自动类型转换。

类模板

类模板·格式

template<class T>
class 类模板名 //注意哦这是类模板名
{
	//类内成员定义
}

比如:

//动态顺序表
//注意哦,这里的vector不是具体的类,是编译器根据被实例化的类型生成具体类的模具,所以它是类模板名
template<class T>
class vector
{
public:
	vector(size_t capacity = 10)
		:_a(new T[capacity])
		,_size(0)
		,_capacity(capacity)
	{}

	//声明(只是为了演示在类外如何定义)
	~vector();

private:
	T* _a;
	size_t _size;
	size_t _capacity;
};

//注意类模板中函数放在类外进行定义时,需要加模板参数列表
template<class T>
vector<T>::~vector()//指定类域
{
	if (_a)
	{
		delete _a;
		_a = nullptr;
		_size = _capacity = 0;
	}
}

注意哦,上面的vector不是具体的类,是编译器根据被实例化的类型生成具体类的模具,所以它是类模板名。

类模板·实例化

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

// vector是类模板名,vector<int>才是类型
//类模板的类型是显式实例化,明确指定的
vector<int> s1;
vector<double> s2;

练习题

1、下面有关C++中为什么用模板类的原因,描述错误的是? ( )
A.可用来创建动态增长和减小的数据结构
B.它是类型无关的,因此具有很高的可复用性
C.它运行时检查数据类型,保证了类型安全
D.它是平台无关的,可移植性

解析:
A.模板可以具有非类型参数,用于指定大小,可以根据指定的大小创建动态结构

B.模板最重要的一点就是类型无关,提高了代码复用性;

C.模板运行时不检查数据类型,也不保证类型安全,相当于类型的宏替换

D.只要支持模板语法,模板的代码就是可移植的。

2、下列关于模板的说法正确的是( )
A.模板的实参在任何时候都可以省略
B.类模板与模板类所指的是同一概念
C.类模板的参数必须是虚拟类型的
D.类模板中的成员函数全是模板函数

解析:
A.不一定,参数类型不同时有时需要显示指定类型参数;

B.类模板是一个类家族,模板类是通过类模板实例化的具体类

C.C++中类模板的声明格式为template<模板形参表声明><类声明>,并且类模板的成员函数都是模板函数;

D.正确,定义时都必须通过完整的模板语法进行定义

相关文章:

  • 【MATLAB教程案例30】基于MATLAB的图像阴影检测和消除算法的实现
  • 字符串拼接