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

CPP模板编程

C++模版编程

C++ 模板编程是 C++ 语言的一个强大特性,通过编写泛型代码,可以创建能够处理多种数据类型的函数或类,而不需要为每种类型重复编写相同的代码逻辑。

一、为什么使用模板

比如我们现在想要完成这样一个工作,比较两个的大小。在C++中,我们可以通过函数重载的方式来完成这个工作。

// 函数重载
int compare(int x, int y) {cout << "int compare(int x, int y)" << endl;if (x > y) return 1;else if (x < y) return -1;else return 0;
}
int compare(double x, double y) {cout << "int compare(double x, double y)" << endl;if (x > y) return 1;else if (x < y) return -1;else return 0;
}

这两个函数除了参数列表不同,其余的功能等都是一样的,根据比较的值类型不同而需要写多个函数,造成代码冗余。C++中可以通过模板编程来简化开发。

二、模板的概念

模板(Templates)允许编写通用的代码,这些代码可以适应不同的数据类型,而无需为每种数据类型重复编写相同的逻辑。

模板是C++中泛型编程的基础, 作为强类型语言,C++ 要求所有变量都具有具体类型,由程序员显式声明或编译器推导。

int  a = 100; //必须明确声明变量a的数据类型

而模板允许程序员在定义类或函数时,编写与数据类型无关的通用代码。

比如:

template<class T> T compare(T x, T y);

或者

template<typename T> T compare(T x, T y);

区别

  • 模板参数列表中(template<...> 里),classtypename 完全等价,都是“类型参数”的意思。

  • 模板内部,如果要声明依赖于模板参数的类型(比如 T::value_type),必须用 typename

    template<typename T>
    void func() {typename T::value_type v; // 这里必须用 typename
    }
    

上面的代码描述了一个具有 T类型 的泛型函数的模板,其返回值和参数(x和 y)都具有此类型。那么T到底是什么类型?实际上,T只是一个占位符,具体是什么类型,此时并不知道,需要在具体调用的时候,指定参数的实际类型。

如下代码:此时T就是int类型。

int a = 10;
int b = 20;
int c = compare(a, b);

如下代码,此时T就是double类型。

double a = 5;
double b = 8;
double c = compare(a, b);

因此,对于T compare(T x, T y)来说,它就是一类函数的万能公式。具有通用性,但是不能直接使用。就好像我们编写简历时用的模板一样,不能直接用,在具体编写简历的时候,指定其中的内容。

模板可以分为函数模板和类模板两大类。

三、函数模版

C++中的函数模板是一种编写通用函数的机制,这些函数能够操作多种数据类型,而无需为每种类型单独编写函数定义。

1、函数模版的定义

语法形式如下:多个参数用逗号分隔。

template <typename t1,typename t2,...... > 返回值类型  函数名(参数列表){ //函数体 
}
template <class t1,class t2,......> 返回值类型  函数名(参数列表){ //函数体
}
  • template关键字表示这是一个模板定义。
  • typename T声明了一个类型参数T,表示函数可以接受任何类型作为此参数。
  • 模板定义中 <> 里的内容即模板参数列表,不能为空

示例:

template <typename T> T minmum(T t1, T t2)
{return t1 < t2 ? t1 : t2;
}
2、函数模板的使用

T的类型会根据实际调用minmum函数时其实参类型进行推导。

int main()
{int a = 100;int b = 200;int result = minmum(a, b);cout << result << endl;  double num1 = 1;double num2 = 2;double min = minmum(num1, num2);cout << min << endl;
}

函数模板本身不是一个函数,函数模板像一个公式,它列好了函数返回值类型、 函数名、函数的参数列表的参数类型,编译器通过这个公式在函数被调用时自动推导参数的类型,然后把这个模板实例化成相应的函数。

3、模板的实例化

函数模板的泛型参数类型的实际类型是在编译期间确定的,这个过程被称为模板实例化。

当编译器遇到一个函数模板的调用时,它会根据传给模板函数的参数类型来推导出模板参数的具体类型,然后生成针对这些具体类型的函数代码。

以上面函数为例,当用double类型使用函数模板时,编译器通过对实参类型的推导,将T确定为double类型,然后产生一份专门处理double类型的代码。这个过程叫实例化函数模板

在这里插入图片描述

4、隐式推导和显式推导

函数模板实例化过程中,其模板参数列表推导过程分为:隐式推导显式推导

(1)显示推导

明确指定参数类型。

int a = 100;
int b = 200;
int result = minmum<int>(a, b);

(2)隐式推导

隐式推导也叫自动类型推导

int a = 100;
int b = 200;
int result = minmum(a, b);

注意:隐式推导,不可以进行隐式类型转换。如下代码编译错误,char c不能隐式转换为int

int a = 100;
//int b = 200;
char c = 'A';
int result = minmum(a, c);
cout << result << endl;

1️⃣ 模板类型推导阶段

  • 当你写 template<typename T> T minmum(T x, T y);
    调用 minmum(a, c)
    • 编译器会尝试 推导 T
    • aint → 推导 T = int
    • cchar → 推导 T = char
    • 冲突 → 编译错误。
  • 重点:模板推导阶段不会做类型提升(char → int)或其他隐式转换。
    所以说“隐式推导,不可以进行隐式类型转换”。

2️⃣ 普通函数调用

  • 如果是普通函数:
int minmum(int x, int y);
minmum(a, c);
  • 这里会允许 隐式类型转换
    • char c 会自动提升为 int
    • 编译成功,返回结果也是 int

✅ 总结:

  • 模板函数:类型推导阶段禁止隐式类型转换,不一致就报错。
  • 普通函数:调用参数类型可以做隐式转换,符合函数参数类型即可。

模板类型推导阶段不会做隐式转换,但显式指定模板参数后,就不是推导阶段,而是普通函数调用阶段,允许隐式转换。如:

double m = 3.5, n = 7.2;
cout << max<int>(m, n) << endl;
m 和 n 是 double
模板要求参数类型为 T,这里就是 int → T a, T b 实际就是 int a, int b
这里发生了普通的隐式类型转换:
double m → 转换成 int(小数部分丢失)
double n → 转换成 int

(3)错误案例

#include <iostream>
template <typename T,typename E> T maxNum(T t1,T t2) {return t1 > t2 ? t1 : t2;
}
int main(){int result =minNum<int>(20, 30); //会报错,因为上面定义E类型,但是使用时没有指定E的类型//解决方案:int result =minNum<int,int>(20, 30); 或者在template模板中把E类型去掉return 0;
}

四、类模板

类模板是面向类型的通用蓝图,让同一个类逻辑可以支持多种数据类型,提高复用性、类型安全性和开发效率。

  • 与函数模板不同,编译器不能为类模板推断模板的参数类型,需要显示指定参数类型;同时类模板参数可以有默认值。
  • 将类模板和函数模板的定义写在头文件中,通常不需要为它们单独创建对应的.cpp文件。这是因为模板的实例化发生在使用模板的地方,编译器需要看到模板的完整定义才能生成函数或类的代码。如果将模板的定义放在.cpp文件中,编译器在处理那些包含模板声明的 .h 文件时无法看到具体的实现,从而无法为每一个具体类型生成对应的模板实例。
1、语法
template <typename T1, typename T2,...,typename Tn> class 类模板名
{
// 类内成员定义
};
template <class T1, class T2,...,class Tn> class 类模板名
{
// 类内成员定义
};
2、类模板的定义及使用
template <class T>    // 或 template<typename T>
class MyTemplateClass 
{
public:MyTemplateClass(T data): data(data) {}void print() {std::cout << data << std::endl;}
private:T data;
};
  • T 就是模板参数,代表类中某个成员的数据类型。
  • 可以有多个模板参数:template <typename T1, typename T2>
  • 可以指定默认类型:template <typename T=int>
#include "MyTemplateClass.h"int main() {MyTemplateClass<int> obj1(100);      // T=intMyTemplateClass<double> obj2(3.14);  // T=doubleMyTemplateClass<std::string> obj3("Hello"); // T=std::stringobj1.print();  // 输出:100obj2.print();  // 输出:3.14obj3.print();  // 输出:Helloreturn 0;
}
  • 使用时 必须显示指定类型(与函数模板不同,函数模板可以让编译器推导类型)。
  • 编译器在看到 MyTemplateClass<int> 时,会根据模板生成一个具体的 int 类,生成 MyTemplateClass<double> 时又生成一个对应的 double 类。

类模板(Class Template)就是 用类型参数化的类,相当于给类开了一个“通用接口”,允许用不同的数据类型生成对应的类。

  • 核心思想:把类的数据类型抽象成模板参数,使用时再指定具体类型。
  • 优点
    1. 复用性强:同一个模板可以生成多种类型的类,而不需要重复写代码。
    2. 类型安全:编译器在实例化时会检查类型。
    3. 减少冗余:避免手动为每种类型写一份几乎一样的类。
http://www.lryc.cn/news/621168.html

相关文章:

  • Redis7学习--持久化机制 RDB与AOF
  • 汽车生产线白皮书:稳联技术Profinet转Ethernet IP网关通信高效性
  • StarRocks优化统计分析
  • Redis入门到实战教程,深度透析redis
  • 零信任架构(Zero Trust Architecture, ZTA)(通过动态验证和最小权限控制,实现对所有访问请求的严格授权和持续监控)
  • Java应用架构实战指南:主流模式解析与Spring落地实践
  • diffusers库学习--pipeline,模型,调度器的基础使用
  • Docker exec进入容器命令的入门教程
  • 使用正则表达式 \s+ 作为分隔符处理字符串
  • 【cmake】编译cpp文件,安装MinGW
  • Python 进阶详解:正则表达式与 JSON —— 文本处理与数据交换的核心技能
  • K8s-持久化存储
  • 第1节:多模态大模型入门(多模态大模型基础教程)
  • 安装 Nginx
  • Spring Boot + Redis + 布隆过滤器防止缓存穿透
  • UML函数原型中constraint的含义,有啥用?
  • 读《精益数据分析》:移情(Empathy)—— 验证真实需求,避免伪需求陷阱
  • 加密货币交易所开发:如何打造安全、高并发的数字资产交易平台?
  • 7、C 语言数组进阶知识点总结
  • 分布式事务、锁、链路追踪
  • Mybatis学习笔记(九)
  • C#WPF实战出真汁01--搭建项目三层架构
  • 计算机视觉第一课opencv(二)保姆级教
  • 【CLR via C#(第3版)阅读笔记】类型基础
  • (论文速读)DiffusionDet - 扩散模型在目标检测中的开创性应用
  • 【C#】跨平台创建你的WinForms窗体应用(WindowsUbuntu)
  • 从零开始的云计算生活——第四十三天,激流勇进,kubernetes模块之Pod资源对象
  • Ansible企业级实战
  • 设计模式(2)
  • sql的关键字 limit 和offset