【C++从0到王者】第八站:模板初阶

news2025/1/18 14:02:01

文章目录

  • 一、泛型编程
  • 二、函数模板
    • 1.函数模板概念
    • 2.函数模板格式
    • 3.函数模板的原理
    • 4.函数模板的实例化
      • 1.隐式实例化
      • 2.显示实例化
    • 5.模板参数的匹配原则
  • 三、类模板
    • 1.类模板的格式
    • 2.类模板的实例化

一、泛型编程

当我们在写一个交换程序的时候
按照我们之前的想法,我们需要写大量的函数重载

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++中,也能够存在这样一个模具,通过给这个模具中填充不同材料(类型),来获得不同材料的铸件(即生成具体类型的代码),那将会节省许多头发。巧的是前人早已将树栽好,我们只需在此乘凉。
泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础

模板又可分为函数模板和类模板

二、函数模板

1.函数模板概念

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

2.函数模板格式

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

例如下面的例子

template <typename T>
void Swap(T& a, T& b)
{
	T c = a;
	a = b;
	b = c;
} 
int main()
{
	double a = 1.1, b = 1.2;
	Swap(a, b);
	return 0;
}

注意:typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替class),我们暂且可以认为这两种写法都是一样的,等价的

而且我们函数模板参数也可以是多个不同的参数,模板参数只在其后面的一个函数有效。它在写函数的时候可以是函数的任意一个地方,可以是函数形参列表中出现类型,也可以是返回值也可以是函数体

template<typename T1,typename T2>
void Func(const T1& x, const T2& y)
{
	cout << x << " " << y << endl;
}
int main()
{
	int a = 1;
	double b = 1.1;
	Func(a, b);
	return 0;
}

3.函数模板的原理

函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器

函数模板实际上并不是调用这个模板函数。而是自动根据类型推导函数。去调用对应的函数。我们可以从汇编的角度去验证这一点
在这里插入图片描述

在这里插入图片描述在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此。

对于类类型,指针类型等等都是可以自动推导的。同样的在库里面也有一个swap函数。就是使用模板来实现的,我们以后就可以直接使用swap函数了

4.函数模板的实例化

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

1.隐式实例化

如下代码所示

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;
	cout << Add(a1, a2) << endl;
	cout << Add(d1, d2) << endl;
	//cout << Add(a1, d1) << endl;
	cout << Add(a1, (int)d1) << endl;
	return 0;
}

这段代码中被注释掉的那行是错误的。因为T的类型不明确。也很好理解,处理方法就是强制类型转换。或者也可使用显示实例化

2.显示实例化

我们将上面的代码改写成这样就是显示实例化了,其中<>用于指定类型。在函数名后的<>中指定模板参数的实际类型

	cout << Add<int>(a1, d1) << endl;
	cout << Add<double>(a1, d1) << endl;

但是我们一般都不是这样适合用显式实例化的,我们一般都是这样才使用的,我们无法自动推导的时候,我们就只能显式实例化了

template<class T>
T* Alloc(int n)
{
	return new T[n];
}
int main()
{
	double* p1 = Alloc<double>(5);
}

5.模板参数的匹配原则

  1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
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版本
}
  1. 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
// 专门处理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函数
}
  1. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换

三、类模板

当我们写一个栈的时候,我们可能需要使用int类型的栈,char类型的栈,double类型的栈…这时候我们就需要使用函数重载了

typedef int DataType;
class StackInt
{
public:
	StackInt(size_t capacity = 3)
	{
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}

		_capacity = capacity;
		_size = 0;
	}

	void Push(DataType data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}

	// 其他方法...

	~StackInt()
	{
		if (_array)
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}

private:
	DataType* _array;
	int _capacity;
	int _size;
};

typedef double DataType;
class StackDouble
{
public:
	StackDouble(size_t capacity = 3)
	{
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}

		_capacity = capacity;
		_size = 0;
	}

	void Push(DataType data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}

	// 其他方法...

	~StackDouble()
	{
		if (_array)
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}

private:
	DataType* _array;
	int _capacity;
	int _size;
};

int main()
{
	StackInt s1; // int
	StackDouble s2; // double

	// char
	// int*
	// Date


	return 0;
}

然而这样实在太麻烦了…而且一旦未来需要增加或者修改一些接口,同样的代码需要改很多份…

1.类模板的格式

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

为了处理上面的情况,我们就需要使用类模板了。它的思路与函数模板是一致的,让编译器去帮我们干活

template<class T>
class Stack
{
public:
	Stack(size_t capacity = 3)
	{
		//_array = (T*)malloc(sizeof(T) * capacity);
		//if (NULL == _array)
		//{
		//	perror("malloc申请空间失败!!!");
		//	return;
		//}
		_array = new T[capacity];
		_capacity = capacity;
		_size = 0;
	}

	void Push(const T& data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}

	// 其他方法...

	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}

private:
	T* _array;
	int _capacity;
	int _size;
};
int main()
{
	Stack<int> st1;
	Stack<double> st2;
	Stack<char> st3  ;
	return 0;
}

利用类模板就可以极大的简化代码,但是还存在一个问题就是如何声明和定义分离。这对于类模板而言还是有一些不同的

  1. 我们还需要加上没模板参数列表
  2. 我们在使用域作用限定符的时候还需要指明模板参数
  3. 普通类,类类型和类名是一样的
  4. 类模板,类类型和类名是不一样的
  5. 类名:Stack
  6. 类名:Stack
  7. 不可以分文件进行分类。
template<class T>
class Stack
{
public:
	Stack(size_t capacity = 3);

	void Push(const T& data);

	// 其他方法...

	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}

private:
	T* _array;
	int _capacity;
	int _size;
};
int main()
{
	Stack<int> st1;
	Stack<double> st2;
	Stack<char> st3  ;
	return 0;
}
template<class T>
Stack<T>::Stack(size_t capacity = 3)
{
	//_array = (T*)malloc(sizeof(T) * capacity);
	//if (NULL == _array)
	//{
	//	perror("malloc申请空间失败!!!");
	//	return;
	//}
	_array = new T[capacity];
	_capacity = capacity;
	_size = 0;
}


template <class T>
void Stack<T>::Push(const T& data)
{
	// CheckCapacity();
	_array[_size] = data;
	_size++;
}

2.类模板的实例化

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/556661.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

谷歌算法快讯0519:近日排名变化频繁,排名或许回温?

从上周末到现在&#xff0c;已经有人注意到排名似乎又有了新的变化&#xff0c;根据WebMaster World上的帖子[1]和业内大家的讨论来看&#xff0c;大家共同的认识是5月16日开始就已经有变化&#xff0c;并且在5月19日的SEMRush Sensor来看已经到达峰值。 有一些在3月份谷歌更新…

yomichan使用笔记

导入词典词典下载 键盘快捷键 Alt Insert 打开搜索页面。 Alt DeleteToggle 打开/关闭扩展。 搜索结果中提供以下快捷方式&#xff1a; Esc取消当前搜索。 Alt PgUpPage 向上浏览结果。 Alt PgDnPage 向下浏览结果。 Alt End 转到最后一个结果。 Alt Home 转到第…

zookeeper的安装使用

zookeeper的安装使用 一、下载安装 https://zookeeper.apache.org/ 点击 download 以我自己的安装为例,linux,3.8.0 准备3台linux服务器&#xff1a;192.168.10.128、192.168.10.129、192.168.10.130 1.上传解压 把apache-zookeeper-3.8.0-bin.tar.gz 上传到 /usr/local/zo…

力扣sql中等篇练习(二十五)

力扣sql中等篇练习(二十五) 1 最繁忙的机场 1.1 题目内容 1.1.1 基本题目信息 1.1.2 示例输入输出 a 示例输入 b 示例输出 1.2 示例sql语句 # Write your MySQL query statement below WITH T as (SELECT t.airport_id,SUM(n) numFROM(SELECT departure_airport airport_i…

【C++进阶之路】内存管理

文章目录 一.内存管理1. 内存布局2. C的内存管理 ①内置类型② 自定义类型 3. operate new 与 operate delete ① 解读operate new源代码② 解读operate delete源代码 4. new和delete的基本原理①new对类对象②delete对类对象 拓展—— 深入理解delete[]和new[]对比 C和C内存…

Java数据类型之字符串

字符集/编码表 ex&#xff1a;ASCII码 字符char&#xff1a;单引号‘ ’引起来的单个字符 转义字符 \n 作用&#xff1a;换行&#xff0c;单引号引用&#xff01;&#xff01;&#xff01; 制表符 \t 作用&#xff1a;加上 \t 前面一共空8个 字符与编码的转换 1.直接输出字…

opencv_c++学习(十九)

一、图像间的距离变换 三种常用的距离计算方法&#xff1a; 欧式距离这里就不在解释。 街区距离&#xff1a;顾名思义&#xff0c;就类似于城市距离一样&#xff0c;并不是通过两点间的距离&#xff0c;而是我们从一个地点到达另一个地点的路程(横纵坐标差值之和)。 棋盘距离…

每日学术速递5.21

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.Going Denser with Open-Vocabulary Part Segmenta 标题&#xff1a;通过开放式词汇部分分割变得更密集 作者&#xff1a;Peize Sun, Shoufa Chen, Chenchen Zhu, Fanyi Xiao, Pi…

代码随想录算法训练营第十三天|239. 滑动窗口最大值、347.前 K 个高频元素

滑动窗口最大值 题目链接&#xff1a;力扣 知识点&#xff1a;单调队列 解题思路&#xff1a; 需要一个队列&#xff0c;放进去窗口里的元素&#xff0c;然后随着窗口的移动&#xff0c;队列也一进一出&#xff0c;每次移动之后&#xff0c;队列告诉我们里面的最大值是什么…

Java基础-Java常用类1(包装类 + Object类)

本篇文章主要讲解Java的常用类 包装类Object类 希望能对你的复习以及面试有帮助,有错误请指正 , 感谢. 目录 包装类 Object类 Object 类的常见方法有哪些&#xff1f; 对象比较(hashcode和equals方法) 和 equals() 的区别 hashCode() 是什么 ? 有什么用&#xff1f; 那…

【数据分享】中国首套10米分辨率的建筑高度数据(tif格式)

建筑是城市最重要的构成要素&#xff0c;高密度高层数的建筑是城市区别于乡村的显著特征&#xff01;建筑数据也是我们在各项研究中都会使用到的数据&#xff01;之前我们分享过2020年全国90个城市市域范围的建筑体块数据&#xff08;可查看之前的文章获悉详情&#xff09;。 …

Ubuntu2004设置共享开发环境

我们都知道Linux操作系统是一个多用户的操作系统&#xff0c;由于大家在实际工作中很少接触到多用户环境&#xff0c;特别是在目前电脑硬件成本不断降低的情况下几乎每个从事IT行业的人员都有一台甚至多台个人PC&#xff0c;因此大家对多用户的理解并不深刻。 ChatGPT引燃了人…

【数据结构】堆堆堆堆堆!

目录 前言 树 树的概念 树的相关概念​编辑 树的表示 二叉树的概念 特殊的二叉树 ​ 二叉树的存储结构 堆 堆的建立(本篇以小堆为例&#xff0c;大堆实现方法一样&#xff09; 堆的结构定义 堆的初始化 堆的插入 堆的基础算法——向上调整算法 插入注意事项 堆的判…

Openai+Coursera: ChatGPT Prompt Engineering(三)

想和大家分享一下最近学习的Coursera和openai联合打造ChatGPT Prompt Engineering在线课程.以下是我写的关于该课程的前两篇博客&#xff1a; ChatGPT Prompt Engineering(一) ChatGPT Prompt Engineering(二) 今天我们来学习第三部分内容&#xff1a;推断(Inferring) 推断…

Android:IPC(进程间通信)机制

Android&#xff1a;IPC&#xff08;进程间通信&#xff09;机制 进程和线程 我们先来了解一些关于线程和进程基本的概念。 按照操作系统中的描述&#xff0c;线程是CPU调度的最小单元&#xff0c;同时线程是一种有限的系统资源。而进程一般指一个执行单元&#xff0c;在PC和…

(学习日记)AD学习 #2

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…

npm ERR! code E404 在vscode安装插件时报错的解决方案

答主在配置commit代码提交规范时【即如下代码】遇到了以下问题 npm i cz-customizable6.3.0 --save-dev 出现了 npm ERR! code E404 npm ERR! 404 Not Found - GET https://registry.npmjs.org/vue%2fvue-loader-v15 - Not found npm ERR! 404 ……等报错情况 解决方案1 检查n…

SVN 导出改动差异文件

文章目录 SVN 导出改动差异文件应用场景/背景介绍具体操作方法 SVN 导出改动差异文件 应用场景/背景介绍 当然下面的两个场景介绍可能用分支管理都会有不错的效果&#xff0c;或者更优&#xff0c;只是记录一下思路&#xff0c;用什么还是看大家个人爱好啦 在开发过程中偶尔会…

nexus私服仓库maven-metadata.xml缺失导致的构建失败或者下载504

环境&#xff1a;maven项目&#xff0c;使用Nexus私服&#xff0c;jenkins实现代码的编译和打包。 问题分析思路&#xff1a;某周末前&#xff0c;jenkins上的编译打包任务一直正常工作&#xff0c;但周末后突然所有项目都编译失败&#xff0c;报错很一致都是Could not find a…

【牛客刷题专栏】0x30:JZ38 字符串的排列(C语言编程题)

前言 个人推荐在牛客网刷题(点击可以跳转)&#xff0c;它登陆后会保存刷题记录进度&#xff0c;重新登录时写过的题目代码不会丢失。个人刷题练习系列专栏&#xff1a;个人CSDN牛客刷题专栏。 题目来自&#xff1a;牛客/题库 / 在线编程 / 剑指offer&#xff1a; 目录 前言问…