泛型编程
什么是泛型编程?简单说泛型编程就是使用一种通用类型来进行程序设计的方法,这个方法可以大规模的减少程序代码的编写量
函数模板,类模板都属于是泛型编程,他们都使用了一种数据类型T
T是一个数据类型占位符,也可以把T当作一种数据类型,即泛型,使用这个占位符的编程方式即是泛型编程
自动推导类型
自动推动类型使得泛型编程使用更方便,在介绍模板之前先介绍自动推导类型
auto关键字
auto关键字作为类型指示符,可以在编译时自动推导数据类型,在使用时用auto代替原来的数据类型(一般是复杂的)
语法:auto 变量名=初始值;
auto声明的变量必须在定义时初始化
初始化的右值可以是具体的数值,也可以是表达式和函数的返回值等
auto不能作为函数的形参类型,即参数列表不能用auto
auto不能直接声明数组
auto不能定义类的非静态成员变量
auto一般用于:
1.代替复杂的变量声明
2.在模板中,用于声明模板参数有关的变量// 例如在模板类中的一个模板函数里面的一个局部变量,还未实例化前无法确定其数据类型,这时用auto,延续其泛用性
3.用于与模板参数有关的函数返回值//同上
4.用于lambda表达式
decltype关键字
decltype关键字用于查询表达式的数据类型
语法:decltype(expression) var;
expression可以是表达式,函数,变量... var与expression类型相同,可以是一个变量也可以是函数指针取决于expression
1)如果expression是一个没有用括号括起来的标识符,则var的类型与该标识符的类型相同,包括const等限定符。
2)如果expression是一个函数调用,则var的类型与函数的返回值类型相同//函数调用func()和函数名func不一样,表达式若是函数名则类型为函数类型
3)如果expression是一个左值(能取地址)(要排除第一种情况)例如a++、或者用括号括起来的标识符,那么var的类型是expression的引用。
4)如果上面的条件都不满足,则var的类型与expression的类型相同。
总的来说decltype获取表达式数据类型或者是表达式数据类型的引用
引用必须初始化
函数后置返回类型
这个是C++14前遗留的东西,面对复杂的函数返回类型时可以用auto代替,然后在后面加上返回类型,即返回类型后置
auto func(int x,double y) -> int
{
// 函数体。
}
在C++14之后后面不用加返回类型
函数模板
什么是函数模板?函数模板是通用函数的描述,用泛型来描述函数,编译时根据实参数据类型和函数模板生成函数定义
生成函数定义的过程被称为实例化
函数模板举例:
template <typename T>
void Swap(T &a, T &b)
{
T tmp = a;
a = b;
b = tmp;
}
1) 不能为虚函数和析构函数创建函数模板
2)使用函数模板时,必须明确数据类型,确保实参与函数模板能匹配上//Swap<int>(a,b)
3)使用函数模板时,推导的数据类型必须适应函数模板中的代码。
4)使用函数模板时,如果是自动类型推导,不会发生隐式类型转换,如果显式指定了函数模板的数据类型,可以发生隐式类型转换。//Swap<int>(7.2,8) double->int
5)函数模板支持多个通用数据类型的参数。
6)函数模板支持重载,可以有非通用数据类型的参数。
函数模板具体化
什么是具体化?具体化又称特例化,特化,是把函数模板中的泛型T具体成某一种数据类型;
具体化又分为部分具体化和完全具体化
部分具体化举例:
template <typename T1,int>
void Swap(T1 &a, int &b)
{
T1 tmp = a;
a = b;
b = tmp;
}
完全具体化举例:
template <char,int>
void Swap(char &a, int &b)
{
char tmp = a;
a = b;
b = tmp;
}
具体化是为了完成一些特殊的需求,而不是把他当成泛型来对待
1)具体化优先于常规模板,普通函数优先于具体化和常规模板。
2)如果希望使用函数模板,可以用空模板参数<>强制使用函数模板。
3)如果函数模板能产生更好的匹配,将使用函数模板
具体化程度高的优先于具体化程度低的
模板类
类模板是通用类的描述,使用泛型来描述类的定义
template <class T>
class 类模板名
{
类的定义;
};
1)在创建对象的时候,必须指明具体的数据类型。//与函数模板不一样
2)使用类模板时,数据类型必须适应类模板中的代码。
3)类模板可以为通用数据类型指定缺省的数据类型
4)可以用new创建模板类对象。
5)在程序中,模板类的成员函数使用了才会创建。//无论是函数模板还是类模板在实例化前都是不存在的,因此不使用模板即使模板内部有语法错误也不会报错
模板类具体化
模板类的具体化和函数模板具体化一样,都分为部分具体化和完全具体化
部分具体化类模板:
template<class T1, int>
class AA {
类定义
}
完全具体化类模板:
template<string, int>
class AA {
类定义
}
模板类的嵌套
例子:假设自定义了一个类A,在创建A类对象时在模板参数列表中使用A//vector<vector<int>> v;
模板类与继承
1)模板类继承普通类(常见)。class BB:public AA
2)普通类继承模板类的实例化版本。class AA:public BB<int,string> ,模板类的实例化版本相当于普通类
3)普通类继承模板类。(常见)
template<class T1, class T2>
class AA:public BB<T1,T2>
4)模板类继承模板类。
template<class T, class T1, class T2>
class CC :public BB<T1, T2>
5)模板类继承模板参数给出的类(不能是模板类),也就是嵌套的另一种情况:模板类继承模板类的具体化版本或者继承普通类
EE<DD<int>> ed1;
普通类继承模板类必须先将自身变成模板
模板类作为函数的参数和返回值
模板类可以用于函数的参数和返回值,有三种形式:
1)普通函数,参数和返回值是模板类的实例化版本。AA<int, string> func(AA<int, string>& aa)
2)函数模板,参数和返回值是某种的模板类。
template <typename T1,typename T2>
AA<T1, T2> func(AA<T1, T2>& aa)
3)函数模板,参数和返回值是任意类型(支持普通类和模板类和其它类型)
template <typename T>
T func(T &aa)
模板类和友元
模板类的友元函数有三类:
1)非模板友元:友元函数不是模板函数,而是利用模板类参数生成的函数,只能在类内定义。 friend void show(const AA<T1, T2>& a)
2)约束模板友元:友元函数是模板,模板类实例化时,每个实例化的类对应一个友元函数。friend void show<>(AA<T1, T2>& a)
3)非约束模板友元:模板类实例化时,如果实例化了n个类,也会实例化n个友元函数,每个实例化的类都拥有n个友元函数。
分文件编写
函数模板,类模板都要进行分文件编写
声明放在头文件中,定义放在源文件中
模板在没实例化之前可以当作声明看待,一旦具体化就要像普通函数普通类一样对待
ps:具体化的类模板声明定义都放在头文件中