C++之模版

来源:互联网 发布:怎么利用大数据抄股票 编辑:程序博客网 时间:2024/06/06 03:06

学完C这个面向过程的语言之后,大家应该都养成写C程序的习惯,介绍模版之前,我们先考虑这样一个问题:交换charintdouble等类型的数据,按照以往习惯是不是如此:

 void swap(int &a,int &b)   {int tmp = a;    a = b;        b = tmp;   }    void swap (char ch1,char ch2)    {    }

等等。。。需要多少个类型就要写多少个交换函数,这样简直大大增加代码的阅读量和对代码的理解,那么就有一个想法,可不可以传类型呢?那么C++提供了函数模版。

 

1、模版

    所谓函数模版。实际上是建立了一个通用函数,其函数类型和形参类型不具体确定,用一个虚拟的类型代表。这个通用函数就称为函数模版,凡是函数体相同的函数都可以用这个模版来代替,不必定义多个函数,只需在模版中定义一次,在调用函数时系统会根据实参的类型来取代模版中的虚拟类型,从而实现函数不同的功能。

    C++中提供两个模版机制:函数模版和类模版。

 

2、函数模版

    语法格式:template  <typename T1typename T2....>

那这样交换函数是

   int a = 10;     int b = 20;     MySwap(a,b);

2)显示调用,<>来显示说明类型

  char ch1 = 'A';     char ch2 = 'B';     swap<char> (ch1,ch2);  

注意:template <typename T>每声明一个模版就要写一次

 

有了模版之后,就方便很多,比如你要写很长一段的冒泡排序,只能对整数交换,如果相对其他类型的数据进行排序,又要写很长一段,就可以用那个函数模版。

 

模版的作用将算法与数据类型分离,将数据类型独立出去,只要关注算法。

 

3、函数模版与普通函数的区别

     (1)普通函数,可以做类型的隐式转换,比如:以%d输出字符'A',是输出它的ASCII65

     (2)函数模版在未指明类型,不支持隐式转换。

 当函数模版遇上函数重载,首先要知道函数模板可以像普通函数一样被重载。

 (1)那么当函数模版和普通函数都可以被调用时,有限调用普通函数(开销问题,用函数模版,它得加工出一个模版函数),如果有的人非要用模版函数呢,可以的,

可以通过空类型列表<>,强制调用模版函数。

 c = Max<>(a,b);

 

 (2)在模版函数可以提供更优的选择情况下,调用函数模版。什么是更优呢,意思就是需要类型转换,这种情况有函数模版就用模版,没有就只能委屈它强转了。

     ch = Max(ch1,ch2);

 

    函数模版的机制;编译器并不是把函数模板处理成能够处理任意类的函数,编译器从函数模板通过具体类型产生不同的函数,编译器会对函数模板进行两次编译,在声明的地方对模板代码本身进行编译;在调用的地方对参数替换后的代码进行编译。

 

函数模版与模版函数之间的关系就像工厂与产品的关系,函数模版加工成模版函数。

 

4、类模版

    为什么要有类模版?或者说类模版解决了什么?

类模板用于实现类所需数据的类型参数化。类模板的定义方式和用法都类似函数模板。但在类模板被用来定义对象时,必须指明类型。不支持类型的自动推导。

  Test<int> a;      Test<int> b(3); 语法格式:template <typename T>class A{public:A(T a){this->a = a;}virtual void print (){cout << "a = " << a << endl;}protected:T a;};

模版类做函数参数时的使用方式:

(1)写出具体的模版类的类型

void func (A<int> &p){p.print();}

(2)写成函数模板

template <typename T>void func(A<T> &p){p.print();}

模版类的派生类怎么写?有两种写法:派生普通类和派生类模版

(1)生成一个具体类,要求A写出具体的类型:

class Bpublic A<int>

注意:类B不是一个模版类,而且前面也没有template <typename T>

(2)生成一个类模版

template <typename T>    class C : public A<T>    {     public:     C(T a, T c): A(a)     {    this->c = c; }     void print()    {    cout << "a = " << a << ", c = " << c << endl;    }     private:T c;};

综合上面的介绍,可以这样声明和使用类模版:

1) 先写出一个实际的类。由于其语义明确,含义清楚,一般不会出错。

2) 将此类中准备改变的类型名(int要改变为floatchar)改用一个自己指定的虚拟类型名(如下面的numtype)

3) 在类声明前面加入一行,格式为:

template <typename 虚拟类型参数>如:    template <typename numtype>     //注意本行末尾无分号    class Compare    {…};        //类体

4) 用类模板定义对象时用以下形式:

    类模板名<实际类型名>对象名;                  

    类模板名<实际类型名>对象名(实参表列);        

如:

    Compare<int> cmp;    Compare<int> cmp(3,7);

5) 如果在类模板外定义成员函数,应写成类模板形式:

   template <typename 虚拟类型参数>

   函数类型 类模板名<虚拟类型参数>::成员函数名(函数形参表列) {}

关于类模板的几点说明:

1) 类模板的类型参数可以有一个或多个,每个类型前面都必须加typename,如:

    template <typename T1,typename T2>    class someclass    {…};

在定义对象时分别代入实际的类型名,如:

    someclass<int,double> obj;

2) 和使用类一样,使用类模板时要注意其作用域,只能在其有效作用域内用它定义对象。

3) 模板可以有层次,一个类模板可以作为基类,派生出派生模板类。

5、类模版的实现在内部

模板类的友元函数要在类的内部实现,意思要写函数体(但它还是友元,不能去掉friend);当然,不用再定义为模板函数了。比如这样一个<< 操作符的重载函数:


template <typename T>  class Complex  {  friend ostream &operator<< (ostream &out, Complex<T> &obj)  {  out << obj.a << " + " << obj.b << "i";  return out;  }  private:  T a;  T b;  };

6、类模版的实现在外部

当类的实现是在类的外部时,所有函数都要定义成模板函数。在函数实现时还要加上域解析符,如:

template <typename T>  Test<T>::Test (){} 

用友元函数在类外部实现,是一个惨痛的经历,过程十分复杂,所以没事别用友元函,下面是步骤:

1、进行模板类声明

2、声明友元函数(模板类)

3、在类的内部进行友元函数的声明(友元,非模板),此时需要在函数名后面加上参数类型 <T>,

friend Complex<T> mySub <T>(Complex<T> &c1, Complex<T> &c2);  

4、在类的外部实现(在友元函数内部定义对象时要指定<T>,返回值也需要加上!!!)

5)当模板类的声明和实现在不同的文件中时:

main 函数中需要包含模板类的实现文件;通常这种需要被包含的实现文件,后缀一般要写成.hpp

 

7、类模版中的static关键字

1)类模板的静态变量 也要在类的定义并初始化

template <typename T>    class A{public:static T a;};template <typename T>T A<T>::a = 0;

2)类模板中不同的模板类中的静态变量是不共享的,每一个模板类都有自己的静态变量,该模板类的所有对象共享一个static数据成员。

A<int> a, b;a.a = 100;printf ("a = %d\n", b.a);     // a = 100A<char> ch1, ch2;ch1.a = 'A';printf ("a = %c\n", ch2.a);   // a = Aprintf ("a = %c\n", b.a);     // a = d

3)每个模板类有自己的类模板的static数据成员副本

8、数组的类模版

以前我们也用类来模仿数组,能实现基本功能,但当时只能存放int类型数据,现在我们想存放任意类型的数据,这时候就可以用模版来实现。

需要注意的几个地方:

1)当自定义类型存放在数组中时,自定义类中需要加上无参构造,如果有指针,一定初始化为 NULL;

    2)由于数组打印是通过重载左移操作符来实现的,所以需要在自定义类中也要实现左移操作符的重载,不重载<<也可以,那就不要用cout<<

    3)数组内部赋值运算符,默认的赋值方式是浅拷贝,一旦涉及指针的操作就会导致问题,所以需要做赋值操作符=的运算符重载

    4)另外,在做delete操作时,在delete数组时,要加上[ ],经常忘记。

5)在我们写程序的时候,拷贝构造和赋值运算符不用的时候就设成私有!!

 

下面附上数组的类模版的头文件:

#ifndef __MYARRAY_H__#define __MYARRAY_H__#include <iostream>using namespace std;template <typename T>class MyArray{friend ostream & operator<< <T>(ostream &out, MyArray<T> &a);public:MyArray(int len);MyArray(const MyArray &obj);~MyArray();int getLen();public:T& operator[] (int index);MyArray& operator= (const MyArray &obj);private:int len;T *m_p;};template <typename T>MyArray<T>::MyArray(int len){this->len = len;m_p = new T[len];}template <typename T>MyArray<T>::MyArray(const MyArray &obj){len = obj.len;m_p = new T[len];for (int i = 0; i < len; i++){m_p[i] = obj.m_p[i];}}template <typename T>MyArray<T>::~MyArray(){if (m_p != NULL){delete [] m_p;m_p = NULL;}len = 0;}template <typename T>int MyArray<T>::getLen(){return len;}template <typename T>T& MyArray<T>::operator[] (int index){return m_p[index];}template <typename T>MyArray<T>& MyArray<T>::operator= (const MyArray &obj){//  赋值运算符重载的3个步骤,准确来说是4个// 1、判断是不是自身if (this == &obj)return *this;// 2、释放旧空间if (m_p != NULL)delete [] m_p;// 3、开辟新空间m_p = new T[obj.len];// 4、赋值len = obj.len;for (int i = 0; i < len; i++){m_p[i] = obj.m_p[i];}return *this;}template <typename T>ostream & operator<< (ostream &out, MyArray<T> &a){for (int i = 0; i < a.len; i++){out << a[i] << " ";}out << endl;return out;}