news 2026/2/9 4:43:37

【C++进阶】1.C++ 模板进阶

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【C++进阶】1.C++ 模板进阶

目录

模板进阶

一、非类型模板参数

二、Array 数组

1. 越界检查

2. 开辟空间

三、模板特化

四、类模板特化

五、模板连接报错

模板总结


模板进阶

一、非类型模板参数

模板不仅能定义类型,还能定义常量。

template<size_t N = 20> class A { private: int _a[N]; int _top; };

这样就能用模板开对应大小的数组了。

A<3> a1; A<5> a2;

本质:在编译期就知道 N 是多少,替换进去,生成了两个不同的类。

注意

  1. 模板参数必须在编译期间就能确定结果。

  2. 类对象、字符串不允许做非类型模板参数(double在 C++20 后支持)。

  3. 可以给缺省值,写法:A<> a3;

二、Array 数组

1. 越界检查

  • 普通数组

    • 越界读:不检查,返回随机值。

    int arr2[10]; cout << arr2[11] << endl; // 输出不确定的随机值
    • 越界写:可能被运行时检查工具(如 VS 的调试模式)捕获,报出类似“缓冲区溢出”的错误。

  • std::array
    会在访问时进行边界检查(通常是assert或抛出异常),直接阻止越界操作。

2. 开辟空间

vector相比,vector在堆上开辟空间,而array在栈上。

如何验证?

  1. 先验证栈的生长方向(向上):

    void fun1() { int a = 1; cout << &a << endl; } int main() { int a = 1; cout << &a << endl; fun1(); return 0; } // 输出地址通常是 main 中的 a > fun1 中的 a,说明栈向下增长(地址减小),此处描述为“上大下小,栈向上建立”有误。但核心是比较地址。
  2. 查看array元素的地址:

    array<int, 10> arr1; cout << &arr1[1] << " " << &arr1[2] << endl; // 相邻元素地址连续且递增,结合其地址与局部变量地址比较,可推断其在栈上。

因此,array创建数组更快,因为它只是栈上的一块连续空间,无需动态内存分配。

三、模板特化

考虑一个比较函数模板:

template<class T> bool lessfunc(T left, T right) { return left < right; }

对于自定义的Date类,如果已经重载了<运算符,可以正常工作:

Date d1(2025, 12, 11); Date d2(2025, 12, 12); cout << lessfunc(d1, d2) << endl; // 没问题

但是,如果比较的是Date*(指针):

Date* pd1 = &d1; Date* pd2 = &d2; cout << lessfunc(pd1, pd2) << endl; // 比较的是地址,结果不确定

因为代码比较的是地址。对于这种特殊情况,就需要进行特殊处理,即模板特化

// 全特化版本,针对 Date* 类型 template<> bool lessfunc<Date*>(Date* left, Date* right) { return *left < *right; }

但是,特化有时会很复杂。

  1. 传引用的情况

    template<class T> bool lessfunc(const T& left, const T& right) { return left < right; }

    特化时,原模板参数变为const T&,特化Date*时需注意指针和引用的结合:

    // 错误示例:特化不匹配 // template<> bool lessfunc<Date*>(const Date*& left, const Date*& right) {...}

    正确写法是Date* const &,因为指针本身是常量引用。

    template<> bool lessfunc<Date*>(Date* const & left, Date* const & right) { return *left < *right; }
  2. const Date*的情况
    如果调用时是const Date*,还需要再特化一个版本:

    template<> bool lessfunc<const Date*>(const Date* const & left, const Date* const & right) { return *left < *right; }

    这种写法很复杂,因此模板特化在实际中通常用得较少。

四、类模板特化

当类模板针对不同类型需要不同处理时,也需要特化。

template<class T1, class T2> class A { public: A() { cout << "普通" << endl; } private: T1 _a; T2 _b; };
  1. 全特化
    先写template<>,再指定完全具体的类型。

    template<> class A<int, char> { public: A() { cout << "全特化" << endl; } private: int _a; char _b; };

    这样,A<int, char> a1;就会调用这个特化版本。

  2. 偏特化(部分特化)
    只对部分模板参数进行特化,或者对参数特性进行限制(如指针、引用)。

    // 第二个参数固定为 char 的偏特化 template<class T1> class A<T1, char> { public: A() { cout << "半特化" << endl; } private: T1 _a; char _b; };

    优先级:当一个类型同时匹配全特化和偏特化时,优先匹配全特化,因为更“特化”。

    A<int, char> a1; // 调用全特化 A<long long, char> a2; // 调用偏特化 (T1=long long, T2=char)
  3. 针对指针的偏特化(以仿函数Less为例)
    在使用自定义的优先队列(堆)时,如果元素是指针,比较的默认行为是地址比较,通常不符合预期。

    bit::priority_queue<Date*> heap1; // 比较的是地址,顺序随机 bit::priority_queue<Date> heap2; // 比较的是日期对象,正常

    此时,可以特化仿函数Less

    template<class T> struct Less { bool operator()(const T& x, const T& y) { return x < y; } }; // 针对所有指针类型的偏特化 template<class T> struct Less<T*> { bool operator()(T* const & x, T* const & y) { return *x < *y; // 先解引用再比较 } };
  4. 特化里的模板变量
    在针对T*的特化版本中,模板参数T代表的是指针指向的类型,而不是指针本身。

    template<class T> struct Less<T*> { bool operator()(T* const& x, T* const& y) { // T* d1 = Date(2025, 1, 5); // 错误: 不能用 Date 初始化 Date* T d1 = Date(2025, 1, 5); // 正确: T 是 Date return *x < *y; } };

    在特化Less<T*>中,TDate,所以T d1Date类型。

五、模板连接报错

含模板的类(或函数)不能将声明和定义分离在不同的文件(如.h.cpp)中,否则会导致链接错误。

原因(编译链接过程简述)
假设有文件:func.h,func.cpp,test.cpp

  1. 预处理:头文件展开,宏替换等。生成func.i,test.i

  2. 编译:检查语法,生成汇编代码。生成func.s,test.s

  3. 汇编:汇编代码转二进制机器码(目标文件)。生成func.o,test.o

  4. 链接:合并目标文件,解析符号,生成可执行程序。

问题所在
模板本身并不是真正的代码,它只是编译器生成代码的“蓝图”。只有在模板被实例化(即指定了具体类型参数)时,编译器才会根据模板生成具体的代码。
如果模板的声明和定义分离:

  • func.cpp中,编译器看到模板定义,但因为没有被实例化,它不会为模板生成任何具体的机器指令

  • test.cpp中,我们#include "func.h",看到了模板声明,并实例化了模板(如MyClass<int> obj)。

  • 编译test.cpp时,它知道需要MyClass<int>的成员函数,但假设这些函数的定义在func.o中。

  • 链接时,链接器去func.o中寻找MyClass<int>::myMethod的地址,func.o里根本没有这个函数(因为模板定义没被实例化),于是报“未解析的外部符号”错误。

解决方案

  1. 推荐:将模板的声明和定义都放在同一个头文件(.hpp.h)中。

  2. 使用显式实例化(不常用,限制多):

    // 在 func.cpp 末尾手动告诉编译器需要生成哪些版本 template class MyClass<int>; template class MyClass<double>;

模板总结

【优点】

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

  2. 灵活性增强:增强了代码的灵活性,支持泛型编程。

【缺陷】

  1. 代码膨胀:模板会导致代码膨胀问题(针对不同类型生成多份代码),也会导致编译时间变长。

  2. 调试困难:出现模板编译错误时,错误信息非常复杂且冗长,不易定位错误。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/7 0:36:25

java计算机毕业设计社区物品交换平台的管理与实现 基于SpringBoot的社区闲置资源分享平台 JavaWeb社区二手物品流通与捐赠系统

计算机毕业设计社区物品交换平台的管理与实现u908q9 &#xff08;配套有源码 程序 mysql数据库 论文&#xff09; 本套源码可以在文本联xi,先看具体系统功能演示视频领取&#xff0c;可分享源码参考。“旧物堆成山&#xff0c;扔掉可惜&#xff0c;卖掉麻烦”——这是多数家庭的…

作者头像 李华
网站建设 2026/2/4 17:01:01

Whisper语音识别模型深度解析:从架构原理到实战应用

Whisper语音识别模型深度解析&#xff1a;从架构原理到实战应用 【免费下载链接】whisper-tiny.en 项目地址: https://ai.gitcode.com/hf_mirrors/openai/whisper-tiny.en Whisper是OpenAI推出的基于大规模弱监督训练的语音识别模型&#xff0c;通过Transformer编码器-…

作者头像 李华
网站建设 2026/2/8 3:16:53

解密umi微前端:从单体应用到分布式架构的实战演进

大型前端项目开发中&#xff0c;你是否面临过这些痛点&#xff1a;构建时间越来越长、团队协作效率低下、技术升级困难重重&#xff1f;微前端架构正是为解决这些问题而生。本文将带你深入理解umi微前端的实现原理&#xff0c;并通过实际案例展示如何将单体应用优雅拆分为分布式…

作者头像 李华
网站建设 2026/2/7 23:01:24

Oracle Database开源项目终极指南:从零开始掌握数据库开发

Oracle Database开源项目终极指南&#xff1a;从零开始掌握数据库开发 【免费下载链接】oracle-db-examples 项目地址: https://gitcode.com/gh_mirrors/ora/oracle-db-examples 想要快速掌握Oracle数据库开发却不知从何入手&#xff1f;Oracle Database Examples开源项…

作者头像 李华
网站建设 2026/2/5 19:04:00

慧荣U盘量产工具终极使用指南:快速修复SM32系列芯片

慧荣U盘量产工具终极使用指南&#xff1a;快速修复SM32系列芯片 【免费下载链接】慧荣U盘量产工具v20.02.04.21使用指南 欢迎使用Dyna Mass Storage Production Tool&#xff0c;本工具专为解决慧荣科技的特定型号U盘量产需求而设计。当前版本号为v20.02.04.21 U0204&#xff0…

作者头像 李华
网站建设 2026/2/8 10:20:08

Unity ML-Agents环境配置终极解决方案:如何规避90%的开发者陷阱

Unity ML-Agents环境配置终极解决方案&#xff1a;如何规避90%的开发者陷阱 【免费下载链接】ml-agents Unity-Technologies/ml-agents: 是一个基于 Python 语言的机器学习库&#xff0c;可以方便地实现机器学习算法的实现和测试。该项目提供了一个简单易用的机器学习库&#x…

作者头像 李华