【C++篇】引领C++模板初体验:泛型编程的力量与妙用

文章目录

  • C++模板编程
    • 前言
      • 第一章: 初始模板与函数模版
        • 1.1 什么是泛型编程?
          • 1.1.1 为什么要有泛型编程?
          • 1.1.1 泛型编程的优势
        • 1.2 函数模板的基础
          • 1.2.1 什么是函数模板?
          • 1.2.2 函数模板的定义格式
          • 1.2.3 示例:通用的交换函数
            • 输出示例:
          • 1.2.4 模板中的`typename`与`class`
        • 1.3 函数模板的原理
          • 1.3.1 函数模板的实例化
          • 1.3.2 隐式实例化与显式实例化
      • 第二章: 类模板
        • 2.1 类模板概念
          • 2.1.1 类模板的定义格式
          • 2.1.2 示例:简单的类模板
            • 输出示例:
        • 2.2 类模板的实例化
        • 2.3 类模板中的成员函数定义
        • 2.4 为什么不建议类模板的定义和声明分离?
          • 2.4.1 模板的编译时行为
          • 2.4.2 链接器无法找到定义
          • 2.4.3 无法预编译模板
          • 2.4.4 解决方案:将声明和定义放在同一个头文件中
      • 第三章: 模板的匹配原则
        • 3.1 模板的匹配原则
  • 写在最后

C++模板编程

💬 欢迎讨论:如果你在学习过程中有任何问题或想法,欢迎在评论区留言,我们一起交流学习。你的支持是我继续创作的动力!

👍 点赞、收藏与分享:觉得这篇文章对你有帮助吗?别忘了点赞、收藏并分享给更多的小伙伴哦!你们的支持是我不断进步的动力!
🚀 分享给更多人:如果你觉得这篇文章对你有帮助,欢迎分享给更多对C++感兴趣的朋友,让我们一起进步!

前言

C++作为一门强大的编程语言,以其丰富的功能和灵活的设计著称。模板编程是C++中非常重要的一个特性,通过模板可以实现泛型编程,编写与数据类型无关的代码,极大地提高了代码的复用性和可维护性。本文将从泛型编程、函数模板、类模板等几个方面详细讲解C++模板的使用,并结合实际的代码示例进行分析,帮助大家全面掌握模板编程的知识。

本篇文章将包含以下几个部分:

  1. 泛型编程的基本概念
  2. 函数模板的定义与使用
  3. 类模板的实现
  4. 模板的匹配原则

通过阅读本文,你将能够掌握C++模板编程的基础知识,理解其背后的工作原理,并学会如何在实际项目中应用这些技术。


第一章: 初始模板与函数模版

1.1 什么是泛型编程?

泛型编程(Generic Programming)是C++中的一种编程范式,旨在编写与数据类型无关的通用代码。这意味着你可以编写一次代码,并通过不同的数据类型进行复用。C++通过模板(Template)来实现泛型编程,模板是泛型编程的核心工具。

1.1.1 为什么要有泛型编程?
  • 问题提出:如何实现一个通用的交换函数呢?
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. 代码的可维护性比较低,一个出错可能所有的重载均出错

那能否告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码呢?

在这里插入图片描述
如果在C++中,也能够存在这样一个模具,通过给这个模具中填充不同材料(类型),来获得不同
材料的铸件(即生成具体类型的代码),那将会节省许多头发。巧的是前人早已将树栽好,我们只需在此乘凉。这就是泛型编程

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

1.1.1 泛型编程的优势

泛型编程的主要优势包括:

  • 代码复用性强:通过模板,你可以避免为每个数据类型单独编写相同功能的代码。
  • 提高代码的可维护性:代码只需编写一次,减少了冗余代码,后续如果需要修改或修复,只需在一处进行。
  • 减少编写错误:重复编写代码时容易出错,而模板可以让编译器自动生成所需代码,减少人为失误。

在这里插入图片描述


1.2 函数模板的基础
1.2.1 什么是函数模板?

函数模板(Function Template)是一个与类型无关的函数“蓝图”。通过模板参数,编译器在编译期间会根据实际的数据类型生成相应的函数版本。

1.2.2 函数模板的定义格式

我们可以通过以下格式来定义一个函数模板:

template<typename T> 
返回类型 函数名(参数列表) {// 函数体
}
  • template:告诉编译器接下来的内容是模板。
  • typename T:定义一个模板参数T,可以用来表示任何类型。
  • 返回类型参数列表可以使用T作为数据类型。

注意:typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替
class),下文会讲到

1.2.3 示例:通用的交换函数

那我们就可以使用模板来编写交换函数,它可以交换任意类型的数据:

/*** @brief 通用的交换函数* @tparam T 通用的类型参数,由编译器根据实参推断* @param left 左侧变量* @param right 右侧变量*/
template<typename T>
void Swap(T& left, T& right) {T temp = left;left = right;right = temp;
}int main() {int a = 10, b = 20;double x = 1.1, y = 2.2;char c1 = 'A', c2 = 'B';// 使用模板函数进行交换Swap(a, b);    Swap(x, y);    Swap(c1, c2);  std::cout << "交换后的整数: " << a << " " << b << std::endl;std::cout << "交换后的浮点数: " << x << " " << y << std::endl;std::cout << "交换后的字符: " << c1 << " " << c2 << std::endl;return 0;
}
输出示例:
交换后的整数: 20 10
交换后的浮点数: 2.2 1.1
交换后的字符: B A
1.2.4 模板中的typenameclass

在定义模板时,typenameclass是可以互换的。你可以选择以下两种方式:

template<typename T>  // 使用 typename
template<class T>     // 使用 class

虽然两者功能相同,但推荐使用typename,因为它能够更好地表达该参数是一个类型参数,避免与类的定义产生混淆。


1.3 函数模板的原理

函数模板的核心在于它不是一个真正的函数,而是一个编译器用来生成特定类型函数的蓝图。编译器根据模板的使用情况推导出具体的类型,并生成相应的代码。这一过程称为模板的实例化

1.3.1 函数模板的实例化

当我们调用模板函数时,编译器会根据实际的参数类型生成对应的函数版本。比如:

template<class T>
T Add(const T& left, const T& right) {return left + right;
}int main() {int a = 10, b = 20;double x = 1.1, y = 2.2;Add(a, b);  // 生成 Add<int> 版本Add(x, y);  // 生成 Add<double> 版本return 0;
}

在这里插入图片描述

在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应
类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,
将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此。

1.3.2 隐式实例化与显式实例化
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);Add(d1, d2);Add(a1, d1);/*
该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型
通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有
一个T,
编译器无法确定此处到底该将T确定为int 或者 double类型而报错
注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要
背黑锅*/// 此时有两种处理方式:1. 用户自己来强制转化 2. 使用显式实例化Add(a1, (int)d1);//自己强制转换return 0;
}

模板实例化分为两种:

  1. 隐式实例化:编译器根据实参推导出模板参数,并自动生成函数。例如上面的Add函数就是隐式实例化。
  2. 显式实例化:如果想要强制指定模板参数,可以使用显式实例化:

就是第二种处理方式

int main(void)
{int a = 10;double b = 20.0;// 显式实例化Add<int>(a, b);return 0;
};

第二章: 类模板

2.1 类模板概念

类模板(Class Template)是用于定义与类型无关的类,它允许我们在类的定义中使用模板参数,编译时再根据实际类型进行类的实例化。类模板与函数模板类似,只不过它是用来生成类的。

2.1.1 类模板的定义格式

定义类模板的语法格式如下:

template<class T>
class 类名 {// 类的成员变量和方法
};
  • template<class T>:告诉编译器这是一个模板类,T是一个类型参数。
  • 类的成员和方法可以使用T作为数据类型,编译时由用户提供的类型来替代T
2.1.2 示例:简单的类模板

下面是一个简单的栈(Stack)类模板,用于存储任意类型的数据:

#include<iostream>using namespace std;/*** @brief 栈的类模板* @tparam T 通用的类型参数*/
template<class T>
class Stack {
public:Stack(size_t capacity = 4) {_array = new T[capacity];_capacity = capacity;_size = 0;}~Stack() {delete[] _array;}/// @brief 将元素压入栈中void Push(const T& data) {if (_size == _capacity) {Expand();  // 扩容}_array[_size++] = data;}/// @brief 弹出栈顶元素void Pop() {if (_size > 0) {--_size;}}/// @brief 返回栈顶元素T& Top() {if (_size > 0) {return _array[_size - 1];}throw out_of_range("栈为空!");}/// @brief 检查栈是否为空bool IsEmpty() const {return _size == 0;}/// @brief 获取栈中元素的数量size_t Size() const {return _size;}private:T* _array;size_t _capacity;size_t _size;/// @brief 扩展栈的容量void Expand() {size_t newCapacity = _capacity * 2;T* newArray = new T[newCapacity];for (size_t i = 0; i < _size; ++i) {newArray[i] = _array[i];}delete[] _array;_array = newArray;_capacity = newCapacity;}
};int main() {Stack<int> st;  // 创建存储整数的栈st.Push(10);st.Push(20);st.Push(30);cout << "栈顶元素: " << st.Top() << endl;st.Pop();cout << "弹出后栈顶元素: " << st.Top() << endl;return 0;
}
输出示例:
栈顶元素: 30
弹出后栈顶元素: 20

在这个类模板中,T是一个通用类型参数。Stack<int>Stack类模板的一个实例化,表示它是一个存储int类型数据的栈。编译器会根据实际使用的类型自动生成相应的类。

2.2 类模板的实例化

与函数模板不同,类模板在使用时必须显示地提供类型参数。实例化类模板时,必须在类名后面的尖括号<>中指定实际的类型参数。例如:

Stack<int> st1;    // 实例化为处理 int 类型的栈
Stack<double> st2; // 实例化为处理 double 类型的栈

这里的Stack<int>Stack<double>分别表示不同的类型,即不同的模板实例。编译器会根据模板参数生成相应的类代码。

2.3 类模板中的成员函数定义

对于类模板,成员函数可以在类定义内或定义外实现。类模板的成员函数定义外置时,需要在函数名之前加上模板声明和模板参数。例如:

template<class T>
void Stack<T>::Push(const T& data) {if (_size == _capacity) {Expand();  // 扩容}_array[_size++] = data;
}
2.4 为什么不建议类模板的定义和声明分离?

在C++中,类模板的实现与普通类有一个显著的区别:模板是在编译时根据实际类型实例化的,而不是像普通的类那样在编译期和链接期处理。这导致了一个很重要的问题:如果将类模板的声明和定义分离到不同的文件中,可能会导致链接错误。以下是详细原因:

2.4.1 模板的编译时行为

类模板的本质是一个“蓝图”,它并不是一个完整的类,而是一个在需要时根据实际类型生成代码的模式。因此,模板只有在实际使用(实例化)时,编译器才会生成对应的类型的代码。编译器无法预先知道你会使用哪些类型来实例化模板,因此它不会为模板生成实际的代码。

2.4.2 链接器无法找到定义

当你将类模板的声明放在头文件中,而把定义放在.cpp文件中时,模板实例化的过程可能发生在不同的编译单元中。因为模板只有在编译期被实例化,链接器在链接时无法看到模板的定义,除非在编译时所有模板的实例化代码都可见。如果定义在.cpp文件中,其他使用模板的编译单元无法找到这个定义,导致链接器报错

2.4.3 无法预编译模板

与普通类不同,类模板无法被预编译或只在一个编译单元中定义然后供其他单元使用。普通的类在编译过程中,编译器会生成目标代码并储存在.obj文件中,链接时其他编译单元可以引用这些已生成的代码。而类模板无法这样做,因为它需要知道使用时的类型才能生成实际的代码。

2.4.4 解决方案:将声明和定义放在同一个头文件中

为了避免上述问题,C++的惯用方法是将类模板的声明和定义都放在同一个头文件中。这使得每个使用模板的编译单元在实例化模板时,编译器能够访问到模板的定义,并根据需要生成实际的代码。这种方式确保了编译器能够在编译期处理模板的实例化,而不会在链接时出现找不到定义的问题。

错误用法:

// Stack.h
template<typename T>
class Stack {
public:void Push(const T& value);// 声明在头文件中
};// Stack.cpp
template<typename T>
void Stack<T>::Push(const T& value) {// 定义在.cpp文件中
}

在这种情况下,如果不同编译单元使用了Stack<int>Stack<double>,链接器可能会报错,因为它无法找到模板的定义。

正确用法:

// Stack.h
template<typename T>
class Stack {
public:void Push(const T& value);
};template<typename T>
void Stack<T>::Push(const T& value) {// 声明和定义都在头文件中
}

这种方法确保每个编译单元都能访问到模板的完整定义,避免链接时的错误。

总结:

类模板的代码只有在实例化时才生成,因此类模板的定义必须在每个使用它的编译单元中可见。将模板的声明和定义放在同一个头文件中,可以确保模板实例化时能够访问到其定义,避免链接错误。这也是为什么大多数C++开发者在编写模板时会将模板的实现放在头文件中的原因。

注意:

函数模板与类模板不同,当代大多数编译器支持函数模板的声明和定义分离,这是因为函数模板的实例化往往只涉及函数的具体调用,不像类模板这么复杂,具体之后的博客会更详细的讲解此处的内容,敬请期待哦💕


第三章: 模板的匹配原则

3.1 模板的匹配原则

C++编译器在调用模板时,会根据实参类型和函数参数类型进行匹配。模板的匹配规则如下:

  1. 优先调用非模板函数:如果存在一个与实参完全匹配的非模板函数,编译器将优先调用非模板函数,而不是通过模板生成一个实例。

    例如:

    int Add(int a, int b) {return a + b;
    }template<typename T>
    T Add(T a, T b) {return a + b;
    }int main() {int a = 10, b = 20;cout << Add(a, b) << endl;  // 调用非模板版本
    }
    
  2. 如果非模板函数没有匹配,则调用模板实例:如果模板函数比非模板函数更能匹配参数类型,编译器将生成模板实例。

    例如:

    double Add(double a, double b) {return a + b;
    }template<typename T>
    T Add(T a, T b) {return a + b;
    }int main() {int a = 10, b = 20;double x = 1.1, y = 2.2;cout << Add(a, b) << endl;  // 调用模板实例 Add<int>cout << Add(x, y) << endl;  // 调用非模板版本 Add(double, double)
    }
    

写在最后

本文基础的讲解了C++模板编程的基础知识,涵盖了泛型编程、函数模板、类模板、模板匹配原则等概念。通过这些模板功能,C++开发者可以编写更加灵活和可复用的代码,大幅提高编程效率。

模板编程虽然强大,但使用时也需要谨慎,尤其是在处理模板特化和匹配规则时。如果能够合理地使用模板技术,相信你的代码质量将会有显著提升。


以上就是关于【C++篇】引领C++模板初体验:泛型编程的力量与妙用的内容啦,在之后会有另一篇博客来讲解有关模板的更多进阶内容,敬请期待哦,然后各位大佬有什么问题欢迎在评论区指正,或者私信我也是可以的啦,您的支持是我创作的最大动力!❤️

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/429874.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

华为HarmonyOS地图服务 11 - 如何在地图上增加点注释?

场景介绍 本章节将向您介绍如何在地图的指定位置添加点注释以标识位置、商家、建筑等&#xff0c;并可以通过信息窗口展示详细信息。 点注释支持功能&#xff1a; 支持设置图标、文字、碰撞规则等。支持添加点击事件。 PointAnnotation有默认风格&#xff0c;同时也支持自定…

文献阅读(220)MRCN

题目&#xff1a;MRCN: Throughput-Oriented Multicast Routing for Customized Network-on-Chips时间&#xff1a;2023期刊&#xff1a;TPDS研究机构&#xff1a;韩国成均馆大学 这篇论文探讨的问题是多播死锁问题&#xff0c;下图中Packet A分成两条路径&#xff0c;但在rou…

Leetcode—1014. 最佳观光组合【中等】

2024每日刷题&#xff08;164&#xff09; Leetcode—1014. 最佳观光组合 实现代码 class Solution { public:int maxScoreSightseeingPair(vector<int>& values) {int mxPre values[0] 0;int ans 0;for(int i 1; i < values.size(); i) {ans max(ans, mxP…

python绘制弦图-科研作图

一、背景 弦图以其直观、精美的展示方式受到越来越多人的关注&#xff0c;它不仅能够有效展示两个变量之间的联系&#xff0c;还能同时展现多个变量间的复杂互动&#xff0c;本文将通过Python语言中的pycirclize库&#xff0c;带你深入了解如何绘制弦图。 弦图是一种圆…

51单片机——矩阵键盘

一、矩阵键盘原理图 我们发现: P17,P16,P15,P14控制行&#xff0c; P13,P12,P11,P10控制列。 所以我们如果要选择第四列&#xff0c;只需要把整个P1先给高电位1&#xff0c;再把P10给低电位0。 二、代码 P10xFF; P100; if(P170){Delay(20);while(P170);Delay(20);KeyNum…

计算机毕业设计python+spark知识图谱房价预测系统 房源推荐系统 房源数据分析 房源可视化 房源大数据大屏 大数据毕业设计 机器学习

《PythonSpark知识图谱房价预测系统》开题报告 一、研究背景与意义 随着城市化进程的加速和房地产市场的不断发展&#xff0c;房价成为影响人们生活质量的重要因素之一。准确预测房价不仅有助于政府制定科学的房地产政策&#xff0c;还能为开发商提供市场参考&#xff0c;同时…

DriveMatriX Highway Dataset :高速公路驾驶数据集(猫脸码客 第196期)

DriveMatriX Highway Dataset 1.0&#xff1a;自动驾驶与ADAS感知验证的里程碑 在当今快速发展的自动驾驶&#xff08;AV&#xff09;和高级驾驶辅助系统&#xff08;ADAS&#xff09;领域&#xff0c;数据的获取与处理成为了推动技术进步的关键因素。为了在这些复杂且多变的交…

【软件测试】Bug 篇

哈喽&#xff0c;哈喽&#xff0c;大家好~ 我是你们的老朋友&#xff1a;保护小周ღ 今天给大家带来的是 【软件测试】Bug 篇&#xff0c;首先了解, 什么是Bug, 如何定义一个Bug, 如何描述一个 Bug, Bug的级别, 和 Bug 的生命周期, 以及测试人员跟开发人员产生争执如何处理,…

【Linux】常用指令【更详细,带实操】

Linux全套讲解系列&#xff0c;参考视频-B站韩顺平&#xff0c;本文的讲解更为详细 目录 一、文件目录指令 1、cd【change directory】指令 ​ 2、mkdir【make dir..】指令​ 3、cp【copy】指令 ​ 4、rm【remove】指令 5、mv【move】指令 6、cat指令和more指令 7、less和…

【设计模式】万字详解:深入掌握五大基础行为模式

作者&#xff1a;后端小肥肠 &#x1f347; 我写过的文章中的相关代码放到了gitee&#xff0c;地址&#xff1a;xfc-fdw-cloud: 公共解决方案 &#x1f34a; 有疑问可私信或评论区联系我。 &#x1f951; 创作不易未经允许严禁转载。 姊妹篇&#xff1a; 【设计模式】&#xf…

多模态大模型应用开发技术学习

前篇提到多模态模型应用是未来的应用方向&#xff0c;本篇就聊聊技术学习方面的内容。 应用场景 多模态大模型技术的应用场景非常广泛&#xff0c;涵盖了从日常生活到专业领域的各个方面。以下是一些主要的应用场景&#xff1a; 办公自动化&#xff1a;多模态大模型可以用于…

计算机网络-小型综合网络的搭建涉及到无线路由交换安全

目录 1 拓扑架构 2 做项目的思路 3 做配置 3.1先做核心交换 3.2 防火墙的配置 4 ac 和ap 的配置 4.1 ac上配置安全的东西 5.1 测试​编辑 1 拓扑架构 要求看上面的图 2 做项目的思路 这张网很明显是一个小综合&#xff0c;设计到我们的无线交换&#xff0c;路由…

jdk11特性介绍

JDK 11&#xff08;也称为Java 11&#xff09;是Java平台的一个重要版本&#xff0c;它引入了许多新特性和改进&#xff0c;旨在提高开发者的生产力和Java平台的性能。以下是一些JDK 11的主要特性&#xff1a; 局部变量类型推断&#xff08;Local-Variable Syntax for Lambda P…

linux中vim编辑器的应用实例

前言 Linux有大量的配置文件&#xff0c;其中编辑一些配置文件&#xff0c;最常用的工具就是 Vim &#xff0c;本文介绍一个实际应用的Vim编辑器开发文档的实例。 Vim是一个类似于Vi的著名的功能强大、高度可定制的文本编辑器&#xff0c;在Vi的基础上改进和增加了很多特性。…

【C语言零基础入门篇 - 17】:排序算法

文章目录 排序算法排序的基本概念冒泡排序选择排序插入排序 排序算法 排序的基本概念 1、什么是排序&#xff1f; 排序是指把一组数据以某种关系&#xff08;递增或递减&#xff09;按顺序排列起来的一种算法。 例如&#xff1a;数列 8、3、5、6、2、9、1、0、4、7 递增排序…

如何防止SQL注入

目录 SQL注入 1、 什么是SQL注入 2、 避免SQL注入 PreparedStatement【重点】 SQL注入 1、 什么是SQL注入 select * from tb_user where username 111 and password 111select * from tb_user where username 111 and password 111 or 11 用户输入的数据中有SQL关键词…

java日志框架之Log4j

文章目录 一、Log4j简介二、Log4j组件介绍1、Loggers (日志记录器)2、Appenders&#xff08;输出控制器&#xff09;3、Layout&#xff08;日志格式化器&#xff09; 三、Log4j快速入门四、Log4j自定义配置文件输出日志1、输出到控制台2、输出到文件3、输出到数据库 五、Log4j自…

【HTTPS】中间人攻击和证书的验证

中间人攻击 服务器可以创建出一堆公钥和私钥&#xff0c;黑客也可以按照同样的方式&#xff0c;创建一对公钥和私钥&#xff0c;冒充自己是服务器&#xff08;搅屎棍&#xff09; 黑客自己也能生成一对公钥和私钥。生成公钥和私钥的算法是开放的&#xff0c;服务器能生产&…

基于阿里云免费部署Qwen1-8B-chat模型并进行lora参数微调从0到1上手操作

文章目录 一、申请资源二、创建实例三、克隆微调数据四、部署Qwen1-8B-chat模型1、环境配置2、模型下载3、本地模型部署 五、模型微调1、拉取Qwen仓库源码2、微调配置3、合并微调参数4、本地部署微调模型 一、申请资源 阿里云账号申请PAI资源详细教程我已于部署ChatGLM3时写过…

MAC如何获取文件数字签名和进程名称

1、安装需要查看数字签名和进程名称的软件包 2、打开终端命令行&#xff08;Terminal&#xff09; 3、查找数字签名 在终端命令行中输入&#xff1a; codesign -dvv 安装的软件路径 2>&1 | grep "Authority" | head -n 1 | cut -d -f2”回显即为进程的数…