最上面一行是在声明 我要创建模板了 其中T可以替换 但是一般都用T 因为他是template的首字母
之后 进行函数定义与声明
使用背景:
当多个函数的形式一致时(如上图 两个函数的框架基本上一样 只是一些类型说明不一样) 就可以使用模板 提高代码的复用性
代码示例:
首先声明模版 并声明且定义函数模版 注意 这些声明都是在main函数外面 且typename可以用class替换
之后在调用函数时 有两种方式:
1、系统自动推导类型 ,直接传入实体变量 之后系统会根据实体变量的类型去推导T的类型
2、显示指定类型,直接在函数名后面加尖括号<数据类型> 在尖括号里声明数据类型即可
对于自动类型推导 使用时必须是一致的数据类型 比如传参的时候 要传入数据类型一致的变量 否则会报错
当我们声明了模版并且声明了一个返回值是void且参数列表为空的函数的时候 即使整个函数没有用到T 那调用的时候也要显示出T的数据类型 此时不可以再使用自动类型推导 而是使用显示指定类型的方式 随便指定一种数据类型
!!!注意 一个模版声明只能对应一个函数 也就是有几个模版函数 那么就有几个模板声明 每个函数声明的上面都要声明模版
1、
普通函数 可以发生隐试类型转换 也就是某些情况下可以发生自动类型转换 比如下面这种 char类型自动转换为int型
2、对于函数模版
如果使用自动类型转换 不会发生隐式转换 可能受限于自动类型转换时的规定:T必须保持一致 因为系统要根据传入的数据去推导T
但是如果使用显示指定类型 那么可以发生隐式类型转换 当使用显示指定类型时 就可以视为是在使用普通函数 因为已经指定好了T
1、对于第一点 如果普通函数没有实现 只有声明 那么并不会去调用函数模版 而是去报错没有函数实现
2、通过空模版参数列表可以调用函数模版
空模版参数列表:<> 类似于显示指定类型的调用格式 只不过<>里不写任何内容
3、函数模版也可以发生重载
4、如果让编译器选择调佣普通函数进行隐式转换还是选择调用函数模板推导T 那么编译器会选择后者
解决方案
普通的函数模版 无法用于自定义的数据类型 这时候有两种解决方案 一种是实现运算符重载 第二种是实现模版的具体化重载 将原本的模板声明加与函数声明二合一 将template<> 加在函数名之前 之后 参数列表里的数据类型写明为自定义数据类型 函数体里进行重载即可 这样原先的函数模版仍然可以用 而自定义数据类型 也有了自己专属的模板
函数模板的具体化与重新写一个函数的区别在于 具体化模板相当于将普通函数进行了重载 保证了函数调用时函数名的统一性
!!! 总结
所以有可能系统为了函数调用名的统一性 而不去使用新建一个函数 而是用模板的具体化重载 学习该知识点可能我们用不到 但是可以读懂系统的模板库
仍然是先声明模板(注意声明模板时 要声明两个虚拟类型(T) 并且分别命名 )前面有说过 虚拟类型的名字可以自定义
之后 在类中就分别用虚拟类型去写这个类
调用时 要在数据类型之后紧跟模板参数列表 指明虚拟类型是什么 可以将此操作与类名一起 视为一个数据类型
1、类模板不能使用自动类型推导的方式
2、定义了模板参数的默认值之后 调用时 模板参数列表默认参数的位置就可以不写了
类模板中的成员函数 在调用某个成员函数时 才去创建某个成员函数 不调用某个成员函数 那么某个成员函数就不会被创建
代码示例:
如图test函数 调用时 参数列表写Person1 创建了MyClass类的对象 m 那么MyClass类中的T全换成了Person1
之后m对象调用func1 不去调用func2 没有出错
m对象调用func2 出错
因为类模板的成员函数只有被调用时才会创建 所以没有调用func2时 func2就不会被创建 相当于不存在 也就是func2的错误不会被发现
但是一旦调用了func2 那么类中的成员函数就会被创建 他的错误就被发现了 编译器报错
1、对于第一种方式 指定传入类型 也就是设置函数时 指定好对象的具体实例化的类型就可以了 最好以引用的方式传入 这样节省空间 并且形参与实参有联系
2、参数模板化
在定义函数的时候 也将对象的类型模板化 同时函数上一行要加上模板的声明 并定义模板变量
3、整个类模板化 不需要对模板的具体类的参数模板化 直接将Person
当父类是一个模板类 那么子类应该指定出父类中T的类型 才可以正常操作 (这个指定可以定死 也可以模板来定)
可以在子类继承的时候 直接定死父类的模板参数列表
弊端是 这样父类就受到了局限
可以将子类也变成一个类模板 之后继承声明时在父类的参数列表里传入T(这里的T可以随意命名 不一定与上面的T命名一致)
之后子类创建对象时 就要在模板参数列表传入具体的数据类型
typeid 关键字
参数 虚拟类型
函数 name 功能:显示数据类型的名称
首先是一个类模板的定义
注意点:类模板中的成员函数只写声明 不写实现
实现步骤与普通的构造函数、成员函数差不太多
不同的是 对于普通的函数 写作用域时只需要写类名::即可
但是模板函数在类名后面要加上参数列表 并写上类已经定义好的虚拟数据类型
最后再在上一行加上模板声明以及虚拟数据类型的定义
或者 还是那句话 可以将Person
首先明确分文件编写 就是.h头文件里面写声明 进行文件引用
.cpp文件写类外实现 并进行文件引用
问题:像以往那样分文件编写 编译可以通过 但是运行时会报错 因为如果只包含.h头文件 由于类模板的成员函数只有在运行调用时才会被创建 所以 不运行就无法看到成员函数 但是在编译之后以及在运行之前 还有个链接阶段 在该阶段会将main函数里的函数调用与函数链接起来 首先编译没有发现问题 因为没有函数也不会编译出错 然后又因为没有进入运行阶段 所以函数也没有被创建 那么链接阶段就会报错 所以会出问题
解决方法一 不包含.h头文件 而是包含.cpp实现文件 由于.cpp文件中包含了.h文件 所以 包含.cpp文件 编译器并不会只看到.h 而是看到了所有的内容 这样 编译器虽然仍然不会去创建函数 但是知道该函数的存在 知道了存在 就可以链接到 就可以运行了
并不常用 一般用第二种方法
将类外实现跟实现声明 写在一个文件里 并以hpp为后缀 之后mian函数直接包含.hpp文件即可 这样同样编译器看到了函数的声明与实现 但不会去创建函数 但是知道了他的存在 就可以通过链接阶段 进入运行阶段
首先明确 全局函数可以在类内实现 直接在一个类里 写全局函数 之后在前面加一个friend即可 这样该函数就是全局函数
首先是第二种方式
1、全局函数的声明还是在类内 并且有friend关键字
2、全局函数的实现:全局函数的类外实现 由于是类模板 比较特殊 应该在类的上方进行全局函数的实现 并且加上模板声明 因为函数声明里有模板参数 其次 还要在该实现的上方 声明存在Person类 也是需要声明模板 之后声明类
该方式不推荐使用
第一种方式 直接在类内进行全局函数的声明 以及实现 之后在函数前加上一个friend关键字即可 这样的函数就是一个全局函数