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

C++------模板初阶

引言

C++ 模板是一种强大的特性,它允许你编写与类型无关的代码,从而实现代码复用。模板分为函数模板和类模板。

 

泛型编程 

基本概念

泛型编程允许你创建通用的函数、类或数据结构,这些通用组件可以处理多种数据类型,而不需要为每种数据类型单独编写代码。例如,一个通用的排序函数可以对整数数组、浮点数数组或字符串数组进行排序。 

C++ 使用模板(templates)实现泛型编程。模板分为函数模板和类模板: 

// 函数模板示例:交换两个变量的值
template <typename T>
void swap(T& a, T& b) {T temp = a;a = b;b = temp;
}// 类模板示例:动态数组
template <typename T>
class Vector {
private:T* data;size_t size;
public:// 类成员函数
};

泛型编程的优势

  • 代码复用:避免为不同数据类型编写重复代码
  • 类型安全:在编译时进行类型检查,减少运行时错误
  • 性能优化:避免装箱和拆箱操作(如 Java 和 C# 中的值类型)
  • 可读性:代码更加清晰,表达意图明确 

 接下来我们我们依次学习函数模板和类模板。

 

函数模板 

基本语法

函数模板的声明以 template 关键字开始,后跟模板参数列表,然后是函数定义: 

template <typename T> // 模板参数列表

返回类型 函数名(参数列表) {

// 函数体

  •  template:声明这是一个模板
  • typename T:定义一个类型参数 T,typename 也可以用 class 替代
  • T:在函数定义中作为通用类型使用

简单示例:交换函数 

template <typename T>
void swap(T& a, T& b) {T temp = a;a = b;b = temp;
}int main() {int x = 5, y = 10;swap(x, y);  // 自动推导 T 为 intdouble a = 3.14, b = 2.71;swap(a, b);  // 自动推导 T 为 double
}
 多模板参数

函数模板可以有多个类型参数和非类型参数: 

template <typename T, typename U, int Size>
T add(T a, U b) {return a + b;
}int main() {int result = add<int, double, 10>(5, 3.14);  // 显式指定类型auto result2 = add(5, 3.14);  // 自动推导 T=int, U=double
}
 模板参数推导

编译器可以根据函数调用的实参自动推导模板参数 

template <typename T>
T max(T a, T b) {return (a > b) ? a : b;
}int main() {int x = max(5, 10);  // T 推导为 intdouble y = max(3.14, 2.71);  // T 推导为 double// max(5, 3.14);  // 错误:T 无法唯一推导(int vs double)max<double>(5, 3.14);  // 显式指定 T,允许不同类型参数
}
 函数模板重载

函数模板可以与普通函数重载,也可以相互重载: 

// 模板函数
template <typename T>
T add(T a, T b) {return a + b;
}// 普通函数(重载)
int add(int a, int b) {return a + b;
}// 模板函数重载(不同参数)
template <typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {return a + b;
}
 模板特化

当模板函数需要针对特定类型提供不同的实现时,可以使用模板特化: 

// 主模板
template <typename T>
bool isEqual(const T& a, const T& b) {return a == b;
}// 特化版本:针对 C 风格字符串
template <>
bool isEqual<const char*>(const char* const& a, const char* const& b) {return std::strcmp(a, b) == 0;
}
非类型模板参数

模板参数可以是类型参数,也可以是非类型参数(如整数、指针等): 

template <typename T, int Size>
class Array {
private:T data[Size];
public:T& operator[](int index) { return data[index]; }
};// 使用示例
Array<int, 5> arr;  // 创建一个包含5个int的数组

常见应用场景

  • 通用算法:如排序、查找、交换等
  • 容器操作:如容器元素的遍历、转换等
  • 类型安全的函数包装:如 std::function
  • 元编程:在编译时执行计算 

注意事项

  1. 模板定义通常放在头文件中:因为模板实例化发生在编译阶段,需要看到完整定义
  2. 模板错误信息可能很复杂:编译器在实例化时才会检查类型兼容性
  3. 隐式实例化 vs 显式实例化: 

// 显式实例化声明

extern template void swap<int>(int&, int&);

// 显式实例化定义

template void swap<double>(double&, double&); 

函数模板的原理 

 一、底层机制:参数化类型与代码生成

函数模板的本质是参数化类型(Parameterized Types),即把数据类型作为参数传递给模板,让编译器在编译时生成对应类型的具体函数。这个过程分为两个阶段:

1模板定义阶段

  • 程序员编写模板代码,使用通用类型参数(如 T)代替具体类型。
  • 模板代码本身不会被编译成可执行代码,而是作为编译器生成代码的 “蓝图”。

 2.模板实例化阶段:

  • 当代码中调用模板函数时,编译器根据实参类型推导出模板参数的具体类型(如 int、double)。
  • 编译器根据推导出的类型,用具体类型替换模板中的类型参数,生成对应版本的函数代码。 

示例:

template <typename T>
T max(T a, T b) {return (a > b) ? a : b;
}int main() {int x = max(5, 10);        // 实例化 max<int>(int, int)double y = max(3.14, 2.71); // 实例化 max<double>(double, double)
}

编译器会生成两个独立的函数: 

// 编译器生成的代码
int max(int a, int b) {return (a > b) ? a : b;
}double max(double a, double b) {return (a > b) ? a : b;
}
二、编译过程:从模板到可执行代码 函数模板的编译分为三个关键步骤: 

函数模板的编译分为三个关键步骤: 

1.模板解析:

  • 编译器读取模板定义,检查语法正确性,但不编译具体实现。

2.模板实例化:

  • 当模板函数被调用时,编译器根据实参类型推导出模板参数的具体类型。
  • 如果该类型组合的实例尚未存在,编译器生成对应的函数代码。

3.代码优化:

  • 编译器对生成的代码进行优化,如同普通函数一样。 

 实例化触发条件:

  • 函数调用(如 max(5, 10))
  • 取函数地址(如 auto func = &max<int>;)
  • 显式实例化声明 / 定义(如 template void swap<int>(int&, int&);) 

 

三、类型推导与重载解析

1. 自动类型推导

编译器根据函数调用时的实参类型自动推导模板参数: 

template <typename T>
void print(T value) {std::cout << value << std::endl;
}print(42);        // T 推导为 int
print("hello");   // T 推导为 const char*
2. 显式指定类型

当自动推导失败时(如函数参数与模板参数无关),需显式指定类型: 

template <typename T>
T fromString(const std::string& str) {// ...
}int num = fromString<int>("123");  // 显式指定 T 为 int
3. 重载解析规则

当存在多个重载版本(普通函数、模板函数、特化模板)时,编译器按以下顺序选择:

  1. 完全匹配的普通函数
  2. 模板函数的特化版本
  3. 主模板的实例化版本 
// 普通函数
void print(int value) {std::cout << "int: " << value << std::endl;
}// 模板函数
template <typename T>
void print(T value) {std::cout << "T: " << value << std::endl;
}int main() {print(42);      // 调用普通函数(完全匹配)print("hello"); // 调用模板函数(实例化为 const char*)
}

总结 

函数模板的核心原理是编译时的类型参数替换和代码生成,通过自动类型推导和重载解析,实现了通用代码的高效复用。理解模板的实例化机制有助于避免常见错误(如分离编译问题、代码膨胀),并充分发挥 C++ 泛型编程的威力。 

 

类模板

类模版与函数模版类似,只不过作用对象是类。 

一、基本语法 

类模板的声明以template关键字开始,后跟模板参数列表,然后是类定义:

template <typename T>  // 模板参数列表
class ClassName {
private:T data;  // 使用泛型类型T
public:ClassName(T value) : data(value) {}T getData() const { return data; }
};
  • template <typename T>:声明一个类型参数T
  • T:在类定义中作为通用类型使用 

二、简单示例:动态数组类 

template <typename T>
class Array {
private:T* data;size_t size;
public:Array(size_t size) : size(size) {data = new T[size];}~Array() {delete[] data;}T& operator[](size_t index) {return data[index];}const T& operator[](size_t index) const {return data[index];}size_t getSize() const {return size;}
};// 使用示例
Array<int> intArray(5);      // 整数数组
Array<double> doubleArray(3); // 双精度数组

 

三、类模板的特性

1. 多模板参数

类模板可以有多个类型参数和非类型参数: 

template <typename T, int Size>
class FixedArray {
private:T data[Size];
public:// ...
};// 使用示例
FixedArray<int, 10> fixedIntArray;  // 包含10个整数的数组
2. 成员函数模板

类的成员函数可以是模板函数: 

template <typename T>
class Container {
private:T value;
public:Container(T val) : value(val) {}template <typename U>U convert() const {return static_cast<U>(value);}
};// 使用示例
Container<int> c(42);
double d = c.convert<double>();  // 将int转换为double
3. 静态成员

类模板的静态成员变量会为每个实例化的类型单独生成:  

template <typename T>
class Counter {
public:static int count;Counter() { count++; }
};template <typename T>
int Counter<T>::count = 0;  // 静态成员初始化// 使用示例
Counter<int> c1, c2;       // Counter<int>::count = 2
Counter<double> c3;       // Counter<double>::count = 1

四、类模板特化

当类模板需要针对特定类型提供不同的实现时,可以使用类模板特化: 

1. 全特化

为特定类型提供完全不同的实现: 

// 主模板
template <typename T>
class Storage {
public:void store(T value) { /* 通用实现 */ }
};// 全特化:针对bool类型
template <>
class Storage<bool> {
public:void store(bool value) { /* 针对bool的优化实现 */ }
};
2. 偏特化

为部分类型参数提供特化实现: 

// 主模板
template <typename T, typename U>
class Pair {
public:// ...
};// 偏特化:第二个参数为int
template <typename T>
class Pair<T, int> {
public:// 针对U=int的特殊实现
};

六、类模板的应用场景

  1. 容器类:如std::vector、std::list、std::map
  2. 智能指针:如std::unique_ptr、std::shared_ptr
  3. 算法包装器:如std::function
  4. 元编程:编译时计算和类型操作
  5. 泛型工具类:如数学向量、矩阵、任意类型的包装器 

七、注意事项

  1. 模板定义通常放在头文件中:因为编译器在实例化时需要看到完整定义
  2. 显式实例化:如果需要控制实例化位置,可以使用显式实例化: 
// 显式实例化定义
template class Array<int>;

模板参数依赖名称:在使用依赖于模板参数的类型时,需使用typename关键字: 

template <typename T>
void func() {typename T::iterator it;  // 使用typename表明iterator是类型
}

八、与函数模板的对比 

特性函数模板类模板
实例化触发函数调用或取地址创建对象或显式实例化
特化方式全特化全特化和偏特化
静态成员不支持(每个实例共享)每个实例化类型单独生成
继承不支持继承支持复杂的继承关系

类模板为何优于 typedef
类模板相比于typedef在C++编程中具有显著的优越性,这主要体现在它们各自的功能和应用场景上。typedef主要用于为现有的类型定义一个新的名称(别名),而类模板则提供了一种更灵活、更强大的方式来定义可以在多种数据类型上工作的类。 

类模板:通过类模板,可以定义一个与具体数据类型无关的类,这个类可以在实例化时指定数据类型。这种特性使得类模板能够在多种数据类型上重用相同的代码,减少了代码冗余,提高了代码的复用性。同时,它也支持泛型编程,使得函数或类可以更加通用,不依赖于具体的数据类型。

typedef:typedef只是为现有的类型定义了一个新的名称,它本身并不支持泛型编程,也不具备在不同数据类型上重用代码的能力。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

http://www.lryc.cn/news/581284.html

相关文章:

  • JS 网页全自动翻译v3.17发布,全面接入 GiteeAI 大模型翻译及自动部署
  • 2025年的前后端一体化CMS框架优选方案
  • 【大模型入门】访问GPT的API
  • 【Halcon】WPF 自定义Halcon显示控件完整流程与 `OnApplyTemplate` 未触发的根本原因解析!
  • day 60 python打卡
  • ffplay6 播放器关键技术点分析 1/2
  • Windows内核并发优化
  • rk3128 emmc显示剩余容量为0
  • 深度学习5(深层神经网络 + 参数和超参数)
  • 力扣网编程55题:跳跃游戏之逆向思维
  • 前端相关性能优化笔记
  • Python数据容器-list和tuple
  • 四、jenkins自动构建和设置邮箱
  • PHP语法基础篇(九):正则表达式
  • CppCon 2018 学习:Smart References
  • 有限状态机(Finite State Machine)
  • 相机位姿估计
  • 2 大模型高效参数微调;prompt tunning
  • 【Linux】自旋锁和读写锁
  • 全素山药开发指南:从防痒处理到高可用食谱架构
  • DeepSeek扫雷游戏网页版HTML5(附源码)
  • C#指针:解锁内存操作的底层密码
  • 机械时代的计算
  • 【Linux】常用基本指令
  • 爬虫工程师Chrome开发者工具简单介绍
  • 推荐算法系统系列五>推荐算法CF协同过滤用户行为挖掘(itembase+userbase)
  • Python实例题:基于 Python 的简单电子词典
  • 洛谷刷题9
  • Django中关于templates目录和static目录存放位置的总结
  • Django跨域