【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;
}
使用函数重载虽然可以实现,但是有几个不好的地方
  1. 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数。
  2. 代码的可维护性比较低,一个出错可能所有的重载均出错。
在 C++ 中,存在这样一个模具,通过给这个模具中填充不同材料(类型),来获得不同材料的铸件( 即生成具体类型的代码),这会给我们节省很多时间。
泛型编程 :编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。


二、函数模板

1、函数模板概念

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

2、函数模板格式

template<typename T1, typename T2, ......, typename Tn>
返回值类型 函数名(参数列表){}
template<typename T> // T代表一个模板类型(虚拟类型)void Swap(T& left, T& right)
{T temp = left;left = right;right = temp;
}

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


3、函数模板的原理

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

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

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;Add(a1, a2);Add(d1, d2);Add(a1, d1); // 无法通过编译// 1、处理方式:用户自己来强制转化Add(a, (int)d);return 0;
    }

    无法通过编译原因:因为在编译期间,当编译器看到该实例化时,需要推演其实参类型。通过实参 a1 将 T 推演为 int,通过实参 d1 将 T 推演为 double 类型,但模板参数列表中只有一个 T,编译器无法确定此处到底该将 T 确定为 int 或者 double 类型而报错。

    注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅。

  2. 显式实例化:在函数名后的 <> 中指定模板参数的实际类型
    int main()
    {int a = 10;double b = 20.0;// 2、处理方式:显式实例化Add<int>(a, b);return 0;
    }// 如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。

5、模板参数的匹配原则

  1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。
    // 专门处理int的加法函数
    int Add(int left, int right)
    {return left + right;
    }// 通用加法函数
    template<class T>
    T Add(T left, T right)
    {return left + right;
    }int Test()
    {cout << Add(1, 2) << endl; // 与非模板函数匹配,编译器不需要特化cout << Add<int>(1.1, 2.2) << endl;; // 调用编译器特化的Add版本return 0;
    }
  2. 对于非模板函数同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板。
    // 专门处理int的加法函数
    int Add(int left, int right)
    {return left + right;
    }// 通用加法函数
    template<class T1, class T2>
    T1 Add(T1 left, T2 right)
    {return left + right;
    }int Test()
    {Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数return 0;
    }
  3. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。

三、类模板

1、类模板的定义格式

template<class T1, class T2, ..., class Tn>
class 类模板名
{// 类内成员定义
};
typedef int STDataType;
class Stack
{
private:STDataType* _a;int _top;int _capacity;
};int main()
{Stack st1; //intStack st2; //doublereturn 0;
}

这里想让 st1 是 int,st2 是 double,显然无法做到。C 语言中的 typedef 只是增强程序的可维护性,不能解决泛型的问题。

template<class T>
class Vector
{ 
public :Stack(size_t capacity = 0){if (capacity > 0){_a = new T[capacity];_capacity = capacity;_top = 0;}}~Stack(){delete[] _a;_a = nullptr;_capacity = _top = 0;}void Push(const T& x); // 在类里面声明,类外面定义void Pop(){assert(_top > 0);--_top;}bool Empty(){return _top == 0;}const T& Top(){assert(_top > 0);return _a[_top - 1];}
private:T* _a = nullptr;size_t _top = 0;size_t _capacity = 0;
};// 注意:类模板中函数放在类外进行定义时,需要加模板参数列表
template<class T>
void Stack<T>::Push(const T& x)
{if (_top == _capacity){size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2; // 开新空间						T* tmp = new T[newCapacity];if (_a){memcpy(tmp, _a, sizeof(T)*_top); // 拷贝数据delete[] _a; // 释放旧空间}_a = tmp;_capacity = newCapacity;}_a[_top] = x;++_top;
}

注意:函数 / 类模板不支持分离编译,比如:声明放在 .h ,定义放在 .cpp。 在 .h 里实例化了,Stack.cpp 里没有实例化,而 test.cpp 去找的时候,只有声明,没有定义,会报链接错误如果声明和定义分离,需要将模板写在同一个文件里。


2、类模板的实例化

类模板实例化与函数模板实例化不同, 类模板实例化 需要在 类模板名字后跟 <> ,然后 实例化的类型放在 <> 中 即可,类模板名字不是真正的类,而实例化的结果才是真正的类
Vector<int> s1;
Vector<double> s2;
// 注意:Vector不是具体的类,是编译器根据被实例化的类型生成具体类的模具

Vector 是类名,Vector<int> 才是类型。

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

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

相关文章

高層建築設計和建造:從避難層到設備間和防風防火防水的設計理念,酒店住宅辦公樓都有什麽房間(精簡)

樓層概覽 標準層居住、辦公、商業等功能的樓層。結構和裝修與其他樓層相同&#xff0c;可供人正常居住、工作和活動避難層專門用於人員避難的樓層&#xff0c;通常會相隔數十個標準層&#xff0c;樓梯通常和標準層是錯開的(非公用)&#xff0c;具有更多的通風口。牆體和樓板具…

【Linux】TCP的服务端 + 客户端

文章目录 &#x1f4d6; 前言1. 服务端基本结构1.1 类成员变量&#xff1a;1.2 头文件1.3 初始化&#xff1a;1.3 - 1 全双工与半双工1.3 - 2 inet_aton1.3 - 3 listen 2. 服务端运行接口2.1 accept&#xff1a;2.2 服务接口&#xff1a; 3. 客户端3.1 connect&#xff1a;3.2 …

linux 笔记 安装 anaconda

1 找到anaconda 安装包 Free Download | Anaconda 2 在linux环境中安装对应安装包 3 安装完毕后查看是否安装好 发现不行&#xff0c;需要配置环境变量 4 配置环境变量 vim /etc/profile使用这个&#xff0c;发现对应的文件是只读文件 sudo vim /etc/profile在最下面加一…

【数据结构与算法】- 数组

数组 1.1 数组的定义1.2 数组的创建1.3 数组在内存中的情况2.1 初始化数组2.2 插入元素2.3 删除元素2.4 读取元素2.5 遍历数组 1.1 数组的定义 数组中的是在内存中是连续存储的&#xff0c;内存是由一个个内存单元组成的&#xff0c;每一个内存单元都有自己的地址&#xff0c;…

【iptables 实战】05 iptables设置网络转发实验

一、网络架构 实验效果&#xff0c;通过机器B的转发功能&#xff0c;将机器A的报文转发到机器C 本实验准备三台机器分别配置如下网络 机器A ip:192.168.56.104 机器C ip:10.1.0.10 机器B 两张网卡&#xff0c;分别的ip是192.168.56.106和10.1.0.11 如图所示 如下图所示 二、…

PPT/PS——设置图片的背景为透明色的方法

方法一 将图片放到PPT内&#xff0c;点击图片&#xff0c;点击格式&#xff0c;删除背景 方法二 PS小白 扣去背景流程PS怎么把背景色改成透明色呢&#xff1f;

npm ,yarn 更换使用国内镜像源,淘宝源

背景 文章首发地址 在平时开发当中&#xff0c;我们经常会使用 Npm&#xff0c;yarn 来构建 web 项目。但是npm默认的源的服务器是在国外的&#xff0c;如果没有梯子的话。下载速度会特别慢。那有没有方法解决呢&#xff1f; 其实是有的&#xff0c;设置国内镜像即可&#x…

c++学习之优先级队列

目录 1.初识优先级队列 库中的实现 使用优先级队列 2.优先级队列的实现 3.仿函数 利用仿函数实现的优先级队列 迭代器区间构造&#xff08;建堆&#xff09; 1.初识优先级队列 如果我们给每个元素都分配一个数字来标记其优先级&#xff0c;不妨设较小的数字具有较…

怎么通过portainer部署一个vue项目

这篇文章分享一下今天通过docker打包vue项目&#xff0c;并使用打包的镜像在portainer上部署运行&#xff0c;参考了vue-cli和docker的官方文档。 首先&#xff0c;阅读vue-cli关于docker部署的说明 vue-cli关于docker部署的说明https://cli.vuejs.org/guide/deployment.html#…

网络层常见协议——IPV4、IPV6、ARP、ICMP、QoS

目录 1、IPV4 协议 IPV4 地址的组成&#xff1a; IPV4地址的分类&#xff1a; 关于多播和组播&#xff1a; 常见组播地址分类&#xff1a; 特殊的 IPV4 地址&#xff1a; 私有地址和公有地址&#xff1a; 私有地址的范围&#xff1a; 子网划分&#xff1a; 子网掩码&…

操作系统--分页存储管理

一、概念介绍 分页存储&#xff1a;一是分内存地址&#xff0c;二是分逻辑地址。 1.分内存地址 将内存空间分为一个个大小相等的分区。比如&#xff0c;每个分区4KB。 每个分区就是一个“页框”&#xff0c;每个页框有个编号&#xff0c;即“页框号”&#xff0c;“页框号”…

【统计学】Top-down自上而下的角度模型召回率recall,精确率precision,特异性specificity,模型评价

最近在学 logistic regression model&#xff0c;又遇见了几个之前的老面孔。 召回率recall, 精确率precision&#xff0c;特异性spcificity&#xff0c;准确率accuracy&#xff0c;True positive rate&#xff0c;false positive rate等等名词在学习之初遇到的困难在于&#x…

【Java每日一题】— —第十八题:求二维数组中的元素最小值及其索引。(2023.10.02)

&#x1f578;️Hollow&#xff0c;各位小伙伴&#xff0c;今天我们要做的是第十八题。 &#x1f3af;问题&#xff1a; 求二维数组中的元素最小值及其索引。 测试结果如下&#xff1a; &#x1f3af; 答案&#xff1a; int [][]anew int[3][];a[0]new int [3];a[1]new int[5…

全国排名前三的直播公司无锋科技入驻天府蜂巢成都直播产业基地

最近&#xff0c;全国排名前三的直播公司——无锋科技&#xff0c;正式宣布入驻位于成都的天府蜂巢直播产业基地&#xff0c;这一消息引起了业内人士的高度关注。成都直播产业基地一直是中国直播产业的重要地标之一&#xff0c;其强大的技术和资源优势为众多直播公司提供了广阔…

postgresql-管理表空间

postgresql-管理表空间 基本概念创建表空间用户授权移动表空间 修改表空间移动表空间位置 删除表空间 基本概念 在 PostgreSQL 中&#xff0c;表空间&#xff08;tablespace&#xff09;表示数据文件的存放目录&#xff0c;这些数据文件代表了数 据库的对象&#xff0c;例如表…

Oracle SQL Developer 中查看表的数据和字段属性、录入数据

在Oracle SQL Developer中&#xff0c;选中一个表时&#xff0c;右侧会列出表的情况&#xff1b;第一个tab是字段的名称、数据类型等属性&#xff1b; 切换到第二个tab&#xff0c;显示表的数据&#xff1b; 这和sql server management studio不一样的&#xff1b; 看一下部门…

常用的分布式ID解决方案原理解析

目录 前言 一&#xff1a;分布式ID的使用场景 二&#xff1a;分布式ID设计的技术指标 三&#xff1a;常见的分布式ID生成策略 3.1 UUID 3.2 数据库生成 3.3 数据库的多主模式 3.4 号段模式 3.5 雪花算法 前言 分布式ID的生成是分布式系统中非常核心的基础性模块&#…

【C语言】宏定义

&#x1f6a9; WRITE IN FRONT&#x1f6a9; &#x1f50e; 介绍&#xff1a;"謓泽"正在路上朝着"攻城狮"方向"前进四"&#x1f50e;&#x1f3c5; 荣誉&#xff1a;2021|2022年度博客之星物联网与嵌入式开发TOP5|TOP4、2021|2222年获评百大博…

Docker下如何构建包含延迟插件的RabbitMQ镜像

&#x1f468;&#x1f3fb;‍&#x1f4bb; 热爱摄影的程序员 &#x1f468;&#x1f3fb;‍&#x1f3a8; 喜欢编码的设计师 &#x1f9d5;&#x1f3fb; 擅长设计的剪辑师 &#x1f9d1;&#x1f3fb;‍&#x1f3eb; 一位高冷无情的编码爱好者 大家好&#xff0c;我是 DevO…

OpenGLES:绘制一个彩色、旋转的3D圆柱

一.概述 上一篇博文讲解了怎么绘制一个彩色旋转的立方体 这一篇讲解怎么绘制一个彩色旋转的圆柱 圆柱的顶点创建主要基于2D圆进行扩展&#xff0c;与立方体没有相似之处 圆柱绘制的关键点就是将圆柱拆解成&#xff1a;两个Z坐标不为0的圆 一个长方形的圆柱面 绘制2D圆的…