标准C++ -- day08

发布时间 2023-08-30 19:53:43作者: BigBig飞
一、类型信息运算符
  • typeid
    • 在C++中typeid可以获取数据的类型,需要加头文件 typeinfo

    • 通过find /usr/include -name typeinfo

    • typeid 是运算符,执行运算符函数,执行的返回值类型是type_info类类对象

    • type_info 中有个 name 的成员函数

    • type_info中还重载了 == 运算符,可以直接比较两个数据的类型是否相同

    • 可以区分父类指针或引用指向的是哪个子类,但是父子类之间必须构成多态

二、模板
1、什么是模板
  • 是一种自动生成代码的技术,这种技术能让程序员在编写代码时不需要考虑数据类型,因此也称为泛型编程技术
2、为什么要使用模板
  • C/C++/Java/C# 属于静态编程语言(编写->预处理->编译->汇编->链接->可执行文件)-强类型语言

    • 静态编程语言

      • 优点:运行速度快

      • 缺点:代码改动后重新编译、实现代码通用比较麻烦

  • C语言中的通用类型是通过 void* + 回调函数实现,实现难度大,使用比较麻烦

  • 借助定义宏的方式实现代码通用,但是宏有一定的缺点

    • 类型不检查,没有返回值,二义性
  • 借助函数重载可以实现函数代码的通用,可能会导致代码段的增多,无法解决未知类型

  • 综上所述,C++之父为了解决代码通用问题,实现了模板技术,让C++摆脱数据类型的困扰

三、函数模板
1、函数模板的定义
template<typename T1=int,typename T2=float>//加了默认形参
void func(T1 num1,T2 num2);
  • 未知类型名可以取任意名字,一般约定T
2、函数模板的原理
  • 函数模板会经历两次编译

    • 检查函数模板的语法是否有错误,如果无误,也不生成函数的二进制指令,代码段没有存储该函数模板

    • 根据调用者提供的实参类型再次编译检查函数模板代码,如果也没有错误,才会生成一份二进制指令存储在代码段中,所以,如果函数模板没有任何一次调用,则不会生成任何二进制指令,如果有不同类型的实参调用函数模板,则会生成另一份二进制指令存储在代码段中

    • 这种函数模板实例化称为 “惰性实例化”准则

3、函数模板的调用
  • C++编译器不会把函数模板当做一个函数的实体,而是当做生成函数实体的工具,当调用函数模板并提供了实际类型参数后,才会生成函数实体

  • 调用函数模板必须提供相应数量的类型参数

    • 自动:编译器会自动根据实参的类型,获取函数模板的类型

    • 手动:函数名 < type1,type2 ,type3>(实参) 会根据< >中提供的类型名去生成函数实体

4、默认形参类型
  • template<typename T1,typename T2=int,typename T3=long>
    T3 func(T1 arg1,T2 arg2)
    {
        T3 ret = arg1 + arg2;
        return ret;
    }
    
  • 函数模板的类型参数可以像普通函数的默认形参设置形参值一样去设置默认的形参类型,靠右原则一致,但是该语法只有C++11后才能支持

    • -std=gnu++0x
5、函数模板的特化
  • 模板虽好但不能直接解决所有类型的问题,有一些特殊类型与普通类型的运算规则不同,例如char*,因此需要给这些特殊类型实现一个特殊版本,这种称为函数模板的特化

  • 特化的方式:

    1. 通过 typeid( ) 分支判断类型执行特殊步骤
    if(typeid(T) == typeid(char*))
    
    1. 实现一个特化版本
  • 特化的格式

    • template<>

    • 特化类型返回值 函数名(特化类型 形参名)

  • 注意

    • 特化前,必须有一个基础版本的函数模板

    • template<> 一定要加,才说明是函数模板的特化

    • 特化函数的形参基础类型要与函数模板的形参基础类型相同,否则报错。例如函数模板中有引用,特化中也需要有

    • 编译器会优先调用函数模板中的特化版本,因此不会与基础的函数模板有冲突

    • 可以同时存在类型完全相同的普通函数、函数模板的特化、函数模板,按照普通 -->特化 -->模板的顺序调用

    • 普通函数无论是否调用都会生成二进制指令,但是函数模板的特化依然遵循 “ 惰性实例化 ”准则,不被调用时不生成二进制指令

四、类模板
使用未知类型来设计一个类类型
1、类模板定义
template<typename T1,typename T2>
class 类名
{
    T1 成员变量;
public:
    T2 func(void);
}
2、类模板的使用
  • 类模板必须实例化才能使用,与函数模板不同的是不支持自动实例化,只能显式提供类型参数手动实例

    • 类名<类型参数>对象名

    • 类名<类型参数>* 对象指针 = new 类名<类型参数>(构造函数的实参)

3、模板中的静态成员
  • 类模板中允许有静态成员,与普通类的静态成员变量一样需要在类外定义

    template<typename T>
    class 类名
    {
        static 类型名 num;
    };
    template<typename T> 
    类型名 Test<T>::num = 初始数据;
    
  • 对于每个类模板实例化出来的对象名,也是共享静态成员变量

4、递归实例化
  • 什么类型都可以是模板的类型参数,包括类模板类型
template<typename T>
class A
{
    T a;
};
template<typename T>
class B
{
    T b;
};
B<A<int>> b;    //    C++11之后才允许
B<A<int> > b;    //    C++11之前必须加空格
5、类模板的默认参数类型
  • 与函数模板的默认参数类型规则一样

    template<typename T=类型名>
    class A
    {
        T a;
    };
    
6、类模板的局部特化
  • 当类模板中的成员函数不能支持所有类型时,可以针对不同类型实现类模板中的成员函数的特化版本,称为类模板的局部特化

    • 方法1:通过 typeid 比较类型,通过分支语句执行特殊操作

    • 方法2:通过实现局部特化成员函数来处理

    template<>
    int List<const char*>::find(const char* const& data);
    
    • 注意:

      • 一般在类外实现局部特化

      • 类型名的格式除了替换的 typename 的类型发生替换,其余所有格式都需要一致,例如常属性(const)、引用等

7、类模板的全局特化
  • 为特殊类型重新特化一个类模板,称为类模板的全局特化

    template<>
    class 类名<特殊类型>
    {
        重新实现类;
    }
    
8、定义类模板和函数模板
  • class 关键字可以替换 typename