浅谈C++模板与泛型编程

 一、泛型编程

STL是一种泛型编程。
泛型编程旨在定义独立于数据类型的代码。
面向对象编程关注的是编程的数据方面,而泛型编程关注的是算法。它们之间的共同点是抽象和创建可重用代码,但他们的理念绝不相同。
C/C++是一种静态编程语言,必须需要把代码翻译成可执行的二进制可执行程序然后在运行,一旦编译好之后就不能再变了(数据类型确定后就无法更改,因此要为每一种数据类型编写一份算法)

  • C语言中的快速排序:
void qsort(void *base, size_t nmemb, size_t size,int(*compar)(const void *, const void *));
  • C++提供了模板的编程方法来解决泛型编程的问题,他的解决思路是,程序员先编写好一份”套路代码”,然后在调用时编译器根据调用时的参数再为这种数据类型生成一份专属代码。

二、迭代器

模板能让算法独立于存储的数据类型,而迭代器使算法独立于使用的容器类型。
那么迭代器到底是什么呢?
举个例子,当我们定义一个排序函数sort()对数组排序,sort()需要对整个数组进行遍历,然后排序;
当我们要对链表排序时,我们需要重新定义一个新的sort()函数对整个链表遍历进行排序。
从细节上来说,数组和链表的sort()函数肯定是不一样的,一个是通过下标,一个是通过node->next;但在广义上来说,它们都是通过遍历整个数据类型容器,依次比较成员数据才达到排序的效果,机理上是相同的。
泛型编程旨在使用同一个sort函数对不同容器进行排序,即函数不仅独立于数据类型,还独立于存储数据类型的容器数据结构,模板提供了存储在容器中的数据类型的通用表示,而迭代器则是遍历容器中值的通用表示。

1.迭代器应具备的功能:
  • 迭代器能够解引用,以便能访问它引用的值。即如果p是一个迭代器,*p可以访问到他引用的值。是不是有点类似于指针(逃
  • 迭代器之间能进行赋值,p= q
  • 迭代器之间能进行比较, p == q,p != q
  • 能够使用迭代器遍历所有元素, p++,++p
2.迭代器类型
迭代器功能 输入迭代器 输出迭代器 正向迭代器 双向迭代器 随机访问迭代器
解除引用读取
解除引用写入
固定和可重复排序
++i,i++
- -i,i- -
i[n]
i+n
i-n
i +=n
i -=n

迭代器的功能强度从左到右依次增长。

3.迭代器的定义:
type::iterator p;
//vector 
vector<int>::iterator p1;
//list
list<double>::iterator p2;

迭代器的遍历

std::vector<int> v(5);
std::vector<int>::iterator p = v.begin();//begin返回指向容器中第一个元素的迭代器
while(p != v.end())//end返回容器最后一个元素的后一个位置的迭代器
{
    p++;
}

三、模板的语法

template T,typename Class>
T func(T num1,T num2)
{
    ...
}
  • T被称为模板的类型参数,可以起名为其他名字,它指的是函数调用时的任何类型的参数
  • 虽然模板的类型可以是任意的,但是必须要支持模板函数中所使用的运算符,因此模板不是万能的

四、函数模板的使用

  • 1、模板的实例化:编译器不会把函数模板编译成一个实例,而是根据调用时的参数,再进行实例化(进一步生成二进制指令)
  • 2、使用模板时才实例化:模板只有在调用时才会实例化,因此模板编译正确并不代表代码没有问题,很多错误会产生于调用时。
  • 3、二次编译:第一次是检查模板的语法,第二次编译时根据调用参数把模板实例化出来,然后在检查运算符是否支持这种类型。

五、函数模板的隐式推断

  • a、使用函数模板时可以根据参数的类型来推断模板的参数
  • b、当函数模板不能通过函数调用时的参数来推断模板参数时,可以使用<类型,类型>来明确指定。
  • c、函数模板参数是可以有默认值的
    1、默认值放右边
    2、C++标准支持:-std=c++0x

六、函数模板的重载

  • 1、同一格式的普通函数和函数模板是可以同时存在的,如果调用时参数类型与普通函数相同,则会优先调用普通函数。(可通过在调用时函数名后加<>调用模板函数)
  • 2、普通函数在同一整型、或浮点型时可以进行类型转换,但是转换调用普通函数的优先级会低于模板函数的实例化。
  • 3、函数模板也可以进行类型提升(int->short/加const等),但同时同格式普通函数也进行类型提升,则会优先调用普通函数。

七、类模板

  • a、类模板的语法
template...>
class classname
{
    C c;
public:
       A func(B b);
}

  注意:typename也可以继续使用,但大多用class以示区别

  • b、类模板的使用
    类模板必须要经过实例化才能使用,也是需要经过两次编译,第一次是把类模板编译成一个”通用板块”,这个过程是为了检查语法,第二次是根据实例化参数,生成一个类,然后再使用这个类创建对象。
    使用类模板创建一个对象 classname a;

  • c、类模板参数不支持隐式推断,必须显示实例化

  • d、静态成员的定义语法

template<class T>
class classname
{
    T data;
    static T num;
    ....
}
template <class T>
T className<T>::num = 10;

静态成员必须在类模板实例化之后才被真正意义上定义出来,每个实例化的类都有份静态成员,这些相同T的类会共用一份静态成员

  • e、嵌套实例化
MyStackint>> stack;

八、类模板的特化(重载)

  • 特化:指的是当类模板有特殊的类型无法处理时,可以为这种特殊类型单独实例化一个类,这种单独的实现叫做模板的特化。
  • 全类的特化:按照类的格式把类完整地再次实现一次。
template <> class classname //例如char*
{
...
};

  成员特化:给指定的类型提供一个特殊的成员函数

template<> 返回值 className ::special_func()
{
    ...
}

  局部特化:若有相同的局部特化,会造成二义性

template<class A,class B> class N 
     {public:N(void){cout <<"1"<template<class A> class N  
     {public:N(void){cout <<"2"<template<class A,class B> class N  //与第一种二义
     {public:N(void){cout <<"2"<template<class A> class N short> 
     {public:N(void){cout <<"3"<template<class A> class N  
     {public:N(void){cout <<"4"<template<class A,class B> class N  
     {public:N(void){cout <<"5"<

九、类模板的参数

  • 1、类模板的参数可以有默认值
    注意:使用类模板默认值时,<>不能省略,可以空着,但不能不写
  • 2、类模板后面的参数可以调用前面的参数
  • 3、普通数值可以对类模板进行实例化,他必须以类似变量的形式存在。注意:只能是常量才能实例化

你可能感兴趣的:(C++,C++)