C++ articles:Guru of the Week #4 -- Class Mechantics

原创 2001年03月14日 18:21:00

 

作者:Hub Sutter
译者:plpliuly

/*此文是译者出于自娱翻译的GotW(Guru of the Week)系列文章第3篇,原文的版权是属于Hub Sutter(著名的C++专家,"Exceptional C++"的作者)。此文的翻译没有征得原作者的同意,只供学习讨论。——译者
*/


#4 类的结构(Class Mechanics)
难度:7.5/10

    你对定义一个类牵涉到的具体细节熟悉多少?这次GotW不仅讨论一些写一个类时很容易犯的错误,也要讨论怎样使你写的类具有专业风格.

问题:
    假设你在看一段代码.其中包含如下一个类定义.这个类定义中有几处风格差劲,还有几处是的的确确的错误.你可以找到几处,该怎样修改?

    class Complex {
    public:
        Complex( double real, double imaginary = 0 )
          : _real(real), _imaginary(imaginary) {};

        void operator+ ( Complex other ) {
            _real = _real + other._real;
            _imaginary = _imaginary + other._imaginary;
        }

        void operator<<( ostream os ) {
            os << "(" << _real << "," << _imaginary << ")";
        }

        Complex operator++() {
            ++_real;
            return *this;
        }

        Complex operator++( int ) {
            Complex temp = *this;
            ++_real;
            return temp;
        }

    private:
        double _real, _imaginary;
    };

答案:


    这个类定义中出现的需要修改或改进的地方远不止我们在下面要提到的几处.我们提出这个问题的目的主要是为了讨论类定义的构成(比如,"<<操作符的标准形式是什么样的","+操作符是不是应该定义为成员函数?"),而不是讨论哪个接口的设计是不是太糟糕.但是,我还是想首先提一下属于后方面的建议,作为第0个值得修改的地方:
    
    0.既然标准库中已经有复数类为什么还要自己定义一个Complex类呢?(而且,标准库中定义的类不会有下面提及的任何一个问题,是由业界中具有多年经验的最优秀的人编写.还是谦虚一点去直接重用这些类吧)
    [忠告]尽量直接使用用标准库算法.那会比你自己动手写一个要快捷,而且不易出错.


    class Complex {
    public:
        Complex( double real, double imaginary = 0 )
          : _real(real), _imaginary(imaginary) {};
    1.风格问题:这个构造函数可以当作单参数函数使用,这样就可能作为一个隐式转换函数使用.但是可能并不是所有时候发生的隐式转换都是你所期望的.
    [忠告]当心不易察觉的类型转换操作在你并不希望的时候发生.一个好的避免办法就是:可能的话就用explicit修饰词限制构造函数.
   
    void operator+ ( Complex other ) {
            _real = _real + other._real;
            _imaginary = _imaginary + other._imaginary;
        }
   2.风格问题:从效率上讲,参数应该是const&类型,而且"a=a+b"形式的语句应该换成"a+=b"的语句.
     [准则]尽量使用const&参数类型代替拷贝传值类型参数.
     [忠告]对于算术运算,尽量使用"a op= b"代替"a = a op b".(注意有些类--比如一个别人写的类--可能通过操作符的重载改变了op和op=之间的关系)
  
   3.风格问题:+操作符不应该定义为成员函数.如果被定义成如上述代码中的成员函数,你就只能用"a=b+1"形式的语句而不能用形如"a=1+b"的语句.这样,你就需要同时提供operator+(Complex,int)和operator+(int,Complex)的定义.
   [准则]在考虑把一个操作符函数定义为成员函数还是非成员函数的时候,尽量参照下面的原则:(Lakos96:143-144;591-595;Murray93:47-49)
   - 一元(单参数)操作符函数应该定义成成员函数
   - =,(),[],和->操作符应该定义成成员函数
   - +=,-=,/=,*=,(等等)应该定义成成员函数
   - 其余的操作符都应该定义成非成员函数
  
   4.错误:+操作符不应该改变对象自身.它应该返回包含相加结果的临时对象.请注意,为了防止类似"a+b=c"的操作,这个操作符函数返回类型应该是"const Complex"(而不仅仅是"Complex").
(实际上,上述的代码更象定义+=操作符函数的代码)

   5.风格问题:一般来讲,当定义了op的操作符函数,就应该同时定义了op=操作符.因此,这儿也应该定义+=操作符.上面的代码就应当是+=的定义(但是返回类型需要修改,参看下面的讨论).

        void operator<<( ostream os ) {
            os << "(" << _real << "," << _imaginary << ")";
        }
   (注意:对于一个真正的<<操作符,你应该检查stream的当前格式标志以支持一般的用法(译者:iostream通过设置标志来对输出格式进行控制).请参考你喜欢的STL的书籍了解细节)
 
   6.错误:操作符<<不应该定义成成员函数(参看上面讨论),而且参数应该是"(ostream&,const Complex&)".注意,正如James Kanze指出,也尽量不要把它定义成友元函数!最好把它定义成通过调用一个"print"的public成员函数来工作.
   7.错误:这个操作符函数应该返回"ostream&",而且应该以"return os"结束.这样就可以支持将多个输出操作链接起来(比如"cout << a << b;").
    [准则]操作符<<和>>都应该返回stream的引用.

        Complex operator++() {
            ++_real;
            return *this;
        }
    8.风格问题:前自增应该返回Complex&,以便调用者可以更直接的操作.(译者:其实我觉得这不仅仅是风格的问题.因为按照前自增的标准定义,应该支持"++++a"的语法,而且两次前自增都应该是对a对象的自身操作,如果返回Complex类型,那第二次前自增调用的是临时对象的前自增操作.)

       Complex operator++( int ) {
            Complex temp = *this;
            ++_real;
            return temp;
        }
   9.风格问题:后自增应该返回"const Complex".这可以防止形如"a++++"的用法.这句话可不会象某些人想当然那样会连续对a对象作两次自增操作.
   10.风格问题:如果通过前自增操作来实现后自增操作符函数将会更好.(译者:将"++_real;"替换为"++(*this);")
   [准则]尽量通过前自增操作来实现后自增操作.
   private:
        double _real, _imaginary;
    };
   11.风格问题:尽量避免使用以下划线开头命名变量.我曾经也很习惯这样定义变量,就连著名的"Design Patterns"(Gamma et al)中也是这样.但是因为标准库的实现中保留了很多下划线开头标识符,如果我们要用下划线开头定义自己的变量就得记住全部已经保留的标识符以免冲突,这太难了.(既然使用下划线作为成员变量的标志容易跟保留标识符冲突,那我就在变量结尾加下划线)
  
   好了.最后,不考虑其他一些上面没有提到的设计和风格上的缺陷,我们可以得到下面的经过修正的代码:
  class Complex {
    public:
        explicit Complex( double real, double imaginary = 0 )
          : real_(real), imaginary_(imaginary) {}

        Complex& operator+=( const Complex& other ) {
            real_ += other.real_;
            imaginary_ += other.imaginary_;
            return *this;
        }

        Complex& operator++() {
            ++real_;
            return *this;
        }

        const Complex operator++( int ) {
            Complex temp = *this;
            ++(*this);
            return temp;
        }

        ostream& print( ostream& os ) const {
            return os << "(" << real_
                      << "," << imaginary_ << ")";
        }

    private:
        double real_, imaginary_;
        friend ostream&
        operator<<( ostream& os, const Complex& c );
    };

    const Complex operator+( const Complex& lhs,
                             const Complex& rhs ) {
        Complex ret( lhs );
        ret += rhs;
        return ret;
    }

    ostream& operator<<( ostream& os,
                         const Complex& c ) {
        return c.print(os);
    }
-----
(结束)

C++中星期几计算公式

蔡勒公式(Zeller):是一个计算星期的公式。 随便给一个日期,就可以使用这个公式推算出事星期几。 公式如下:Weeks = [C/4] - 2C + y + [y/4] + [13*(M +1)/...
  • u012307430
  • u012307430
  • 2016年11月30日 18:12
  • 280

斯坦福Machine Learning Week4总结(包括作业)

第一讲:神经网络(Neural Network)的有关背景 对于一个非线性(non-linear)的分类问题,当特征数较少时,可以通过多项式拟合出决策边界(decision boundary)。然而对...
  • Wyy_dd
  • Wyy_dd
  • 2017年01月26日 19:12
  • 675

windows下用eclipse+goclipse插件+gdb搭建go语言开发调试环境

windows下用eclipse+goclipse插件+gdb搭建go语言开发调试环境 目前go语言在window或者linux操作系统上,最好的go语言开发调试环境都是由eclipse+goc...
  • manlyboy1
  • manlyboy1
  • 2016年07月28日 10:27
  • 4248

mysql的week函数与JAVA计算周的差别问题

1、问题: 在某些情况下,会需要将日期按周来进行排序或统计,mysql就要用到week()或yearWeek()函数,就会发现,比如2016年的某一天,在mysql里面是属于第30周,但在JAVA中使...
  • cwfreebird
  • cwfreebird
  • 2017年01月22日 15:06
  • 1215

C++程序设计 - Week 4 运算符重载

C++程序设计公开课笔记。 运算符重载、赋值运算符的重载、运算符重载为友元函数、可变长整型数组、流插入运算符和流提取运算符的重载、自加/自减运算符的重载。...
  • u013819100
  • u013819100
  • 2015年06月04日 17:10
  • 390

Django框架全面讲解 -- Django 路由系统

URL配置(URLconf)就像Django 所支撑网站的目录。它的本质是URL模式以及要为该URL模式调用的视图函数之间的映射表;你就是以这种方式告诉Django,对于这个URL调用这段代码,对于那...
  • shentong1
  • shentong1
  • 2017年12月15日 15:24
  • 61

C++中class的简单使用

C++中class的简单使用
  • fengbingchun
  • fengbingchun
  • 2016年07月30日 21:37
  • 3190

MSBuild和Jenkins搭建持续集成环境(2)

Jenkins中如何运用MSBuild进行.NET项目自动化构建
  • jiang1986829
  • jiang1986829
  • 2016年03月18日 16:38
  • 748

《算法分析与设计》Week 4

310. Minimum Height Trees Description: For a undirected graph with tree characteristics,...
  • Small_Hacker
  • Small_Hacker
  • 2017年03月29日 22:14
  • 131

虚幻4C++编程入门深入了解

这部分我们将讨论基础构建块以及它们之间相互关联的方式。在此我们将了解虚幻引擎如何使用 继承和合成构建自定义游戏性功能。游戏性类:对象、Actor 和组件多数游戏性类派生自 4 个主要类型。它们是...
  • qq_31828929
  • qq_31828929
  • 2017年05月18日 21:51
  • 990
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:C++ articles:Guru of the Week #4 -- Class Mechantics
举报原因:
原因补充:

(最多只允许输入30个字)