C++模板从入门到入土

2024-02-22 17:52
文章标签 模板 c++ 入门 入土

本文主要是介绍C++模板从入门到入土,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1. 泛型编程  

如果我们需要实现一个不同类型的交换函数,如果是学的C语言,你要交换哪些类型,不同的类型就需要重新写一个来实现,所以这是很麻烦的,虽然可以cv一下,有了模板就可以减轻负担。

下面写一个适合所有类型的交换就可以这样写。

template<typename T>
void Swap(T& left, T& right)
{T temp = left;left = right;right = temp;
}
int main()
{int a1 = 10, a2 = 20;double d1 = 1.0, d2 = 2.2;swap(a1, a2);swap(d1, d2);return 0;
}

让我们先从文字上来了解什么是泛型编程,泛型指的是广泛类型的意思。

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

问题:我们其实如果用函数重载也能解决问题,但是为什么我们还是有模板这个东西呢?

1.重载的只是函数类型不同,代码相同的部分很多,代码复用率很高。
2.如果有一行代码是有问题的话,这些重载的代码都是需要修改的
那我们就可以给编译器一个例子,然后让编译器自己去生成,就像古代的磨具一样,我们再磨具上印出东西,然后就拿的这个磨具去印出相同的东西,这不是很方便的东西。

函数模板

1.函数模板的概念

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

2.函数模板的格式

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

我们可以拿Swap这个例子来模拟

template<typename T>
void Swap( T& left, T& right)
{T temp = left;left = right;right = temp;
}

1.template是关键字

2.typename是修饰后面T的关键字,也有class这个关键字,class这个关键字比较短,所以我们用这个比较多。
3.T1, T2, ..., Tn 表示的是函数名,可以理解为模板的名字,名字你可以自己取。

注意事项:函数模板不是一个函数,而是我们的编译器拿的这个函数模板去实例化出一个一个的函数来的,我们可以理解为函数的模板

函数模板的原理

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

 

#include<iostream>using namespace std;
template<class T>
void Swap(T& x, T& y)
{T tmp(x);x = y;y = tmp;
}
int main()
{int x = 1;int  y = 2;double d1 = 2.2;double d2 = 3.3;cout << "交换前->" << x << " " << y << endl;cout << "交换前->" << d1 << " " << d2 << endl;Swap(x, y);Swap(d1, d2);cout << "交换后->" << x << " " << y << endl;cout << "交换后->" << d1 << " " << d2 << endl;return 0;
}

我们可以看到的是我们的数据也是成功的进行交换了,那我们来想想他的原理是什么呢,首先编译器是会根据函数模板生成不同的函数,他们的类型是不同的。而且他们的函数栈帧不是同一个。

 编译器是会根据这个函数模板去实例化不同的函数出来,所以在函数栈帧上调用的不是同一个函数栈帧,我们也可以来看汇编代码,看看call的地址是不是同一个地址。

所以可以看出我们不是调用的用一个函数。

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

那我们下面就来探讨编译器是怎么进行实例化的。

 

函数模板的实例化

 其实过程是很简单的,我们在编译阶段的时候,告诉我们的函数模板你要去根据类型进行实例化,然后因为T是函数模板的参数,所以如果我们传int过去的时候他就知道T是int,所以的T改成int去实例化出一个函数出来。

但是函数模板在实例化的过程中也是会出现问题的,比如我们可以来下面的这种情况,我们下一个简单的Add函数模板,然后在main函数里面进行相加计算出结果,我们可以来看看如果不是同一个类型的化会出现怎样的问题。

#include<iostream>using namespace std;
template<class T>
T Add(const T& x, const T& y)
{return x + y;
}int main()
{int x = 1;int  y = 2;double d1 = 2.2;double d2 = 3.3;int ret1 = Add(x, y);double ret2 = Add(d1, d2);cout << ret1 << " " << ret2;return 0;
}

首先这样的代码是没有问题的,但是如果我们是x+d1呢,我们来看看他的报错信息。、

 

如果是这样写的化报错信息就是这个样子的,所以我们需要怎么进行修改才行呢。

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

没错,我们是需要进行显示实例化的,但是我们应该如何进行显示实例化呢,规则很简单。

上面的Add就可以写成。

Add<int>(x,d1);

 我们的代码是可以运行的,但是会有这样的警告,其实是可以忽略的,因为我们本生就是不同类型的相加,肯定会产生强转的。

对于模板函数的使用,编译器需要根据传入的实参类型来推演,生成对应类型的函数以供调用。但是我们可以显示的去实例化,规则和Add是一样的道理。 

像第一个 Add<int>(a1, a2)  ,a2 是 double,它就要转换成 int 。

第二个 Add<double>(a1, a2),a1 是 int,它就要转换成 double。

这种地方就是类型不匹配的情况,编译器会尝试进行隐式类型转换。

像 double 和 int 这种相近的类型,是完全可以通过隐式类型转换的。

🔺 总结:

  • 函数模板你可以让它自己去推,但是推的时候不能自相矛盾。
  • 你也可以选择去显式实例化,去指定具体的类型。

 模板参数的匹配原则

场景:我们会写一个关于Add的函数模板和实现一个Add的函数,类型是int那他到底会配对那个呢。

#include<iostream>using namespace std;
template<class T>
T Add(const T& x, const T& y)
{return x + y;
}
int Add(int x, int y)
{return x + y;
}
int main()
{int x = 1;int  y = 2;double d1 = 2.2;double d2 = 3.3;int ret1 = Add(x, y);double ret2 = Add(d1, d2);Add<int>(x, d1);cout << ret1 << " " << ret2;return 0;
}

就是像这样的场景,那我们如果函数是Add(int ,int)的时候是调用哪个呢。

规则:有现成的就用现成的呗,我们函数模板进行实例化是要根据类型去实例化的,但是我们已经有一个关于它的函数了,这个函数是最适合你的,你还要去生成一个,都多余了。

 

所以我们就不会再去麻烦编译器去再生成一个函数来实现了。

总结:

① 一个非模板函数可以和一个同名的模板函数同时存在,

而且该函数模板还可以被实例化为这个非模板函数:

② 对于非模板函数和同名函数模板,如果其他条件都相同,

在调用时会优先调用非模板函数,而不会从该模板生成一个实例。

如果模板可以产生一个具有更好匹配的函数,那么将选择模板。

3. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换

 

第三点解释一下,就是我们再根据模板生成的时候,只会根据你给的类型去生成,而不存在强转这些,除非是隐式类型转换,隐式类型转换是会存在强转的可能性的。

类模板

1 类模板的定义格式

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

 规则其实和函数模板是差不多的。

1.template是关键字

2.typename是修饰后面T的关键字,也有class这个关键字,class这个关键字比较短,所以我们用这个比较多。
3.T1, T2, ..., Tn 表示的是函数名,可以理解为模板的名字,名字你可以自己取。

这里要强调一下函数模板和类模板都是不支持分离声明和定义的,你可以再同一个文件里,但是不能在不同的文件进行声明和定义(指的是在一个.h进行声明,在一个.cpp进行定义)这个情况是会我们程序进行链接的时候出现找不到这个地址的现象,因为我们的模板函数是不知我们要实例化的类型是什么,所以就会出现最后链接的时候Call(没地址),所以就会链接错误,后面会深入的讲解。

继续回归我们对类模板的认识,首先是引出问题,我们没有类模板的栈是怎么写的。来看看吧。

class Stack {
public:Stack(int capacity = 4) : _top(0) , _capacity(capacity) {_arr = new int[capacity];}~Stack() {delete[] _arr;_arr = nullptr;_capacity = _top = 0;}
private:int* _arr;int _top;int _capacity;
};

这个栈是只能来存int,有人就会说,如果我们对int进行typedef不就行了,如果我想要其他类型的时候就只需要改类型就行了,但是这样就有了第二个问题,那就是如果我们需要的是一个存放int的栈,一个存放的是node* 节点的栈,或者一个日期的时候,那问题就很大了,每当我们需要这个类型的时候就需要ctrl c + v然后改一下类型这个操作其实很简单,也很快,但是最终结果就是造成代码相同的还是很多,这样和我们之前的函数模板是一样的问题,所以就有了我们的类模板,那我们来改造一下上面的代码吧。

template<class T>
class Stack {
public:Stack(int capacity = 4) : _top(0) , _capacity(capacity) {_arr = new T[capacity];}~Stack() {delete[] _arr;_arr = nullptr;_capacity = _top = 0;}
private:T* _arr;int _top;int _capacity;
};int main(void)
{Stack<int> st1;   // 存储intStack<double> st2;   // 存储doublereturn 0;
}

这样就可以解决了我们要存放int和double或者其他类型的问题了。

但是我们发现,类模板他好像不支持自动推出类型,

 它不像函数模板,不指定它也可以根据传入的实参去推出对应的类型的函数以供调用

这就是为什么我们需要在类模板后面根生类型,这里大家就要记住的是类模板必须要显示实例化的方法写,它不能像函数模板一样去推演类型。

类模板实例化

模板实例化在类模板名字后跟 < >,然后将实例化的类型放在 < > 中即可。

注意事项:

① Stack 不是具体的类,是编译器根据被实例化的类型生成具体类的模具。

② Stack 是类名,Stack<int> 才是类型:

我们上面说过类模板不能在两个文件里声明和定义分离,但是没说不能在同一个文件了,但是在同一个文件里有些讲究,我们得来探究一下。

就继续拿我们栈来说话。

#include<iostream>using namespace std;
template<class T>
class Stack {
public:Stack(int capacity = 4): _top(0), _capacity(capacity) {_arr = new T[capacity];}// 这里我们让析构函数放在类外定义void Push(const T& x);~Stack();
private:T* _arr;int _top;int _capacity;
};/* 类外 */void Stack::Push(const T& x) {//::::
}

 如果我们是这样写的化就是会存在一些小的问题,编译器是不认识外面的这个T,那我们要改的话是需要在下面函数上加上模板的参数的,

 

template<class T>
class Stack {
public:Stack(T capacity = 4): _top(0), _capacity(capacity) {_arr = new T[capacity];}// 这里我们让析构函数放在类外定义void Push(const T& x);~Stack();
private:T* _arr;int _top;int _capacity;
};/* 类外 */
template<class T>
void Stack<T>::Push(const T& x) {//::::
}

虽然是能编译通过,但是链接的时候又是会存在问题的,所以我的建议就是大家声明和定义都放在类模板里,多一事不如少一事。

对于这个需要记住的是----------> Stack 是类名,不是类型,Stack<T> 才是类型! 

初阶模板就分享到这里了,我们后面还有进阶的模板,今天的分享就到这里了,下次再见了~

 

这篇关于C++模板从入门到入土的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/736039

相关文章

C++中实现调试日志输出

《C++中实现调试日志输出》在C++编程中,调试日志对于定位问题和优化代码至关重要,本文将介绍几种常用的调试日志输出方法,并教你如何在日志中添加时间戳,希望对大家有所帮助... 目录1. 使用 #ifdef _DEBUG 宏2. 加入时间戳:精确到毫秒3.Windows 和 MFC 中的调试日志方法MFC

基于Java实现模板填充Word

《基于Java实现模板填充Word》这篇文章主要为大家详细介绍了如何用Java实现按产品经理提供的Word模板填充数据,并以word或pdf形式导出,有需要的小伙伴可以参考一下... Java实现按模板填充wor编程d本文讲解的需求是:我们需要把数据库中的某些数据按照 产品经理提供的 word模板,把数据

深入理解C++ 空类大小

《深入理解C++空类大小》本文主要介绍了C++空类大小,规定空类大小为1字节,主要是为了保证对象的唯一性和可区分性,满足数组元素地址连续的要求,下面就来了解一下... 目录1. 保证对象的唯一性和可区分性2. 满足数组元素地址连续的要求3. 与C++的对象模型和内存管理机制相适配查看类对象内存在C++中,规

在 VSCode 中配置 C++ 开发环境的详细教程

《在VSCode中配置C++开发环境的详细教程》本文详细介绍了如何在VisualStudioCode(VSCode)中配置C++开发环境,包括安装必要的工具、配置编译器、设置调试环境等步骤,通... 目录如何在 VSCode 中配置 C++ 开发环境:详细教程1. 什么是 VSCode?2. 安装 VSCo

C++11的函数包装器std::function使用示例

《C++11的函数包装器std::function使用示例》C++11引入的std::function是最常用的函数包装器,它可以存储任何可调用对象并提供统一的调用接口,以下是关于函数包装器的详细讲解... 目录一、std::function 的基本用法1. 基本语法二、如何使用 std::function

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

poj3468(线段树成段更新模板题)

题意:包括两个操作:1、将[a.b]上的数字加上v;2、查询区间[a,b]上的和 下面的介绍是下解题思路: 首先介绍  lazy-tag思想:用一个变量记录每一个线段树节点的变化值,当这部分线段的一致性被破坏我们就将这个变化值传递给子区间,大大增加了线段树的效率。 比如现在需要对[a,b]区间值进行加c操作,那么就从根节点[1,n]开始调用update函数进行操作,如果刚好执行到一个子节点,

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�