C++语法知识点合集:11.模板

文章目录

  • 一、非类型模板参数
    • 1.非类型模板参数的基本形式
    • 2.指针作为非类型模板参数
    • 3.引用作为非类型模板参数
    • 4.非类型模板参数的限制和陷阱:
    • 5.几个问题
  • 二、模板的特化
    • 1.概念
    • 2.函数模板特化
    • 3.类模板特化
      • (1)全特化
      • (2)偏特化
      • (3)类模板特化应用示例
  • 三、模板分离编译
    • 1.概念
    • 2.模板的分离编译
  • 模版总结


一、非类型模板参数

模板参数分类类型形参与非类型形参

  • 非类型模板参数(non-type template parameter) 是指模板参数列表中一个非类型的参数。这些参数可以是常量表达式,比如整数、指针或引用等,而不是类型(如 int, double, class 等)。
  • 非类型模板参数的主要作用是让模板可以根据常量值进行不同的实例化,而不仅仅是根据类型。
  • 在类(函数)模板中可将该参数当成常量来使用。

1.非类型模板参数的基本形式

template <typename T, int N>
class Array {
public:
    T data[N];
};

在这个例子中,N 就是一个非类型模板参数,它表示数组的大小。这意味着你可以创建不同大小的 Array 实例,而不需要定义不同的类型。例如:

Array<int, 10> arr1;  // 一个包含10个整数的数组
Array<double, 20> arr2;  // 一个包含20个双精度浮点数的数组

常见的非类型模板参数:

  1. 整型值:可以是 int, char, bool, enum 等。
  2. 指针:指向常量数据的指针,例如:指向函数、对象、字符串字面量等。
  3. 引用:可以是常量引用。
  4. 枚举:可以使用枚举常量。

非类型模板参数的限制:

  • 参数值必须在编译时是一个常量表达式。
  • 非类型模板参数不能是浮点类型或类类型。
namespace s
{
    // 定义一个模板类型的静态数组,模板参数包括类型T和数组大小N(默认为10)
    template <class T, size_t N = 10>
    class array
    {
    public:
        // 重载下标操作符,允许通过数组的索引访问元素
        T &operator[](size_t index)
        {
            return _array[index]; // 返回数组中对应索引位置的元素
        }

        // const 版本的下标操作符,保证不会修改数组内容,用于常量对象
        const T &operator[](size_t index) const
        {
            return _array[index]; // 返回数组中对应索引位置的元素
        }

        // 返回数组的实际大小(_size 表示当前数组中实际存储的元素数量)
        size_t size() const
        {
            return _size;
        }

        // 判断数组是否为空
        bool empty() const
        {
            return 0 == _size;
        }

    private:
        T _array[N];      // 定义一个大小为N的数组,类型为模板参数T
        size_t _size = 0; // 用于记录数组中的实际元素数量,默认为0
    };
}

2.指针作为非类型模板参数

除了整型值,指针也可以作为非类型模板参数。这允许你在编译时传递某些内存地址或指向常量的指针。
在这个例子中,Ptr 是一个非类型模板参数,它接收一个指向 global_value 的指针。

template<int* Ptr>
class PointerWrapper {
public:
    void print() const {
        std::cout << *Ptr << std::endl;
    }
};

int global_value = 42;

int main() {
    PointerWrapper<&global_value> pw;
    pw.print();  // 输出42
    return 0;
}

3.引用作为非类型模板参数

在这个例子中,Ref 是一个常量引用的非类型模板参数。

template<const int& Ref>
class ReferenceWrapper {
public:
    void print() const {
        std::cout << Ref << std::endl;
    }
};

int global_value = 99;

int main() {
    ReferenceWrapper<global_value> rw;
    rw.print();  // 输出99
    return 0;
}

4.非类型模板参数的限制和陷阱:

  • 浮点类型不能作为非类型模板参数,这是由于浮点数在不同平台上可能存在的精度差异,无法保证其在编译时的一致性。
  • 类对象以及字符串也不能作为非类型模板参数

5.几个问题

  1. 非类型模板参数和类型模板参数的区别:
  • 类型模板参数允许我们根据不同的类型实例化模板。可以用 typename 或 class 来声明类型模板参数,例如 template。
  • 非类型模板参数是根据值(而不是类型)来进行实例化的。这些值必须在编译时是已知的常量。比如 int、指针、引用 等。

问题:什么时候使用非类型模板参数,而不是类型模板参数?

如果需要根据常量值(如整数、指针)进行编译时的优化、数组大小设定或策略选择,非类型模板参数是更合适的选择。而如果只是根据类型变化来进行不同实例化,类型模板参数更直接。

  1. 为什么非类型模板参数必须是编译时常量?
  • 非类型模板参数必须是编译时常量,这意味着模板的实例化发生在编译期间,允许编译器在编译时做出优化并生成不同的代码。
  • 如果非类型模板参数是运行时的值,编译器将无法在编译时对其进行优化或实例化。

问题:什么样的值可以作为编译时常量?

  • 常量表达式(constexpr)
  • 常量整数、字符、布尔类型
  • 指向常量对象的指针或引用
  • 枚举常量 浮点数不能作为编译时常量,因为浮点数的精度在不同的硬件平台上可能会有差异。
  1. 编译时和运行时的区别是什么?
  • 编译时是指程序被编译器处理生成可执行文件的阶段。在这个阶段,模板会被根据类型或非类型模板参数进行实例化。
  • 运行时是指程序执行的阶段,在这个阶段,所有的变量、对象和指令都会实际运行。

问题:编译时与运行时的边界是什么?
编译时发生的事情是静态的,例如模板实例化和优化。而运行时则动态处理输入数据、调用函数并执行指令。非类型模板参数的特殊之处就在于它们能让编译器根据编译时的常量生成不同的代码路径。

  1. 在实际项目中,非类型模板参数的常见应用场景是什么?
    非类型模板参数主要用于:
  • 固定大小数组:用于处理编译时已知的数组大小,减少运行时的检查和动态分配。
  • 元编程:实现编译时计算,例如递归的阶乘、斐波那契数列。
  • 策略选择:根据编译时常量选择不同的算法或实现,从而提高性能。

问题:什么时候我应该选择使用非类型模板参数,而不是简单的函数或类?
当你的算法或数据结构依赖于编译时的常量值,并且你希望编译器在编译时生成不同的代码,而不是在运行时进行选择时,非类型模板参数是理想的选择。

二、模板的特化

1.概念

模板特化(template specialization) 是一种为特定类型或值定制模板的机制。通常,模板是通用的,适用于任何类型或值。但有时希望为某些特定类型或值提供不同的实现或对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理。这时可以使用模板特化。

// 函数模板 -- 参数匹配
template <class T>
bool Less(T left, T right)
{
    return left < right;
}
int main()
{
    cout << Less(1, 2) << endl; // 可以比较,结果正确
    Date d1(2022, 7, 7);
    Date d2(2022, 7, 8);
    cout << Less(d1, d2) << endl; // 可以比较,结果正确
    Date *p1 = &d1;
    Date *p2 = &d2;
    cout << Less(p1, p2) << endl; // 可以比较,结果错误
    return 0;
}

p1指向的d1显然小于p2指向的d2对象,但是Less内部并没有比较p1和p2指向的对象内容,而比较的是p1和p2指针的地址,这就无法达到预期而错误。此时,就需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。

2.函数模板特化

函数模板的特化步骤:

  1. 必须要先有一个基础的函数模板
  2. 关键字template后面接一对空的尖括号<>
  3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
  4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
#include <iostream>
using namespace std;

// 定义 Date 类
class Date
{
public:
    int year;
    int month;
    int day;

    // 构造函数
    Date(int y, int m, int d) : year(y), month(m), day(d) {}

    // 重载 < 运算符,用于比较两个 Date 对象
    bool operator<(const Date &other) const
    {
        if (year != other.year)
            return year < other.year;
        if (month != other.month)
            return month < other.month;
        return day < other.day;
    }
};

// 函数模板 -- 参数匹配
template <class T>
bool Less(T left, T right)
{
    return left < right; // 使用 < 运算符比较两个 T 类型的对象
}

// 对 Less 函数模板进行特化,专门处理 Date* 指针类型
template <>
bool Less<Date *>(Date *left, Date *right)
{
    return *left < *right; // 解引用指针,比较指针指向的 Date 对象的内容
}

int main()
{
    // 比较两个整数,调用通用模板
    cout << Less(1, 2) << endl; // 输出 1(true),因为 1 < 2

    // 创建两个 Date 对象
    Date d1(2022, 7, 7);
    Date d2(2022, 7, 8);

    // 比较两个 Date 对象,调用通用模板,使用 < 运算符
    cout << Less(d1, d2) << endl; // 输出 1(true),因为 d1 < d2

    Date *p1 = &d1;
    Date *p2 = &d2;

    // 比较两个 Date* 指针,调用特化后的版本
    cout << Less(p1, p2) << endl; // 输出 1(true),因为特化版本解引用指针后比较 d1 和 d2

    return 0;
}

3.类模板特化

(1)全特化

全特化即是将模板参数列表中所有的参数都确定化。

#include <iostream>
using namespace std;

// 通用模板类定义,适用于任意类型 T1 和 T2
template <class T1, class T2>
class Data
{
public:
    // 构造函数,输出"Data<T1, T2>",表明使用的是通用模板
    Data()
    {
        cout << "Data<T1, T2>" << endl;
    }

private:
    T1 _d1;
    T2 _d2;
};

// 针对特定类型 <int, char> 的全特化
template <>
class Data<int, char>
{
public:
    // 构造函数,输出"Data<int, char>",表明使用的是特化版本
    Data()
    {
        cout << "Data<int, char>" << endl;
    }

private:
    int _d1;
    char _d2;
};

// 测试函数,用于创建不同类型的 Data 对象
void TestVector()
{
    // 创建 Data<int, int> 对象,调用的是通用模板版本
    Data<int, int> d1;

    // 创建 Data<int, char> 对象,调用的是特化的版本
    Data<int, char> d2;
}

int main()
{
    TestVector(); // 运行测试函数
    return 0;
}

(2)偏特化

偏特化:任何针对模版参数进一步进行条件限制设计的特化版本
偏特化是指模板的部分特化,它只对模板参数中的部分类型进行定制,而保持其他部分为泛型。这与全特化(完全特化)不同,全特化是针对特定的类型组合进行完全独立的实现。
偏特化允许我们为一部分类型组合定制实现,而不必对所有类型参数进行特化。这样可以在保留通用模板的同时,针对某些特殊情况进行优化或修改行为。

#include <iostream>
using namespace std;

// 通用模板类 Data 的定义
template <class T1, class T2>
class Data
{
public:
    Data()
    {
        cout << "Data<T1, T2>" << endl;
    }

private:
    T1 _d1;
    T2 _d2;
};

// 将第二个模板参数特化为 int 类型
template <class T1>
class Data<T1, int>
{
public:
    // 构造函数:创建 Data 对象时输出 "Data<T1, int>"
    Data()
    {
        cout << "Data<T1, int>" << endl;
    }

private:
    T1 _d1;  // 成员变量类型为 T1
    int _d2; // 成员变量类型为 int
};

// 将两个参数偏特化为指针类型,即当两个模板参数均为指针类型时使用该特化版本
template <typename T1, typename T2>
class Data<T1 *, T2 *>
{
public:
    // 构造函数:创建 Data 对象时输出 "Data<T1*, T2*>"
    Data()
    {
        cout << "Data<T1*, T2*>" << endl;
    }

private:
    T1 _d1; // 成员变量类型为指针类型的 T1
    T2 _d2; // 成员变量类型为指针类型的 T2
};

// 将两个参数偏特化为引用类型,即当两个模板参数均为引用类型时使用该特化版本
template <typename T1, typename T2>
class Data<T1 &, T2 &>
{
public:
    // 构造函数:接受两个引用类型的参数并进行初始化,同时输出 "Data<T1&, T2&>"
    Data(const T1 &d1, const T2 &d2)
        : _d1(d1), _d2(d2)
    {
        cout << "Data<T1&, T2&>" << endl;
    }

private:
    const T1 &_d1; // 成员变量类型为 T1 的常量引用
    const T2 &_d2; // 成员变量类型为 T2 的常量引用
};

// 测试函数
void test2()
{
    // 使用特化版本,第二个模板参数为 int
    Data<double, int> d1; // 输出 "Data<T1, int>"

    // 使用基础模板,没有进行特化
    Data<int, double> d2; // 输出 "Data<T1, T2>"

    // 使用特化版本,当两个模板参数均为指针类型时
    Data<int *, int *> d3; // 输出 "Data<T1*, T2*>"

    // 使用特化版本,当两个模板参数均为引用类型时
    Data<int &, int &> d4(1, 2); // 输出 "Data<T1&, T2&>"
}

(3)类模板特化应用示例

#include <vector>
#include <algorithm>
using namespace std;

// 通用的 Less 模板类
template <class T>
struct Less
{
    bool operator()(const T &x, const T &y) const
    {
        return x < y;
    }
};

// 对 Less 类模板进行特化,处理 Date* 类型
template <>
struct Less<Date *>
{
    // 重载运算符 (),比较两个指向 Date 对象的指针
    bool operator()(Date *x, Date *y) const
    {
        return *x < *y; // 比较指针所指向的 Date 对象
    }
};

int main()
{
    // 假设 Date 类已经定义并实现了 < 运算符
    Date d1(2022, 7, 7);
    Date d2(2022, 7, 6);
    Date d3(2022, 7, 8);

    // 创建一个存储 Date 对象的向量 v1
    vector<Date> v1 = {d1, d2, d3};
    // 使用 Less<Date> 进行排序
    sort(v1.begin(), v1.end(), Less<Date>());

    // 创建一个存储 Date 指针的向量 v2
    vector<Date *> v2 = {&d1, &d2, &d3};
    // 使用特化后的 Less<Date*> 进行排序
    sort(v2.begin(), v2.end(), Less<Date *>());

    return 0;
}

三、模板分离编译

1.概念

  • 分离编译(Separate Compilation)是指将一个程序的各个部分(通常是源代码文件)单独编译成目标文件(如 .obj 文件),然后在链接阶段将所有目标文件链接为一个最终的可执行文件。
  • 分离编译的主要好处是提高编译效率:当修改了某个源文件时,只需重新编译修改后的文件,而不需要重新编译整个项目。
    在这里插入图片描述

2.模板的分离编译

  • 模板的分离编译是指将模板代码分离到不同的源文件中进行编译。但是,模板的分离编译和普通的分离编译略有不同,因为模板的定义和实例化在编译时必须是可见的,这使得模板代码的编译和管理变得复杂。
  • 有几种方法可以实现模板的分离编译:
  1. 将模板定义放在头文件中:
    通常,模板的定义和声明都放在头文件中,以确保编译器在编译每个使用模板的源文件时能够看到模板的完整定义。这是最常见的解决方案。
  2. 显式实例化: 模板的定义仍然可以放在 .cpp 文件中,但需要在这个文件中显式地为你需要的模板参数实例化模板。方法不实用,不推荐使用。

模版总结

  • 优点
  1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
  2. 增强了代码的灵活性
  • 缺点
  1. 模板会导致代码膨胀问题,也会导致编译时间变长
  2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

微带结环行器仿真分析+HFSS工程文件

微带结环行器仿真分析HFSS工程文件 工程下载&#xff1a;微带结环行器仿真分析HFSS工程文件 我使用HFSS版本的是HFSS 2024 R2 参考书籍《微波铁氧体器件HFSS设计原理》和视频微带结环行器HFSS仿真 1、环形器简介 环行器是一个有单向传输特性的三端口器件&#xff0c;它表明…

使用Qt编程QtNetwork无法使用

使用 VS 构建 Qt 项目时 QtNetwork 无法使用的问题 - 摘叶飞镖 - 博客园 (cnblogs.com) 另外,强烈建议在使用QNetworkAccessManager之前看看这篇文章: Qt 之 QNetworkAccessManager踏坑记录-CSDN博客 C Qt开发&#xff1a;QNetworkAccessManager网络接口组件 阅读目录 1.1 …

在Ubuntu上运行QtCreator相关程序

背景&#xff1a;希望尝试在Linux系统上跑一下使用QtCreator相关的程序&#xff0c;因为有一些工作岗位要求有Linux上使用Qt的经验。 (1)我是把Windows上的程序移过来的&#xff0c;Windows上文件名称是不区分大小写的。 而Ubuntu上是区分的 所以一部分头文件需要进行修改&am…

idea创建SpringBoot项目

目录 1. 新建一个SpringBoot项目 2. 使用Springboot官网创建项目 3. 使用阿里云地址创建SpringBoot项目 4. 使用maven创建SpringBoot项目 5. 在Idea中隐藏指定文件/文件夹 1. 新建一个SpringBoot项目 Springboot2 要求jdk版本: 1.8 maven: 3.3 内嵌的tomcat: tomcat9 我们…

深度学习(一)-感知机+神经网络+激活函数

深度学习概述 深度学习的特点 优点 性能更好 不需要特征工程 在大数据样本下有更好的性能 能解决某些传统机器学习无法解决的问题 缺点 小数据样本下性能不如机器学习 模型复杂 可解释性弱 深度学习与传统机器学习相同点 深度学习、机器学习是同一问题不同的解决方法 …

11.5.软件系统分析与设计-面向对象的程序设计与实现

面向对象的程序设计与实现 设计模式 Java代码 C代码

SQL进阶技巧:每年在校人数统计 | 区间重叠问题

目录 0 问题分析 1 数据准备 2 问题分析 3 小结 区间重叠问题 0 问题分析 有一个录取学生人数表 in_school_stu,记录的是每年录取学生的人数及录取学生的学制,计算每年在校学生人数。 1 数据准备 create table in_school_stu as ( select stack(5,1,2001,2,1200,2,2000…

UML的图及其他图补充

一、UML图 1.类图 ‌类图‌是统一建模语言&#xff08;UML&#xff09;中的一种静态结构图&#xff0c;主要用于描述软件系统的静态结构。它显示了模型中的类、类的内部结构以及它们与其他类的关系。类图是面向对象建模的主要组成部分&#xff0c;用于对系统的词汇进行建模、对…

SigLIP——采用sigmoid损失的图文预训练方式

SigLIP——采用sigmoid损失的图文预训练方式 FesianXu 20240825 at Wechat Search Team 前言 CLIP中的infoNCE损失是一种对比性损失&#xff0c;在SigLIP这个工作中&#xff0c;作者提出采用非对比性的sigmoid损失&#xff0c;能够更高效地进行图文预训练&#xff0c;本文进行…

93. UE5 GAS RPG 应用负面效果表现

在上一篇文章里&#xff0c;我们实现了添加负面效果GE&#xff0c;并且在添加GE时&#xff0c;也会给角色应用一个负面效果标签作为标识。在这一篇里&#xff0c;我们将通过负面效果标签标识&#xff0c;应用角色身上展现对应的负面效果的表现。 我们将在这篇文章里添加一个自定…

【c++进阶[五]】list相关接口介绍及list和vector的对比

&#x1f493;博主CSDN主页::Am心若依旧&#x1f493; ⏩专栏分类c从入门到精通⏪ &#x1f69a;代码仓库:青酒余成&#x1f69a; &#x1f339;关注我&#x1faf5;带你学习更多c   &#x1f51d;&#x1f51d; 1.前言 本章重点 本章重点讲解list的接口函数的熟悉&#xf…

Linux-RPM与YUM

目录 前言&#xff1a; rpm包的管理 rpm包的简单查询指令 ​编辑 rpm包名的基本格式 rpm包名基本格式 ​编辑 卸载rpm包 细节问题 安装rpm包 yum yum的基本指令 安装指定的yum包 yum报错 问题描述&#xff1a; 解决方法&#xff1a; 前言&#xff1a; Linux操…

电脑硬盘数据丢失了怎么恢复?简单实用的硬盘数据找回的方法

我们的电脑使用硬盘作为存储设备来保存数据&#xff0c;硬盘里的数据是存储在扇区上&#xff0c;这些存储数据的单元则位于表面有磁性材料的旋转的盘片上。硬盘内部的磁头悬浮于高速旋转的盘片上&#xff0c;用于读写和检索数据。 假如我们使用电脑时不小心删除了某个文件&…

Vue3使用Uni-ui的popup弹出层组件

由于uni-ui中有些组件文档的基于vue2编写的&#xff0c;比如popup组件 下面是vue3的写法 除了文档中要求的aleterDialog外&#xff0c;还得利用v-if设置一个isDialog判断 // template // script 解决

Linux基础2-权限2(操作权限,粘滞位,umask,目录文件的rwx权限)

上篇内容&#xff1a;Linux基础2-权限1(用户&#xff0c;权限是什么&#xff1f;)-CSDN博客 目录 一. 权限的操作&#xff08;命令&#xff09; 1.1 chmod 1.2 chown 1.3 chgrp 二. 粘滞位 三. umask&#xff08;遮掩码&#xff09; 四. 目录文件的 r w x 权限 一. 权限…

Ubuntu22.04版本左右,开机自动启动脚本

Ubuntu22.04版本左右&#xff0c;开机自动启动脚本 1. 新增/lib/systemd/system/rc-local.service中[Install]内容 vim /lib/systemd/system/rc-local.service 按 i 进入插入模式后&#xff0c;新增内容如下&#xff1a; [Install] WantedBymulti-user.target Aliasrc-local.…

如何读.Net Framework 的源码?

.Net Framework的源码可以从这里下载 Download 也可以在线直接浏览 https://referencesource.microsoft.com 这里我们以System.IO.Directory.CreateDirectory函数为例&#xff0c;来说明如何去读.Net Framework的源码。 在ReferenceSource在线界面的搜索框里输入Directory.Cr…

分享基于PDF.JS的移动端PDF阅读器代码

一、前言 在之前的文章《分享基于PDF.js的pdf阅读器代码》里提到了PC端基于PDF.js的阅读器&#xff0c;本文将提供针对移动端的版本。 二、pdfViewer 为了能够直接使用&#xff0c;这里分享一下经过简单修改后能直接使用的pdfViewer代码&#xff1a; pdfViewer代码目录&…

CAN总线的位同步详细讲解

接收方数据采样 &#xff08;1&#xff09;CAN总线没有时钟线&#xff0c;总线上的所有设备通过约定波特率的方式确定每一个数据位的时长 &#xff08;2&#xff09;发送方以约定的位时长每隔固定时间输出一个数据位 &#xff08;3&#xff09;接收方以约定的位时长每隔固定…

Kafka 分布式消息系统详细介绍

Kafka 分布式消息系统 一、Kafka 概述1.1 Kafka 定义1.2 Kafka 设计目标1.3 Kafka 特点 二、Kafka 架构设计2.1 基本架构2.2 Topic 和 Partition2.3 消费者和消费者组2.4 Replica 副本 三、Kafka 分布式集群搭建3.1 下载解压3.1.1 上传解压 3.2 修改 Kafka 配置文件3.2.1 修改z…