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

C++模板:泛型编程与函数模板详解(上)

C++模板:泛型编程与函数模板详解(上)

1. 泛型编程:解决通用交换函数的困境

1.1 传统方法的局限性

在C++编程中,我们经常需要实现通用的功能,比如交换两个变量的值。传统的方法是使用函数重载:

void Swap(int& left, int& right)
{int temp = left;left = right;right = temp;
}void Swap(double& left, double& right)
{double temp = left;left = right;right = temp;
}void Swap(char& left, char& right)
{char temp = left;left = right;right = temp;
}// 需要更多类型时,继续重载...

这种方法的缺点很明显:

  1. 代码冗余:每个重载函数除了类型不同外,逻辑完全一样
  2. 维护困难:修改算法时需要修改所有重载版本
  3. 扩展性差:每增加一个新类型,就需要手动添加对应的函数

1.2 泛型编程的解决方案

泛型编程的核心思想:编写与类型无关的通用代码,实现代码复用。

想象一下,如果有一个"模具",我们只需要告诉编译器用这个模具,它就能自动为我们生成各种类型的具体代码,那该多方便!

// 伪代码:理想中的通用交换函数
void Swap(任何类型& left, 任何类型& right)
{任何类型 temp = left;left = right;right = temp;
}

幸运的是,C++通过模板机制实现了这个梦想!

在下面我们将介绍函数模板和类模板

C++函数模板详解

2.1 函数模板概念

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

简单来说,函数模板就像一个"函数生成器",我们可以通过它来创建处理不同类型数据相似函数,而无需为每种类型都手动编写一遍。

2.2 函数模板格式

函数模板的基本语法格式如下:

template<typename T1, typename T2, ..., typename Tn>
返回值类型 函数名(参数列表)
{// 函数体
}

示例:通用交换函数模板

template<typename T>
void Swap(T& left, T& right)
{T temp = left;left = right;right = temp;
}

重要说明:

  • typename 是用来定义模板参数的关键字
  • 也可以使用 class 关键字(效果相同)
  • 注意: 不能使用 struct 代替 class
// 这两种写法是等价的:
template<typename T>
template<class T>  // 同样有效

这里的T就是我们的模板参数,他将会根据需要变成我们需要的类型,例如(int,char…)而无需我们再单独为他们实现相应的函数代码。

2.3 函数模板的原理

模板的工作机制

函数模板本身并不是函数,而是编译器用来生成具体函数的模具。这就像是工业革命中的机器生产:

  • 手工生产 → 函数重载:为每种类型手动编写函数
  • 机器生产 → 函数模板:编译器自动为各种类型生成函数

编译器的工作流程:

  1. 模板推演:编译器根据传入的实参类型推导模板参数
  2. 代码生成:编译器用推导出的类型替换模板参数,生成具体的函数代码
  3. 编译执行:将生成的具体函数编译为机器代码

具体示例分析:

template<typename T>
T Add(const T& left, const T& right)
{return left + right;
}int main()
{Add(1, 2);      // 编译器生成 Add<int> 版本Add(1.5, 2.5);  // 编译器生成 Add<double> 版本return 0;
}

编译器会为我们生成:

int Add_int(const int& left, const int& right)
{return left + right;
}double Add_double(const double& left, const double& right)
{return left + right;
}

可以看到,模板大大提升了我们编写代码的效率,同时也为后面的STL类库提供了语法基础。

2.4 函数模板的实例化

用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为两种方式:

2.4.1 隐式实例化

让编译器根据实参自动推演模板参数的实际类型:

template<class T>
T Add(const T& left, const T& right)
{return left + right;
}int main()
{int a1 = 10, a2 = 20;double d1 = 10.0, d2 = 20.0;Add(a1, a2);    // 编译器推演 T 为 intAdd(d1, d2);    // 编译器推演 T 为 double/** 上面两句代码没有问题,而下面那个语句编译错误!* 编译器通过 a1 将 T 推演为 int* 通过 d1 将 T 推演为 double* 但模板参数列表中只有一个 T* 编译器无法确定 T 应该是 int 还是 double*/// Add(a1, d1);  // 错误!return 0;
}

模板类型转换规则:
在模板中,编译器一般不会进行自动类型转换,因为一旦转换出问题,责任难以界定。

2.4.2 显式实例化

在函数名后的 <> 中指定模板参数的实际类型:

int main()
{int a = 10;double b = 20.0;// 显式实例化Add<int>(a, b);     // 指定 T 为 int,b 会被隐式转换为 intAdd<double>(a, b);  // 指定 T 为 double,a 会被隐式转换为 doublereturn 0;
}

显式实例化的特点:

  • 明确告诉编译器使用什么类型
  • 如果类型不匹配,编译器会尝试进行隐式类型转换(前面提到,如果是利用模板隐式类型转换,它不会自动类型转换,而显示实例化可以)
  • 如果无法转换成功,编译器将会报错(例如string类型可能无法很好的转化成size_t)

处理混合类型参数的解决方案

int main()
{int a = 10;double d = 20.5;// 方法1:用户自己强制转换Add(a, (int)d);         // 将 double 转换为 intAdd((double)a, d);      // 将 int 转换为 double// 方法2:使用显式实例化Add<int>(a, d);         // 显式指定为 intAdd<double>(a, d);      // 显式指定为 doublereturn 0;
}

2.5 模板参数的匹配原则

原则1:模板函数与普通函数共存

一个非模板函数可以和一个同名的函数模板同时存在:

// 专门处理 int 的加法函数(普通函数)
int Add(int left, int right)
{std::cout << "调用普通函数 Add" << std::endl;return left + right;
}// 通用加法函数(函数模板)
template<class T>
T Add(T left, T right)
{std::cout << "调用模板函数 Add" << std::endl;return left + right;
}void Test()
{Add(1, 2);           // 与非模板函数完全匹配,调用普通函数Add<int>(1, 2);      // 显式调用模板函数,调用编译器特化的 Add 版本
}

原则2:优先匹配规则

对于非模板函数和同名函数模板:

  • 如果其他条件都相同,优先调用非模板函数
  • 如果模板能产生更好匹配的函数,则选择模板
// 专门处理 int 的加法函数
int Add(int left, int right)
{std::cout << "调用普通函数 Add" << std::endl;return left + right;
}// 通用加法函数模板(支持不同类型参数)
template<class T1, class T2>
auto Add(T1 left, T2 right) -> decltype(left + right)
{std::cout << "调用模板函数 Add" << std::endl;return left + right;
}//这里的auto和decltype都是用于推导返回值类型的,是C++11里面的知识,暂时可以不必在意void Test()
{Add(1, 2);        // 完全匹配普通函数,调用普通函数Add(1, 2.0);      // 模板函数能生成更好匹配的版本,调用模板函数
}

原则3:类型转换规则

  • 模板函数:不允许自动类型转换
  • 普通函数:可以进行自动类型转换
// 普通函数
void Print(int value)
{std::cout << "普通函数: " << value << std::endl;
}// 模板函数
template<typename T>
void Print(T value)
{std::cout << "模板函数: " << value << std::endl;
}void Test()
{Print(10);        // 调用普通函数Print(10.5);      // 调用模板函数(T 推导为 double)Print('A');       // char 可以转换为 int,但模板匹配更好,调用模板函数
}

C++类模板详解

3.1 类模板的定义格式

类模板允许我们创建通用的类,这些类可以处理不同类型的数据(这也是STL类库的语法基础)。类模板的基本定义格式如下:

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

需要注意,这样定义还不算真的类,它和函数模板一样,暂时还是个蓝图,只有当它实例化时,才是真正的类

动态顺序表示例

让我们通过一个动态顺序表(Vector)来理解类模板:

// 动态顺序表模板
// 注意:Vector不是具体的类,而是编译器生成具体类的模具
template<class T>
class Vector
{ 
public:// 构造函数Vector(size_t capacity = 10): _pData(new T[capacity]), _size(0), _capacity(capacity){}// 析构函数 - 在类中声明,在类外定义~Vector();// 成员函数void PushBack(const T& data);void PopBack();// 其他操作...size_t Size() { return _size; }// 重载下标运算符T& operator[](size_t pos){assert(pos < _size);return _pData[pos];}// const版本的下标运算符const T& operator[](size_t pos) const{assert(pos < _size);return _pData[pos];}private:T* _pData;          // 指向动态数组的指针size_t _size;       // 当前元素个数size_t _capacity;   // 容量
};

类模板成员函数的类外定义

重要规则: 类模板的成员函数在类外定义时,需要添加模板参数列表。

// 析构函数的类外定义
template <class T>
Vector<T>::~Vector()
{if(_pData)delete[] _pData;_size = _capacity = 0;
}

3.2 类模板的实例化

类模板实例化的特殊性

类模板的实例化与函数模板有一个重要区别:

  • 函数模板:可以通过参数推导隐式实例化
  • 类模板:必须显式指定模板参数,也就是说必须在类模版名字后面跟<>,然后<>里面写上实例化的类型

实例化语法

类模板名<实际类型> 对象名(构造函数参数);

实际使用示例

// Vector是类名,Vector<int>才是类型Vector<int> s1;Vector<double> s2;//还有例如:stack<int> s1;string s2;

3.3 多参数类模板

类模板可以接受多个类型参数:

如下示例,有K,V两个参数:其中一个是键,一个是value(值)

这是一个红黑树的类模版:

template<class K, class V>
struct RBTreeNode
{RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;pair<K, V> _kv;Colour _col;RBTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _col(RED){}
};

3.4 类模板的分离编译问题

问题描述

在C++中,类模板的声明和定义通常需要放在同一个文件中,这是因为:

根本原因:C++的编译机制

  1. 模板不是真正的代码

    • 类模板只是"蓝图",不是具体的类
    • 编译器看到模板时,不会立即生成代码
  2. 编译时的实例化

    • 只有当使用具体类型实例化模板时,编译器才生成实际代码
    • 例如:Vector<int> v; 让编译器生成 Vector<int> 的具体实现
  3. 分离编译的问题

    • 如果声明在 .h 文件,实现在 .cpp 文件
    • 编译 main.cpp 时,编译器只知道模板声明,看不到具体实现
    • 链接时找不到实际生成的代码,导致链接错误
  4. 解决方案

    • 把模板的声明和定义都放在头文件(.h.hpp

    • 这样使用模板的代码能同时看到声明和实现

      错误示例:

// Vector.h
template<class T>
class Vector
{
public:void PushBack(const T& data);  // 声明
};// Vector.cpp
template<class T>
void Vector<T>::PushBack(const T& data)  // 定义
{// 实现...
}// main.cpp,注意这已经是另一个文件了
#include "Vector.h"
int main()
{Vector<int> v;v.PushBack(10);  // 链接错误!找不到PushBack<int>的实现
}

解决方案

  1. 将实现放在头文件中(推荐)
// Vector.h
template<class T>
class Vector
{
public:void PushBack(const T& data);
};// 在头文件中直接实现
template<class T>
void Vector<T>::PushBack(const T& data)
{// 实现代码...
}
http://www.dtcms.com/a/603336.html

相关文章:

  • 佛山网站建设公司电话温州市瓯海建设局网站
  • 反编译要会几种语言|深入理解反编译技术及其应用
  • 网站开发所需资料网站建设需要哪些专业技术
  • 湖南网站建设联系电话网站开发带后台
  • C语言反编译器 | 如何使用反编译工具恢复源代码
  • 哈尔滨网站设计公司枣庄三合一网站开发
  • C语言编译器哪个好学 | 选择合适的C语言编译器,助力编程学习
  • 太原微信网站开发微商城手机网站模板
  • JUC的常见类
  • win7iis配置网站vps搭建个人网站
  • 【剑斩OFFER】算法的暴力美学——点名
  • 昆明做网站公司我网站建设
  • word文档文字的尾部空格设置不了下划线【解决办法】
  • 网站建设上线多久网站设计公司网页设计
  • 外贸网站一站式海外推广什么软件做网站链接
  • 做货代哪个网站上好找客户想搞一个自己的网站怎么做
  • BitLocker磁盘锁定解决方法
  • jEasyUI 使用标记创建树形菜单
  • 合肥模板网站建设费用帮网站做点击
  • 怎样创建网站dw龙岩平面设计
  • 从脚本到程序:如何构建一个可维护的Python项目结构?
  • 网站建设与维护试题及答案长沙网站建立公司
  • eventfd
  • YOLOv8轻量化改进实战——使模型更适配边缘设备
  • C语言程序编译器 | 提高开发效率,掌握C语言编程的关键工具
  • 泉州 网站建设公司首选东营网站建设培训
  • 南沙网站建设优化微信营销课2013是谁讲的
  • 英语网站排名访问网站速度很慢
  • 3.55基于51单片机温度报警器基于51proteus的DS18B20温度报警器+报告可设置上下限,低于或者高于都会有声光报警,蜂鸣器响LED灯亮。
  • 站牛网是做什么的网站页面如何架构