初探 C++模板:开启高效编程之门

news2024/12/29 10:12:59

目录

模版的引入

泛型编程

模板的概念

模板的使用

函数模版

函数模板概念

函数模板格式

函数模板的原理

函数模板的实例化

模板参数的匹配原则

类模版

类模板的定义格式

类模板的由来

类模板的实例化

模板的总结


模版的引入

如下代码,我们想实现交换两个值,如果我想交换多种类型呢?

众所周知,C语言中不支持函数重载,如果想实现不同类型的交换,就需要写很多不同的函数。

在C++中,我们如果想实现不同类型的交换,就需要写多个函数重载,如果想交换其它类型的还要写相同类似的代码。每次写一个不同的类型的交换就要写一个相同类似的函数,写了很多不同类似的代码,这样就显得很挫,所以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;
}
int main()
{
	int a = 0, b = 1;
	Swap(a, b);
	double c = 1.11, d = 2.22;
	Swap(c, d);
	char e = 'A', f = 'Z';
	Swap(e, f);
	cout << a << " " << b << endl;
	cout << c << " " << d << endl;
	cout << e << " " << f << endl;
	return 0;
}

泛型编程

模板的概念

模板是一种编程工具,允许您创建通用的、可复用的代码,能够处理不同的数据类型。

生活中的例子就可以形象的说明模板的概念:

有一个模具(模板)决定了冰块的形状,可能是正方体或者圆柱体 (这些不同的形状的模具代表不同功能的函数模板,除了交换值的函数,还有容器、算法、类模板)。要制作普通的水冰块,就把水倒入模具;要制作果汁冰块,就把果汁倒入模具。水和果汁就是不同的类型参数。

模板的使用

函数模板和类模板的使用方式有相似之处,首先我们来看函数模板的使用。以下这个模板函数能够实现多种类型的数据交换,这充分体现了泛型编程的理念,下面会详细介绍模板的具体使用方法。

//模板 ->写根类型无关的代码
template<class T>
void Swap(T& x1, T& x2)
{
	T x = x1;
	x1 = x2;
	x2 = x;
}
int main()
{
	int a = 0, b = 1;
	Swap(a, b);
	double c = 1.11, d = 2.22;
	Swap(c, d);
	char e = 'a', f = 'b';
	Swap(e, f);
	cout << a << " " << b << endl;
	cout << c << " " << d << endl;
	cout << e << " " << f << endl;
	return 0;
}

泛型编程

在这个示例中,Swap 函数通过模板实现了对不同类型数据的交换操作,而无需为每种具体类型单独编写一个交换函数。它能够处理 intdoublechar 等多种类型,体现了泛型编程的思想,即编写与具体类型无关、通用且可复用的代码。

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


函数模版

函数模板概念

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

函数模板格式

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

template<class T>  //或者 template<typename T>   模板参数定义类型,这里的T相对于类型的名称可以随便取,一般取T(type)
void Swap(T& x1, T& x2)
{
	T x = x1;
	x1 = x2;
	x2 = x;
}

注意:typename 是用来定义模板参数关键字,也可以使用class,通常使用class (切记:不能使用struct代替class)。


函数模板的原理

下面调用的是否是同一个函数

template<class T>
void Swap(T& x1, T& x2) 
{
	T x = x1;
	x1 = x2;
	x2 = x;
}
int main()
{
	int a = 0, b = 1;
	Swap(a, b);
	double c = 1.11, d = 2.22;
	Swap(c, d);
	char e = 'a', f = 'b';
	Swap(e, f);
	cout << a << " " << b << endl;
	cout << c << " " << d << endl;
	cout << e << " " << f << endl;
	return 0;
}

在反汇编中,我们会看到每次 Swap 调用对应的是不同的函数指针,指向为特定类型生成的不同函数实现。(调用的都不是同一个函数)

 


所以模板的原理是什么:

我们写了模板,编译器通过模板实例化出对应的函数或者类,编译器不会编译模板,编译器编译模板实例化出的对应的函数或者类 (编译器只会编译实例化的代码,不会编译模板(模具))。

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

注意:早期的编译器,写好了模板,在不使用模板的情况下,编译器不编译模板(模具),但是模板的架子不能有问题,模板里面的代码实现编译器不管,也不会去检查里面的语法。

现在的编译器,即使模板没有被实际使用,编译器也可能会对模板的语法进行一定程度的检查,以尽早发现潜在的问题。


函数模板的实例化

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

模板参数实例化分为:隐式实例化和显式实例化。

  • 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.1, d2 = 20.2;
	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(a1, (int)d1); 把double强制类型转成 int


  • 2,显式实例化:在函数名后的<>中指定模板参数的实际类型
template<class T> 
T Add(const T& left, const T& right)
{
	return left + right; 
} 
int main()
{
	int a1 = 10, a2 = 20;
	double d1 = 10.1, d2 = 20.2;
	Add(a1, a2);   //隐式实例化
	Add<double>(d1, d2); //显示实例化
	Add<int>(a1, d1);  //显示实例化,类型不一样会进行类型转换成Add<int>,int类型,如果类型转换失败,编译器会报错
	return 0;
}

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

模板参数的匹配原则

  • 1,一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
// 专门处理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); 
}
int main() 
{
	// 与非模板函数匹配
	Add(1, 2);
	return 0;
}

对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板函数。

  • 2,模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
template<class T> 
T Add(const T& left, const T& right)
{
	return left + right; 
} 
int main()
{
	int a1 = 10, a2 = 20;
	double d1 = 10.1, d2 = 20.2;
	Add(a1, d1);  //编译出错, //显示实例化 Add<int>(a1,d1); 或者强转 Add(a1,(int)d1);
	return 0;
}

类模版

类模板的定义格式

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

类模板的由来

 C 语言是如何实现一个 Stack

C语言定义栈存在的问题

1,忘记初始化和销毁
2,没有封装
3,没办法支持泛型,如果想同时定义两个栈,一个存int,一个存double,做不到

看起来不容易忘记初始化和销毁,实际中,我们很容易忘记。

C语言中没有封装,谁都可以修改内部的值。

如果想用栈存不同类型的数据,就需要写两个不同类型的栈

typedef int STDateType;
typedef struct Stack_C
{
	STDateType* _a;
	int _size; // 或者取名叫 _top
	int _capacity;
}Stack_C;

void Stack_CInit(Stack_C* ps)
{}
void Stack_CDestory(Stack_C* ps)
{}
void Stack_CPush(Stack_C* ps, STDateType x)
{}
void Stack_CPop(Stack_C* ps)
{}
void Test_C()
{
	Stack_C st_c;
	Stack_CInit(&st_c);
	Stack_CPush(&st_c, 1);
	Stack_CPush(&st_c, 2);
	Stack_CPush(&st_c, 3);
	Stack_CPush(&st_c, 4);
	//非法修改
	st_c._capacity = 0;
	Stack_CDestory(&st_c);
}

 C++ 是如何实现一个 Stack

要实现存储多种不同类型的栈,我们可以使用类模板,类模板的使用和函数模板都是同理

1,类在定义的时候就会自动调用构造函数,出了作用域自动调用析构函数完成清理工作

2,拥有了封装,有访问限定符的限制,增强了代码的安全性和可维护性

3,有了模板,可以实现多种不同类型的数据储存到栈中

template<class T>
class Stack_CPP
{
public:
	Stack_CPP()
	{
	}
	~Stack_CPP()
	{
	}
	void Push(T x)
	{
	}
private:
	T* _a;
	int _size;
	int _capacity;
};
void Test_CPP()
{

	Stack_CPP<int> st_cpp_int;
	st_cpp_int.Push(1);  //实际也是两个参数,一个是隐含的this指针
	st_cpp_int.Push(2);
	st_cpp_int.Push(3);
	st_cpp_int.Push(4);

	//存double类型的栈
	Stack_CPP<double> st_cpp_double;
	st_cpp_double.Push(1.1);
	st_cpp_double.Push(2.2);
	st_cpp_double.Push(3.3);
}

类模板的实例化

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

//vector 类名, vector<int>才是类型
vector<int> s1;
vector<double> s2; 

我们现在来实现一个顺序表(动态增长的数组),C语言中的顺序表用SeqList表示,而C++喜欢用vector表示。

#include <iostream>
#include <assert.h>
// C语言中的顺序表SeqLIst,C++中的顺序表喜欢用 vector这个名字
template<class T>
class vector  //动态增长的数组
{
public:
	vector() 
		:_a(nullptr)
		,_size(0)
		,_capacity(0)
	{
	}
	//一般不给空间,不管给不给空间插入都需要判断是否需要增容
	vector(size_t n)  //重载构造函数,一般不写这个,因为通常不给空间
		:_a(new T[n])
		, _size(0)
		, _capacity(n)
	{
	}
	~vector()
	{
		delete[] _a;
		_a = nullptr;
		_size = _capacity = 0;
	}
	//类里面声明,类外面定义
	void push_back(const T& x); //如果传的不是内置类型就会引发无穷递归拷贝构造,所以使用引用,减少拷贝提高效率
	void pop_back();

	size_t size() 
	{
		return _size; //返回个数
	}
	T operator[](size_t i)   //重载 运算符[]:运算符的重载是为了让内置类型能够像内置类型一样使用该运算符
	{
		assert(i < _size);
		return _a[i];
	}
private:
	T* _a;
	int _size;
	int _capacity;
};

//类外面定义,每次都要写一个template<class T>,外面的定义已经和类分离了,脱离了这个类,所以必须再写一个
template<class T>
void vector<T>::push_back(const T& x) 
{
	if (_size == _capacity) 
	{
		size_t newcapacity = _capacity == 0 ? 2 : _capacity * 2;
		T* tmp = new T[newcapacity];
		if (_a != nullptr) 
		{
			memcpy(tmp, _a, sizeof(T) * _size);
			delete[] _a; //自动调用析构,自动置nullptr
		}
		_a = tmp;
		_capacity = newcapacity;
	}
	_a[_size] = x;
	++_size;
}
int main() 
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	
	for (size_t i = 0; i < v.size(); ++i) 
	{
		//[] 读
		cout << v[i] << " ";
	}
	cout << endl;

    //可以修改吗??
    for (size_t i = 0; i < v.size(); ++i)
	{
		//[] 写
		//v[i] *= 2;  //返回值是临时的,具有常性,不能修改
		cout << v[i] << " ";
	}
	return 0;
}

我们只知道传值返回和传引用返回的区别,传值返回会在先把这个变量赋值给临时的变量,而临时变量具有常性,所以不能够修改其中的值。

如果想变成可写的(可修改的),可以使用引用返回,引用就是它的别名,可以修改。


模板的总结

泛型编程: 使用模板,编写跟类型无关的代码。

在使用一些函数和类的时候,针对不同类型需要写很多重复的代码。

函数:比如我们想实现交换int、double、char等等各种类型对象函数swap

类:比如我们想实现一个数据结构栈stack,stack的多个对象 st1 存int,st2 存double,等等。

在没有模板之前,我们得针对各个类型写很多个swap函数 和 stack类。而这些函数和类,逻辑是一样的,只是处理的对象类型不同。

使用模板,我们就可以编写一套通用的代码,无需为每种类型重复编写相同逻辑的函数和类,大大提高了代码的复用性和可维护性。

学到这里, 可以说我们C++入门了, 因为C++的基础语法铺垫已经完成, 接下来才是正式学习C++的开始。加油 ~

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

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

相关文章

OMS 2.0至3.0升级项目成功案例:木九十

作为眼镜行业的标杆品牌木九十&#xff0c;近期成功完成了OMS系统从2.0版本到3.0版本的全面升级。此次升级旨在提升全渠道库存管理能力&#xff0c;优化与SAP系统的无缝对接&#xff0c;实现与WMS系统的全面集成&#xff0c;并改进加工业务、维修单、特权订单和小程序服务。通过…

Ubuntu 无法进行SSH连接,开启22端口

我们在VM中安装好Ubuntu 虚拟机后&#xff0c;经常需要使用Xshell等工具进行远程连接&#xff0c;但是会出现无法连接的问题&#xff0c;原因是Ubuntu中默认关闭了SSH 服务。 1、 查看Ubuntu虚拟机IP地址 2、 利用Tabby等工具进行远程连接 命令&#xff1a;ssh ip地址 这里就是…

Java包

目录 1.包基本介绍 应用场景 包的三大作用 包基本语法 2.包原理 包的本质分析 3.包快速入门 4.包的命名 命名规则 命名规范 5.常用的包 6.包的使用细节 如何引入包 注意事项和使用细节 1.包基本介绍 应用场景 包的三大作用 区分相同名字的类&#xff0c;类…

浏览器用户文件夹详解 - Extensions(十二)

1.Extensions 简洁 1.1 什么是Extensions Extensions是Chromium浏览器中用于存储用户安装的扩展程序的一个重要目录。每当用户从Chrome Web Store或其他来源安装扩展程序时&#xff0c;这些扩展程序的文件都会被下载并存储在这个中。通过管理Extensions&#xff0c;用户和开发…

【时时三省】Code::Blocks 17.12 软件的使用----创建c工程

目录 1&#xff0c;软件下载 2&#xff0c;软件安装 3&#xff0c;软件下载 4&#xff0c;创建工程 5&#xff0c;编译运行 6&#xff0c;调试代码 一&#xff1a;第一种场景调试&#xff1a; 二&#xff1a;第二种场景调试&#xff1a; 三&#xff1a;第三种场景调试 …

哪个牌子手持洗拖一机好?多款热门家用洗地机推荐

以前打扫卫生&#xff0c;每次拖地前都要先扫地&#xff0c;然后再用拖把拧水&#xff0c;拖完还要清洗拖把&#xff0c;整个过程既费时又费力&#xff0c;还容易弄脏手&#xff0c;更重要的是还会出现清洁不干净的情况。而洗地机作为一种集吸尘、拖地、洗地于一体的智能清洁设…

2_stm32定时中断点灯

定时器是个好东西啊~ 之前搞上层应用时&#xff0c;通过定时器可以以某种频率刷新状态&#xff0c;stm32定时器的一种功能就是如此。此外&#xff0c;stm32的定时器还有很多其他功能&#xff0c;如PWM输出等。定时器具体再细分可以分为高级控制定时器、通用定时器、基本定时器等…

stm32应用、项目、调试

主要记录实际使用中的一些注意点。 1.LCD1602 电路图&#xff1a; 看手册&#xff1a;电源和背光可以使用5v或者3.3v&#xff0c;数据和控制引脚直接和单片机引脚连接即可。 单片机型号&#xff1a;stm32c031c6t6 可以直接使用推完输出连接D0--D7,RS,EN,RW引脚&#xff0c;3…

uni-app可替换radio-group的控件uni-segmented-control(十九)

【前言】 以前写过一篇对radio-group中的元素进行分列展示的文章,有兴趣的朋友可以看以下uni-app将radio-group元素分列展示(七):专栏管理-CSDN创作中心https://mp.csdn.net/mp_blog/manage/column/columnManage/12711831当时主要是因为radio-group中的元素过多,如果利用手…

600道大模型面试题,看完它手撕面试官,非常详细收藏我这一篇就够了

大模型面试题及答案 什么是大模型&#xff1f; 答&#xff1a; 大模型通常指的是那些拥有大量参数&#xff08;例如数十亿甚至更多&#xff09;的人工智能模型&#xff0c;这些模型经过大规模数据集的训练&#xff0c;能够处理复杂的任务。大模型的一个重要特点是它们往往能够捕…

977. 有序数组的平方(双指针)

目录 一题目: 二&#xff1a;代码&#xff1a; 三&#xff1a;结果&#xff1a; 一题目: 给你一个按 非递减顺序 排序的整数数组 nums&#xff0c;返回 每个数字的平方 组成的新数组&#xff0c;要求也按 非递减顺序 排序。 二&#xff1a;代码&#xff1a; class Solution…

汽车动态:小米汽车对开门车型热议:技术储备与量产计划的辨析

小米汽车&#xff0c;作为科技巨头小米集团进军汽车行业的新尝试&#xff0c;自宣布以来就备受市场关注。近日&#xff0c;有关小米汽车是否会推出对开门车型的问题引起了广泛讨论。小米汽车对此作出回应&#xff0c;明确表示技术储备并不直接代表一定会进行量产。 首先&#…

【Material-UI】按钮与第三方路由库的集成详解

文章目录 一、ButtonBase 组件简介二、与第三方路由库的集成1. React Router示例代码 2. Next.js示例代码 三、客户端导航的优势四、其他自定义集成1. 使用自定义组件示例代码 五、总结 在现代前端开发中&#xff0c;单页应用&#xff08;SPA&#xff09;变得越来越普遍。这种应…

Vision Pro使用GLFT 加载模型shader错误解决办法

Glft shader在vision pro上加载错误 前言相关背景解决办法 参考文章 前言 之前在Vision Pro上尝试加载Glb文件&#xff0c;但是加载完成后发现加载出来的Glb文件材质不正确。材质是黑色的。因此整理一下解决方案。 相关背景 使用Unity开发&#xff0c;Glb的加载插件为gltf F…

SQL二次注入

目录 1.什么是二次注入&#xff1f; 2.二次注入过程 2.1寻找注入点 2.2注册admin#用户 2.3修改密码 1.什么是二次注入&#xff1f; 当用户提交的恶意数据被存入数据库后&#xff0c;因为被过滤函数过滤掉了&#xff0c;所以无法生效&#xff0c;但应用程序在从数据库中拿…

动手学深度学习V2每日笔记(深度卷积神经网络AlexNet)

本文主要参考沐神的视频教程 https://www.bilibili.com/video/BV1h54y1L7oe/spm_id_from333.788.recommend_more_video.0&vd_sourcec7bfc6ce0ea0cbe43aa288ba2713e56d 文档教程 https://zh-v2.d2l.ai/ 本文的主要内容对沐神提供的代码中个人不太理解的内容进行笔记记录&…

COCO格式json转yolo唯一官方指定转换方法

自带转换程序&#xff0c;谁还在那自己写呢&#xff1f; https://docs.ultralytics.com/reference/data/converter/?hconvert_coco#ultralytics.data.converter.convert_coco """ 这个脚本用于将coco数据集格式转换为yolo数据集格式 """ from …

【最新版】Windows10纯净专业版下载:无捆绑软件!

今天系统之家小编给大家带来2024年最新的Windows10纯净专业版系统&#xff0c;经过精心地优化&#xff0c;确保系统无捆绑软件&#xff0c;系统资源占用少&#xff0c;是非常干净的专业版系统&#xff0c;且兼容性强&#xff0c;配置不高的老电脑也适合安装&#xff0c;安装后运…

linux 查看一个端口是否被占用

1 linux命令 要在Linux中查看一个端口是否被占用&#xff0c;可以按照以下步骤进行操作&#xff1a; 打开终端&#xff08;Terminal&#xff09;。 运行以下命令来列出系统上所有正在监听的端口及其对应的进程&#xff1a; sudo netstat -tuln | grep LISTEN这将显示所有正在…

【LeetCode每日一题】2024年8月第二周(上)

2024.8.5 困难 链接&#xff1a;600. 不含连续1的非负整数 &#xff08;1&#xff09;题目描述&#xff1a; &#xff08;2&#xff09;示例 &#xff08;3&#xff09;分析 思路1&#xff1a; 题目要求的数值&#xff0c;是将数二进制转换后&#xff0c;不存在连续的1&#x…