模板的定义
模板只是一个框架,不能直接使用,模板的通用性不是万能的,
使用模板可以方便我们快速设计某一类的问题
函数的模板
c++的一种设计思想就是泛型变成,其核心就是模板,c++提供了函数模板和类模板
1如上,使用template<typename T>来表示,我告诉编译器要写模板了,其中,typename可以用class替换(不过一般class常用作类模板,typename用作函数模板,但是都是可以互换的),如果你紧跟着函数,那么就是一个函数的模板
2我们很简单就会写一个交换值的函数,如果我们要替换其他值就要写别的方法,这样就十分重复,函数体基本差不多,就是类型不一样,
3如上,我们给函数加上模板,这里我们不能用swap的原因是std下默认有了swap函数,我们就不要再使用了,会冲突。这里等于是默认上编译器去识别typename为int和double,当然我们也可以调用时指定类型,让编译器明确
4如上,当然我们传入的类型要和指定的对应上。
函数模板的注意事项
1自动类型的推导必须一致
2模板必须确定了类型才能使用
5如上,我们上面就是没有指定类型,结果编译器也无法判断你是要给什么类型使用而报错
函数模板的案例
编写方法,实现将char,int都能选择排序的方法,且从大到小排序。
6如上,我们多次使用模板,将函数进行调研,这里不能讲len获取使用到函数中。
普通函数和模板函数使用的区别
普通函数调用时可以自动发生隐式转换
模板函数使用编译器自动推导类型时,不会使用隐式转换,如果指定类型可以发生隐式转换
7如上,我们定义了个int数字相加的方法,我们传入了char字符,也能输出结果,是因为将‘a’转换成int的ascii码值,这就是普通函数的隐式转换
8如上,当我们使用了函数模板,就会发现模板是不会将其转换,而是自动判别为2个不同的类型
9如上,如果我们指定了类型,就会实现强转的效果
普通函数和函数模板的调用规则
如果普通函数和模板函数都可以实现,优先使用普通函数
可以使用空模板参数(不是函数参数列表)列表的模板来强制调用模板
函数模板也可以发生重载
如果函数模板可以更好的匹配,优先使用模板
10针对第一项,我们写了2个同名函数,其中一个被模板修饰,我们运行发现是调用的普通函数。
11如上,我们尝试将普通函数的变成声明,编译没有报错,但是运行报错了,提示无法解析外部命令,他没有像我们想象的去调用模板函数,如果我们想使用模板函数,就要调用时加上空模板参数列表
12如上,就是加上调用时类型不指定<>,里面不声明任何类型,可变成模板函数的调用
13如上,我们使用同一个函数名,函数模板的参数列表不一样,也是重载
14如上,我们如果给参数都传入字符型,则可看到调用的是模板,因为没有必要转换,使用模板匹配更方便
模板的局限性
通用性模板的不是万能的,比如我们比较大小,可以使用模板比较int,double,字符串等等,但是自定义类型比如Persion就不那么方便了。
我们这是就可以使用
15如上,我们简单比较数字可以。
16如上,如果我们尝试传入自定义的Person就因为我们没有定义其==方法而运行故障,当然我们就不使用运算符重载了,这里可以使用模板的重载
17如上,我们需要将模板设定为使用引用 ,然后这里使用的指定具体类型的模板重载,将函数返回类型前加template<>,然后里面的参数类型就可以传入Person指定类型了,然后就调用到了我们后定义的模板
类模板
类模板定义
类模板和函数模板一样,就是在类前面加上模板,然后模板类型指代特定的成员类型
18如上,其实也没什么说的,就是指定了不同的类型,然后拿去使用就行了,注意的是这里实例化时要给模板指定类型,并不能自动识别类型。
类模板和模板函数的区别
如上张图,1模板并不能自动识别我们的类型,而是需要我们显示指定类型
2 类模板可以指定默认参数
19如上,我们设置了Agetype可以默认是int,则实例对象时可以只传入string类型给模板
类模板函数创建时机
普通成员函数一开始就可以创建,而类模板函数是调用时才创建
20如上,比如我们定义了2个类,仅仅方法名不一样,然后我们做一个测试类,然后定义2个方法去调用,显然目前我们定义的类是不可能同时有2个方法都有的,但是编译没有报错,就是因为模板T类类并不知道我要传入什么,所以函数并没有生成,而是我们指定后才能生成,当然我们运行test就会报错,因为实际传入的类并没有2个方法。
类模板对象作为函数参数
一共三种方式
方式1 将参数类型的模板类对象传给方法
21如上,如果我们test方法参数不给指定模板参数类型会报错,这里我们还是使用引用
方式2 参数模板化
22如上,我们将方法修改,将参数不写明确类型,用t1,t2替代,但是需要在方法上使用模板,变成模板函数,这样就可以识别对象了
23这里再告诉一个typeid方法,将类传入,可以获得对象然后使用name()方法获得对象名,如上,t1就是string,t2是int
方式3 将整个类作为模板指定类型传入
24如上,我们就是把Person对象都给传入了,也可以实现调用,这个没啥说的。
类与模板的继承
当类继承遇到类模板时需要注意以下几点:
1当类继承的父类是一个类模板时,子类继承时需要指定父类的类型
2 如果不指定,无法编译器无法给子类分配内存
3 如果想灵活的指定类型,子类也必须变成类模板
25如上,我们Base类是一个类模板,然后使用Son去继承他,可以看到提示Base是缺少参数列表的,这是因为Son类目前没法分配空间,虽然想象中时是继承T m,但是这个类型的内存空间不确定,所以就必须继承时指定类型
26如上,就是在父类后面加上<int>上面是给指定了整型,然后就没有报错了。
27如上,我们可以看到指定类型后是可以实例化的。接下来我们看动态指定类型,此时类必须使用类模板
28如上,我们还是用原来的Base类,这里我们声明一个模板类,这里,我们使用模板,就可以实例时再传入类型,这里可以看到2个类型t1,t2,t1是给父类继承指定类型使用的,t2是给自身属性类型使用的
类模板成员函数类外实现
29再拿模板类来说下最简单的Person的问题,如上是简单的类内定义方法。
下面把方法注释,先把构造方法给注释,只有声明,类外定义
30如上,如果我们单纯的把类方法考过来不行,因为方法不认识T1,T2,就需要我们加上模板,但是上面的还会报错,就是因为我们没有给Person加上类型
31如上,我们就成功实现了类模板构造方法的类外实现,下面我们类外实现show方法
32你也许会认为方法和模板没有关系,事实上,就是需要指定类型而且还要加template声明
33如上,我们还是要使用模板在方法前面,然后Person后面要加上对应传入的类型。
类模板分文件编写
类模板分文件编写有时候会因为使用模板编译,调用时链接不到报错。有两种处理方法,一种是导入cpp文件,一种是cpp和h文件合为hpp文件。
我们为什么类模板要分文件编写呢,就是因为当我们定义的类多起来以后,都在一个cpp里就显得比较乱,就会一个类定义一个h文件,把类外实现定义到cpp文件中,最后调用cpp作为引用
34如上,我们将Person的基本模板定义添加到Person.h中,这里添加使用vs的项目添加项,里面带的#pragma once表示不重复导入
35我们将类外实现的模板含数,这里包括之前的构造方法和show方法都导入到Person.cpp里。
36我们在原来的demo.cpp里使用main方法,注意,然后运行,会发现出现如上的错误,提示2个外部命令无法解析,这是因为我们的h文件只传入了Person的模板定义,而构造和show方法因为没有指定类型,Person.cpp里的内容没有关联到h文件中,所以实例化和show方法调用都提示无法解析。
37如上,如果我们将cpp导入则关联正确,因为cpp文件是导入了头文件的,同时实现了方法,让编译器在调用时知道找哪里。
接下来我们将h文件和cpp合并,其实本质是一样的,也能实现
38类模板函数与友元
我们知道模板函数的类外实现,我们也知道友元,如果属性设置的是private就需要friend修饰来使其能访问成员属性,
39我们如上,定义了个全局函数,注意因为是全局函数,所以想让识别属性,必须使用template模板,但是就是这样我们也报错了,首先,我们需要解决函数问题,因为我们类内使用的是普通函数,虽然参数里有模板,但是全局函数是个模板函数,我们就不能在类内简单声明,这相当于类内friend是个普通函数,如果想让其匹配外部的全局函数,需要加上<>
40我们给声明友元的地方加上<>,可以看到运行报了更多奇怪的错误,别慌,其实这个是因为你方法是在类里声明,而函数定义在类后边,需要把方法移动到类前面,当然,因为方法里又用到了Person类对象,需要我们在方法前写上类的声明,当然还是要加上模板,这就是编译器脑残之处,一级一级告诉他我定义了什么后面有,他才会去找
41类模板案例
42今天我们来自定义数组,数组要求可以存储指定类型,数据存储到堆区,构造方法可以传入数组的容量,提供对应得拷贝构造方法及operator=防止浅拷贝的问题,提供尾插和尾删实现数据增删,使用下标获取数组中对应得元素,获取数组元素个数和数组的容量
先来写模板类吧
43首选我们只需要指定一个不确定类T,然后这里给数组定义3个属性,容量cap,大小size,T类型指针p,然后构造使用容量传入,就同时开辟堆区空间,析构当然是把堆区空间释放,为了防止野指针,将其职位NULL,记住删除数组要加[],然后还定义了拷贝构造方法,因为拷贝要深拷贝防止浅拷贝问题,我们就将p开辟新的空间,同时将元素覆盖。
44接下来说说删除和插入,对于插入就是维护size位置的对象插入,对于删除,我们逻辑删除即可,使用size--,可以对越界做判断
45对于索引取对象,这里使用,重载运算符[]因为返回的是T类型对象,我们使用index调用,对于=可以使其使用=赋值初始化,如果非空就清空内容,将其深拷贝复制,同时为了能继续=调用,返回自身。
46最后一个show方法,用于显示数组
47然后在test1里测试下,看来是可行的,我们就还差自定义数据类型
48对于Person对象,我们定义有参和默认构造,注意,这里默认参数构造必须要有,否则报错,我们重载<<将对象进行输出。
49如上,我们对自定义类也做一个测试,证明是可用的
网友评论