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

C++模板的使用

在平时的工作和学习过程中,经常会用到泛型,这里对泛型和模板进行一下梳理,以便理解和使用。

模板关键字

	template。

为什么要使用模板?

假如设计一个两个参数的函数,用来求两个对象的乘积,在实践中我们可能需要定义n多个函数

int multiplication(int a,int b){return a+b;}
char multiplication(char a,char b){return a+b;}
float multiplication(float a,float b){return a+b;}
...

这些函数几乎相同,唯一的区别就是形参类型不同,在这种情况下,不必定义多个函数,只需要在模板中定义一次即可。
在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现不同的函数功能。

模板与泛型

模板是泛型编程的基础,泛型编程是指独立与任何类型的代码编程。
模板是一种对类型进行参数化的工具,有两种形式:函数模板和类模板。

函数模板

函数模板 针对仅参数类型不同的函数
定义格式

template<typename Type>
Type funName(Type val)
{//Code
}

模板形参表使用typename或者class定义都可以,没有任何区别,为了区分类的定义,一般使用typename.
//对于任意类型的两个对象相加,的函数模板

template<typename T>
T multiplication(T a,T b)
{return a * b;
}

函数模板调用
对于函数模板,有两种调用方式
显示类型调用 需要在函数调用处的函数名后面加上类型参数
multiplication(2,4);
自动类型推导 根据参数的类型进行推导,但是两个参数的类型必须一致,否则会报错。
multiplication(‘a’,‘c’);
那么需要传两个不一样的参数要怎么做呢?写两个模板类型即可:

template <typename T,typename U>
auto multiplication(T a,U b)
{return a * b;
}

函数模板和普通函数

函数模板:不提供隐式类型转换,必须是严格匹配。
普通函数:提供隐式类型转换。

 template<typename T>
void multiplication(T a,T b)
{cout<<"模板函数"<< a * b <<endl;
}
show('A',65);		//“void showSum(T,T)”: 未能从“char”为“T”推导 模板 参数
show<int>('A',65);	//显示指定模板类型后,‘A’可以转换到int

函数模板和普通函数构成重载时,调用规则:

template<typename T>
void multiplication(T a,T b)
{cout<<"模板函数(2)"<<a*b<<endl;
}
template<typename T>
void sum(T a,T b,T c)
{cout<<"模板函数(3)"<<a*b*c<<endl;
}
void sum(int a,int c)
{cout<<"普通函数"<<a*c<<endl;
}
void Test()
{multiplication(1,2);			//当函数模板和普通函数参数都符合时,优先选择普通函数multiplication<>(1,2);			//若显示使用模板函数,则使用<>类型列表multiplication(3.0,4.2);		//如果函数模板产生更好的匹配,则使用函数模板multiplication(5.0,6.5,8.2);	//只有一个合适multiplication('a',12);		//调用普通函数,可以隐式类型转换}

类模板

类模板与函数模板的定义和使用类似。 有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,如下面语句声明了一个类:
//模板的参数类型定义写在类的定义之前,在类里的任意位置都可以使用

template<typename T>
class Factory
{
public:Factory(T val) :_value(val){}void Factory(){cout << _value << endl;}
private:T _value;
};

类模板用于实现类所需数据的类型参数化
类模板在表示如数组、表、图等数据结构显得特别重要,这些数据结构的表示和算法不受所包含的元素类型的影响。

单个类模板语法

定义一个类模板非常简单,重要的是如何去用类模板定义对象

如果没有指定模板的参数列表,编译器是会报错的

Factory d(20);			//error C155: “Factory”: 使用 类 模板 需要 模板 参数列表

指定参数列表只需要在类名的后面加上<类型>即可

Factory<string> d("aaa");
Factory<string> d1(string("bbb"));

实际上,类模板的使用就是将类模板实例化成一个具体的类
类模板不代表一个具体的、实际的类,而代表一类类。

只有那些被调用的成员函数,才会产生这些函数的实例化代码。对于类模板,成员函数只有在被使用的时候才会被实例化。显然,这样可以节省空间和时间;

如果类模板中含有静态成员,那么用来实例化的每种类型,都会实例化这些静态成员。

来个示例

#include<iostream>
#include<string>
using namespace std;template<typename T>
class Object
{
public:Object(int size) :_capacity(size),_size(0),_base(nullptr){if (_capacity == 0)_capacity = 1;_base = new T[_capacity]{T()};}T& operator[](int index){if (index < 0 || index >= _capacity){//return T(); //不能返回临时对象的引用,对于int() 是一个0throw std::out_of_range("Object 越界");	//抛异常是最合适的}return _base[index];}
private:T* _base;int _size;int _capacity;
};
int main()
{Object<int> arr(10);for (size_t i = 0; i < 10; i++){cout << arr[i] << " ";}cout << endl;Object<string> arr1(10);arr1[0] = string("cdef");for (size_t i = 0; i < 10; i++){cout << arr1[i] << " ";}return 0;
}

继承中的类模板——类模板派生普通类

子类从模板类继承的时候,需要让编译器知道,父类的数据类型具体是什么(数据类型的本质:如何分配内存空间)

template<typename T>
class Factory 
{
public:Factory(T val) :_value(val){}void factory(){cout << _value << endl;}
protected:T _value;
};
class A:public Factory<int>	//指定具体类型
{
public:using Factory<int>::Factory;void show(){cout << "A" <<" "<<_value<< endl;}
};

继承中的类模板——类模板派生模板

template<typename T>
class Factory
{
public:Factory(T val) :_value(val){}void factory(){cout << _value << endl;}
protected:T _value;
};
template<typename U>
class A:public Factory<U>
{
public:using Factory<U>::Factory;void show(){cout << "A" <<" "<<_value<< endl;}
};

代码看起来没有问题,但是在子类中使用父类的成员,会提示找不到标识符

void show()
{cout << "A" <<" "<<_value<< endl;		//error C2065: “_value”: 未声明的标识符
}

解决办法
1,通过this指针访问:this->_value
2,通过父类访问: Factory< U >::_value

模板特化

提到特化这个概念,就想到泛化的概念。模板函数的T参数只能传入类类型的参数;特化函数的参数只能传入对应的参数类型。

函数模板特化

假设有一个比较两个对象的模板函数

template<typename T>
int compare(T a, T b)
{cout << "T" << endl;return a == b ? 0 : (a > b ? 1 : -1);
}

对于支持operator== 和 operator>操作的类型, 包括基本的int,float,double等类型,是完全没有问题的,但是它不能用来比较字符串(char*),因为这个函数比较的是串指针,而不是字符串本身。

cout << compare("A", "a") << endl;	//类型是const char*,比较的是地址,需要做特化版本才能比较

特化版本:

template<>	//必须写,不然就是重载函数,而不是函数模板,特化版本了
inline  int compare(const char* str1, const char* str2)
{cout << "const char *" << endl;return strcmp(str1, str2);
}或者//test.h
template<>
int compare(const char* str1, const char* str2);
//test.cpp
template<>
int compare(const char* str1, const char* str2)
{cout << "特化 const char *" << endl;return strcmp(str1, str2);
}

这样,就能正确比较字符串了。

类模板特化

全特化:所有类型模板参数都用具体类型代表,特化版本模板参数列表为空 template<>

template<typename T,typename U>
struct Test
{void show(){cout<<"非特化版本"<<endl;}
};
//全特化版本
template<>
struct Test<int,int>
{void show(){cout<<"int,int特化版本"<<endl;}
};
//特化版本可以有任意多个
template<>
struct Test<double,string>
{void show(){cout<<"double,string特化版本"<<endl;}
};
//测试
int main()
{Test<int,int> t;t.show();					//int,int特化版本Test<double, string> t1;	t1.show();					//double,string特化版本Test<char, char> t2;t2.show();					//非特化版本return 0;
}

局部特化(偏特化):指定一部分模板参数用具体类型代替

从模板参数数量上

//从模板参数数量上
template<typename T,typename U>
struct Test
{void show(){cout<<"非特化版本"<<endl;}
};
//局部特化
template<typename U>
struct Test<double,U>
{void show(){cout<<"非特化版本"<<endl;}
};
//测试
int main()
{Test<char,string> tt;tt.show();				//局部特化版本return 0;
}

从模板参数范围上(int -> int&)

//从模板参数范围上
template<typename T>
struct Test
{void show(){cout<<"非特化版本"<<endl;}
};
//const T
template<typename T>
struct Test<T&>
{void show(){cout<<"T&特化版本"<<endl;}
};
//T*
template<typename T>
struct Test<T*>
{void show(){cout<<"T*特化版本"<<endl;}
};
//测试
int main()
{Test<int> test;test.show();			//非特化版本Test<int*> test1;test1.show();			//T*特化版本Test<int&> test2;test2.show();			//T&特化版本return 0;
}

总结

泛型编程和面向对象编程,都依赖与某种形式的多态。面向对象编程的多态性在运行时应用于存在继承关系的类。在泛型编程中,编写的代码可以用作多种类型的对象。面向对象编程所依赖的多态性称为运行时多态性,而泛型编程所依赖的多态性称为编译时多态性或参数式多态性。
通过上面这些介绍,相信会对模板与泛型理解的更深入。

相关文章:

C++模板的使用

在平时的工作和学习过程中&#xff0c;经常会用到泛型&#xff0c;这里对泛型和模板进行一下梳理&#xff0c;以便理解和使用。 模板关键字 template。为什么要使用模板? 假如设计一个两个参数的函数,用来求两个对象的乘积,在实践中我们可能需要定义n多个函数 int multipli…...

三天Golang快速入门—面向对象

面向对象Golang接口的定义go中类空接口空接口作为函数的参数切片实现空接口map的值实现空接口类型断言值接收者和指针接收者值接收者指针接收者接口嵌套Golang接口的定义 接口interface是一种抽象的类型。接口定义了一个对象的行为规范&#xff0c;只定义规范不实现&#xff0…...

开发手册——一、编程规约_6.并发处理

这篇文章主要梳理了在java的实际开发过程中的编程规范问题。本篇文章主要借鉴于《阿里巴巴java开发手册终极版》 下面我们一起来看一下吧。 1. 【强制】获取单例对象需要保证线程安全&#xff0c;其中的方法也要保证线程安全。 说明&#xff1a;资源驱动类、工具类、单例工厂…...

ACM---大一第三周周赛(Floyd算法+并查集算法学习周)

&#x1f680;write in front&#x1f680; &#x1f4dd;个人主页&#xff1a;认真写博客的夏目浅石.CSDN &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​ &#x1f4e3;系列专栏&#xff1a;ACM周训练题目合集.CSDN &#x1f4ac;总结&#xff1a…...

spring整合mybatis和Junit

该项目使用spring纯注解方式开发&#xff0c;用配置类取代spring的配置文件 一、导入依赖 整合Junit需要导入spring-test 整合mybatis需要导入spring-jdbc、mybatis-spring <dependencies><!-- https://mvnrepository.com/artifact/org.springframework/spring-cont…...

Spring Boot 3.0系列【7】核心特性篇之JSON

有道无术,术尚可求,有术无道,止于术。 本系列Spring Boot版本3.0.3 源码地址:https://gitee.com/pearl-organization/study-spring-boot3 文章目录 前言JSON什么是JSON常用JSON 库GsonFastJsonJacksonJackson 还是 FastjsonSpring Boot 中的 JSON1. 自动配置 Jackson2. @…...

【数据结构初阶】二叉树顺序结构:堆的实现

前言 前边077带着大家学习了树与二叉树的相关概念&#xff0c;这篇文章我们来实现一个二叉树的顺序结构。 二叉树的顺序结构 普通的二叉树是不适合用数组来存储的&#xff0c;因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉…...

C/C++:动态内存管理

目录 一. C/C内存分布 二. C/C动态内存管理 2.1 C语言动态内存管理 2.2 C动态内存管理 2.2.1 new/delete操作符 2.2.2 operator new与operator delete函数 2.3 new/delete的实现原理 2.4 定位new&#xff08;placement - new&#xff09; 2.5 new/delete和malloc/free的…...

黑猫带你学eMMC协议第28篇:eMMC的开漏和推挽模式(push-pull open drain)

本文依据eMMC JEDEC5.1及个人工作经验整理而成,如有错误请留言。 文章为个人辛苦整理,付费内容,已加入原创侵权保护,禁止私自转载。 文章所在专栏:《黑猫带你学:eMMC协议详解》 1 什么是开漏和推挽 1.1 推挽电路是什么 关于推挽和开漏电路,更多介绍详见我的另一篇文章…...

simulink PID控制

系列文章目录 文章目录系列文章目录前言一、非线性系统线性化原理二、反馈控制开环控制反馈or闭环控制PID ControllerPID微调案例总结前言 将非线性系统近似线性化PIDblock与微调 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、非线性系统线性化 …...

如何在for循环内执行异步操作

var定义的i是全局的&#xff0c;每次遍历都会覆盖&#xff0c;最后i的值为10&#xff0c;所以输出10次10 for (var i 0; i < 10; i) {setTimeout(function () {console.log(i) // 输出10遍10}, 1000 i * 100)}setTimeOut 第三个函数 for (var i 0; i < 10; i) {setT…...

性能测试——LoadRunner: Controller的使用

Controller Controller是用来创建测试环境&#xff0c;执行在VUG中编写的测试脚本 可以直接点击Controller的快捷方式打开,也可以在VUG中打开 这里将虚拟用户数设置为3,比较适合自己的电脑性能 整个controller分为下面几个模块 这里先设置左下角的目标计划 设置初始化:双击…...

ChatGPT解答:纯前端文档预览,Vue实现,无需后端,支持Word、Excel、PPT、pdf、文本、图片,附接入demo和文档

ChatGPT解答&#xff1a;纯前端文档预览&#xff0c;Vue实现&#xff0c;无需后端&#xff0c;支持Word、Excel、PPT、pdf、文本、图片&#xff0c;附接入demo和文档 ChatGPTDemo Based on OpenAI API (gpt-3.5-turbo). 纯前端文档预览&#xff0c;Vue实现&#xff0c;无需后…...

刷题记录:牛客NC13950 Alliances 到树上联通点集的最短距离

传送门:牛客 题目描述: 题目较长,此处省略 输入: 7 1 2 1 3 2 4 2 5 3 6 3 7 2 2 6 7 1 4 3 5 1 2 1 1 1 5 2 1 2 输出: 2 1 1一道比较复杂的树题.需要一些复杂的讨论以及LCA知识 对于LCA,可以使用树链剖分进行解决 然后我们看一下题目,我们会发现有这样一个简单的结论,那就…...

行为型模式 - 状态模式State

学习而来&#xff0c;代码是自己敲的。也有些自己的理解在里边&#xff0c;有问题希望大家指出。 个人理解&#xff1a;感觉像桥接模式 代理模式。不知道这么想对不对&#xff0c;还希望笔记在放出后&#xff0c;有大佬彻底了解了给我解解惑。 策略模式的定义与特点 策略&…...

电视剧《狂飙》太过诡异,主演各个悄无声息,龙套演员却身价倍增

说起电视剧《狂飙》&#xff0c;相信很多人都有过观看&#xff0c;这部以反腐为题材的大剧&#xff0c;尺度之大近年来绝无仅有。不过观众在被剧情震撼的同时&#xff0c;也发现了一些诡异的事情&#xff0c;比如说主角和配角的反差&#xff0c;让人感觉很不适应。 在电视剧《狂…...

【微信小程序】-- 案例 - 本地生活(二十)

&#x1f48c; 所属专栏&#xff1a;【微信小程序开发教程】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &…...

LeetCode 每日一题 2023/2/27-2023/3/5

记录了初步解题思路 以及本地实现代码&#xff1b;并不一定为最优 也希望大家能一起探讨 一起进步 目录2/27 1144. 递减元素使数组呈锯齿状2/28 2363. 合并相似的物品3/1 2373. 矩阵中的局部最大值3/2 面试题 05.02. 二进制数转字符串3/3 1487. 保证文件名唯一3/4 982. 按位与为…...

SpringMVC中JSON数据的设置、RestFul风格

Java知识点总结&#xff1a;想看的可以从这里进入 目录3.4、JSON数据3.4.1、前端使用3.4.2、后端使用1、Jackson2、fastjson3.5、RestFul风格3.5.1、简介3.5.2、使用3.4、JSON数据 3.4.1、前端使用 前端在JavaScript中有封装的JSON对象&#xff0c;可以直接用来操作JSON数据。…...

Clion连接Docker,使用HElib库

文章目录需求Clion连接服务器内的DockerDockerCLionDocker内配置HElib库参考需求 HElib库是用C编写的同态加密开源库&#xff0c;一般在Linux下使用为了不混淆生产环境&#xff0c;使用Docker搭建HElib运行环境本地在Windows下开发&#xff0c;使用的IDE为Clion&#xff0c;本…...

go网络编程-websocket

1. WebSocket编程 文章目录1. WebSocket编程1.1.1. webSocket是什么1.1.2. 举个聊天室的小例子server.go文件代码hub.go文件代码data.go文件代码local.html文件代码1.1.1. webSocket是什么 WebSocket是一种在单个TCP连接上进行全双工通信的协议 WebSocket使得客户端和服务器之…...

Microsoft designer 使用教程

继各种ai绘图软件诞生之后 dell 2 playground.... 微软自己研发的重量级产品 Microsoft designer 上线了 Microsoft Designer 是微软公司推出的一款设计工具&#xff0c;主要用于快速创建Web和移动应用程序的原型设计。它提供了一系列的工具和模板&#xff0c;可以帮助用户…...

《Docker系列》Docker容器修改配置文件后,重启失败,如何修改配置并启动容器?

Docker容器修改配置文件后&#xff0c;重启失败&#xff0c;如何修改配置并启动容器&#xff1f; docker部署的MySQL容器&#xff0c;修改了my.cnf配置文件&#xff0c;重启的时候导致无法启动 通过查日志发现&#xff0c;配置文件中的binlog-db-dbhw写错了&#xff0c;应该是…...

遇到多个构造器参数时要考虑使用构建器

静态工厂和构造器有个共同的局限性&#xff1a;他们都不能很好地扩展到大量的可选参数。比如用一个类表示包装食品外面显示的营养成分标签&#xff08;包括必选域和可选域&#xff09;。 重叠构造器 对于这样的类一般习惯采用重叠构造器&#xff08;telescoping constructor&…...

【Storm】【五】Storm集成Kafka

Storm集成Kafka 一、整合说明二、写入数据到Kafka三、从Kafka中读取数据一、整合说明 Storm 官方对 Kafka 的整合分为两个版本&#xff0c;官方说明文档分别如下&#xff1a; Storm Kafka Integration : 主要是针对 0.8.x 版本的 Kafka 提供整合支持&#xff1b;Storm Kafka …...

GVRP-LNP-VCMP讲解

目录 GVRP讲解 动态创建Vlan并将端口加入Vlan GVRP消息类型 GVRP工作原理 LNP讲解 动态修改接口链路类型 VCMP讲解 动态创建Vlan 相关概念 Vlan同步 VCMP与GVRP的区别 GVRP讲解 动态创建Vlan并将端口加入Vlan GVRP&#xff08;GARR Vlan Registration Protocol&#xf…...

28个精品Python爬虫实战项目

先来说说Python的优势&#xff01;然后给大家看下这28个实战项目的实用性&#xff01;Python跟其他语言相比&#xff0c;有以下优点&#xff1a;1. 简单Python是所有编程语言里面&#xff0c;代码量最低&#xff0c;非常易于读写&#xff0c;遇到问题时&#xff0c;程序员可以把…...

相信人还是相信ChatGPT,龙测首席AI专家给出了意料之外的答案

最近&#xff0c;关于ChatGPT的话题太火了&#xff01;各大社交软件都是他的消息&#xff01;从去年12月份ChatGPT横空出世&#xff0c;再到近期百度文心一言、复旦Moss的陆续宣布&#xff0c;点燃了全球对AIGC&#xff08;内容人工智能自动生成&#xff09;领域的热情&#xf…...

安卓逆向_5 --- jeb 和 AndroidStudio 动态调试 smali

Jeb 工具的使用 &#xff1a;https://www.52pojie.cn/forum.php?modviewthread&tid742250&#xff1a;https://zhuanlan.zhihu.com/p/302856081动态调试 smali 有两种方法&#xff1a; Jeb 调试AndroidStudio smalidea 插件动态调试。1、Jeb 动态调试 smali ​JEB是一个…...

docker-容器命令

1.新建启动 docker run options image command [arg..] options: --name"容器新名字" -d&#xff1a;后台运行程序 -it&#xff1a;交互式运行 -P: 随机端口 -p: 指定端口 docker run -it ubuntu /bin/bash docker run -it ubuntu:v1 /bin/bash docker run -it 1c352…...