当前位置:编程学习 > C/C++ >>

C++学习笔记(七) 模板与泛型编程

模板简介
模板是C++一个非常重要的特性,它是C++泛型编程的基础。某些对C++持极度偏见的人甚至说模板是C++对这个世界的

唯一贡献(当然,我是不赞同的),可见模板在C++中的重要性,而整个STL都是基于模板的,可见其应用之广泛。
C++引入模板的一个重要原因是算法的重用,比如下面一个例子:

[cpp] 
bool mless_than(const int& v1, const int& v2) { 
    return v1 < v2; 

程序很简单,就是比较第一个参数是否小于第二个参数而已,这个算法在我们的程序可以说是非常常见,这个是int

版本的,如果我们还需要一个string版本的,一个double版本的,甚至是一个自定义类版本的怎么办呢?如果没有模

板,我们就不得不为其定义多个实现,即使他们的代码都是一样的,如果算法很长,例如一个排序算法,写这么多个

版本将是一件冗长而乏味的事情,况且我们是无法预见未来需要为什么样的类型定制算法的,写算法库的人也无法知

道使用者可能会定义什么样的类型。说了模板的必要性,现在来看一个它的实现吧,还是刚才那个函数:
[cpp] 
template<typename T> 
bool mless_than(const T& v1, const T& v2) { 
    return v1 < v2; 

使用的方式也很简单,直接调用就是了,聪明的编译器会自动为我们推导出模板的参数类型:

[cpp] view plaincopy
bool result = mless_than(2.8, 4.1);//double version 
在某些情况下,编译无法从调用参数推到出所有的模板类型,或是我们传入的参数类型不是我们希望用于实例化函数

的模板参数类型时,我们也可以手动的指定模板参数类型,调用方式如下:
[cpp]
bool result = mless_than<int> (2.8, 4.1);//double version 

模板除了用在函数上,还可以用在类中,例如:
[cpp] 
template<typename T> 
class A{ 
//...other definition 
private: 
    T v; 
//...other definition 

如果经常使用STL的话,使用方式我们应该已经习惯了:
[cpp] 
A<int> a; 

这个是用一个int版本的类A去定义了一个对象a.
需要注意的是,无论是函数模板,还是类模板,它们都不是真正的函数或是类,它只是告诉编译器该如何生成真正的

函数实例或是类实例(这里并非对象哦),也就是说A并不是类,A<int>才是类。

关于模板的简介就说到这吧,有了这个初步概念后,我们来看看class和typename的区别。

class和typename
在上面的模板定义中,我都是使用typename关键字来定义模板参数的,以前学习过或了解过模板的同学可能还会发现

另一个关键字class被重用在这里用于定义模板参数,那么它们究竟有上面区别呢?答案是在定义模板参数这里,它

们是没有区别的,由于typename是后引进的关键字,所以,在一些比较旧的代码中,class关键可能会更加常见些。
我说了它们在这里是没有区别的,但既然我单独列了一个小标题,说明这中间还是有点内容滴。typename在模板中还

有一些别的作用。在谈这个之前,我们先来了解一个概念:nested dependent type names(嵌套依赖类型名)。考

虑下面的定义:
[cpp]  
template<typename T> 
void test(const T& c) { 
    c::key_type *ptr; 
//other implementation 

其中,c::key_type *ptr代表什么意思呢?如果你对STL比较熟悉的话,你可能会说,嘿,这是利用c::key_type定义

了一个指针ptr,在map和set里面都有这个类型,它表示的是其封装的键的类型。可是,只是可是,如果有哪个傻瓜

自定义了一个类,并且他恰好在这个类内部又定义了一个名叫key_type的静态变量,那么这句话就不再是变量定义了

,而是一个乘法运算。那么编译器是如何看待这条语句的呢?首先还是说一下潜逃依赖类型名这个概念,像这种定义

在类内部的类型,而外部类的类型又是依赖于模板参数的,就是所谓的潜逃依赖类型名,默认情况下,编译器是把它

当做是变量名,而非类型名的,也就是说默认下,编译器会将其当做乘法运算的。如果我们想让编译器把它当做是类

型名,可以在其前面加上typename关键字进行修饰。
[cpp] 
typename c::key_type *ptr; 

对于像这种潜逃依赖类型名,我们在使用的时候都应该加上typename关键字进行修饰。但是,还是有例外的,例如在

基类列表里面,如果有用到嵌套依赖类型,则不用typename关键字,因为这里出现的标示符只可能是类型名。
Nontype 模板
前面所谈及的都是类型模板,实际上C++的模板机制还支持非类型模板。为了更直观的说明它是什么,我们先来看一

个它在STL中一个实际运用的例子,那就是位图类:
[cpp] 
bitset<32> b; 

如上的定义方式是定义了一个大小为32的位图类,它利用非类型参数去指定其大小。我们来看一个非类型模板的简单

实现:
[cpp] 
template<int SIZE> 
class A{ 
//some definition 
    int data[SIZE]; 

可以看出,非类型模板参数在这里的作用是指定A<SIZE>内部维护的一个数组变量的大小。在我们进行参数传递时,

非类型参数有时候会先得非常有用,例如:
[cpp] 
template <class T, size_t N> void array_init(T (&parm)[N]) 

    for (size_t i = 0; i != N; ++i) { 
        parm[i] = 0; 
    } 

该函数的作用是对任意大小的数组进行初始化,这里巧妙的利用非类型参数指定了所传递的数组引用的大小。
模板的特化与偏特化
模板的作用是为任意的类型提供统一的实现方式,或是算法逻辑,或是数据结构。但有时候,对于某些类型,统一的

实现方式并不能满足我们的要求,考虑最开始的那个模板函数,如果我们传递的是常量字符串,编译器会为我们实例

化出这样的函数代码:
[cpp]
bool mless_than(const char* const& v1, const char* const& v2) { 
    return v1 < v2; 

编译运行都木有问题,可关键是,它比较的两个字符串的地址,而非字符串本身,这显然不是我们想要的。在这种情

况下,我们就需要利用模板的特化功能为它定制一个专门的版本用于处理C_style的字符串,特化的实现方式如下:
[cpp] 
template<> 
bool mless_than(const char* const& v1, const char* const& v2) { 
    return strcmp(v1, v2) < 0; 

当我们传递C_style的字符串时,编译器就会调用我们特化的这个版本,而不是利用模板去给我们呢生成。
模板的特化也可以用在类模板上,在特化的类中,我们不必遵循原先模板的定义方式。类模板的特化定义方式和函数

模板大致类似,不过,它必须在类名后面显示的指定特化的模板参数类型。
除了模板特化,C++还允许我们对类模板进行偏特化(函数模板不行),就是只针对部分模板参数进行特化,例如:
[cpp]
template<typename T, typename V> 
class A{ 
//other definition 
    T d1; 
    V d2; 
}; 
 
template<typename T> 
class A<T, int>{ 
//other definition 
    T d1; 
    int d2; 
}; 

在偏特化中,我们将模板参数V特化为int,需要注意的是,偏特化后的模板仍然是一个模板类,而非实际的类。
模板元编程
所谓模板元编程实际上并没有引入新的C++特性,它是C++非类型模板与模板特化的一个非常奇妙的用法。它能够将一

些运行时计算的任务放到编译期来完成,从而提高运行效率。例如,我们希望以常量的阶乘作为一个静态数组的大小

,就可以利用模板元编程了:
[cpp] 
template<unsigned N> 
class Factorial { 
public: 
    unsigned VALUE = N*factorial<N-1>::VALUE; 
}; 
 
template<> 
class Factorial<0> { 
public: 
    unsigned VALUE = 1; 
}; 

上面的模板类Factorial用于计算阶乘,它巧妙的利用递归在编译器就可以计算出我们所需要的阶乘值,值得注意的

是,这里的递归出口是一个偏特化模板,很神奇吧。
模板的编译机制
谈完模板的一些基本特性与使用方式,我们最后来看一下模板编译机制。我们知道,模板只是提供被编译器供编译器

生成实例的一种方式。而模板是按需进行实例化的,也就是说,如果我们按照我们的习惯将类模板的定义放在头文件

里,而

补充:软件开发 , C++ ,
CopyRight © 2022 站长资源库 编程知识问答 zzzyk.com All Rights Reserved
部分文章来自网络,