【C++初阶】八、初识模板(泛型编程、函数模板、类模板)
温馨提示:这篇文章已超过392天没有更新,请注意相关的内容是否还可用!
=========================================================================
相关代码gitee自取:
C语言学习日记: 加油努力 (gitee.com)
=========================================================================
接上期:
【C++初阶】七、内存管理
(C/C++内存分布、C++内存管理方式、operator new / delete 函数、定位new表达式)
-CSDN博客
=========================================================================
目录
一 . 泛型编程
二 . 函数模板
函数模板的概念
函数模板的格式
函数模板的原理
函数模板的实例化
隐式实例化:
显式实例化:
模板参数的匹配原则
三 . 类模板
类模板的定义格式
类模板的实例化
图示 -- 以栈类为例:
本篇博客相关代码
Test.cpp文件 -- C++文件
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
一 . 泛型编程
- 我们以前写的函数一般都是针对某种类型的,如:实现两值交换Swap函数,
如果交换的两值是int类型,那就要将Swap函数的参数设置为int类型;
如果交换的两值是double类型,那就要将Swap函数的参数设置为double类型……
(通过函数重载实现)
-
对于函数,虽然函数重载可以实现函数参数多类型的问题,
但也有一些不好的地方:
1、重载的函数仅仅是类型不同而已,具体实现和实现逻辑都是很类似的,
当接收的函数参数类型不同时,就需要用户自己增加对应的重载函数
2、代码的可维护性比较低,其中一个重载函数出错可能所有的重载函数都会出错 - 那能不能实现一个通用的Swap函数呢?实现泛型编程呢?
(泛型编程 -- 编写与类型无关的通用代码,是代码复用的一种手段)
C++中为解决这个问题,有了模板的概念,模板是泛型编程的基础 - 有了模板,相当于告诉编译器一个模子,
让编译器能够根据不同的类型利用该模子来生成对应类型的代码,
模板分为函数模板和类模板图示:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
二 . 函数模板
函数模板的概念
函数模板代表了一个函数家族,该函数模板与类型无关,
在使用时被参数化,根据实参类型产生函数的特定类型版本
函数模板的格式
-
注意:
typename是用来定义模板参数的关键字(上面的T1、T2、Tn就是模板参数),
除了可以使用typename来定义,还可以使用class来定义//函数模板格式: template 函数返回值类型 函数名(参数列表) { // 函数体 }图示:
函数模板的原理
- 函数模板是一个蓝图,它本身并不是函数,
是编译器使用后能产生特定具体类型函数的摸具。
所以模板就是将本来应该由我们完成的重复的事情交给了编译器完成 - 在编译器编译阶段,对于函数模板的使用,
编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。
比如:
当使用double类型调用函数模板时,编译器通过对实参类型的推演,
将模板参数T确定为double类型,然后产生一份专门处理double类型的代码图示:
函数模板的实例化
用不同类型的参数调用模板时,称为函数模板的实例化。
模板参数实例化分为:隐式示例化和显式实例化隐式实例化:
- 让编译器根据实参推演模板参数的实际类型
(上面的图示中的模板参数实例化都是隐式实例化)
图示:
- 隐式实例化中:
如果只设置了一个模板参数,但实参中却有多种类型,这时将不能通过编译,
此时有两种处理方式:
1、用户自己来强制转化 ;2、使用显式实例化图示:
---------------------------------------------------------------------------------------------
显式实例化:
- 不通过模板参数推演识别出实参的类型,而是自己显式设置模板参数的类型,
在函数名后的中指定模板参数的实际类型即可 - 显式实例化时,如果类型不匹配,编译器会尝试进行隐式类型转换,
如果无法转换成功编译器将会报错 - 显式实例化的真正用法:
设置了一个模板,但函数参数中并没有设置模板参数,
而函数体中却使用了模板参数类型,或者返回值是模板参数类型,
这种情况就需要显式实例化来确定实参类型图示:
模板参数的匹配原则
- 一个非模板函数可以和一个同名的函数模板同时存在,
而且该函数模板还可以被实例化为这个非模板函数图示:
- 对于非模板函数和同名函数模板,如果其它条件都相同,
在调动时会优先调用非模板函数而不会从该模板产生出一个示例。
如果模板可以产生一个具有更好匹配的函数,那么将选择模板图示:
- 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
三 . 类模板
类模板的定义格式
- 类模板定义和函数模板定义类似,定义时将函数的位置换成类即可,
模板参数类型在类中定义成员类型时进行使用//类模板定义格式: template class 类模板名 { // 类内成员定义 }类模板的实例化
- 类模板实例化和函数模板实例化不同,类模板实例化需要在类模板名字后跟,
然后将实例化的类型放在中即可 - 类模板名字不是真正的类名,显式实例化后的结果才是真正的类名
- 同一个类模板显式实例化出的不同类,这些类的类型是不一样的,
以栈类模板为例:
Stack st1 和 Stack st2
st1 的类型是 Stack ,是用于存储int类型数据的栈;
st2 的类型是 Stack ,是用于存储double类型数据的栈,
st1 和 st2 的类型是不一样的图示 -- 以栈类为例:
- 注意:
类模板成员函数的声明和实现分离不能分离到两个文件中,
分离时通常都写在一个.h文件中。
而且分离后的成员函数实现部分,需要设置对应的函数模板,
分离后不指定成员函数的类域,而是指定其“类模板类型”图示:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
本篇博客相关代码
Test.cpp文件 -- C++文件:
#define _CRT_SECURE_NO_WARNINGS 1 //包含IO流: #include ; //完全展开std命名空间: using namespace std; //Swap函数 -- 交换两个int类型数据: void Swap(int& left, int& right) { int temp = left; left = right; right = temp; } //Swap函数 -- 交换两个double类型数据: void Swap(double& left, double& right) { double temp = left; left = right; right = temp; } //Swap函数 -- 交换两个char类型数据: void Swap(char& left, char& right) { char temp = left; left = right; right = temp; } /* * 这里实现了三个Swap函数, * 分别交换了三种不同的类型, * 但实现的逻辑都是相同的,就只有交换类型不同, * 所以就造成了某种程度的“冗余” * * 上面的函数都需要针对具体的类型, * 那能不能让一个代码能够针对广泛的类型呢, * C++中就有了泛型编程: */ //函数模板: template //tyename 也可以写成 class //template //Swap函数 -- 交换两类型数据(泛型编程): void Swap(T& left, T& right) { char temp = left; left = right; right = temp; } /* * 使用函数模板即可实现泛型编程, * 让函数能够针对广泛的类型, * 而不只能针对一种类型, * * 通过关键字template即可定义一个模板, * Swap函数的参数设置为模板参数 */ //主函数: int main() { int a = 0; //int类型变量 int b = 1; //int类型变量 double c = 1.1; //double类型变量 double d = 2.2; //double类型变量 //调用设置了模板参数的Swap函数: Swap(a, b); //int类型 -- 模板参数T Swap(c, d); //double类型 -- 模板参数T /* * 这里调用的两个Swap函数实际不是同一个, * 两个Swap函数的函数地址不同, * 不同类型调用的Swap函数不同是由模板参数导致的 * * 模板的原理: * 模板参数接受参数如果是int类型, * 需要调用到int类型的函数, * T 模板参数就会推演成 int类型,(模板参数推演) * 然后就会实例化出具体的函数: * T 是int类型的对应函数。(模板实例化) * * 如果接收的是double类型数据, * T 模板参数就会推演成 double类型,(模板参数推演) * 然后就会示例化出具体的函数: * T 是double类型的对应函数。(模板实例化) */ return 0; } //如果一个函数需要接收不同类型的参数: template /* * 如果需要接收不同类型的参数, * 直接在模板中设置多个模板参数即可, *(模板参数名可以随便取,但一般会取为T -- type) * * 模板参数 和 函数参数 类似, * 但是 函数参数 定义的是 形参对象, * 而 模板参数 定义的则是 类型 */ void func(const T1& t1, const T2& t2) //模板参数T1接收一种类型,T2接收另一种类型 { cout
- 注意:
- 类模板实例化和函数模板实例化不同,类模板实例化需要在类模板名字后跟,
- 类模板定义和函数模板定义类似,定义时将函数的位置换成类即可,
- 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
- 对于非模板函数和同名函数模板,如果其它条件都相同,
- 一个非模板函数可以和一个同名的函数模板同时存在,
- 不通过模板参数推演识别出实参的类型,而是自己显式设置模板参数的类型,
- 隐式实例化中:
- 让编译器根据实参推演模板参数的实际类型
- 函数模板是一个蓝图,它本身并不是函数,
-










