浅谈C与C++的设计与编程风格(二)

上次总结了C++(面向对象)设计的核心思想,并且例举了使用类模型来替代if和switch的一种较为典型的情况。下面想来谈谈C++在编码方面的特点。
在很多经典的C++教程中都有一个建议:应尽量使用户代码(库的使用者)看起来短小而简单。按照常识,简单的代码通常要比大段的代码好理解,而用户代码通常实现的是最上层的功能或者界面,它的不确定性更大,经验告诉我们,最容易出错的代码正是那些被频繁修改的代码!因此简单的用户代码是有好处的,在开发一个库时我们应尽量遵守这个建议。
C++有两个机制可以帮助我们实现这一点,一个是运算符重载,另一个就是模板。
C++的运算符重载机制非常强大,这里就举一个简单的例子。C语言中有一种对数组的特殊初始化方式,比如:int a[4] = {1,4,2,3}; 这样的语法非常清晰且容易让人理解,然而很可惜的是,这样的语法不能用于对数组的赋值,对数组的赋值必须使用一个循环操作,当这个数组中的内容不是按顺序排列时,对数组的赋值很可能会演变成一个非常复杂的过程。
但是在C++中,我们却可以利用重载运算符来实现一种相似的赋值语法,方法就是我们设计一个迭代器,并重载该迭代器的 , 运算符,然后再设计一个可以使用这个迭代器的数组类,并重载这个数组的=运算符,它们的代码大致会是这样:
// 一个重载了,运算符的迭代器类
class CCopyIterator
{
    int * m_piItem;
public:
    CCopyIterator(int in_aiItems[])
    {
        m_piItem = in_aiItems;
    };
    CCopyIterator & operator ,(int in_iVal)
    {
        *(m_piItem++) = in_iVal;
        return *this;
    }
};
//可以使用CCopyIterator赋值的数组
class CSmartArray
{
    int m_aiItems[100]; // 这里使用一个定长的数组是为了回避回收内存空间的问题,毕竟这不是我们要讨论的主要问题
public:
    CCopyIterator operator =(int in_iFirstVal)
    {
        CopyIterator rtn(m_aiItems);
        return (rtn, in_iFirstVal);
    }
};
 
//用户代码:
CSmartArray sa;
sa = 2, 0, 1, 0;
sa = 110, 119, 120;
//怎么样,很酷吧?
在这个例子中我们不难发现,这个CCopyIterator只能给int的数组赋值,而且这个CSmartArray也只能存放int型的数据,如果我们想给任意类型的数组都使用这样的语法赋值该怎么办呢?很简单,使用模板!
// 被改进的CCopyIterator
template<typename val_type> class CCopyIterator
{
    val_type * m_pItem;
public:
    CCopyIterator(val_type in_aFirstItem[])
    {
        m_pItem = in_aFirstItem;
    };
    CCopyIterator & operator ,(const val_type & in_rVal) // 我们稍稍调整一下参数的类型,不妨思考一下为什么要做这样的调整
    {
        *(m_pItem++) = in_rVal;
        return *this;
    }
};
//被改进的CSmartArray类
template<typename val_type, const size_t ARRAY_LENGTH> class CSmartArray
{
    val_type m_aItems[ARRAY_LENGTH]; // 这里我们通过一个模板参数来定义数组的大小
public:
    CCopyIterator<val_type> operator =(const val_type & in_rFirstVal) // 我们稍稍调整一下参数的类型
    {
        CopyIterator<val_type> rtn(m_aItems);
        return (rtn, in_rFirstVal);
    }
};
 
//用户代码:
CSmartArray<char, 20> sa1;
sa1 = '2', '0', '1', '0';
CSmartArray<shot, 100> sa2;
sa2 = '中', '华', '人', '民', '共', '和', '国', '万', '岁';
//这里只是为了演示不同类型的CSmartArray的使用,实际上对于字符串的赋值,可以有更好的方式
从以上的例子可以看出,使用运算符重载和模板,可以大大的简化用户代码,使得用户代码变得更容易理解!另外,模板除了能简化代码外,还有更强大的功能,以后会继续讨论。
下面我们再来看一下如何使用模板函数来及简化用户代码。在C语言中,很多时候我们都需要用到for循环语句,而且循环中可能会需要做很多的判断,这样的代码往往非常难以理解,修改的时候也很容易出错,这时候一个ForEach模板函数就会非常有用:
//一个最常用的ForEach模板函数
template<typename val_type, typename func_type>
void ForEach(val_type & io_rBegin, const val_type & in_rEnd, func_type in_routine)
{
    while(io_rBegin != in_rEnd)
        in_routine(io_rBegin++); // in_routine可以是一个函数指针,也可以是一个函数对象
}
这里ForEach函数所做的就是从io_rBegin到in_rEnd,对每一个元素都调用in_routine,如果用一个相似的C函数来做个比较:
typedef void (*for_each_routine_t)(int & rVal);
void ForEachInt_CStyle(int & io_riBegin, const int & in_riEnd, for_each_routine_t in_pfnRoutine)
{
    while(io_riBegin != in_riEnd)
        in_pfnRoutine(io_riBegin++);
}
不难发现,这个ForEach模板函数能够比ForEachInt_CStyle函数适用于更多类型的数据,然而最重要的是,ForEachInt_CStyle调用的in_routine只能是只有一个参数的函数指针(这使得这个函数的实用性几乎为0),而ForEach模板函数却不同,它的in_routine参数还可以是一个函数对象。什么是函数对象?就是一个重载了()运算符的类的对象,比如:
//一个做累加的函数对象类
class FSum
{
public
    int m_iRtn;
    FSum()
    {
        m_iRtn = 0;
    }
    void operator ()(int in_iVal)
    {
        m_iRtn += in_iVal;
    }
};
不难发现,使用函数对象的好处是,它的参数是可以扩展的(例子中的m_iRtn成员变量就是这个函数对象类的返回参数),正是函数对象的这种灵活性,使得将它和ForEach模板函数配合使用,可以实现很多功能!
//使用ForEach函数做累加
//首先我们将FSum改写成模板
template<typename val_type> class FSum
{
public
    val_type m_Rtn
    FSum()
        : m_Rtn(0) // 使用显式构造语法是为了适应val_type参数是一个类时的情形
    {
    }
    void operator ()(const val_type & in_rVal)
    {
        m_Rtn += in_rVal;
    }
};

// 用户代码:
FSum<int> foSum;
int i = 1;
ForEach(i, 100, &foSum);

//再来看一段C实现:
int sum = 0;
for (int i = 1; i <= 100; ++i)
    sum += i;
从表面上看,上面这个例子中,使用ForEach模板函数似乎并没有带来多少好处(同时也说明不是所有地方都应该套用ForEach模板函数),但是在一些更复杂的算法中,比如遍历搜索,使用ForEach模板函数的好处就能体现出来!另外C++标准模板库的sort模板函数,就是一个典型的ForEach型模板函数。
(未完待续)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章