C++模版总结(1/2)

STL是C++中泛型编程(GP)的经典,而语言支持的模版语法是进行泛型编程的基础。泛型编程是C++实现编译时多态性的一种最重要的方式。这里对C++语言的模版相关点进行一个总结,作为对深入理解泛型编程思想的基础。

所谓模版(template)是一种蓝图或者公式,用来创建类或者函数。从抽象层次上看
- 类是对现实世界中同一类别有相同或相似特点的真实对象的一种抽象,定义相关数据成员,提供相关操作接口,通过类的实例化来表示现实世界真实对象从而进行软件建模,这大大降低了代码的重复。
- 模版是对相同或相似的类的一种更高层次的抽象,模版使用的类型参数就代表了这些类,它们可能有相似的功能,或者有相似的成员等等,通过实例化指定某一类型,就创建了一个类,这同样大大降低了代码的重复。

定义(definition)

模版定义以关键字template开始,后接模版形参表,用一对尖括号包括一个或多个(逗号分隔)模版形参。模版形参以typename或者class关键字起始,class在旧的C++中就使用,typename是之后加入的,二者无其他差别。
函数模版的定义:

template<typename T>
int cmp(const T & a, const T & b)
{
    if (a < b) return -1;
    else if (b < a) return 1;
    else return 0;
}

类模版的定义:

template<typename T>
class Stack
{
...
    void push(const T & );
...
};
template<typename T>
void Stack<T>::push(const T &v)
{
    ...
}

模版类声明完成之后,类的名称就变为了 ClassName< T>,因此在类外定义成员函数时需要用上述语法进行域解析运算符声明。

模版形参:时刻当做是一种类型,对泛化类型的一个称呼

  • 与函数声明中的形参类似,同样遵循标识符命名原则,可任意指定,也可以指定多个。同样也需要遵循形参作用域的规则,会屏蔽模板类或者模版函数外的同名类型声明。
  • 模版形参作为一种类型,而不是一个函数调用的参数变量,因此,如果在模版类或者模版函数内使用typedef重新声明同名的类型,会产生redeclare的错误。
  • 模版形参是一种泛化的类型,因此编写的代码最好对这种类型支持的操作做最少的假设。上述函数模版的定义中仅仅假设类型T支持operator<运算符即可。

模版类型形参
模版类型形参就是使用typename或者class指定的形参,这是需要在实例化阶段指定特定类型,编译器才能创建特定的类。此时,如果类定义了类型成员,如果直接使用类名+作用域解析运算符访问,编译器默认会当做一个数据成员,必须使用typename显示告知编译器才能作为一个类型成员。

template<typename A>
void fun(A * a)
{
...
    typename A::ref_type  p;
...
}

上述定义假设泛型类A定义了类型成员ref_type,那么在模版函数或模板类中使用A的类型成员时,必须使用typename,否则编译器默认会将ref_type当做A的一个数据成员对待。
模版非类型形参
模版形参不一定都要是泛型的,可以是确定的某个类型,这是模版定义内部的常量值,在需要常量表达式的时候,可以使用非类型形参。

template<typename T, int n>
class Array{
private:
    T arr[n];
public:
    explict Array(const T &v);
    ...
};

这里的非类型参数指定为特定的int类型,参数n就作为模版内部定义的一个常量,从而可以作为数据成员arr数组的大小。因此,非类型模版参数有如下限制:
- 非类型参数只能是整形、枚举、引用、指针
- 模版定义内部不能修改非类型参数的值,也不能使用其地址(即不允许n++,&n等操作),因为必须将其当做为常量对待
- 模版实例化时必须使用常量或者常量表达式
对于上述定义的Array模板类,使用自动变量声明的数组arr维护的是内存栈,与使用在构造函数通过动态传入数组大小进行new/delete管理的堆内存相比,执行速度更快,对于频繁操作的小的数组来说更明显。但是,有一个缺点是,每种大小的数组编译器都会重新生成独立的类;另外,使用构造函数的进行动态分配方法更通用,可以在不同大小的数组间进行赋值等操作。

默认模版形参
当使用模版类型形参时,可按照函数默认值的规则,为模版类模版形参也提供默认值,默认值为某个特定的类型,必须放在非默认形参之后。但是,非类型的模版函数形参不能使用默认值。

template<typename T, typename V = int>
class M
{
...
};
M<double> c1;           //c1 is M<double,int>
M<string, string> c2;   //c2 is M<string,string>

对于,非类型参数,模板类和模版函数均可以使用默认值。

template<typename T, int n = 1>
class Array{
private:
    T arr[n];
...
};

实例化(instantiation)

类模版实例化

类模版本身是类的抽象,以泛型方式描述行为,需要进行实例化后才能由编译器创建相应的类,从而创建对应类的对象并进行使用。编译器用模板产生指定的类或函数的特定类型版本的实例的过程称为实例化。分为隐式实例化和显示实例化。

Stack<string> ss; //隐式
template class Stack<double>; //显示声明一个Stack<double>类

隐式实例化时声明类对象的同时指定模版参数,显示实例化则是只指定模版参数从而实例化模板类,并没有定义类对象。
通过实例化模版参数后,得到的Stack<string>这个整体作为一个从模版实例化得到的类,所有需要与该类相关的操作,都将这个作为一个整体对待。针对默认形参可以省略,或单独指定。
对于模版非类型形参,需要使用常量或者常量表达式进行实例化,否则编译器是不能通过的。

Array<double, 20>  arrd;

函数模版实参推断

对于函数模版,编译器会根据调用函数传递的实参的类型自动实例化为相应类型的函数,这个过程称为模版实参推断(template argument deduction)。这在STL中的traits编程技法中充分进行了使用。

cmp(1, 11);
cmp(1.2, 2,4);

这两次调用分别实例化了两个函数:

int cmp(const int & a, const int & b)
{
    if (a < b) return -1;
    else if (b < a) return 1;
    else return 0;
}
int cmp(const double & a, const double & b)
{
    if (a < b) return -1;
    else if (b < a) return 1;
    else return 0;
}

在进行推断时,有多个形参的实参必须完全匹配,类型推断如上述示例,会为每种类型都产生一个函数。如果匹配错误则编译出错。另外,对于普通函数调用时实参的提示转换,模版函数的匹配不会转换实参,而是重新进行实例化产生新的函数,编译器仅仅执行两种转换:
- const 转换:接收const引用或者const指针的函数可以分别用非const对象的引用或者指针来调用,此时不会产生新的实例化。对于非引用类型形参,会忽略形参与实参的const。
- 数组或函数到指针的转换:若模版形参不是引用类型,则对数组或函数类型的实参应用常规指针转换,数组作为指向第一个元素指针,函数实参作为指向函数类型的指针。

template<typename T> T f1(T, T);
template<typename T> T f2(const T&, const T&);
string s1("s1");
const string s2("const s2");

f1(s1, s2);  //调用 f1(string, string),忽略const
f2(s1, s2);  //将s1转换为const引用

int a[10], b[20];
f1(a, b); //调用f1(int *, int *),都视为指向首元素的指针
f2(a, b); //调用失败,引用类型引用的两个数组大小不相等,类型不匹配

另外,可以使用模版函数初始化函数指针,此时编译器会使用函数指针声明的类型作为模版实参来实例化模版函数:

template<typename T> int cmp(const T&, const T&);

int (*pf) (const int &, const int &) = cmp;

显示实参
当函数返回值类型与形参表中的类型均不同时,需要使用显示实参来解决这个问题:

temp1ate <c1ass T1 , c1ass T2 , c1ass T3>
Tl sum(T2 , T3);

temp1ate <class Tl , class T2 , class T3>
T3 a1ternative_sum(T2 , Tl);

由于传入T2和T3的类型未知,求和之后无法确定返回值为何类型,因此将返回类型也指定为了模版形参。此时仅仅调用sum函数来进行实参推断时无法指定返回类型,因此需要显示指定。使用显示模版实参列表语法,编译器从左到右依次匹配,最后顺序匹配实参的类型。调用方法:

long va13 = sum<long> (110L);

long va12 = a1ternative_sum<long , int , 1ong>(1 , 10L);

暂时总结到此,后续总结类模版成员、成员模版以及模版特化等。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值