一、类型信息运算符typeid
在C++中typeid可以获取数据的类型,但是需要加头文件 typeinfo
find /usr/include -name typeinfo
① typeid是运算符,执行运算符函数,执行的返回值类型是type_info类类型对象
②type_info中有个 name 的成员函数
③type_info中还重载了 == 运算符,可以直接比较两个数据的类型是否相同
④它可以区分父类指针或引用指向的是哪个子类,但是父子类之间必须构成多态
二、什么是模板
是一种自动生成代码的技术,这种技术能让程序员在编写代码时不需要考虑数据类型,因此也称为泛型编程技术
三、为什么要使用模板
①C/C++/Java/C# 属于静态编程语言(编写->预处理->编译->汇编->链接->可执行文件)-强类型语言,静态编程语言优点是运行速度快,缺点代码改动后重新编译、实现代码通用比较麻烦
Javascript/Python/shell 弱类型语言 动态编程语言
②C语言中的通用类型是通过 void* + 回调函数实现的,实现难度大,使用比较麻烦
③借助定义宏的方式实现代码通用,但是宏有有一定的缺点:类型不检查、没有返回值、二义性等问题
④借助函数重载可以实现函数代码的通用,可能会导致代码段的增多,无法解决未知类型
⑤综上所述,C++之父为了解决代码通过问题,实现了模板技术,让C++摆脱数据类型的困扰
四、函数模板
1、函数模板的定义
template<typename T>
void func(T num)
{
}
未知类型名可以任意取名字,一般约定T
2、函数模板的原理
函数模板会经历两次编译
①检查函数模板的语法是否有错误,如果无误,也不生成函数的二进制指令,代码段没有存储该函数的模板
②根据调用者提供的实参类型再次编译检查函数模板代码,如果也没有错误,才会生成一份二进制指令存储在代码段中,所以,如果函数模板没有任何一次调用则不会生成任何二进制指令,如果有不同类型的实参调用函数模板,则会生成另一份二进制指令存储在代码段
这种函数模板实例化称为 “惰性实例化” 准则
3、函数模板的调用
C++编译器不会把函数模板当作一个函数实体,而是当作生成函数实体的工具,当调用函数模板并提供了实际类型参数后,才会生成函数实体
调用函数模板必须提供相应数量的类型参数
自动:编译器会自动根据实参的类型,获取函数模板的类型
手动:函数名<type1, type2, …>(实参) 会根据<>中提供的类型名去生成函数实体
4、默认形参类型
template<typename T1, typename T2=int, typename T3 = long>
T3 函数名(T1 arg1, T2 arg2)
{
T3 ret = arg1+arg2;
return ret;
}
函数模板的类型参数可以像普通函数的默认形参设置形参值一样去设置默认的形参类型,靠右原则一致,但是该语法只有C++11后才支持 -std=gnu++0x
5、函数模板的特化
模板虽好但不能直接解决所有类型的问题,有一些特殊类型与普通类型的运算规则不同,例如char*,因此需要给这些特殊类型实现一个特殊版本,这种称为函数模板的特化
特化的方式
①通过typeid()分支判断类型执行特殊步骤
if(typeif(T) == typeid(char*))
②实现一个特化版本
特化的格式
template<>
特化类型返回值 函数名(特化类型 形参名){}
注意:
①特化前,必须有一个基础版本的函数模板
②template<>一定要加,才说明是函数模板的特化
③特化函数的形参基础类型要与函数模板的形参基础类型相同,否则报错,例如函数模板中有引用,特化中也需要有
④编译器会优先调用函数模板中的特化版本,因此不会与基础的函数模板有冲突
⑤可以同时存在类型完全相同的普通函数、函数模板特化、函数模板,按照普通->特化->函数模板顺序调用
⑥普通函数无论是否被调用都会生成二进制指令,但是模板特化依然遵循"惰性实例化"准则,不被调用时不生产二进制指令
五、类模板
使用未知类型来设计类类型
1、类模板定义
template<typename T1, typename T2>
class 类名
{
T1 成员变量;
public:
T2 func(void)
{
T2 ret;
return ret;
}
};
2、类模板的使用
类模板必须先实例化才能使用,与函数模板不同的是不支持自动实例化,只能显式提供类型参数手动实例化
类名<类型参数> 对象名;
类名<类型参数>* 对象指针 = new 类名<类型参数>(构造函数的实参);
实现一个链式队列的类模板
#include <iostream>
using namespace std;
template<typename T>
class Node
{
public:
T data;
Node* next;
Node(const T& data):data(data),next(NULL){}
};
template<typename T>
class Queue
{
Node<T>* head;
Node<T>* tail;
public:
Queue(void):head(NULL),tail(NULL) {}
~Queue(void)
{
while(!empty()) pop();
}
bool empty(void)
{
return NULL == head;
}
void push(const T& data)
{
Node<T>* node = new Node<T>(data);
if(empty())
{
head = node;
tail = node;
}
else
{
tail->next = node;
tail = node;
}
}
bool pop(void)
{
if(empty()) return false;
Node<T>* temp = head;
head = head->next;
delete temp;
if(NULL == head) tail = NULL;
return true;
}
T& front(void)
{
return head->data;
}
T& back(void)
{
return tail->data;
}
size_t size(void)
{
size_t s = 0;
Node<T>* t = head;
while(NULL != t)
{
s++;
t = t->next;
}
return s;
}
};
int main(int argc,const char* argv[])
{
Queue<int> queue;
for(int i=0; i<10; i++)
{
queue.push(i+10);
cout << "tail:" << queue.back() << "size:" << queue.size() << endl;
}
cout << "-----------" << endl;
while(!queue.empty())
{
cout << "head:" << queue.front() << "size:" << queue.size() << endl;
queue.pop();
}
}
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的类型发生替换,其余所有格式都需要一致,例如常属性、引用等
7、类模板的全局特化
为特殊类型重新特化一个类模板,称为类模板的全局特化
template<>
class 类名<特殊类型>
{
重新实现类
}