C++ STL 基础及应用(2) 模板与操作符重载

本章将阐述一些具体的 STL 模板思想,并简单介绍操作符重载与模板的联系。

模板是 C++ 语言中重要的概念。它提供了一种通用的方法来开发重用的代码,即以创建参数化的 C++ 类型。模板分为两种类型:函数模板和类模板。函数模板的用法同 C++ 预处理器的用法有一定的类似之处,它们都提供编译代码过程中的文本替换功能,但函数模板还能对类型进行一定的保护。使用类模板可以编写通用的、类型安全的类。

1.编写一个数组元素求和的函数模板。

#include <iostream>
using namespace std;

/*
int sum(int data[],int nsize)
{
    int sum=0;
    for(int i=0;i<nsize;i++)
    {
        sum+=data[i];
    }
    return sum;
}
*/

template <class T>
T sum(T data[],int nsize)
{
    T sum=0;
    for(int i=0;i<nsize;i++)
    {
        sum+=data[i];
    }
    return sum;
}

int main()
{
   int data1[]={1,2,3,4,5};
   float data2[]={1.1,2.2,3.3,4.4,5.5};
   double data3[]={1.1,2.2,3.3,4.4,5.5};
   cout<<sum(data1,5)<<endl<<sum(data2,5)<<endl<<sum(data3,5);
   return 0;
}

注释中的代码为int型数组相加的函数,将其改为下方模板函数时,只需要将数据类型抽象出来,使用 template<class name> 代替原先的变量,即可实现模板功能,函数中的类型相应地也要改为 name 。

该函数已经能够实现对不同的基本数据类型进行求和,若进一步思考,希望用该函数能够实现对链表、集合等元素的求和,这即是 STL 的思维方式。

另外,用于模板时,class 关键词可以用 typename 代替,即 template<typename name> 。

2.编写动态数组的模板类,体会 STL vector 的编写思想。

#include <iostream>
using namespace std;

template <class T>
//一个简单的模板类,只有构造函数、析构函数、添加元素函数,但已经能够说明模板类的特征了。
class MyArray
{
private:
    int m_nTotalSize;    //数组总长度
    int m_nValidSize;    //数组有效长度,即当前元素数
    T * m_pData;    //数据指针
public:
    //构造函数
    MyArray(int nSize=2)    //假设默认数组长度为2
    {
        m_pData=new T[nSize];
        m_nTotalSize=nSize;
        m_nValidSize=0;
    }

    //获取数组有效长度
    int GetValidSize()
    {
        return m_nValidSize;
    }

    //获取数组总长度
    int GetTotalSize()
    {
        return m_nTotalSize;
    }

    //返回某一位置元素
    T Get(int pos)
    {
        return m_pData[pos];
    }

    //添加一个元素
    void Add(T value)
    {
        if(m_nValidSize<m_nTotalSize)    //若数组未满
        {
            m_pData[m_nValidSize]=value;
            m_nValidSize++;
        }
        else    //数组满时动态增加数组大小
        {
            T * pOldData=m_pData;    //保存当前数据指针
            m_pData=new T[m_nTotalSize*2];    //原先数组空间大小扩大两倍
            for(int i=0;i<m_nTotalSize;i++)    //拷贝原先数据
            {
                m_pData[i]=pOldData[i];
            }
            m_nTotalSize*=2;    //当前数组总长度更新
            delete pOldData;    //释放旧数组占用的内存
            m_pData[m_nValidSize]=value;    //添加新元素
            m_nValidSize++;    //更新数组有效程度
        }
    }
    //析构函数
    virtual ~MyArray()
    {
    if(m_pData!=NULL)
      {
        delete []m_pData;
        m_pData=NULL;
       }
    }
};

int main()
{
  MyArray<int> array1;
  cout<<"数组总长度:"<<array1.GetTotalSize()<<" 数组有效长度:"<<array1.GetValidSize()<<endl;
  array1.Add(1);
  array1.Add(2);
  array1.Add(3);
  cout<<"数组总长度:"<<array1.GetTotalSize()<<" 数组有效长度:"<<array1.GetValidSize()<<endl;
  cout<<array1.Get(0)<<" "<<array1.Get(1)<<" "<<array1.Get(2)<<endl;

  MyArray<double> array2;
  cout<<"数组总长度:"<<array2.GetTotalSize()<<" 数组有效长度:"<<array2.GetValidSize()<<endl;
  array2.Add(1.1);
  array2.Add(2.2);
  array2.Add(3.3);
  cout<<"数组总长度:"<<array2.GetTotalSize()<<" 数组有效长度:"<<array2.GetValidSize()<<endl;
  cout<<array2.Get(0)<<" "<<array2.Get(1)<<" "<<array2.Get(2)<<endl;

  return 0;
}

该模板类模拟了一个简化的类似 vector 的动态数组,并实现了添加元素功能。可以看到该数组类默认数组大小是2,当插入3个元素之后,数组大小动态增加为原来的两倍,这也是 vector 中的实现方法。虽然实现了数组动态大小,但是需要将数组的原先数据做一份拷贝,因此此时会有效率上的损失。

程序中演示了对于 int 型和 double 型类的使用,说明了模板函数抽象的意义所在,即能实现代码的重用。

本类能体现出 STL 容器关于内存"内存分配、销毁、再分配"的思想,也就是把内存管理的部分进一步抽象,编程系统代码,应用方不必明了过程中的内存变化,用专家级编写的代码,而不是自己编写的代码来管理内存。STL 是编写普通模板类发展的必然结果,不是一种新技术。

3.traits技术

STL标准模板库非常强调软件的复用,traits 技术是重要的手段。traits 的中文意思就是特性,traits 就像特性萃取机,提取不同类的特性,以便能够统一处理,traits 依靠显式模板特殊化来把代码中因类型不同而发生变化的片段拖出来,用统一的接口来包装。这个接口可以包含 C++ 类所能包含的任何东西,如内嵌类型、成员函数、成员变量。作为客户的模板代码,可以通过
traits 模板类所公开的接口来间接访问。下面通过一个简单的例子加以理解。

已知整形数组类 IntArray 与浮点型数组 FloatArray ,求数组和与一个数的乘积。

按普通方式编写的代码如下:(红色为两个数组类的区别)

#include <iostream>
using namespace std;

class IntArray
{
private:
    int a[10];
public:
    IntArray()
    {
        for(int i=0;i<10;i++)
        {
            a[i]=i+1;
        }
    }
    //该函数输出所有元素的和与times的乘积
    int Calculate(int times)
    {
        int sum=0;
        for(int i=0;i<10;i++)
        {
            sum+=a[i];
        }
        return sum * times;
    }
};

class FloatArray
{
private:
    float a[10];
public:
    FloatArray()
    {
        for(int i=0;i<10;i++)
        {
            a[i]=i+1;
        }
    }
    //该函数输出所有元素的和与times的乘积
    float Calculate(float times)
    {
        int sum=0;
        for(int i=0;i<10;i++)
        {
            sum+=a[i];
        }
        return sum * times;
    }
};

int main()
{
  IntArray a;
  FloatArray f;
  cout<<"整形数组和的 2 倍是:"<<a.Calculate(2)<<endl;
  cout<<"浮点型数组和的 2.2倍是: "<<f.Calculate(2.1)<<endl;
}

可以发现,两个数组类的 Calculate 函数出了参数类型、返回值类型不同外,其余都一样,那么能否通过一个类的接口函数来完成上述功能呢?可以,当然要用到模板。这里要增加一个类 Array 。

template<class T>
class Array
{
public:
    float Calculate(T &t,float times)
    {
        return t.Calculate(times);
    }
};

并且主函数需要改变:

int main()
{
  IntArray a;
  FloatArray f;
  Array<IntArray> A1;
  Array<FloatArray> A2;
  cout<<"整形数组和的 2 倍是:"<<A1.Calculate(a,2)<<endl;
  cout<<"浮点型数组和的 2.2倍是: "<<A2.Calculate(f,2.1)<<endl;
}

这里使用 Array 接口函数实现对整形数组和浮点型数组类的操作。但是仔细分析一下就能发现,细节上还是有问题的。比如在 IntArray 中的 Calculate() 函数的参数和返回值都是 int 类型,FloatArray 中的 Calculate() 函数的参数和返回值都是 float 类型,而模板类 Array 中将 Calculate() 函数的参数和返回值都固定为了 float 类型,虽然从程序的结果来看似乎是正确的,但是并不够严密,当程序复杂点时极有可能出错,那么如何解决输出、输出参数类型的不同呢?traits
技术就是很好的解决方法,步骤与如下所示。

步骤一:定义基本模板类

template<class T>

class NumTraits

{

};

NumTraits 类可以什么都不写,只是说明它是一个模板类。

步骤二:模板特化。

template<>

class NumTraits<IntArray>

{

public:

typedef int inputType;

typedef int resultType;

};

template<>

class NumTraits<FloatArray>

{

public:

typedef int inputType;

typedef int resultType;

};

可以看出相应模板特化类中只是用了 typedef 重定义函数,将 IntArray 和 FloatArray 两个数组类中 Calculate() 函数的参数类型、返回值类型重新定义成 inputType 和 resultType,为编写模板类共同的调用接口做准备。

步骤三:统一模板调用类编写

template<class T>

class Array

{

public:

typename NumTraits<T>::resultType Calculate(T &t,typename NumTraits<T>::inputType times)

{

return t.Calculate(times);

}

};

这里 typename 关键字的作用是告诉编译器 NumTraits<T>::resultType 和 NumTraits<T>::inputType 是一个类型而不是变量。

乍一看 Array 类的 Calculate() 函数有些难懂。当模板参数代表 IntArray 时,该定义变为如下代码:

typename NumTraits<IntArray>::resultType Calculate(T &t,typename NumTraits<IntArray>::inputType times)

根据(2)中模板特化定义,NumTraits<IntArray>::resultType 代表 int,NumTraits<IntArray>::inputType 也代表 int ,于是上述定义就变为 int Calculate(T &t,int times)。当模板参数代表 FloatType 时情况类似这里就不做赘述了。

因此 Array 类中的 Calculate() 函数的参数类型和返回值类型是可变的,随着模板参数的不同而不同。因此在模板特化类中给输入、输出参数进行 typedef 重定义非常重要,而且起的对应名称还要相同。

最后,Array 类中的 Calculate() 函数定义看起来很繁琐,这里再次采用 typedef 定义使其清晰:

template<class T>

class Array

{

public:

typedef typename NumTraits<T>::resultType result;

typedef typename NumTraits<T>::inputType input;

result Calculate(T &t,input times)

{

return t.Calculate(times);

}

};

最后给上完整代码:

#include <iostream>
using namespace std;

class IntArray
{
private:
    int a[10];
public:
    IntArray()
    {
        for(int i=0;i<10;i++)
        {
            a[i]=i+1;
        }
    }
    //该函数输出所有元素的和与times的乘积
    int Calculate(int times)
    {
        int sum=0;
        for(int i=0;i<10;i++)
        {
            sum+=a[i];
        }
        return sum * times;
    }
};

class FloatArray
{
private:
    float a[10];
public:
    FloatArray()
    {
        for(int i=0;i<10;i++)
        {
            a[i]=i+1;
        }
    }
    //该函数输出所有元素的和与times的乘积
    float Calculate(float times)
    {
        int sum=0;
        for(int i=0;i<10;i++)
        {
            sum+=a[i];
        }
        return sum * times;
    }
};

//基本模板类
template<class T>
class NumTraits
{
};

//模板特化
template<>
class NumTraits<IntArray>
{
public:
    typedef int inputType;
    typedef int resultType;
};

//模板特化
template<>
class NumTraits<FloatArray>
{
public:
    typedef float inputType;
    typedef float resultType;
};

//统一模板调用类编写
template<class T>
class Array
{
public:
    typedef typename NumTraits<T>::resultType result;
    typedef typename NumTraits<T>::inputType input;

    result Calculate(T &t,input times)
    {
        return t.Calculate(times);
    }
};

int main()
{
  IntArray a;
  FloatArray f;
  Array<IntArray> A1;
  Array<FloatArray> A2;
  cout<<"整形数组和的 2 倍是:"<<A1.Calculate(a,2)<<endl;
  cout<<"浮点型数组和的 2.2倍是: "<<A2.Calculate(f,2.1)<<endl;
  return 0;
}

4.模板与操作符重载

例如有关于大小比较的模板函数时,有下列代码:

#include <iostream>
using namespace std;

template <class U,class V>
bool Grater(U const &u,V const &v)
{
    return u>v;
}

int main()
{
    cout<<Grater(2,1)<<endl;
    cout<<Grater(2.2,1.1)<<endl;
    cout<<Grater('a',10)<<endl;
    return 0;
}

该模板函数在比较基本类型时完全正确,但是当 U 或者 V 中有一个表示类时,无法比较,此时就需要重载操作符。

假设有一个学生类:

class Student
{
private:
    char name[20];
    int grade;
public:
    Student(char name[],int grade)
    {
        strcpy(this->name,name);
        this->grade=grade;
    }
    bool operator>(const int &value)const
    {
        return this->grade>value;
    }
};

需要比较他的成绩是否大于99分,使用 Grater(s,99) 时,需要重载 Student 类的 ">" 操作符,代码如下所示:

bool operator>(const int &value)const

{

return this->grade>value;

}

现在已经可以将 Student 类与 int 型进行比较了。

完整的模板与重载操作符函数如下:

#include <iostream>
#include <string.h>
using namespace std;

class Student
{
private:
    char name[20];
    int grade;
public:
    Student(char name[],int grade)
    {
        strcpy(this->name,name);
        this->grade=grade;
    }
    bool operator>(const int &value)const
    {
        return this->grade>value;
    }
};

template <class U,class V>
bool Grater(U const &u,V const &v)
{
    return u>v;
}

int main()
{
    Student s("Raito",100);
    cout<<Grater(s,99)<<endl;
    return 0;
}

由于 STL 中有大量的模板函数,因此很多时候都要重载与之对应的操作符。模板函数相当于已经编写好的应用框架,操作符重载相当于调用的接口。

时间: 2024-09-29 20:46:17

C++ STL 基础及应用(2) 模板与操作符重载的相关文章

【STL基础】deque

deque (double-ended queue) 构造函数: //default: deque<T> d; //空的vector //fill: deque<T> d(n); //n个元素的deque,元素默认初始化 deque<T> d(n, value); //n个元素值为value的deque //range: deque<T> d(first, last); //两个迭代器之间的元素构成的deque deque<T> d(arr, a

【STL基础】list

list 构造函数: //default: list<T> l; //空的list //fill: list<T> l(n); //n个元素, 元素默认初始化 list<T> l(n, value); //n个元素值为value //range: list<T> l(first, last); //两个迭代器之间的元素构成 list<T> l(arr, arr + sizeof(arr) / sizeof(T)); //由内置数组构造 //cop

STL源码分析--仿函数 &amp; 模板的模板参数 &amp; 临时对象

STL源码分析-使用的一些特殊语法 关于泛型编程中用到的一些特殊语法,这些语法也适用于平常的模板编程 1.  类模板中使用静态成员变量 Static成员变量在类模板中并不是很特殊,同时这个变量不属于对象,属于实例化以后的这个类类型.每一个实例化对应一个static变量 2.  类模板中可以再有模板成员 3.  模板参数可以根据前一个模板参数而设定默认值 4.  类模板可以拥有非类型的模板参数 所谓非类型的模板参数就是内建型的模板参数 Template <class T,class Alloc =

四.OC基础--1.文档安装和方法重载,2.self和super&amp;static,3.继承和派生,4.实例变量修饰符 ,5.私有变量&amp;私有方法,6.description方法

四.OC基础--1.文档安装和方法重载, 1. 在线安装 xcode-> 系统偏好设置->DownLoads->Doucument->下载 2. 离线安装 百度xcode文档 3. 方法重载: 是指在一个类中定义多个同名的方法 在OC中没有重载 2.self和super&static, self和super: 1. self理解: 谁调用当前方法, self就代表谁. 比如: 在对象方法中,self代表的是对象, 因为只有对象才可以调用对象方法 在类方法中, self代表的

在c++ 模板类外写 操作符重载函数,并且是模板类的友元函数

看视频教程说不能在c++ 模板类外写 操作符重载函数,并且是模板类的友元函数 我试了试,可以,放出测试代码: #include <iostream> using namespace std; template<typename T> class A { public: A(T a) { this->a = a; } template<typename T> //加上这句就可以了 friend A<T> operator+(A<T> &

聊聊C++模板函数与非模板函数的重载

函数重载在C++中是一个很重要的特性.之所以有了它才有了操作符重载.iostream.函数子.函数适配器.智能指针等非常有用的东西. 平常在实际的应用中多半要么是模板函数与模板函数重载,或者是非模板函数与非模板重载.而让模板函数与非模板函数重载的情况却很少. 前几天在项目中偶然遇到了一个模板函数与非模板函数重载的诡异问题,大概相当于下面这种情况: 1 template <typename T> 2 int compare(const T& lhs, const T& rhs)

C++ Primer 学习笔记_86_模板与泛型编程 --重载与函数模板

模板与泛型编程 --重载与函数模板 引言: 函数模板可以重载:可以定义有相同名字但参数数目或类型不同的多个函数模板,也可以定义与函数模板有相同名字的普通非模板函数. 但是,声明一组重载函数模板不保证可以成功调用它们,重载的函数模板可能会导致二义性. 一.函数匹配与函数模板 如果重载函数中既有普通函数又有函数模板,确定函数调用的步骤如下: 1.为这个函数名建立候选函数集合,包括: a.与被调用函数名字相同的任意普通函数. b.任意函数模板实例化,在其中,模板实参推断发现了与调用中所用函数实参相匹配

C++ STL 基础及应用(3) 迭代器

迭代器(Iterator)是 STL 的核心技术,提供了统一访问容器元素的方法,为编写通用算法提供了坚实的技术基础. 本章将带你编写一个自带迭代器的数组类和一个自带迭代器的链表类,模拟 STL 中的容器,这两个实例能够很清晰地展示 STL 的迭代器思想.并探讨迭代器类应该作为容器类的内部类的原因,然后对 STL 迭代器做一下归纳理解,最后阐述一下 STL 中真正的迭代器概况. 那么什么是迭代器呢? 迭代器即指针,可以是需要的任意类型,它的最大好处是可以使容器和算法分离.例如,有两个容器类,MyA

STL学习一:标准模板库理论基础

STL(Standard Template Library,标准模板库)是惠普实验室开发的一系列软件的统称.现然主要出现在C++中,但在被引入C++之前该技术就已经存在了很长的一段时间. STL的从广义上讲分为三类:algorithm(算法).container(容器)和iterator(迭代器),容器和算法通过迭代器可以进行无缝 地连接.几乎所有的代码都采 用了模板类和模板函数的方式,这相比于传统的由函数和类组成的库来说提供了更好的代码重用机会.在C++标准中,STL被组织为下面的13个头文