【C++复习】模版类和模版函数
创始人
2025-05-28 02:49:00

模版

  • 写在前面
  • 模版
    • 函数模版
      • 函数模版的实例化
      • 模版参数匹配原则
    • 类模版
      • 类模板的实例化
    • 非类型模版参数
    • 模版特化
      • 函数模版特化
      • 类模版特化
        • 全特化
        • 偏特化
    • 模版分离编译
    • 模版总结

写在前面

泛型程序设计(英文:generic programming)是程序设计语言的一种风格或范式。 泛型允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型。 各种程序语言和其编译器、运行环境对泛型的支持均不同。

模版就是泛型编程的工具,也是C++面向对象的一个体现。我们需要掌握的就是模版是什么、分类、模版怎么定义、模版怎么特化、模版分离编译的解决办法。

模版

函数模版

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

格式:

template
//typename 是用来定义模版参数的关键字,也可以使用class,但不能用struct
函数的定义...

举例:

template
void Swap(T& a, T& b)
{T tmp = a;a = b;b = tmp;
}

理解:
函数模版本身不是函数,是编译器使用特定的方式来产生特定的具体类型函数的模版。
在编译器编译阶段,对于函数模版的使用,编译需要根据传入的实参类型来推演生成对应类型的函数,以供调用。

函数模版的实例化

用不同类型的参数使用函数模版,就是函数模版的实例化。
分为:隐式实例化和显式实例化。

  • 隐式实例化:编译器自己推到模版参数的实际类型。

    template
    void Swap(T& a, T& b)
    {T tmp = a;a = b;b = tmp;
    }int main(){int a1 = 10, a2 = 20;double d1 = 10.00, d2 = 20.00;Swap(a1, a2);Swap(d1, d2);return 0;
    }
    

    在模版中,编译器一般不会进行隐式类型转换,因为编译器要依仗传入参数的类型来进行推演,最终在编译阶段实例化出函数。所以如果传入类型不同的两个值,并且使用隐式实例化,会报错。

    改正:1. 用户自己强转 2. 使用显式实例化

  • 显式实例化:函数名后<>中指定模版参数的实际类型。

int main()
{int a1 = 10, a2 = 20;int d1 = 10.00, d2 = 20.00;Swap(a1, a2);Swap(d1, d2);Swap(a1, d2);//可以return 0;
}

​ 如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功就会报错。

模版参数匹配原则

  1. 一个非模版函数可以与一个同名的函数模版同时存在,而且该函数模版还可以被实例化为这个非模版函数。
  2. 对于非模版函数和同名函数模版,如果其他条件都相同,在调用时会优先调用非模版函数而不会从该模版产生出一个实例。如果模版可以产生一个具有更好匹配的函数,那么将选择模版。
  3. 函数模版不允许自动类型转换,但是普通函数可以进行自动类型转换。

类模版

定义格式:

template
class ClassName{//
};

注意:
类模版中的函数放在类外定义时,需要在前面加模版参数列表,如下:

template
class A{
private:int a_;
public:A(int a = 10) :a_(a){}~A();//...
}template
A::~A(){//...
}

类模板的实例化

类模板的实例化与函数模板的实例化不同,类模板的实例化需要在类模板的名字后面+<实例化类型>。

类模板的名字不是真正的类,实例化的结果才是真正的类。

(上面的例子里,A不是真正的类,A才是真正的类。)

非类型模版参数

模版参数分为两种,分别是类型形参 和 非类型形参。

  • 类型形参:出现在模版参数列表中,跟在class 或者 typename 之后的参数类型名称。
  • 非类型形参:用一个常量作为类(或函数)模版的一个参数,在类(函数)模版中可以将该参数当成常量来使用。

格式:

template
class A{
private:size_t size_;int a_;
public:size_t size() const { return _size; }...
}

注意:

  1. 浮点数、类对象以及字符串不允许作为非类型模版参数。
  2. 非类型的模版参数必须在编译期间就能确认结果。

模版特化

通常情况下,可以使用模版实现一些与类型无关的代码,但对于一些很特殊的类型可能会得到一些错误的结果,或者对于特殊的类型必须经过特殊处理。这时候就需要用到模版特化。

比如一个小于的模版函数。

template
bool ComLess(T& left, T& right){return left < right;
}
class Date{
private:int year_;int month_;int day_;
public:Date(int year = 0,int month = 1,int day = 1):year_(year),month_(month),day_(day){}
};int main(){cout << ComLess(1, 2) << endl;cout << ComLsee(Date(1000,1,1), Date()) << endl;//如果Date类中没有 <的重载,那么就无法比较return 0;	
}

但是如果你想对Date 类的指针,或者int 的指针进行比较,由于ComLess 内部并没有比较指向的对象的内容,而是会直接比较指针的地址,就无法达到比较的目的。

所以就需要对模版进行特化。在原模版类的基础上,针对特殊类型所进行特殊化的实现方式。模版特化中分为函数模版特化 和 类模版特化。

函数模版特化

特化步骤:

  1. 必须先有一个基础的函数模版。
  2. 关键字template 后面接一对空的<>
  3. 函数名后跟 <你想指定特化的类型>
  4. 函数形参表,必须要和模版参数的基础参数类型完全相同,如果不同编译器会报错。

举例:

template
bool ComLess(T& left, T& right) const
{return left < right;
}template<>
bool ComLess(int* pa1, int* pa2) const
{return *pa1 < *pa2;
}

注意:
一般情况下如果函数模版碰到了不能处理或者处理错误的类型,为了实现简单,通常都是将这个函数直接给出。

类模版特化

全特化

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

//未经特化的类模版
template
class Data{
private:T1 d1_;T2 d2_;
public:Data(){}
};//全特化:
template<>
class Data
{
private:int d1_;char d2_;
public:...
}

偏特化

偏特化指任何针对模版参数进一步进行条件限制设计的特化版本。就是函数模版的某个类型被限制成特定的类型了。
偏特化并不仅仅是指的是特化部分参数,而是针对模版参数更进一步的条件限制所设计出来的一个特化版本。

有两种表现方法:

  1. 部分特化。即将模版参数类表中的一部分参数特化。

    template
    class Data
    {
    private:T1 d1_;int d2_;
    }
    
  2. 参数更进一步的限制。比如把参数偏特化为指针类型,把参数偏特化为引用类型。如下

    //偏特化为指针类型
    template
    class Data
    {
    private:T1 d1_;T2 d2_;
    public:Data(){cout << "Data "<< endl;}
    }//偏特化为引用类型
    template
    class Data
    {
    private:const T1& d1_;const T2& d2_;
    }
    

模版分离编译

分离编译:一个程序或者是项目,由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最终将所有的目标文件链接起来形成单一的可执行文件的过程。

模版的分离编译:模版的声明和定义分离开,在头文件中进行声明,在源文件中完成定义。

C/C++程序要运行,需要经历以下步骤:
预处理->编译->汇编->链接。

编译阶段做的工作是:堆程序按照语言特性进行词法、语法、语义分析,错误检查无误后生成汇编代码。头文件不参与编译,编译器对工程中的多个源文件是分离开单独编译的。

链接:将多个.o文件(.obj) 文件合并成一个,并处理没有解决的地址问题。

如果我们定一个test.h 文件,在其中进行模版类(函数)的声明,定一个test.cc 文件,其中进行模版类(函数)的实现;定义一个main.cc 文件,其中进行该函数的调用。

那么在a.cpp 中,编译器没有看到对模版的实例化,所以不会生成具体的加法函数;

在main.o(main.obj)文件中调用了模版,指定了参数的类型。在链接的时候编译器才会找到他们的地址,但是这两个函数没有实例化,所以没有生成具体的代码,所以链接的时候会报错。

解决办法:

  1. 将声明和定义放到一个 .hpp 文件中, 或者放到 .h文件中也可以。推荐
  2. 模版定义的位置显式实例化。不实用。

模版总结

优点:

  1. 模版复用了代码,节省资源,更快的迭代开发,这也是C++的标准模版库的出现的原因。
  2. 增强了代码的灵活性。

缺点:

  1. 模版会导致代码膨胀,也会导致编译的时间变长。
  2. 出现模版编译错误时,错误信息经常很凌乱,不容易定位错误。

模版小节完。

相关内容

热门资讯

【实验报告】实验一 图像的... 实验目的熟悉Matlab图像运算的基础——矩阵运算;熟悉图像矩阵的显示方法࿰...
MATLAB | 全网最详细网... 一篇超超超长,超超超全面网络图绘制教程,本篇基本能讲清楚所有绘制要点&#...
大模型落地比趋势更重要,NLP... 全球很多人都开始相信,以ChatGPT为代表的大模型,将带来一场NLP领...
Linux学习之端口、网络协议... 端口:设备与外界通讯交流的出口 网络协议:   网络协议是指计算机通信网...
kuernetes 资源对象分... 文章目录1. pod 状态1.1 容器启动错误类型1.2 ImagePullBackOff 错误1....
STM32实战项目-数码管 程序实现功能: 1、上电后,数码管间隔50ms计数; 2、...
TM1638和TM1639差异... TM1638和TM1639差异说明 ✨本文不涉及具体的单片机代码驱动内容,值针对芯...
Qt+MySql开发笔记:Qt... 若该文为原创文章,转载请注明原文出处 本文章博客地址:https://h...
Java内存模型中的happe... 第29讲 | Java内存模型中的happen-before是什么? Java 语言...
《扬帆优配》算力概念股大爆发,... 3月22日,9股封单金额超亿元,工业富联、鸿博股份、鹏鼎控股分别为3.0...
CF1763D Valid B... CF1763D Valid Bitonic Permutations 题目大意 拱形排列࿰...
SQL语法 DDL、DML、D... 文章目录1 SQL通用语法2 SQL分类3 DDL 数据定义语言3.1 数据库操作3.2 表操作3....
文心一言 VS ChatGPT... 3月16号,百度正式发布了『文心一言』,这是国内公司第一次发布类Chat...
CentOS8提高篇5:磁盘分...        首先需要在虚拟机中模拟添加一块新的硬盘设备,然后进行分区、格式化、挂载等...
Linux防火墙——SNAT、... 目录 NAT 一、SNAT策略及作用 1、概述 SNAT应用环境 SNAT原理 SNAT转换前提条...
部署+使用集群的算力跑CPU密... 我先在开头做一个总结,表达我最终要做的事情和最终环境是如何的,然后我会一...
Uploadifive 批量文... Uploadifive 批量文件上传_uploadifive 多个上传按钮_asing1elife的...
C++入门语法基础 文章目录:1. 什么是C++2. 命名空间2.1 域的概念2.2 命名...
2023年全国DAMA-CDG... DAMA认证为数据管理专业人士提供职业目标晋升规划,彰显了职业发展里程碑及发展阶梯定义...
php实现助记词转TRX,ET... TRX助记词转地址网上都是Java,js或其他语言开发的示例,一个简单的...
【分割数据集操作集锦】毕设记录 1. 按要求将CSV文件转成json文件 有时候一些网络模型的源码会有data.json这样的文件里...
Postman接口测试之断言 如果你看文字部分还是不太理解的话,可以看看这个视频,详细介绍postma...
前端学习第三阶段-第4章 jQ... 4-1 jQuery介绍及常用API导读 01-jQuery入门导读 02-JavaScri...
4、linux初级——Linu... 目录 一、用CRT连接开发板 1、安装CRT调试工具 2、连接开发板 3、开机后ctrl+c...
Urban Radiance ... Urban Radiance Fields:城市辐射场 摘要:这项工作的目标是根据扫描...
天干地支(Java) 题目描述 古代中国使用天干地支来记录当前的年份。 天干一共有十个,分别为:...
SpringBoot雪花ID长... Long类型精度丢失 最近项目中使用雪花ID作为主键,雪花ID是19位Long类型数...
对JSP文件的理解 JSP是java程序。(JSP本质还是一个Servlet) JSP是&#...
【03173】2021年4月高... 一、单向填空题1、大量应用软件开发工具,开始于A、20世纪70年代B、20世纪 80年...
LeetCode5.最长回文子... 目录题目链接题目分析解题思路暴力中心向两边拓展搜索 题目链接 链接 题目分析 简单来说࿰...