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

C++学习笔记——模板初阶

目录

1、泛型编程(概念引入)

2、函数模板

核心概念​:

​基本格式​:

​工作原理​:

​实例化方式​:

匹配原则​:

代码分析:

函数模板的隐式实例化和类型推演

实例化方式​

3、类模板

​核心概念​:

​基本格式​:

​实例化方式​:

代码分析:

Stack栈的类模板实现

题目

1.下面有关C++中为什么用模板类的原因,描述错误的是? ( )

2.下列关于模板的说法正确的是( )

3.下列的模板声明中,其中几个是正确的( )

4.下列描述错误的是( )


1、泛型编程(概念引入

问题:如何实现一个通用的交换函数呢?

答:实现一个通用的交换函数 Swap 是常见需求。初始解决方案通常采用函数重载,即为不同的基本类型(如 int、double、char 等)分别编写逻辑相同但类型不同的多个重载函数(如下)。

// 交换 int
void Swap(int& left, int& right) 
{ int temp = left; left = right; right = temp; 
}// 交换 double
void Swap(double& left, double& right) 
{ double temp = left; left = right; right = temp; 
}// 交换 char
void Swap(char& left, char& right) 
{ char temp = left; left = right; right = temp; 
}

然而,这种方案存在明显缺点:首先,代码复用率较低,每增加一种新类型就需要手动编写一个几乎完全相同的新函数,导致代码冗余;其次,可维护性较差,若未来需要调整交换逻辑(尽管对于 Swap 函数可能性较小),必须逐一修改每个重载函数,容易遗漏而产生错误。

因此,理想解决方案是采用泛型编程:通过创建一个与类型无关的“模具”,让编译器根据实际需要的类型自动生成具体类型的代码。这种“编写与类型无关的通用代码”的方式正是泛型编程的核心思想。在 C++ 中,模板(template)作为实现泛型编程的基础工具,能够高效解决此类问题,提升代码的复用性和可维护性。

泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。

2、函数模板

核心概念​:

函数模板是一个与类型无关的蓝图,用于生成针对特定类型的函数家族。它通过参数化类型来实现泛型编程。

​基本格式​:

1.使用关键字 template 后跟模板参数列表(typename T 或 class T)来定义。
2.示例中的 Swap 模板是经典案例,展示了如何用模板实现通用的交换功能。

//(在模板参数中,typename 和 class 可互换):
// 使用 typename 关键字
template <typename T>
return_type function_name(parameter_list) {// 函数体
}
// 使用 class 关键字
template <class T>
return_type function_name(parameter_list) {// 函数体
}

​工作原理​:

模板本身不是函数在编译阶段,编译器根据调用时传入的实参类型进行类型推演,然后为每种实际使用的类型生成一份独立的函数代码(实例化)。

​实例化方式​:

a.隐式实例化​:编译器自动根据调用函数的实参类型来推演模板参数的类型。
​b.显式实例化​:用户在函数名后的 <> 中手动指定模板参数的类型(如 Add<int>(a, b))。
注意:当编译器无法通过推演确定类型时(例如 Add(int, double)),需要使用者通过显式实例化或强制类型转换来解决。

匹配原则​:

1.非模板函数优先​:如果存在一个参数完全匹配的非模板函数,则优先调用它。
​2.模板可生成更好匹配时优先模板​:如果模板能生成一个比非模板函数或类型转换后更匹配的版本,则选择模板。
​3.类型转换​:模板函数不允许自动类型转换,而普通函数可以。


代码分析:

函数模板的隐式实例化和类型推演

// 交换函数模板
template<typename T> 
void Swap( T& left, T& right) 
{T temp = left; left = right; right = temp; 
}int main()
{double d1 = 2.0;double d2 = 5.0;Swap(d1, d2); // 编译器推演 T 为 double,生成并调用 Swap(double&, double&)int i1 = 10;int i2 = 20;Swap(i1, i2); // 编译器推演 T 为 int,生成并调用 Swap(int&, int&)char a = '0';char b = '9';Swap(a, b); // 编译器推演 T 为 char,生成并调用 Swap(char&, char&)return 0;
}

实例化方式​

#include<iostream>
using namespace std;template<class T>
T Add(const T& T0, const T& T1)
{return T0 + T1;
}int main()
{//隐式实例化int a1 = 1, b1 = 2;cout << Add(a1, b1) << endl;double a2 = 1.2, b2 = 2.2;cout << Add(a2, b2) << endl;int x = 1;double y = 2.5;// 编译器无法确定此处到底该将T确定为int 或者 double类型而报错// 此时有两种处理方式:1.使用显式实例化 2.用户自己来强制转化 // cout << Add(x, y) << endl;// 显示实例化cout << Add<int>(x, y) << endl;cout << Add<double>(x, y) << endl;// 强制类型转换cout << Add((double)x, y) << endl;cout << Add(x, (int)y) << endl;return 0;
}

注意:当编译器无法通过推演确定类型时(例如 Add(int, double)),需要使用者通过显式实例化或强制类型转换来解决。

3、类模板

​核心概念​:

类模板是用于生成类的蓝图。它允许你定义一个类,其中某些成员的类型、返回值类型或参数类型可以被参数化,从而可以用一套代码定义出处理不同数据类型的类家族。


​基本格式​:

1.使用 template <class/typename T1, class T2, ...> 关键字声明模板参数列表。
2.类名后的 <T> 是类模板名的一部分(例如 Stack<T>)。

template<class T1, class T2, ..., class Tn>
class ClassName
{// 类成员定义...
};

​实例化方式​:

1.类模板的实例化与函数模板有一个关键区别:​必须显式实例化。
2.类模板名本身(如 Stack)不是真正的类型,​**类模板名后跟 <类型>**​(如 Stack<int>)才是编译器生成的真正的类类型。(// Stack是类名,Stack<int>才是类型
3.实例化是编译器根据你指定的类型,用该类型替换模板中的所有 T,从而生成一个具体的类的过程。

注意模板的声明和定义不能分离到 .h 和 .cpp 文件中。这会导致链接错误。最佳实践是将它们统一放在一个头文件(.h 或 .hpp)​​ 里。原因在于编译器编译单元的工作方式与模板实例化的机制有关。

代码分析:

Stack栈的类模板实现

#include<iostream>
using namespace std;// 1. 类模板声明
template<typename T> // 模板参数列表:定义一个类型参数 T
class Stack
{
public:// 构造函数Stack(size_t capacity = 4){_array = new T[capacity]; // 使用 T 分配数组,决定了数组元素的类型_capacity = capacity;_size = 0;}// 成员函数声明void Push(const T& data); // 参数类型和返回值类型都可以使用 Tprivate:T* _array;        // 成员变量类型使用 Tsize_t _capacity;size_t _size;
};// 2. 类模板的成员函数在类外定义
template<class T> // 必须再次声明模板参数列表
void Stack<T>::Push(const T& data) // 类名必须指定为 Stack<T>,而不是 Stack
{// 检查并扩容的代码应在此处 (示例中省略了)_array[_size] = data; // 对 T 类型的对象进行赋值操作++_size;
}// 3. 类模板的实例化
int main()
{Stack<int> st1;    // 显式实例化:编译器生成一个专门处理 int 类型的 Stack 类Stack<double> st2; // 显式实例化:编译器生成一个专门处理 double 类型的 Stack 类st1.Push(1);       // 操作的是 int 类型的栈st2.Push(3.14);    // 操作的是 double 类型的栈return 0;
}

**Stack<int> st1;** -> _array 的类型是 int*
**Stack<double> st2;** -> _array 的类型是 double*

​**void Stack<T>::Push(const T& data)**:在类外定义成员函数时,函数名前的类名必须加上模板参数 Stack<T>。必须在函数定义前再次加上 template<class T> 模板声明。

题目

1.下面有关C++中为什么用模板类的原因,描述错误的是? ( )

选C
A.可用来创建动态增长和减小的数据结构
B.它是类型无关的,因此具有很高的可复用性
C.它运行时检查数据类型,保证了类型安全
D.它是平台无关的,可移植性

A→ 模板可以具有非类型参数,用于指定大小,可以根据指定的大小创建动态结构

B→ 模板最重要的一点就是类型无关,提高了代码复用性

C→ 模板的类型检查发生在编译时​(而非运行时),编译时即可保证类型安全,但运行时不会再次检查数据类型,也不保证类型安全,相当于类型的宏替换,故错误

D→ 模板类基于源代码实现,不依赖特定平台特性(只要编译器支持),模板的代码就是可移植的


2.下列关于模板的说法正确的是( )

选D
A.模板的实参在任何时候都可以省略
B.类模板与模板类所指的是同一概念
C.类模板的参数必须是虚拟类型的
D.类模板中的成员函数全是函数模板

A→ 模板实参省略意思为隐式实例化,一般情况下都使用隐式实例化,不需指定模板类型参数,让编译器进行推导。当模板参数无法通过上下文推导时(如类模板实例化),必须显式指定实参。

B→ 类模板是模板的定义(如template<typename T> class A{};),而模板类是类模板实例化后的具体类(如A<int>)。

C→ 类模板的参数可以是虚拟类型(如typename T),但也可以是非类型参数(如整型、指针等,例如template<int N> class B{};)。

D→ 类模板中的成员函数在未实例化时本身就是函数模板(因为依赖于模板参数列表,如template<typename T> void Stack<T>::Push(const T& value){}),因此该描述正确。


3.下列的模板声明中,其中几个是正确的( )

答:只有4,6,7为正确声明,3个是正确的
1)template→  缺少模板参数列表(如<>),无效声明
2)template<T1,T2>→  参数T1、T2未用typename或class修饰。
3)template<class T1,T2>→  T2缺少修饰符(应为class T2或typename T2)。
4)template<class T1,class T2>→   正确,两个参数均用class修饰。
5)template<typename T1,T2>→  T2缺少修饰符。
6)template<typename T1,typename T2>→  正确,两个参数均用typename修饰。
7)template<class T1,typename T2>→  正确,混合使用class和typename允许(语义相同)。
8)<typename T1,class T2>→  缺少template关键字


4.下列描述错误的是( )

选D
A.编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础
B.函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具
C.模板分为函数模板和类模板
D. 模板类跟普通类以一样的,编译器对它的处理时一样的

A→ 模板通过泛型编程实现类型无关的代码,提高复用性

B→ 函数模板是编译器生成具体类型函数的蓝图

C→ 模板分为函数模板(生成函数)和类模板(生成类),这是C++标准分类。

D→ 模板类(类模板实例化后的结果)与普通类在编译处理方式上不同。模板类需要编译器进行实例化(根据类型参数生成具体代码),而普通类直接编译。此外,模板类的定义通常需放在头文件中(因编译机制要求),普通类可分离声明与实现。

http://www.dtcms.com/a/393484.html

相关文章:

  • Windows 下 WSL2 生态:Ubuntu/Docker Desktop 关系、VLLM 部署差异与性能解析
  • 智能体技术革命:从华为金融智能体FAB看AI智能体的未来发展与行业影响
  • CIKM 2025 | FinCast:用于金融时间序列预测的基础模型
  • 论文解读——矩形隧道中MIMO信道特性的模态理论解释
  • Mac brew VSCode Python3
  • 【C++】list 的使用及迭代器底层详解
  • NumPy 系列(三):numpy 数组的索引
  • STL源码探秘(一):深入剖析List的迭代器设计与实现
  • PNP机器人九月具身智能引领FRANKA机器人具身智能技术创新与人形机器人产业全球化新格局,全球具身领先生态推动模型部署和泛化
  • ACP(八):用插件扩展答疑机器人的能力边界
  • 迁移学习:从理论到实践,让模型 “举一反三” 的核心技术
  • ACP(六)自动化评测机器人的表现
  • 【MySQL数据库】MySQL的第一步:从安装启动到用户权限配置的一站式实战指南
  • MySQL笔记7
  • 【C语言】C语言预处理详解,从基础到进阶的全面讲解
  • Spotify:递归嵌入与聚类(四)
  • 三种查询语言比较:SQL、SPL、PromQL
  • [Windows] 迅连科技音频处理工具 CyberLink AudioDirector 2026 16.0.5703.0 中文多语免费版
  • (一)React面试(虚拟DOM/类组件)
  • 亲历 2025 机器人大赛:科技碰撞的震撼与启迪
  • Chromium 138 编译指南 Ubuntu篇:Python环境与开发工具配置(五)
  • 在CentOS上配置SVN至Web目录的自动同步
  • 一款不错的PDF工具,吾爱出品
  • Sleuth + Zipkin:微服务监控之分布式链路追踪技术
  • JVM 调优在分布式场景下的特殊策略:从集群 GC 分析到 OOM 排查实战(一)
  • 【开题答辩全过程】以 基于Vue技术实现权限管理系统为例,包含答辩的问题和答案
  • Redis 高可用架构全解析:主从复制、哨兵与集群模式
  • Redis全面解析:从基础配置到高可用集群
  • Redis:高性能Key-Value存储与缓存利器
  • Redis 三种核心服务架构详解:主从复制、哨兵模式与集群模式