C++超详细讲解泛型

目录
  • 1.了解泛型编程
  • 2.函数模板
    • 2.1简单示例
    • 2.2多个模板参数
    • 2.3模板实例化
    • 2.4模板和普通函数同时存在
    • 2.5函数模板不支持定义和声明分离
  • 3.类模板
    • 3.1简单示例
    • 3.2成员函数声明和定义分离

1.了解泛型编程

就好比活字印刷术,可以灵活调整印刷的板块和内容,比只能固定印刷某一个内容的雕版印刷术效率更高,也让印刷术由此得到了更广泛的应用。

在C++中,函数重载和模板的出现,让泛型编程得到了实际的应用。其中模板,就是类似活字印刷术一样的存在。

2.函数模板

八八了那么多没用的,让我们来看看函数模板的语法实现吧

2.1简单示例

下面是一个最简单的交换函数的例子,通过标明模板参数T,让编译器自动识别函数传参,并调用出不同的函数

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

其中,typename是定义模板的关键字,我们可以使用class来替代,但不能使用struct

可以看到,编译器成功调用了Swap函数,交换了int类型和double类型

2.2多个模板参数

如果我们尝试把int和double同时传参给这个函数,会发生什么呢?

编译器会报错,表示模板参数T不明确

这时候我们有几种解决方法

首先是将double强转为int(反过来亦可)

你会发现还是不行,那是因为强转并不支持用double引用int。所以我们把函数传参中的引用去掉,即可正常调用这个函数(暂且不提传引用和传值的区别)

使用多个模板参数

和函数传参类似,我们也可以设置多个模板参数

在下图中,我使用typeid关键字来打印模板参数T1和T2的类型。

使用typeid需要包含头文件#include <typeinfo>

可以看到,实际上函数在调用这个模板的时候,已经实例化了这个函数(即替换模板参数为正确参数类型)这时候在后台处理的时候,其实Show函数已经实例化为了下面这个样子

void Show(int left, double right)
{
    cout << typeid(left).name() << endl;
    cout << typeid(right).name() << endl;
}

2.3模板实例化

上面的方式,是编译器自动帮我们实例化模板参数。在实际使用中,我们还可以自己指定实例化为什么类型

  • 利用强制类型转换
  • 使用<int>直接指定实例化为int类型

使用第二种方式的时候,编译器会对另外一个不匹配的参数进行隐式类型转换。如果转换不成功,则会报错。

另外注意的是,函数模板参数T同样可以用来作为返回值,但是不能通过返回值来推断参数T的类型。比如下面这个函数,我们在使用的时候就需要直接指定模板参数T,而不能写一个int* ptr=test(10)让编译器通过“返回值是int*接收的,所以函数模板参数T是int”来推断。

template<typename T>
T* test(int num)
{
	return new T[num];
}

函数模板支持给予参数缺省值

当一个参数不确定的时候,函数模板是支持给予缺省值的

template<typename T=char>
T* test(int num)
{
	return new T[num];
}

比如这样,当我们没有直接指定的时候,编译器就会将T作为char类型,返回一个num大小的char(一个字节)的空间

注意:当有多个模板参数时,缺省值需要从右往左给

函数模板的传参也支持缺省值

template<typename T1>
void Add(T1 left, T1 right=10)
{
    cout << "Add temp "<<typeid(left).name() << " " << typeid(right).name() << endl;
    cout << left + right << endl << endl;
}
int main()
{
    int a=1;
    Add(a);
}

在这种情况下,编译器会正确调用该函数模板

2.4模板和普通函数同时存在

以Add函数为例,在函数模板存在的同时,我们还可以单独写一个int类型的add函数。这都归功于函数重载的存在。

同时,我们还可以使用<int>来指定函数模板重载为已存在的Add函数。因为本质上这两个函数是不同的,并不会冲突。

函数在调用的时候,首先会去调用已经存在的函数。当参数和已存在的函数不匹配时,才会调用函数模板

2.5函数模板不支持定义和声明分离

一般情况下,我们都会在头文件中生命函数,在另外一个源文件中定义函数。

但是模板是不支持这么做的!编译器会报错 链接错误

error LNK2019:无法解析的外部符号……

所以我们需要将函数模板的声明和定义放在一个头文件中。在部分使用场景,会使用.hpp来表示这个头文件是包含了函数定义的(即.h和.cpp的集合体)。需要注意,这并不是一个硬性要求,你也可以直接使用.h,并将声明和定义放入其中。

这是为什么呢?

因为单独的.h声明会在源文件顶部展开,而此时函数模板正常推演参数,但编译器并没有找到函数的实现,即这是一个没有地址的函数。从而导致编译器找不到函数的地址,产生了符号表的链接错误

有无解决办法?

其实是有的,我们可以在模板函数定义的.cpp中对我们需要使用的函数进行显式实例化指定

//头文件
//声明
template<typename T1>
void Add(T1 left, T1 right);
//源文件
//定义
template<typename T1>
void Add(T1 left, T1 right)
{
   cout << left + right << endl << endl;
}
//在源文件中显式实例化
template
void Add<int>(int left, int right);
template
void Add<double>(double left, double right);

显式实例化需要对我们要用的所有函数进行实例化,比如你需要用double类型,只显示实例化了int类型是不行的,依旧会报错。

这样感觉非常多余……对吧!所以还是老老实实把声明和定义放在同一个文件里面吧!

3.类模板

类模板的基本形式如下,这里作为一个小区分,我用class来当作模板参数名。实际上typename也是可以的

template<class T1, class T2, ...>class 类模板名{<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->// 类内成员定义}; template<class T1, class T2, ...>
class 类模板名
{
// 类内成员定义
};

3.1简单示例

下面用一个非常简单的顺序表代码来演示一下类模板

template<class T>
class List
{
public:
    List(int capacity = 10)
        : _a(new T[capacity])
        , _size(0)
        , _capa(capacity)
    {}

    ~List();
    T& operator[](int pos)
    {
        assert(pos < _size);
        return _a[pos];
    }
private:
    T* _a;
    int _size;
    int _capa;
};
//类模板中函数放在类外进行定义时,需要加模板参数列表
template <class T>
List<T>::~List()
{
    delete[] _a;
    _size = _capa = 0;
}

可以看到,通过显式实例化的方式,我们成功让这个类模板变成了两个不同类型的顺序表

3.2成员函数声明和定义分离

其中需要注意的是析构函数,声明和定义分离的时候(同一文件),在定义的时候也需要加上模板参数

//类模板中函数放在类外进行定义时,需要加模板参数列表
template <class T>
List<T>::~List()
{
    delete[] _a;
    _size = _capa = 0;
}

个人觉得这样也非常麻烦,既然模板最好是声明和定义放在同一个文件,那还不如直接将类的成员函数直接定义到类内部。多省事!

如果是声明和定义放在不同文件中,显式实例化方式如下

template
class List <int>;
template
class List <double>;

需要什么类型的类,就得实例化这个类型。

到此这篇关于C++超详细讲解泛型的文章就介绍到这了,更多相关C++泛型内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++泛型编程函(数模板+类模板)

    目录 一.函数模板 1.函数模板介绍 2.函数模板与重载函数的关系 3.函数模板实现机制 二.类模板 1.类模板基本语法 2.类模板内函数的整体布局[分文件使用类模板] 3.类模板的static与模板类的static 4.数组实现万能容器 前言: 由于C++是静态语言,也就是说使用一个数据的时候必须先指定类型,这样的操作在编译后变量的类型是无法轻易改变的,就导致扩展性太差.或者一个函数需要很多次重载的时候,代码显得冗杂,由此产生了C++函数模板. 一.函数模板 1.函数模板介绍 ① 函数模板的产

  • C++ 泛型编程详解

    泛型编程与面向对象编程的目标相同,即使重用代码和抽象通用概念的技术更加简单.但是面向对象编程强调编程的数据方面,泛型编程强调的是独立于特定数据类型. 这一篇介绍一下 C++ 编程中与面向对象并列的另一大分支--泛型编程,这一篇主要介绍函数模板.类模板和成员模板三大部分 如有侵权,请联系删除,如有错误,欢迎大家指正,谢谢 泛型编程 模板是泛型编程的一种重要思想,STL(Standard Template Library,标准模板库)是采用模板实现的一个实例 函数模板 对比函数重载(同一作用域内函数

  • 基于C++泛型编程职工管理系统

    目录 一.泛型编程思想 二.单链表是什么? 1.图示 2.链表的节点结构[节点类] 3.链表类 三.泛型编程核心 1.实现数据类 2.实现链表类 四.运行截图 1.主菜单 2.查看信息 3.更换数据类型 4.再次显示所有信息[抛转] 五.源码 前言: 前面介绍到了C++的泛型编程,并实现了万能容器,不过那使用的是数组,今天呢咱带大家实践一下使用泛型技术,结合单链表实现一个职工管理系统.保证大家看完之后有所感悟. 一.泛型编程思想 所谓泛型就是类型不固定,只需修改少量代码就可以扩展为其他类型的应用

  • C++泛型编程Generic Programming的使用

    目录 一.容器 array vector deque list map 键值对key/value 二.迭代器iterator(泛型指针) 三.泛型算法Generic Programming insert()插入 erase()删除 find()用于无序搜索,搜素范围[first, last), 返回iterator, 找不到则返回last copy() 复制 泛型编程最初提出时的动机很简单直接:发明一种语言机制,能够帮助实现一个通用的标准容器库. 所谓通用的标准容器库,就是要能够做到,比如用一个

  • C++泛型编程基本概念详解

    目录 1.什么是泛型编程? 2.函数模板 (1)函数模板概念 (2)函数模板格式 (3)函数模板的原理 (4)函数模板的实例化 (5)模板参数的匹配原则 3.类模板 (1)类模板的定义格式 (2)类模板的实例化 总结 1.什么是泛型编程? 比如说,我们如何实现一个通用的交换函数呢?int型.double型.char型的交换 void Swap(int& left, int& right) { int temp = left; left = right; right = temp; } vo

  • C++ primer超详细讲解泛型算法

    目录 初识泛型算法 只读算法 写容器算法 定制操作 lambda表达式 lambda捕获和返回 再探迭代器 插入迭代器 iostream迭代器 反向迭代器 初识泛型算法 只读算法 只读取输入范围内的函数,不改变元素,find,accumula也是如此 (1)accumulate算法为求和算法,前两个参数指出求和元素范围,第三个是和的初值,例: int sum=accumulate(v.begin(),v.end(),0) (2)操作两个序列的算法 equal算法,确定两个序列是否保存相同的值,将

  • C++超详细讲解泛型

    目录 1.了解泛型编程 2.函数模板 2.1简单示例 2.2多个模板参数 2.3模板实例化 2.4模板和普通函数同时存在 2.5函数模板不支持定义和声明分离 3.类模板 3.1简单示例 3.2成员函数声明和定义分离 1.了解泛型编程 就好比活字印刷术,可以灵活调整印刷的板块和内容,比只能固定印刷某一个内容的雕版印刷术效率更高,也让印刷术由此得到了更广泛的应用. 在C++中,函数重载和模板的出现,让泛型编程得到了实际的应用.其中模板,就是类似活字印刷术一样的存在. 2.函数模板 八八了那么多没用的

  • Swift超详细讲解指针

    目录 Swift指针Unsafe Pointer 对照Objective-C 例子 Swift指针Unsafe Pointer 如果不是只读,可以修改 ( 写入 ),就加一个 Mutable, 如果没有具体的类型( 通过泛型的方式 ),就加一个 Raw, 如果不是一个单独的对象 ( 指向集合类型 ),就加上 buffer. Unsafe [ Mutable ] [ Raw ] [ Buffer ] Pointer [ ] 苹果没有编译保护的 [ 可变的 ] [没有类型的] [ 是集合的 ] 指针

  • C++函数模板与重载解析超详细讲解

    目录 1.快速上手 2.重载的模板 3.模板的局限性 4.显式具体化函数 5.实例化和具体化 6.重载解析 6.1 概览 6.2 完全匹配中的三六九等 6.3 总结 7.模板的发展 1.快速上手 函数模板是通用的函数描述,也就是说,它们使用泛型来定义函数. #include<iostream> using namespace std; template <typename T> void Swap(T &a,T &b);//模板原型 struct apple{ st

  • C++ Boost System超详细讲解

    目录 一.说明 二.关于 Boost.System库 一.说明 以下库支持错误处理. Boost.System 提供类来描述和识别错误.自 C++11 以来,这些类已成为标准库的一部分. Boost.Exception 使得在抛出异常后附加数据成为可能. 二.关于 Boost.System库 Boost.System Boost.System 是一个库,本质上定义了四个类来识别错误.所有四个类都已添加到 C++11 的标准库中.如果您的开发环境支持 C++11,则无需使用 Boost.Syste

  • SpringBoot封装响应处理超详细讲解

    目录 背景 报文基本格式 创建枚举类 定义统一返回结果实体类 定义返回工具类 统一报文封装在接口中的使用 统一异常处理 小结 背景 越来越多的项目开始基于前后端分离的模式进行开发,这对后端接口的报文格式便有了一定的要求.通常,我们会采用JSON格式作为前后端交换数据格式,从而减少沟通成本等. 报文基本格式 一般报文格式通常会包含状态码.状态描述(或错误提示信息).业务数据等信息. 在此基础上,不同的架构师.项目搭建者可能会有所调整. 但从整体上来说,基本上都是大同小异. 在SpringBoot项

  • java反射超详细讲解

    目录 Java反射超详解✌ 1.反射基础 1.1Class类 1.2类加载 2.反射的使用 2.1Class对象的获取 2.2Constructor类及其用法 2.4Method类及其用法 Java反射超详解✌ 1.反射基础 Java反射机制是在程序的运行过程中,对于任何一个类,都能够知道它的所有属性和方法:对于任意一个对象,都能够知道它的任意属性和方法,这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制. Java反射机制主要提供以下这几个功能: 在运行时判断任意一个对象所属

  • 超详细讲解Linux C++多线程同步的方式

    目录 一.互斥锁 1.互斥锁的初始化 2.互斥锁的相关属性及分类 3,测试加锁函数 二.条件变量 1.条件变量的相关函数 1)初始化的销毁读写锁 2)以写的方式获取锁,以读的方式获取锁,释放读写锁 四.信号量 1)信号量初始化 2)信号量值的加减 3)对信号量进行清理 背景问题:在特定的应用场景下,多线程不进行同步会造成什么问题? 通过多线程模拟多窗口售票为例: #include <iostream> #include<pthread.h> #include<stdio.h&

  • 超详细讲解Linux DHCP服务

    目录 一.DHCP服务(动态主机配置协议) 1.背景 2.概述 3.优点 4.DHCP报文类型 5.DHCP 的分配方式 二.安装 DHCP 服务器 1.DHCP 服务软件 2.主配置文件 三.配置步骤 1.使用 DHCP 动态的给 PC 机分配 IP 地址 ① eNSP ②虚拟机 ③验证 ④进入命令行"ipconfig"测试 一.DHCP服务(动态主机配置协议) 1.背景 1.手动设置工作量大且容易冲突 2.用DHCP可以减少工作量和避免地址冲突 2.概述 作用:为局域网内的电脑分配

  • 超详细讲解python正则表达式

    目录 正则表达式 1.1 正则表达式字符串 1.1.1 元字符 1.1.2 字符转义 1.1.3 开始与结束字符 1.2 字符类 1.2.1 定义字符类 1.2.2 字符串取反 1.2.3 区间 1.2.4 预定义字符类 1.3 量词 1.3.1 量词的使用 1.3.2 贪婪量词和懒惰量词 1.4 分组 1.4.1 分组的使用 1.4.2 分组命名 1.4.3 反向引用分组 1.4.4 非捕获分组 1.5 re模块 1.5.1 search()和match()函数 1.5.2 findall()

随机推荐