【C++】模板(超详细!!!!!!)

文章目录

  • 前言
  • 1. 泛型编程
  • 2. 函数模板
    • 2.1 函数模板概念
    • 2.2 函数模板格式
    • 2.3 函数模板的原理
    • 2.4 函数模板的实例化
    • 2.5 模板参数的匹配原则
    • 2.6 声明和定义分离
  • 3. 类模板
    • 3.1 类模板的定义格式
    • 3.2 类模板的实例化
  • 4. 模板分离编译
    • 4.1 什么是分离编译
    • 4.2 模板的分离编译
  • 总结


前言

提示:这里可以添加本文要记录的大概内容:
C++模板是一项强大的特性,为泛型编程提供了有力的支持。通过使用函数模板和类模板,程序员可以编写通用的代码,使其适用于不同的数据类型,从而提高代码的复用性和灵活性。本文将深入探讨C++模板的基础概念、泛型编程的思想以及函数模板和类模板的使用方法。通过深入了解模板,读者将能够更好地运用这一强大的特性,写出更具通用性和可维护性的代码。

提示:以下是本篇文章正文内容,下面案例可供参考

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++中,也能够存在这样一个模具,通过给这个模具中填充不同材料(类型),来获得不同材料的铸件(生成具体类型的代码),那将会节省许多头发。巧的是前人早已将树栽好,我们只需在此乘凉。

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

在这里插入图片描述

2. 函数模板

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)

2.3 函数模板的原理

函数模板是一个蓝图,它本身并不是函数,是编译器使用特殊方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器去做
示例:

#include <iostream>
using namespace std;
template<class T>
void Swap(T& left, T& right)
{T tmp = left;left = right;right = tmp;
}
int main()
{double d1 = 2.0;double d2 = 5.0;Swap(d1, d2);int a1 = 10;int a2 = 20;Swap(a1, a2);cout << d1 <<" "<< d2 << endl;cout << a1 <<" "<<a2 << endl;return 0;
}

通过反汇编,从汇编代码中可以看出,通过模板和参数,编辑器自己推导出了两个函数,可以这么认为,编译器可以通过模板实例化出多个函数,这些函数本来需要我们自己实现,但是现在编译器可以帮我们做了!!!!
在这里插入图片描述

在这里插入图片描述

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

总结:

我们不能调用函数模板,实际上我们调用的是函数模板实例化生成的对应类型的函数

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

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<int>(a1, d1);return 0;
}

2.5 模板参数的匹配原则

1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数

#include <iostream>
using namespace std;
int add(int left,int right)//非模板函数
{return left + right;
}
template<class T>
T add(T left, T right)//函数模板
{return left + right;
}
int main()
{int x = 10;int y = 20;cout << add(10, 20) << endl;// 与非模板函数匹配,编译器不需要特化cout << add<int>(10, 20) << endl; 调用编译器特化的Add版本(利用函数模板实例化后的函数)return 0;
}

正常情况下,非模板函数优先,如果指定类型,那么函数模板优先,这一点通过逐语句调试可以看出

2. 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么将选择模板

// 专门处理int的加法函数
int Add(int left, int right)
{return left + right;
}
// 通用加法函数
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{return left + right;
}
void Test()
{Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数
}

3. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换

2.6 声明和定义分离

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

也可以声明定义分离,不同的是模板参数声明定义都要给

3. 类模板

3.1 类模板的定义格式

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

示例(动态顺序表):
看这个代码仓库,查看代码

3.2 类模板的实例化

类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。

// Vector类名,Vector<int>才是类型
Vector<int> s1;
Vector<double> s2;

Vector类名,Vector< int >才是类型

4. 模板分离编译

4.1 什么是分离编译

一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式

4.2 模板的分离编译

模板的声明与定义分离开,在头文件中进行声明,源文件中完成定义

// a.h
template<class T> 
void Swap(const T& left, const T& right);
// a.cpp
template<class T> 
void Swap(const T& left, const T& right) 
{return left + right; 
}
// main.cpp
#include"a.h"
int main()
{Swap(1, 2);Swap(1.0, 2.0);return 0; 
}

运行之后出现链接错误!!!LNKerro
在这里插入图片描述

在上述的代码中,由于函数模板的定义和声明被分开放置在不同的文件中(头文件 a.h 和源文件
a.cpp),你可能会遇到链接错误。为了解决这个问题,可以考虑以下几种方法:

方法一:显式实例化
a.cpp 文件的末尾显式实例化 Add 函数模板:

// a.cpp
template int Add<int>(const int& left, const int& right);
template double Add<double>(const double& left, const double& right);

这样可以确保在 main.cpp 中调用时,编译器能够找到模板的实例化。

方法二:将模板定义和声明放在头文件中
将函数模板的定义和声明都放在头文件 a.h 中:

// a.h
template<class T> 
T Add(const T& left, const T& right) 
{return left + right; 
}

这样可以避免分离编译时的链接错误,同时也符合模板的典型使用方式。

方法三:使用inline关键字
a.h 文件中使用 inline 关键字:

// a.h
template<class T> 
inline T Add(const T& left, const T& right) 
{return left + right; 
}

inline 关键字将告诉编译器在每个使用模板的地方生成模板的实例,从而避免链接错误。

选择其中一种方法即可,根据实际需求和项目规模来决定使用哪种方式。
总结:个人理解,函数模板和类模板不支持声明和定义分离,因为在编译器在推导类型时,只是推到的是声明中的类型,但是链接时发现,链接的函数类型T确实不可知的,这里不好理解,建议记住也行!!!!(本质还是T不明确的问题

总结

C++模板是一种强大的编程工具,使得泛型编程在语言层面得以实现。通过函数模板和类模板,我们能够编写通用、灵活的代码,适应不同的数据类型。泛型编程的思想带来了更高的代码抽象程度和可重用性。通过本文的学习,读者将能够深入理解C++模板的核心概念,并能够运用它们解决实际问题,提高代码的可维护性和适用性。

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

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

相关文章

Spring Boot 笔记 018 创建接口_文章列表(分页)

1.1 分页查询 1.1.1 创建pageBean封装分页的数据 package com.geji.pojo;import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;import java.util.List;//分页返回结果对象 Data NoArgsConstructor AllArgsConstructor public class PageB…

【51单片机】蜂鸣器(江科大)

11.1蜂鸣器 1.蜂鸣器介绍 蜂鸣器是一种将电信号转换为声音信号的器件,常用来产生设备的按键音、报警音等提示信号 蜂鸣器按驱动方式可分为有源蜂鸣器和无源蜂鸣器 有源蜂鸣器:内部自带振荡源,将正负极接上直流电压即可持续发声,频率固定 无源蜂鸣器:内部不带振荡源,需…

【谁说0基础小白不能开发游戏!点这里,国家首批架构师亲授开发技巧!】

很多同学都想要在入行前有项目经验 但一直都不知道如何获取 所以我们为想要获取项目经验的同学 提供了不同游戏类型 能体现不同能力的训练营 让你在没入行前就拥有 让面试官眼前一亮的游戏Demo 《一周学会ARPG游戏开发》 《元气骑士》全屏弹幕攻击集训营 《潜行机器人》…

山西电力市场日前价格预测【2024-02-13】

日前价格预测 预测说明&#xff1a; 如上图所示&#xff0c;预测明日&#xff08;2024-02-13&#xff09;山西电力市场全天平均日前电价为178.29元/MWh。其中&#xff0c;最高日前电价为455.64元/MWh&#xff0c;预计出现在19:00。最低日前电价为0.00元/MWh&#xff0c;预计出…

C++ STL: list使用及源码剖析

list使用 list常用函数及使用&#xff08;1&#xff09; #include <iostream> #include <list> #include <algorithm>int main() {// 创建liststd::list<int> myList {5, 2, 9, 1, 5, 6};// 打印liststd::cout << "Original list: &quo…

《剑指Offer》笔记题解思路技巧优化 Java版本——新版leetcode_Part_2

《剑指Offer》笔记&题解&思路&技巧&优化_Part_2 &#x1f60d;&#x1f60d;&#x1f60d; 相知&#x1f64c;&#x1f64c;&#x1f64c; 相识&#x1f353;&#x1f353;&#x1f353;广度优先搜索BFS&#x1f353;&#x1f353;&#x1f353;深度优先搜索DF…

petalinux2018.3安装步骤

1、虚拟机安装ubuntu-16.04.7-desktop-amd64.iso &#xff08;注意&#xff1a;安装ubuntu-18.04.6-desktop-amd64.iso和ubuntu-16.04.6-desktop-i386.iso会报以下错误&#xff09; environment: line 314: ((: 10 #15~1 > 10 #3: syntax error in expression (error toke…

挑战杯 python图像检索系统设计与实现

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; python图像检索系统设计与实现 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;3分工作量&#xff1a;3分创新点&#xff1a;4分 该项目较为新颖&#xff0c…

第20讲投票帖子排行实现

后端&#xff1a; /*** 投票选型Controller控制器* author java1234_小锋 &#xff08;公众号&#xff1a;java1234&#xff09;* site www.java1234.vip* company 南通小锋网络科技有限公司*/ RestController RequestMapping("/voteItem") public class VoteItemCo…

【Go语言】Go项目工程管理

GO 项目工程管理&#xff08;Go Modules&#xff09; Go 1.11 版本开始&#xff0c;官方提供了 Go Modules 进行项目管理&#xff0c;Go 1.13开始&#xff0c;Go项目默认使用 Go Modules 进行项目管理。 使用 Go Modules的好处是不再需要依赖 GOPATH&#xff0c;可以在任意位…

Spring Boot 笔记 019 创建接口_文件上传

1.1 创建阿里OSS bucket OSS Java SDK 兼容性和示例代码_对象存储(OSS)-阿里云帮助中心 (aliyun.com) 1.2 编写工具类 package com.geji.utils;import com.aliyun.oss.ClientException; import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import com.aliyun…

线段树总结

线段树的定义 线段树&#xff08;Segment Tree&#xff09;是一种二叉搜索树&#xff0c;与区间树相似&#xff0c;它将一个区间划分成一些单元区间&#xff0c;每个单元区间对应线段树中的一个叶结点。使用线段树可以快速地查找某一个节点在若干条线段中出现的次数&#xff0c…