C++模板编程--学习C++类库的编程基础

2023-05-16

目录

    • 一、函数模板
    • 二、类模板
    • 三、实现C++ STL向量容器vector代码
    • 四、理解容器空间配置器allocator的重要性

一、函数模板

1、模板的意义:对类型也可以进行参数化了

2、函数模板<=是不进行编译的,因为类型还不知道

模板的实例化<=函数调用点进行实例化

模板函数<=要被编译器所编译的

3、模板类型参数typename/class

模板非类型参数

4、模板的实参推演=>可以根据用户传入的实参类型,来推导出模板类型的具体类型

模板的特例化(专用化):特殊(不是编译器提供的,而是用户提供的)的实例化

函数模板、模板的特例化、非模板函数的重载关系

5、模板代码是不能在一个文件中定义,在另一个文件中使用的,否则链接的时候会出现错误。

模板代码调用之前,一定要看到模板定义的地方,这样的话,模板才能进行正常的实例化,产生能够被编译器编译的代码。

所以,模板代码都是放在头文件当中的,然后在原文件当中直接进行#include包含

#include <iostream>
using namespace std;

//模板的声明
template<typename T>//定义一个模板参数列表
bool compare(T a, T b)//compare是一个函数模板
{
    cout << "template compare" << endl;
    return a > b;
}
//针对compare函数模板,提供const char*类型的特例化版本
template<>
bool compare(const char* a, const char* b)
{
    cout << "compare<const char*>" << endl;
    return strcmp(a, b) > 0;
}

//非模板函数 - 普通函数
bool compare(const char* a, const char* b)
{
    cout << "normal compare" << endl;
    return strcmp(a, b) > 0;
}

int main()
{
    //函数的调用点
    compare<int>(10, 20);
    compare<double>(10.5, 20.5);

    //函数模板实参推演
    compare(20, 20);
    //compare(30, 40.5);//error

    //函数模板实参的推演T const char*,比较的是两个常量的地址,并不是比较字符串的字典顺序
    //对于某些类型来说,依赖编译器默认实例化的模板代码,代码处理逻辑是错误的
    //编译器优先把compare处理成函数名字,没有的话,才去找compare模板
    compare("aaa", "bbb");

    compare<const char*>("aaa","bbb");

    return 0;
}
/*
    在函数调用点,编译器用用户指定的类型,从原模板实例化一份函数代码出来
    模板函数。
    在相应文件的符号表中产生相应的符号,每个函数名的符号只能出现一次
    bool compare<int>(int a,int b)
    {
        return a>b;
    }
    bool compare<double>(double a,double b)
    {
        return a>b;
    }
    bool compare(const char*a,const char*b)
    {
        return strcmp(a,b)>0;
    }
*/

编译器优先把compare处理成函数名字,没有的话,才去找compare模板特例化,还没有才找模板实例化。这样做的事情最少。

6、模板的非类型参数:必须是整数类型(整数或者地址/引用都可以)是常量只能使用,而不能修改

#include <iostream>
using namespace std;

template<typename T,int size>
void sort(T* arr)
{
	for (int i = 0; i < size - 1; i++)
	{
		for (int j = 0; j < size - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}

int main()
{
	int arr[] = { 12,3,4,56,7,89,0 };
	const int size = sizeof(arr) / sizeof(arr[0]);
	sort<int, size>(arr);
	for (int val : arr)
	{
		cout << val << " ";
	}
	cout << endl;
	return 0;
}

二、类模板

类模板=>实例化=>模板类

构造和析构函数名不用加,其他出现模板的地方都加上类型参数列表

#include <iostream>
using namespace std;

//类模板
//template<typename T = int>//类模板可以加默认类型参数
template<typename T>
class SeqStack //模板名称+类型参数列表=类名称
{
public:
    //构造和析构函数名不用加<T>,其他出现模板的地方都加上类型参数列表
    SeqStack(int size = 10)
        :_pstack(new T[size])
        ,_top(-1)
        ,_size(size)
    {}
    ~SeqStack()
    {
        delete[]_pstack;
        _pstack = nullptr;
    }
    SeqStack(const SeqStack<T>& stack)
        :_top(stack._top)
        ,_size(stack._size)
    {
        _pstack = new T[_size];
        for (int i = 0; i < _top; i++)
        {
            _pstack[i] = stack._pstack[i];
        }
    }
    SeqStack<T>& operator=(const SeqStack<T>& stack)
    {
        if (this == &stack)
        {
            return *this;
        }
        delete[]_pstack;
        _pstack = new T[_size];
        for (int i = 0; i < _top; i++)
        {
            _pstack[i] = stack._pstack[i];
        }
        return *this;
    }
    void push(const T& val);
    void pop()
    {
        cout << "pop():"<<_pstack[_top] << endl;
        if (empty())
            return;
        --_top;
    }
    T top()
    {
        if (empty())
            throw "stack is empty";//抛异常也代表函数逻辑结束
        return _pstack[_top];
    }
    bool full() { return _top == _size - 1; }
    bool empty() { return _top == -1; }
private:
    T* _pstack;
    int _top;
    int _size;
    void expand()
    {
        T* ptmp = new T[_size * 2];
        for (int i = 0; i < _top; i++)
        {
            ptmp[i] = _pstack[i];
        }
        delete[]_pstack;
        _pstack = ptmp;
        _size *= 2;
    }

};
template<typename T>
void SeqStack<T>::push(const T& val)
{
    cout << "push(const T & val):"<<val << endl;
    if (full())
        expand();
    _pstack[++_top] = val;
}
int main()
{
    //类模板的选择性实例化
    //模板类 class SeqStack<int>{};
    SeqStack<int> s1;
    s1.push(20);
    s1.push(10);
    s1.push(13);
    s1.push(35);
    s1.pop();
    cout << s1.top() << endl;
    return 0;
}

运行结果:
在这里插入图片描述

三、实现C++ STL向量容器vector代码

#include <iostream>
using namespace std;

template<typename T>
class vector
{
public:
    vector(int size = 10)
    {
        _first = new T[size];
        _last = _first;
        _end = _first + size;
    }
    ~vector()
    {
        delete[]_first;
        _first = _last = _end = nullptr;
    }
    vector(const vector<T>& rhs)
    {
        int size = rhs._end - rhs._first;
        _first = new T[size];
        int len = rhs._last - rhs._first;
        for (int i = 0; i < len; i++)
        {
            _first[i] = rhs._first[i];
        }
        _last = _first + len;
        _end = _first + size;
    }
    vector<T>& operator=(const vector<T>& rhs)
    {
        if (this == &rhs)
            return*this;
        delete[]_first;
        int size = rhs._end - rhs._first;
        _first = new T[size];
        int len = rhs._last - rhs._first;
        for (int i = 0; i < len; i++)
        {
            _first[i] = rhs._first[i];
        }
        _last = _first + len;
        _end = _first + size;
    }
    void push_back(const T& val)//向容器末尾添加元素
    {
        if (full())
            expand();
        *_last++ = val;
        cout << val << " ";
    }
    void pop_back()//从容器末尾删除元素
    {
        if (empty())
            return;
        _last--;
    }
    T back()const//返回容器末尾元素的值
    {
        
        return *(_last - 1);
    }
    bool full() { return _last == _end; }
    bool empty() { return _last == _first; }
    int size() { return _last - _first; }
private:
    T* _first;//指向数组起始位置
    T* _last;//指向数组中有效元素的后继位置
    T* _end;//指向数组空间的后继位置
    void expand()
    {
        int size = _end - _first;
        T* ptmp = new T[size * 2];
        for (int i = 0; i < size; i++)
        {
            ptmp[i] = _first[i];
        }
        delete[]_first;
        _first = ptmp;
        _last = _first + size;
        _end = _first + size * 2;
    }
};
int main()
{
    vector<int> vec;
    for(int i=0;i<20;i++)
    {
        vec.push_back(rand() % 100);
    }
    cout << endl;
    vec.pop_back();
    while (!vec.empty())
    {
        cout << vec.back() << " ";
        vec.pop_back();
    }

    return 0;
}

运行结果:
在这里插入图片描述

四、理解容器空间配置器allocator的重要性

容器的空间配置器allocator做四件事:内存开辟、内存释放;对象构造、对象析构

#include <iostream>
using namespace std;

/*
空间配置器allocator

template<class _Ty,
    class _Alloc = allocator<_Ty>>
    class vector
*/
//定义容器的空间配置器和C++标准库的allocator实现一样
template<typename T>
struct Allocator
{
    T* allocate(size_t size)//负责内存开辟
    {
        return (T*)malloc(sizeof(T) * size);
    }
    void deallocate(void* p)//负责内存释放
    {
        free(p);
    }
    void construct(T* p, const T& val)//负责对象构造
    {
        new(p)T(val);//定位new
    }
    void destroy(T* p)//负责对象析构
    {
        p->~T();//~T()代表了T类型的析构函数
    }
};
//容器底层内存开辟,内存释放,对象构造,对象析构;都通过allocator空间配置器实现
template<typename T,typename Alloc = Allocator<T>>
class vector
{
public:
    vector(int size = 10)
    {
        //需要把内存开辟和对象构造分开处理
        //_first = new T[size];
        _first = _allocator.allocate(size);
        _last = _first;
        _end = _first + size;
    }
    ~vector()
    {
        //析构容器有效元素,然后释放_first指针指向的堆内存
        //delete[]_first;
        for (T* p = _first; p != _last; ++p)
        {
            _allocator.destroy(p);//把_first指针指向的数组的有效元素进行析构操作
        }
        _allocator.deallocate(_first);//释放堆上的数组内存
        _first = _last = _end = nullptr;
    }
    vector(const vector<T>& rhs)
    {
        int size = rhs._end - rhs._first;
        //_first = new T[size];
        _first = _allocator.allocate(size);
        int len = rhs._last - rhs._first;
        for (int i = 0; i < len; i++)
        {
            //_first[i] = rhs._first[i];
            _allocator.construct(_first + i, rhs._first[i]);
        }
        _last = _first + len;
        _end = _first + size;
    }
    vector<T>& operator=(const vector<T>& rhs)
    {
        if (this == &rhs)
            return*this;
        //delete[]_first;
        for (T* p = _first; p != _last; ++p)
        {
            _allocator.destroy(p);//把_first指针指向的数组的有效元素进行析构操作
        }
        _allocator.deallocate(_first);
        int size = rhs._end - rhs._first;
        //_first = new T[size];
        _first = _allocator.allocate(size);
        int len = rhs._last - rhs._first;
        for (int i = 0; i < len; i++)
        {
            //_first[i] = rhs._first[i];
            _allocator.construct(_first + i, rhs._first[i]);
        }
        _last = _first + len;
        _end = _first + size;
    }
    void push_back(const T& val)//向容器末尾添加元素
    {
        if (full())
            expand();
        //*_last++ = val;//last指针指向的内存构造一个值为val的对象
        _allocator.construct(_last, val);
        _last++;
       
    }
    void pop_back()//从容器末尾删除元素
    {
        if (empty())
            return;
        //_last--;//不仅要把_last指针--,还要析构删除的元素
        --_last;
        _allocator.destroy(_last);
    }
    T back()const//返回容器末尾元素的值
    {
        return *(_last - 1);
    }
    bool full() { return _last == _end; }
    bool empty() { return _last == _first; }
    int size() { return _last - _first; }
private:
    T* _first;//指向数组起始位置
    T* _last;//指向数组中有效元素的后继位置
    T* _end;//指向数组空间的后继位置
    Alloc _allocator;//定义容器的空间配置器对象
    void expand()
    {
        int size = _end - _first;
        //T* ptmp = new T[size * 2];
        T* ptmp = _allocator.allocate(size * 2);
        for (int i = 0; i < size; i++)
        {
            //ptmp[i] = _first[i];
            _allocator.construct(ptmp + i, _first[i]);
        }
        //delete[]_first;
        for (T* p = _first; p != _last; ++p)
        {
            _allocator.destroy(p);
        }
        _allocator.deallocate(_first);
        _first = ptmp;
        _last = _first + size;
        _end = _first + size * 2;
    }
};
class Test
{
public:

    Test() { cout << "Test()" << endl; }
    ~Test() { cout << "~Test()" << endl; }
    Test(const Test&) { cout << "Test(const Test&)" << endl; }
};
int main()
{
    Test t1, t2, t3;
    cout << "------------------------" << endl;
    vector<Test> vec;
    cout << "------------------------" << endl;
    vec.push_back(t1);
    vec.push_back(t2);
    vec.push_back(t3);
    cout << "------------------------" << endl;
    vec.pop_back();//只需要析构对象,要把对象的析构和内存释放分开
    cout << "------------------------" << endl;
    return 0;
}

运行结果:
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++模板编程--学习C++类库的编程基础 的相关文章

随机推荐

  • FPGA基于XDMA实现PCIE X8通信方案测速 提供工程源码和QT上位机程序和技术支持

    目录 1 前言2 我已有的PCIE方案3 PCIE理论4 总体设计思路和方案5 vivado工程详解6 驱动安装7 QT上位机软件8 上板调试验证9 福利 xff1a 工程代码的获取 1 前言 PCIE xff08 PCI Express
  • FPGA基于XDMA实现PCIE X8的HDMI视频采集 提供工程源码和QT上位机程序和技术支持

    目录 1 前言2 我已有的PCIE方案3 PCIE理论4 总体设计思路和方案5 vivado工程详解6 驱动安装7 QT上位机软件8 上板调试验证9 福利 xff1a 工程代码的获取 1 前言 PCIE xff08 PCI Express
  • Zynq实现SDI视频解码PCIE传输 提供工程源码和QT上位机源码加技术支持

    目录 1 前言2 我已有的SDI编解码方案3 我已有的PCIE方案4 基于zynq架构的PCIE5 总体设计思路和方案SDI摄像头Gv8601a单端转差GTX解串SDI解码VGA时序恢复YUV转RGB图像缓存PCIE发送通路SDI同步输出通
  • FPGA实现MPEG2视频压缩PCIe传输 提供软硬件工程源码和技术支持

    目录 1 前言2 MPEG2视频压缩实现3 我已有的FPGA图像视频编解码方案4 我已有的PCIE方案5 MPEG2视频压缩PCIE传输设计方案FPGA硬件设计软件设计 6 Vivado工程详解7 Linux下的XDMA驱动安装8 上板调试
  • FPGA基于GS2971/GS2972实现SDI视频收发 提供工程源码和技术支持

    目录 1 前言2 我目前已有的SDI编解码方案3 GS2971 GS2972芯片解读GS2971解读GS2972解读 4 详细设计方案5 vivado工程1解读硬件逻辑工程软件SDK工程 6 vivado工程2解读硬件逻辑工程软件SDK工程
  • STM32 CubeMX生成DAC+DMA+TIM生成正弦波

    1 首先配置好系统时钟 2 打开DAC 3 配置DMA xff0c 在DAC中的 34 DMA Setting 34 选项卡中添加DMA DMA模式选择循环模式 4 配置定时器 在第二步中选择的是TIM6 在第一步中设置的定时器频率是36M
  • allegro 使用汇总

    1 如何在allegro中取消花焊盘 十字焊盘 set up gt design parameter gt shape gt edit global dynamic shape parameters gt Thermal relief co
  • 操作系统的分页和分段式管理

    计算存储的层次结构 xff1a 当前技术没有能够提供这样的存储器 xff0c 因此大部分的计算机都有一个存储器层次结构 xff0c 即少量的非常快速 昂贵 易变的高速缓存 cache xff1b 若干兆字节的中等速度 中等价格 易变的主存储
  • 三极管什么时候工作在饱和区

    http www dzsc com dzbbs 20070115 200765213434343495 html 三极管什么时候工作在饱和区 xff1f 以前我以为Ib大的时候就在饱和区 现在看了书 xff0c 看来应该是两个PN结都正偏的
  • shell脚本基础

    摘自 xff1a 一篇教会你写90 的shell脚本 1 注释 xff1a 单行注释 xff1a 多行注释 xff1a 注意第一种注释方法的 34 后面有一个空格 xff09 39 多行注释内容 39 lt lt block 多行注释内容
  • RT-thread 中CAN总线的应用

    准备 xff1a RT thread Studio 2 2 5 CubeMX 6 6 1 rt thread驱动包 4 0 3 1 新建项目 2 打开CubeMX Settings xff0c 设置CAN 找到CAN1 xff0c 并勾选激
  • Rt-thread的CAN应用2

    1 rtt中使能CAN1 2 CubeMX生成部分代码 xff0c 完成下面操作然后生成MDK ARM项目文件 3 将CubeMX生成的 HAL CAN MspInit 函数 粘贴到drv can c中 并在rt hw can init 函
  • STM32的IIC接口输入输出定义

    IO方向设置 PC11 端口 define SDA IN GPIOC gt CRH amp 61 0XFFFF0FFF GPIOC gt CRH 61 8 lt lt 12 define SDA OUT GPIOC gt CRH amp 6
  • 从头开始用树莓派做一个NAS【最新超详细教程】

    一 概述 众所周知在办公的时候两台电脑之间经常倒数据资料非常麻烦 xff0c 而NAS可以很好的解决这个问题 树莓派搭建NAS方法有很多 xff0c 我们之前也拍过直接用Samba FTP这些来实现NAS功能 xff0c 但是这些需要你会在
  • 串口发送通信---UART发送---STM32F4实现

    串口发送程序配置过程 xff08 HAL库 xff09 初始化串口相关参数 xff0c 使能串口 HAL StatusTypeDef span class token function HAL UART Init span span cla
  • 基于UDP的文件传输VS编程

    要求 xff1a 实现基于UDP的文件传输 xff0c 客户端发送文件 xff0c 服务端接受文件 服务端实现 xff1a include 34 stdafx h 34 include 34 iostream 34 include 34 a
  • 成功解决VSCODE运行C++的报错问题g++

    成功解决VSCODE运行C 43 43 的报错问题g 43 43 解决方案调试 很久写C 43 43 了 xff0c 直接打开C 43 43 写代码测试一下 xff0c 报错 xff0c 真一脸懵逼 捯饬很久 xff0c 解决了问题 解决方
  • linux下的环境变量

    环境变量 环境变量 xff0c 是指在操作系统中用来指定操作系统运行环境的一些参数 通常具备以下特征 xff1a 字符串 本质 有统一的格式 xff1a 名 61 值 值 值用来描述进程环境信息 存储形式 xff1a 与命令行参数类似 ch
  • 【004 关键字】extern “C“的作用是什么?

    一 extern 34 C 34 的作用 extern 34 C 34 关键字常用于C 43 43 和C混合编程中 xff0c 用于指定函数或变量采用C语言的命名和调用约定 加上extern 34 C 34 后 xff0c 会指示编译器这部
  • C++模板编程--学习C++类库的编程基础

    目录 一 函数模板二 类模板三 实现C 43 43 STL向量容器vector代码四 理解容器空间配置器allocator的重要性 一 函数模板 1 模板的意义 xff1a 对类型也可以进行参数化了 2 函数模板 lt 61 是不进行编译的