C++:模板详解

模板详解

  • 1.函数模板
    • 1.概念
    • 2.语法
    • 3.原理
    • 4.实例化
      • 1.隐式实例化
      • 2.显示实例化
    • 5.匹配原则
  • 2.类模板
    • 1.格式
    • 2.实例化
  • 3.非类型模板参数
    • 注意点
  • 4.特化
    • 1.概念
    • 2.函数模板特化
      • 1.前提
      • 2.语法说明
      • 3.示例
    • 3.类模板特化
      • 1.全特化
      • 2.偏特化/半特化
      • 3.选择顺序
    • 4.按需实例化
  • 5.模板的分离编译
    • 1.分离编译
    • 2.模板的分离编译
      • 问题
      • 分析问题
      • 解决方案
    • 3.模板的两次编译
  • 6.总结
    • 优点
    • 缺点

1.函数模板

1.概念

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

2.语法

template<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表){ ...}
template<typename T>
void Swap( T& left, T& right)
{T temp = left;left = right;right = temp;
}

3.原理

函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器。

4.实例化

1.隐式实例化

通过传入参数的类型让编译器自己推理。

2.显示实例化

自己手动写出传入的类型。在函数名后的<>中指定模板参数的实际类型

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

int main()
{int a = 10;double b = 20.0;// 显式实例化Add<int>(a, b);return 0;
}

5.匹配原则

有现成的(现有的函数重载)使用现成的,没有现成的就使用模板实例化。

注意:模板函数不允许隐式类型转换,但普通函数可以。

int Add(int left, int right)
{return left + right;
}template<class T1, class T2>
T1 Add(T1 left, T2 right)
{return left + right;
}void Test()
{Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数,而不会进行隐式类型转换
}

2.类模板

1.格式

template<class T1, class T2, ..., class Tn>
class 类模板名
{// 类内成员定义
}; 

2.实例化

类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。

vector<int> s1;

3.非类型模板参数

在之前模板参数都是给的类型,比如Int,double之类,但是我们也可以在模板参数之中给一个常量。

namespace test
{// 定义一个模板类型的静态数组template<class T, size_t N = 10>class array{public:T& operator[](size_t index){return _array[index];}const T& operator[](size_t index)const{return _array[index];}size_t size()const{return _size;}bool empty()const{return 0 == _size;}private:T _array[N];size_t _size;}}

比如在实现静态顺序表时,其的空间大小是确定的,使用N来确定,如果需要同时创建一个数组大小为10和大小为1000的顺序表呢?我们只能将N改为1000来满足需求,但是这样会浪费一定的空间。如果可以将常量也可以作为模板参数使用,那么我们分别需要一个数组大小为10和数组大小为1000的顺序表时,可以通过传入参数来确定大小,从而不会浪费多余的空间。

注意点

1.非类型模板参数默认只能给整型家族的,直到C++20以后才支持double,string等

2.非类型模板参数必须在编译的时候就能确定大小因为在编译期就需要开辟空间。

4.特化

由于模板,我们可以实现出与类型无关的代码,来实现相同的功能,但是对于某些类型,使用模板的逻辑会产生我们预期之外的结果,这种类型需要特殊处理,因此产生了特化。

1.概念

在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化与类模板特化

2.函数模板特化

1.前提

必须有函数模板—>模板特化不能单独存在

2.语法说明

template<>
返回值类型 函数名<需要特化的类型> (形参1,....)
{
}

注意:函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。

3.示例

template<class T>
bool Less(T x, T y)
{cout << "Less(T x, T y)" << endl;return x < y;
}template<>
bool Less<int*>(int* x, int* y)
{cout << "Less<int*>(int* x, int* y)" << endl;return *x < *y;
}
template<class T>
bool Less(T* x, T* y)
{cout << "Less(T* x, T* y)" << endl;return *x < *y;
}
template<class T>
bool Less(T* x, T* y)
{cout << "Less(T* x, T* y)" << endl;return *x < *y;
}
int main()
{int* a = new int(4);int* b = new int(5);cout << Less(a, b) << endl;double* c = new double(1.1);double* d = new double(2.2);cout << Less(c, d) << endl;return 0;
}

在这里插入图片描述

总结:尽量不要使用函数模板的特化,因为语法等各种比较复杂,如果有需要,直接函数重载即可。

注意:其调用规则为有重载调用重载,没有重载再看特化中是否有符合的,如果特化中没有调用模板实例化的函数。

3.类模板特化

1.全特化

全特化就是把该类模板的所有参数特化(确定化)。

//函数模板
template<class T1, class T2>
class Data
{
public:Data() {cout<<"Data<T1, T2>" <<endl;}
private:T1 _d1;T2 _d2;
};//全特化
template<>
class Data<int, char>
{
public:Data() {cout<<"Data<int, char>" <<endl;}
private:int _d1;char _d2;
};

2.偏特化/半特化

偏特化有两种形式:

1.部分参数特化部分特化就是把该类模板的一部分参数确定化。
2.参数限制(即类型特化)参数限制特化就是对该类模板的参数符合一定格式要求的确定化。(比如正常类型走模板实例化,指针类型走特化等)
//函数模板
template<class T1, class T2>
class Data
{
public:Data() { cout << "Data<T1, T2>" << endl; }
private:T1 _d1;T2 _d2;
};//参数半特化
template<class T1>
class Data<T1, char>
{
public:Data() { cout << "Data<T1, char>" << endl; }
private:T1 _d1;char _d2;
};//类型限制
template<class T1,class T2>
class Data<T1*, T2*>
{
public:Data() { cout << "Data<T1*, T2*>" << endl; }
private:T1 _d1;char _d2;
};int main()
{Data<int, int> d1;Data<int, char> d2;Data<int*, double*> d3;return 0;
}

在这里插入图片描述

3.选择顺序

有现成的(即特化好的)使用现成的,没有现成的使用模板实例化出来的。

4.按需实例化

按需实例化就是编译器在实例化时用到哪个函数才会实例化哪个函数。

情景:

//array.h
namespace test
{// 定义一个模板类型的静态数组template<class T, size_t N = 10>class array{public:T& operator[](size_t index) { size(1); //此处是有一个错误的!!!return _array[index]; }size_t size()const { return _size; }bool empty()const { return 0 == _size; }private:T _array[N];size_t _size = 0;};
}
//test.cpp
int main()
{test::array<int, 100> a;cout << a.empty() << endl;cout << a.size() << endl;a[10];return 0;
}

上面的代码如果运行起来是没有报错的,因为模板是一个半成品,编译器在预处理之后,编译之前会对模板的大体框架进行粗略检查,比如有无分号括号等,但是不会检查内部细节,比如上面size()中参数个数不一致,在创建a这个对象时实例化了默认构造函数,由于我们只调用empty和size(),因此只实例化了这两个函数,没有实例化错误的operator[],就不会报错,除非我们调用它。

5.模板的分离编译

1.分离编译

分离编译就是每一个源文件都是独立编译生成其各自的目标文件,然后所有的目标文件在链接过程中才会整合最后形成可执行文件。

2.模板的分离编译

如果一个类使用了模板,然后其的函数声明和定义分开,声明在.h文件,定义在.cpp文件,那么运行后编译器会报错:无法解析的外部符号。

问题

//array.h
#pragma once
#include<iostream>
using namespace std;
namespace Array
{// 只支持整形做非类型模板参数// 非类型模板参数  类型 常量// 类型模板参数   class 类型template<class T, size_t N = 10>class array{public:size_t size() const;private:T _array[N];size_t _size = 0;};void func();
}//array.cpp
#include"array.h"
namespace Array
{template<class T, size_t N>size_t array<T, N>::size() const{T x = 0;x += N;return _size;}void func(){cout << "I am func" << endl;}
}//test.cpp
#include"array.h"
int main()
{Array::array<int, 100> a;cout << a.size() << endl;Array::func();
}

运行后报错:

在这里插入图片描述

分析问题

出现这种链接错误是因为找不到这个函数的地址。

可是我们已经定义了该函数,为什么会没有它的地址呢?

由于每一个文件都是分离编译生成对应的目标文件,然后再进行链接。

对于func函数,array.h在test.cpp文件中展开,由于有函数的声明,并且其也有定义,在生成符号表时会正确的加入符号表从而被查找到。

对于size函数,array.h在test.cpp文件中展开,其有函数声明,在编译时不会报语法错误,但是在链接时,由于调用了size(),因此需要去符号表找其对应的地址去调用,但是我们的size()定义时只是一个模板,并没有实例化出来,因此在符号表中无法找到对应的函数去调用,因此报链接错误。

那么我们明明已经在test.cpp中实例化了对象,为什么size()没有实例化呢?

因为每一个文件是分离编译的!!!只有test.cpp文件知道array这个类需要用int和100来实例化,但是array.cpp并不知道,因此也不会实例化test.cpp在链接时直接去找自然找不到。

解决方案

1.显示实例化

即在.cpp文件中加入这样的代码:

template
array<int, 100>;

这样可以显示告诉编译器需要实例化出的案例,这样这样在分离编译时,array.cpp才会实例化出对应的函数。

但是不推荐这样的写法。因为如果这样,那么每一次实例化出类型不同的类,都需要再次进行显示实例化。

2.将声明和定义写在同一个.h中(强烈推荐!!!)

因为array.h在test.cpp中展开时,由于声明和定义在一个文件,展开后也在一个文件,那么自然知道需要实例化成什么类型。

3.模板的两次编译

模板的两次编译是指:
第一次在预处理之后,编译之前,会进行按需实例化,第二次是在编译的时候对实例化的部分进行语法排查。

6.总结

优点

1.模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
2.增强了代码的灵活性

缺点

1.模板会导致代码膨胀问题,也会导致编译时间变长
2.出现模板编译错误时,错误信息非常凌乱,不易定位错误

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

力扣-分隔链表

题目 86. 分隔链表 思路 本来想直接在链表上进行修改&#xff0c;但是发现太乱。 定义两个新的空链表&#xff0c;一个保存小于 x 的节点&#xff0c;另一个保存大于等于 x 的节点。 public ListNode partition (ListNode head, int x){ListNode smallNode new ListNode(0…

lementui el-menu侧边栏占满高度且不超出视口

做了几次老是忘记&#xff0c;这次整理好逻辑做个笔记方便重复利用&#xff1b; 问题&#xff1a;elementui的侧边栏是占不满高度的&#xff1b;但是使用100vh又会超出视口高度不美观&#xff1b; 解决办法&#xff1a; 1.获取到侧边栏底部到视口顶部的距离 2.获取到视口的高…

Java集合面试题2024年4月20记录

一、集合的作用&#xff1f; 集合是可以存储一批类型不同的对象&#xff0c;针对集合的实现类有很多&#xff0c;作用都是一样的&#xff0c;即存储传输数据&#xff0c;但存储的数据结构不一样&#xff0c;其速度、安全性也不一样。 二、集合框架的组成&#xff1f; 1、接口…

运行django

确保app被注册 urls.py中编写url 视图对应关系 命令行启动 python manage.py runserver

MyBatis可以如何实现分页查询?

在 MyBatis 中实现分页查询&#xff0c;主要有以下几种方法&#xff1a; 1. 使用 MyBatis 分页插件&#xff1a; MyBatis 有一个非常流行的分页插件叫做 PageHelper。它允许你通过简单的配置就能实现分页查询。使用 PageHelper&#xff0c;你只需要在你的 Mapper 接口的方法上…

Bootstrap 5 保姆级教程(十一):模态框 提示框

一、模态框 1.1 创建模态框 以下实例创建了一个简单的模态框效果 &#xff1a; <div class"container mt-3"><h3>模态框实例</h3><p>点击按钮打开模态框</p><button type"button" class"btn btn-primary" d…

有哪些网站设计教程

网站设计教程是帮助人们学习如何设计和开发网站的资源&#xff0c;它们提供了从基础知识到高级技巧的全方位指导。无论您是初学者还是经验丰富的开发者&#xff0c;都可以从这些教程中获益。下面是一些广受欢迎的网站设计教程&#xff0c;它们涵盖了各种技术和工具&#xff1a;…

监督算法建模前数据质量检查

一、定义缺失值检测函数 def missing_values_table(df):# 总的缺失值mis_val df.isnull().sum()# 缺失值占比mis_val_percent 100 * df.isnull().sum() / len(df)# 将上述值合并成表mis_val_table pd.concat([mis_val, mis_val_percent], axis1)# 重命名列名mis_val_table_…

轻量级SQLite可视化工具Sqliteviz

什么是 Sqliteviz &#xff1f; Sqliteviz 是一个单页面离线优先的渐进式网络应用&#xff08;PWA&#xff09;&#xff0c;用于完全客户端的 SQLite 数据库或 CSV 文件的可视化。 所谓完全客户端&#xff0c;就是您的数据库永远不会离开您的计算机。使用 sqliteviz&#xff0c…

2024 抖音欢笑中国年之渲染技术实践与探索

SAR Creator是一款基于 Typescript 的高性能、轻量化的互动解决方案&#xff0c;目前支持了浏览器和跨端框架平台&#xff0c;服务于字节内部的各种互动业务。 前言 抖音在2024年春节期间推出了欢笑中国年系列活动&#xff0c;为用户带来了全新的体验和乐趣。而SAR Creator则为…

01 MySQL--概念、三范式、表、字段设计方法与规范

1. 定义 1.1 SQL的分类 DQL - 数据查询语言&#xff08;Data Query Language, DQL&#xff09;负责进行数据查询而不会对数据本身进行修改的语句。 SELECT、FROM、WHERE、GROUP BY、HAVING、ORDER BY。DDL - 数据定义语言 (Data Definition Language, DDL) 负责数据结构定义与…

【RV1106的ISP使用记录之基础知识】硬件连接关系与设备树的构建

RV1106具备2个mipi csi2 dphy硬件&#xff0c;1个VICAP硬件和1个ISP硬件。其中&#xff1a; 1、mipi csi2 dphy 用于对数据流的解析&#xff0c;支持MIPC,LVDS,DVP三种接口&#xff1b; 2、VICAP用于数据流的捕获&#xff1b; 3、ISP用于对图像数据进行处理&#xff1b; 这三个…

leetcode39--组合总数I

1. 题意 给一堆数&#xff0c;和一个目标和&#xff0c;求所有可能的不重复的组合。 组合总数 2. 题解 主要是去重&#xff0c;每次推入数到目标数组&#xff0c;将下次枚举的数的开头设为当前的下标就不会重复噜噜o_O 即如何避免 2 3 2 2\ 3\ 2 2 3 2 和 3 2 2 3\ 2\ 2 3…

文件夹变白之谜:原因、恢复与防范之道

在日常电脑使用中&#xff0c;我们有时会遇到一些文件夹突然变成白色文件的情况。这些文件夹看起来就像失去了原有的属性&#xff0c;无法被正常打开或访问。这种情况往往让人感到困惑和焦虑&#xff0c;因为文件夹中可能存储着重要的数据和信息。那么&#xff0c;文件夹为何会…

git代码管理常用命令以及示例

Git 是一个开源的分布式版本控制系统&#xff0c;用于跟踪文件的变更。以下是一些 Git 代码管理的常用命令及其示例&#xff1a; 1、初始化仓库 git init这会在当前目录创建一个新的 Git 仓库。 2、克隆仓库 git clone <repository-url>这会克隆一个远程仓库到本地。…

匿名对象 与 new delet初识

一.匿名对象 1.定义&#xff1a; 没有名称的临时创建的对象&#xff0c;通常用于临时操作或作为函数的实参或返回值。 2.声明周期与作用域&#xff1a; 仅仅在定义所在代码行中&#xff0c;执行完就销毁。 3.使用格式 类名(构造参数) 4.使用场景 临时调用成员函数 mid…

【Vue3】reactive对象类型的响应式数据

文章目录 reactive简介 reactive简介 用法: let xx reactive({xxx:“xxx”})返回值: 一个Proxy的对象&#xff0c;就是响应式对象特别注意: 数组也属于对象范畴&#xff0c;所以也能使用reactive&#xff0c;使其变成响应式数据reactive修改值时&#xff0c;不需要在值后写.v…

css 如何设置背景图片定位

在CSS中设置背景图片的位置&#xff0c;可以使用 background-position 属性。这个属性用于指定背景图像在元素背景区域中的起始位置。它可以接受多种类型的值&#xff0c;包括关键字、长度单位&#xff08;像素、百分比等&#xff09;以及组合值。 各种用法示例&#xff1a; 关…

MapReduce 机理

1.hadoop 平台进程 Namenode进程: 管理者文件系统的Namespace。它维护着文件系统树(filesystem tree)以及文件树中所有的文件和文件夹的元数据(metadata)。管理这些信息的文件有两个&#xff0c;分别是Namespace 镜像文件(Namespace image)和操作日志文件(edit log)&#xff…

书生浦语大模型实战训练营--第二期第六节--Lagent AgentLego 智能体应用搭建--homework

一、基础作业 1.完成 Lagent Web Demo 使用&#xff0c;并在作业中上传截图 根据以下命令启动成功&#xff01; 2.完成 AgentLego 直接使用部分&#xff0c;并在作业中上传截图 这是原图 使用AgentLego进行自动目标检测后&#xff0c;很明显图中的物体已经被识别出来了 二、…