目录
函数模板
模板机制
函数模板语法
函数模板和普通函数的区别
函数模板和普通函数调用规则
函数模板机制
排序模板函数
类模板
类模板语法
模板继承
类模板中的static关键字
模板声明
.hpp文件
类模板小结
上节学习了运算符重载,本节开始学习函数模板和类模板!
函数模板
C++提供了函数模板(function template)。所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。凡是函数体相同的函数都可以用这个模板来代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能。
直接上代码讲解
我们之前说过这个会构成函数重载
两个函数除了返回类型和参数类型不一样,其他几乎一模一样
那我们就可以把这两个函数共同抽象一下,C++里面可以一个通过的函数来表述这两个函数
比如像这样:
但是编译器并不知道这个T是什么,肯定会报错,我们需要声明一下这个T表述的是某种类型的名字
这样我们可以写int或者float型的数据,这个T就会自动替换
这就是函数模板
但是还有一个问题,如果两个参数的类型不一样,那么T就不知道应该是转换成什么
有两种方法可以解决这个问题
首先这两种的调用方式是不同的,第一种方法就是用显示调用,用< >自己指定转换的目标类型
第二种方法:
首先这个是一个虚拟类型的参数列表
这个参数列表可以写多个
并且这两个模板函数构成了重载关系
我们就可以这样调用
但是最后不要写成这种代码,容易造成歧义。
可以这样:
注意T的作用域仅限于一个函数,如果我们再写一个函数,使用这个T,编译器就会报错说这个T没有声明
必须重新声明
还有一个问题,如果模板的参数列表中写着是三个参数,那么显示调用的时候也要写三个参数
模板机制
1、C++提供两种模板机制:函数模板、类模板;
2、类属(比如我们刚刚写的T) — 类型参数化,又称参数模板(可以理解成虚拟参数)。
使得程序(算法)可以从逻辑功能上抽象,把被处理的对象(数据)类型作为参数传递。
总结:
模板把函数或类要处理的数据类型参数化,表现为参数的多态性(比如说只有在运行后才知道指针指向谁),称为类属。
模板用于表达逻辑结构相同,但具体数据元素类型不同的数据对象的通用行为。
为什么需要函数模板机制?
写 n 个函数,交换char类型、int类型、double类型变量的值。
如果使用传统的写法,需要写 n 个函数:
void swap(char c1, char c2);
void swap(int n1, int n2);
void swap(double d1, double d2);
有没有一种机制,可以只写一个函数?当然有,那就是函数模板。
函数模板语法
函数模板定义形式:
template <虚拟类型列表>
虚拟类型列表的形式为:
typename T1 , typename T2 , ... , typename Tn
或
class T1 , class T2 , …… , class Tn
用typename和class这两个关键字都可以。
函数模板和普通函数的区别
函数模板不允许自动类型转化;
普通函数能够进行自动类型转换;
比如:
template <typename T>
void swap(T x, T y);
swap(1, 'a'); //编译器不会自动转换,要指定
函数模板和普通函数调用规则
1、函数模板可以像普通函数一样被重载;
2、C++编译器优先考虑(调用)普通函数;
3、如果函数模板可以产生一个更好的匹配,那么选择模板;
4、可以通过空模板实参列表的语法限定编译器只通过模板匹配。
比如说现在有一个模板函数和普通函数
如何限定编译器只调用模板函数?
可以这样调用
所以说我们写成show<>(1);这样那调用的就是模板函数,如果我们写成show(1);那肯定就是调用普通函数了。
函数模板机制
1、编译器并不是把函数模板处理成能够处理任意类型的函数;
2、编译器从函数模板通过具体类型产生不同的函数;
3、编译器会对函数模板进行两次编译;
4、在声明的地方对模板代码本身进行编译;在调用的地方对参数替换后的代码进行编译。
比如:
编译器编译这三句后告诉编译器这个函数是模板函数,也就是说三句只是起到一个通知的作用。
然后走到这里时进行第二次编译,它要回头把T换成int
因此,这就要求模板函数的定义和函数的调用放在同一个文件里面,要不然它就不能回头再次编译了。
排序模板函数
用模板函数来写一个冒泡排序,支持整型,字符串和float等类型排序
直接上代码:
现在换成char类型的数组
小数也可以
现在换成一个学生的类的几个对象的排序,
目前这样是不可以的,
首先在输出的时候array[i]代码一个对象,如果没有输出运算符重载的话,一个对象不能直接被这样输出。
再者,两个对象也不允许这样比较
另外,因为学生的类里面有很多属性,到底是按学生的姓名排序还是按学号/年龄排序?
因此我们要重载输出运算符和>运算符,并且我们需要在>运算符重载中指定一个排序规则。
这样就按学号从大到小排序好了。
类模板
为什么需要类模板?
类模板用于实现类所需数据的类型参数化
类模板在表示如数组、表、图等数据结构显得特别重要,这些数据结构的表示和算法不受所包含的元素类型的影响。
举了例子,链表是用来存放数据的,存放的数据可能是int,char或者其他类型,那么我们就不能在实现链表的时候把类型固定死了。因此链表抽象出来的类就可以用类模板来实现。
类模板语法
类模板由模板说明和类说明构成:
template <虚拟类型列表>
类声明
template <typename T>
class Test
{
private: T member;//类属参数在类声明中至少出现一次。
};
直接上代码:
像这样类里面有虚拟类型的类我们通常就把它叫做类模板
类模板创建对象的时候必须显示调用
模板继承
继承和类模板-模板类派生普通类(没有虚拟类型)
class TestB : public TestA<int> //classB为普通类
{
};
继承和类模板-模板类派生模板类
template <Typename T>
class TestB : public TestA<T> //classB为模板类
{
};
直接上代码讲解:
普通类在继承模板类的时候不可以直接这样写
要这样写,在继承的时候一定要把要继承的那个成员变量的类型确定下来
这样Derived1就把m_a当做char类型继承下来了。
接下来用模板类派生模板类
注:派生的模板类在继承的时候后面的<>里面可以是T可以是其他任何字母,我们为了和基类中的T做个区分,所以就取别的字母。
类模板中的static关键字
1、从类模板实例化的每个模板类有自己的类模板数据成员,该模板类的所有对象共享一个static数据成员;
2、和非模板类的static数据成员一样,模板类的static数据成员也应该在文件范围(全局)定义和初始化;
3、每个模板类有自己的类模板的static数据成员副本。
代码讲解:
我们创建了3个对象,静态变量num的确统计了3个,说明静态变量在模板类的所有成员中的确是共享的。
但是请再看如果我们类型换成char,再创建两个对象,这时num计算出来是多少?
结果是2
因此结论:模板类的所有对象也和非模板类的所有对象一样共享同一个静态成员变量,但是它仅仅是同一种类型的对象共享同一个静态成员变量。
如果我们通过类型去访问写成这样更容易理解
这样写我们可以理解成这两个被示例化成了不同的类。
g++在编译的时候会进行两次编译,它发现第一个是int类型,它会在上面生成一个Array并且虚拟类型是int的类,然后它发现第二个是char类型,它会在上面生成一个Array并且虚拟类型是char的类。所以我们可以把这两个理解成不同的类,既然是不同的类,那第一个类里面有一个静态成员变量,那第二个类里面也有一个自己的静态成员变量。
模板声明
直接上代码:
之前我们写的这三个函数都是在类里面实现的,现在放在外面来实现是这样的:
现在如果我们想要实现这样的操作就必须重载
重载
也可以将全局注释掉
在里面来写
注意:这里虽然是在里面来写,但是它仍然属于全局函数,只是写法不一样。
.hpp文件
类模板里面的最后一个知识点,.hpp是一种文件的格式
这个格式的使用的场景是什么?
刚刚我们写的那个代码虽然是将成员函数都放在类外面来写,但是还不够正规,在正式的工程项目里面我们是分文件来写的,如果将以上代码分成多个文件来写的话就会出问题。
array.h
array.cpp
main.cpp
这样分成三个文件来写的话编译和运行都没有问题
但是当我们把这句加上后再次编译就有问题了,也就是说这样在模板类创建对象的时候有问题
编译和运行是不一样的,编译是从程序的第一行开始,而程序运行是从主函数开始。
问题就出现在模板编译要经历两次编译,第一次是模板定义的地方,比如说构造函数是一个模板就先记下来,等编译到这里的时候,就把这个int代到模板里面的T再去编译,相当于生成了一个这么一个构造函数,里面的T不再是T,而是一个int型。
也就是从头往下编译的时候,先编译模板(告诉编译器这个构造函数是一个模板),之后再根据实际的类型再去编译(把虚拟类型变成实际的类型,相当于实例化),需要两次编译,这就要求模板的定义和模板的调用必须要放在同一个文件里面。
但是我们前面已经把构造函数的定义封装在了另外一个文件了,直接的办法就是将array.cpp里面这些函有定义剪切到main.cpp里面
但是剪过去的话我们的代码就又太乱了,违背了我们多文件编写程序的初衷。
怎么办?
其实我们可以直接在main.cpp里面包含array.cpp
这样编译也能通过
但是我们编译的时候是编译所有的.cpp,那么为了区分,我们又把array.cpp改个名字叫array.hpp(.hpp是介于.cpp和.h之间的一种文件格式)
然后我们把这里也改成.hpp
这样的话我们g++编译的时候就可以直接编译所有的.cpp文件就可以了
这个.hpp文件格式一般是在我们用到模板时才会用到。
类模板小结
1、模板是C++类型参数化的多态工具;C++提供函数模板和类模板。
2、模板定义以模板说明开始。类属参数必须在模板定义中至少出现一次。
3、同一个类属参数可以用于多个模板。
4、类属参数可用于函数的参数类型、返回类型和声明函数中的变量。
5、模板由编译器根据实际数据类型实例化、生成可执行代码;实例化的函数模板称为模板函数;实例化的类模板称为模板类。
6、函数模板可以用多种方式重载。
7、类模板可以在类层次中使用 。
下节开始学习异常和文件!
如有问题可评论区或者私信留言,如果想要进扣扣交流群请私信!