65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 C++ 模板处理函数和运算符

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.38/5 (5投票s)

2008年6月4日

CPOL

3分钟阅读

viewsIcon

67217

downloadIcon

206

关于模板如何帮助您泛化代码的示例。

引言

前几天,我在为我的一个项目编写一些存档类,我需要通过存档序列化不同类型的数据。所以我开始编写一组 << 和 >> 运算符来处理所有这些不同的数据类型,这时我脑海中突然闪过一个念头:“为什么不使用模板呢?”,我的确是这么想的。你可能会问什么是 C++ 模板?嗯,我很确定你熟悉 STL 类,例如 <string>, <vector>, <list> 等等……所有这些类都使用模板。你可以像这样定义一个整数向量
vector<int> myintvector;
这是可能的,因为 vector 类是使用模板指令声明的。

背景

那么模板如何帮助我覆盖所有这些我需要的不同数据类型呢? 基本上,模板所做的是允许您参数化类或函数本身的定义。这意味着类或函数不需要在设计时考虑特定的数据类型,而是更通用,并且可以与许多(如果不是全部)数据类型一起使用。

使用代码

回到我的存档类,如果我没有使用模板,类的定义将如下所示

class mystream
{
public:
    mystream(LPCSTR fileName, STREAMMODE m);
    ~mystream(void);
    
// Import data    
    mystream& operator>>(char& val);
    mystream& operator>>(short& val);
    mystream& operator>>(int& val);
    mystream& operator>>(float& val);
    mystream& operator>>(bool& val);
    mystream& operator>>(double& val);
    mystream& operator>>(long& val);
    mystream& operator>>(USHORT& val);
    mystream& operator>>(UINT& val);
    mystream& operator>>(ULONG& val);    

// Export data
    mystream& operator<<(const char& val);
    mystream& operator<<(const short& val);
    mystream& operator<<(const int& val);
    mystream& operator<<(const float& val);
    mystream& operator<<(const bool& val);
    mystream& operator<<(const double& val);
    mystream& operator<<(const long& val);
    mystream& operator<<(const USHORT& val);
    mystream& operator<<(const UINT& val);
    mystream& operator<<(const ULONG& val);
    
virtual void Write(const void* data, unsigned long size);
    virtual void Read(void* data, unsigned long size);

private:
    FILE* m_filePtr;    
};

如你所见,每种数据类型都有一个运算符重载,而且我甚至还没有接触到不同数组的序列化。 现在让我们尝试将所有这些转换为模板解决方案。 首先声明我们的类
class mystream
{
public:
    mystream(LPCSTR fileName, STREAMMODE m);
    ~mystream(void);
    
virtual void Write(const void* data, unsigned long size);
    virtual void Read(void* data, unsigned long size);

private:
    FILE* m_filePtr;    
};

如你所见,不存在导入/导出运算符。 让我们声明一个导出和导入运算符
template<class T>
inline mystream & operator<<(mystream& s, T val)
{
    s.Write(&val,sizeof(T));
        return s;
}

template<class T>
inline mystream & operator>>(mystream& s, T& val)
{
s.Read(&val,sizeof(T));
        return s;
} 

如你所见,我在函数声明之前加上了 "template" 关键字,并且我使用 T 的定义作为函数参数的数据类型。将会发生的是,T 将根据我传递给函数的数据类型而接收到不同的含义。 所以如果我编写这段代码
mystream s("C:\\test.dat", write);
DWORD somevalue = 123;
s << somevalue;
    

在这种情况下,T 的含义是 DWORD(尝试将 T 替换为 DWORD,看看它的样子)。 这适用于几乎所有数据类型。 但数组有点不同,因为在序列化数组时,我们需要指定数组中元素的数量,以便稍后我们可以检索数据。 所以我们将为 STL vector 类编写另一个运算符,如下所示
template<class T>
inline mystream & operator<<(mystream & s, const vector<T>& _arr)
{
    s << (long)_arr.size();
    if (_arr.size() > 0) s.Write(&_arr.front(), (ULONG)sizeof(T)*_arr.size());
    return s;
}

template<class T>
inline mystream & operator>>(mystream & s, vector<T>& _arr)
{
    long size;
    s >> size;
    if (size > 0)
    {
        _arr.resize(size);
        s.Read(&_arr.front(), (ULONG)sizeof(T)*_arr.size());
    }
    return s; }

同样的规则适用,只是现在如你所见,T 参数实际上是 vector 声明的一部分。 让我们尝试一下
mystream s("C:\\test.dat", write);
// Declare a vector of 100 integers and fill it with a value of 123
vector<int> somevector(100,123);
s << somevalue; 

因此,在这种情况下,我们的 T 参数是 "int",尝试我之前提到的替换方法,看看会发生什么:写入向量的大小,然后写入一块内存,其大小为向量数据类型乘以向量大小。 所以,这就是使用模板的结果,我们最终只使用了 4 个运算符重载,它们涵盖了所有基本数据类型以及所有可能的向量数据类型。

© . All rights reserved.