当前位置: 首页 > news >正文

【C++】特化妙技与分文件编写 “雷区”

目录

  • 目录
  • 非类型模板参数
    • 非类型模板参数vs宏代换
  • 模板的特化
    • 函数模板的特化
    • 函数模板特化的坑
  • 类模板特化
    • 全特化
    • 偏特化
  • 模板分离编译
    • 原理
    • 解决方案
  • end

目录

非类型模板参数

模板参数可分为类型形参和非类型形参。

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

例如,我们要实现一个静态数组的类,就需要用到非类型模板参数。

template<class T, size_t N> //N:非类型模板参数
class StaticArray
{
public:size_t arraysize(){return N;}
private:T _array[N]; //利用非类型模板参数指定静态数组的大小
};

使用非类型模板参数后,我们就可以在实例化对象的时候指定所要创建的静态数组的大小了。

int main()
{StaticArray<int, 10> a1; //定义一个大小为10的静态数组cout << a1.arraysize() << endl; //10StaticArray<int, 100> a2; //定义一个大小为100的静态数组cout << a2.arraysize() << endl; //100return 0;
}

注意:非类型参数C++11只支持整形,C++20支持浮点数,所以到现在只支持这两个类型

非类型模板参数vs宏代换

这里我们回顾一下在C语言中我们学过一个叫宏的东西。

#include<iostream>
using namespace std;
#define N 10
int main()
{int a[N] = { 0 };cout << sizeof(a) / sizeof(a[0]) << endl;return 0;
}

在这里插入图片描述

  • 这里就是把数组的长度位置方式N这个宏,然后在预处理的阶段替换为宏的常量10,所以我们计算出数组的长度就是10.
  • 那接下来我们把上面静态数组的类也用宏来替换如何?
#include<iostream>
#define N 10
using namespace std;
template<class T> //N:非类型模板参数
class StaticArray
{
public:size_t arraysize(){return N;}
private:T _array[N]; //利用非类型模板参数指定静态数组的大小
};
int main()
{cout << StaticArray<int>().arraysize() << endl;return 0;
}

在这里插入图片描述

  • 可以看到确实是10这个长度

在这里插入图片描述

  • 但是现在我们还想实例化出一个20长度的数组可以吗,是不是不行了,因为我们只有一个模板参数
  • 所以宏和非类型模板参数相比,宏才固定死板了。

模板的特化

  • 我们通过一个例子来引出特化
bool IsEqual(T x, T y)
{return x == y;
}
  • 比如说这里我要判断两个变量是否相等。
cout << IsEqual(1, 1) << endl; //1
cout << IsEqual(1.1, 2.2) << endl; //0
  • 嗯,这样写确实可以判断两个变量的是否想等。我们这个函数模板好像可以完成所有的任务
  • 它的判断结果也是我们所预期的,但是我们也可能会这样去使用该函数模板:
char a1[] = "hehe";
char a2[] = "hehe";
cout << IsEqual(a1, a2) << endl; //0
  • 这里我们的本意是判断两个字符串是否相等,但是传了两个数组的地址,那我们比较的就是两个数组的地址是否相等,结构肯定是不符合预期的两个地址肯定不相等,但是我们肉眼看到,字符串值是相等的。

函数模板的特化

对于上述实例,我们知道当传入的类型是char时,应该依次比较各个字符的ASCII码值进而判断两个字符串是否相等,或是直接调用strcmp函数进行字符串比较,那么此时我们就可以对char类型进行特殊化的实现。

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

  • 对于上述实例char*类型的特化如下:
//基础的函数模板
template<class T>
bool IsEqual(T x, T y)
{return x == y;
}
//对于char*类型的特化
template<>
bool IsEqual<char*>(char* x, char* y)
{return strcmp(x, y) == 0;
}

函数模板特化的坑

  • 通过上面的例子,好像函数模板特化很有用,但是我们真的要多使用他吗?
    在这里插入图片描述
  • 现在我期望按照p1和p2的值来比较,但是我原来的函数模板如下图,不支持这样比较,因为new出来的空间是随机的,那么指针值(指向空间地址)也是随机的, 那么结果也是随机的
    我们现在希望按照指向空间的值来比较,所以我们现在可以使用模板特化

在这里插入图片描述

  • 为了不改变指针的值,我们应该对他加上const

在这里插入图片描述

  • 但是这里,并不能编译成功,因为我们的const在之前限定的是left(指向空间的值),正确写法应该是在*之后,修饰指针变量本身(指向的空间),所以这里很容易出错,而且还有比这个情况更复杂的情况
    在这里插入图片描述

综上写一个小小的函数模板特化稍不注意就有一个坑,如果在大型项目里面坑还要多,这个时候我们再来Debug就够痛苦了

  • 那有同学问,那还有什么办法,不用特化还能有什么方法可以按照我的意思来处理同一个函数的不同功能。
  • 有的兄弟有的,这个时候,我们回忆一下,如果函数模板和重载的普通函数同时存在,如果那个同名的函数是刚好完全匹配我们模板想要实例化的类型,我们模板有现成吃现成
    在这里插入图片描述

类模板特化

类模板的特化步骤:
1 . 首先必须要有一个基础的类模板。
2.关键字template后面接一对空的尖括号<>。
3.类名后跟一对尖括号,尖括号中指定需要特化的类型。

全特化

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

//普通模板
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;
};
void TestVector()
{
Data<int, int> d1;
Data<int, char> d2;
}
  • 当T1和T2分别是int和char时,我们若是想对实例化的类进行特殊化处理,那么我们就可以对T1和T2分别是double和int时的模板进行特化。

偏特化

  • 偏特化是指任何针对模板参数进一步进行条件限制设计的特化版本。

例如,对于以下类模板:

template<class T1, class T2>
class Date
{
public://构造函数Date(){cout << "Date<T1, T2>" << endl;}
private:T1 _D1;T2 _D2;
};

偏特化又可分为以下两种表现形式:
1、部分特化
我们可以仅对模板参数列表中的部分参数进行确定化。
例如,我们可以对T1为int类型的类进行特殊化处理。

//对T1为int的类进行特化
template<class T2>
class Date<int, T2>
{
public://构造函数Date(){cout << "Date<int, T2>" << endl;}
private:int _D1;T2 _D2;
};
  • 此时只要实例化对象时指定T1为int,就会使用这个特化的类模板来实例化对象。所以d2即使两参数一样,也是匹配第二个特化
    在这里插入图片描述
    2、参数更进一步的限制
    偏特化并不仅仅是指特化部分参数,而是针对模板参数进一步的条件限制所设计出来的一个特化版本。
    例如,我们还可以指定当T1和T2为某种类型时,使用我们特殊化的类模板。
//两个参数偏特化为指针类型
template<class T1, class T2>
class Dragon<T1*, T2*>
{
public://构造函数Dragon(){cout << "Dragon<T1*, T2*>" << endl;}
private:T1 _D1;T2 _D2;
};
//两个参数偏特化为引用类型
template<class T1, class T2>
class Dragon<T1&, T2&>
{
public://构造函数Dragon(){cout << "Dragon<T1&, T2&>" << endl;}
private:T1 _D1;T2 _D2;
};
  • 此时,当实例化对象的T1和T2同时为指针类型或同时为引用类型时,就会分别调用我们特化的两个类模板。
    在这里插入图片描述

模板分离编译

原理

  • 什么是分离编译

个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。

  • 比如我们在string类里面,分文件编写string头文件和源文件,和测试test文件。这三个文件就会分离编译,
  • 到后来vector我们用来模板就没有再进行分文件编写,不是不想用而是语法不行,今天我们就来搞清楚为什么不行。

在这里插入图片描述

  • a.h文件
    在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 先补充一下:test.cpp和a.cpp这两个文件在前面三步都是各做各的单向三步,在第四步才开始把他们链接起来
    在这里插入图片描述
  • 说一下前面四个步骤的第二步编译,C++语法允许test.i这个文件调用函数的时候如果函数没有定义但是有声明,编译器相信有这个函数,所以test.i成功通过了编译。到了第四步开始把所有文件进行链接的时候,那么编译器就要开始去寻找声明函数的地址完成调用了。这时候我们引出一个概念叫符号表,链接的时候编译器就从这里确定调用函数的地址
    在这里插入图片描述
  • 但是从运行结果来看,func可以链接成功,但是Add却无法连接成功,这是为啥呢?
    就是因为模板的特性和前面三步的单向操作

我们看第一步预处理完后的两个文件的大致结果
在这里插入图片描述

  • Func链接完后:
    嗯,现在我需要去找一下我这个声明函数的地址完成调用,现在链接完了,有一个新文件test.i, 哦哟,原来我这个定义在这个文件里,函数名一样,返回值类型一样,形参类型一样上去问test.i中Add的定义。Add的定义一看,嗯,函数名,形参类型,返回值类型都和我一样,原来你就是我要找的声明。链接成功
  • Add链接完后:
    嗯,现在我需要去找一下我这个声明函数的地址完成调用,现在链接完了,有一个新文件test.i, 哦哟,原来我这个定义在这个文件里,函数名一样,返回值类型一样,形参类型一样上去问test.i中Add的定义。Add的定义一看,嗯,函数名的确和我一样,但是我的形参类型和返回值类型都是不确定的啊,我不知道我要找的那个声明是什么类型,我可不相信你说你是我的声明你就是,Add的声明也就无法完成实例化了。

这就是模板的特性造成无法确定类型,又恰好因为没确定类型,后面单向操作导致test.i有定义不知道初始化什么类型,a.i有类型缺无法实例化。

  • 有的同学就说了,现在既然知道原因,模板的特性是无法改变的。那么能不能让a和test编译的时候交叉一样,让a.i中的模板定义去test.i文件中去看一下类型,是可以这么做,但是这只是两个文件,那么如果说文件多了呢?1000个,10000个,十万个呢?
    那么编译都要编译半天,那还写什么代码,老板来看你,你在哪里玩,老板问你为啥不工作,你说,哎呀你看,在编译嘛,我没办法。那行嘛,这个项目马上工期要到了,客户会等你编译完嘛?

解决方案

第一个方法:
在这里插入图片描述

  • 那现在我们又要实例double类型的呢?
    在这里插入图片描述

第二个方法:
在这里插入图片描述
通俗一点,声明就是一个承诺,我现在要结婚买房了,但是钱还差5w,这时候你想起你的好哥们,让他借你5w,你哥们说好,我过几天就给你打过来(声明),这时候你就要等5天后去找他(链接);

第二种情况:
你哥们太关心你了,知道你还差钱,早偷偷的就把钱打给你了,这时候你还需要5天后去找他(链接)吗

end

感谢各位的阅读,希望对你们有帮助

相关文章:

  • 前端渲染pdf文件解决方案
  • 免杀对抗-Webshell篇
  • 2.4 函数的运行原理
  • 常用 Git 命令详解
  • 关于视频的一些算法内容,不包含代码等
  • 计算serise数据的唯一值数量
  • 【2-12】CRC循环冗余校验码
  • 从原理到实践:NFS复杂故障处理方法论
  • 【人工智能】大模型的Prompt工程:释放DeepSeek潜能的艺术与科学
  • 快速迭代收缩-阈值算法(FISTA)
  • Python学习笔记(五)(列表与元组)
  • vue3 element-plus el-time-picker控制只显示时 分,并且控制可选的开始结束时间
  • AOSP世界时间的更新
  • 基于多模态双路TCN-SE-YOLO的小目标检测
  • 三维领域的语义分割
  • 【深基18.例3】查找文献-图的储存与遍历
  • 无线uniapp调试设备
  • EthernetiP转modbusTCP网关在加氢催化中的应用
  • Flask(补充内容)配置SSL 证书 实现 HTTPS 服务
  • Flask(2): 在windows系统上部署项目2
  • 重温经典|开播20周年,仙剑的那些幕后你知道吗?
  • 印方称所有敌对行动均得到反击和回应,不会升级冲突
  • 呼和浩特推进新一轮国企重组整合:杜绝一项目一公司、一业务一公司
  • “苏河超级管”调研:桥下公园“留白”很好,指引差点
  • 中国词学研究会原会长、华东师大教授马兴荣逝世,享年101岁
  • 前4个月我国货物贸易进出口同比增长2.4%,增速较一季度加快1.1个百分点