动手实现自己的 C++ 复数类

一出手就是大家风范

请注意,本文编写于 142 天前,最后修改于 142 天前,其中某些信息可能已经过时。

前言

最近在看侯捷的一套课程《C++面向对象开发》,刚看完第一节 introduction 之后就被疯狂圈粉。感觉侯捷所提及所重视的部分也正是我一知半解的知识盲区,我之前也写过一些 C++ 面向对象的程序,不过正如侯捷所说,我还仅仅停留于 Object-based 层面,写程序时总是在想如何封装好一个类,而不是 Object-oriented 强调类与类之间关系的设计。

这门课程分为两部分,第一部分讲 Object-based,第二部分讲 Object-oriented;第一部分又分为两部分:带指针的类的封装和不带指针类的封装。

本文将以模板库中的 complex 复数类的部分内容为核心,在分析源代码的同时,讲解一些良好的代码风格和编程习惯,比如inline 内联函数的使用、friend 友元函数的使用、函数参数及返回值何时 pass by value 何时 pass by reference 等等。

部分代码

complex.h

#ifndef __COMPLEX__
#define __COMPLEX__

class complex
{
    public:
        complex(double r = 0, double i = 0)
            : re (r), im (i)
        { }
        complex& operator += (const complex&);
        double real () const { return re; }
        double imag () const { return im; }
    private:
        double re, im;

        friend complex& __doapl (complex*, const complex&);
};

#endif
complex.cpp

#include "complex.h"
#include <iostream>

using namespace std;

inline complex& __doapl(complex* ths, const complex& r)
{
    ths->re += r.re;
    ths->im += r.im;
    return *ths;
}

inline complex& complex::operator += (const complex& r)
{
    return __doapl (this, r);
}

inline double imag (const complex& x)
{
  return x.imag ();
}

inline double real (const complex& x)
{
  return x.real ();
}

inline complex operator + (const complex& x, const complex& y)
{
    return complex (real (x) + real (y), imag (x) + imag (y));
}

inline complex operator + (const complex& x, double y)
{
    return complex (real (x) + y, imag (x));
}

inline complex operator + (double x, const complex& y)
{
    return complex (x + real (y), imag (y));
}

ostream& operator << (ostream& os, const complex& x)
{
    return os << ' (' << real (x) << "," << imag (x) << ')';
}

源码解析

complex.h

initialization list

       //程序1.1 
       complex(double r = 0, double i = 0)
            : re (r), im (i)
        { }

构造函数参数缺省,比较常规。

值得注意的是,变量的初始化尽量放在初始化列表中(initialization list)。当然,完全可以在构造函数的函数体中赋值进行初始化。不过,侯捷指出,一个对象在产生过程中分为初始化和成功产生两部分,initialization list 相当于在初始化过程中对变量赋值,而在函数体中赋值则是放弃了 initialization list 初始化这一过程,会降低效率。对于“性能榨汁机”的 C++ 语言来讲,重视每个细节效率的重要性是毫无疑问的。

参数及返回值传递方式

      //程序1.2
      complex& operator += (const complex&);

传递参数时,如果能用引用传递那么一定不要用值传递,因为值传递的过程中变量需要 copy 一份样本传入函数中,当参数很多或参数类型复杂时,会导致效率变慢。

其次,如果函数不会改变参数的值,一定要加 const 限定,在初学时养成良好的变成习惯尤为重要。

关于函数的返回值,同样是最好按引用传递,当然,有些情况无法按引用传递,这点将在2.3讲解。

其实,参数列表中还隐藏一个 this,这点将在2.2讲解。

友元函数

       //程序1.3
       friend complex& __doapl (complex*, const complex&);

我们可以看到,在 complex.h 文件的末尾定义了一个友元函数,友元函数打破了类的封装,它不是类的成员函数,却可以使用点操作符来获取类的 private 变量。当然,非友元函数也可以通过 get 函数来获取,不过速度会慢一些。

complex.cpp

 友元函数及内联函数

//程序2.1
inline complex& __doapl(complex* ths, const complex& r)
{
    ths->re += r.re;
    ths->im += r.im;
    return *ths;
}

我们首先来分析一下这个友元函数,这里有两点值得探讨:

第一这个函数将 r 的实部和虚部加到 ths 上,r 在函数体中值没用发生改变,所以使用 const 限定。

第二这个函数被设计成 inline 内联函数,我们都知道,内联函数是把代码块直接复制到函数需要调用的地方,通过省略函数调用这一过程来提高效率,那么我们为什么不将所有函数都设计成内联函数呢?其实我们的 inline 声明只是对编译器的一个建议,对于过于复杂的函数来讲,及时我们声明了 inline,编译器也会调用执行。所以,对于一些“小巧”的函数,我们尽量设计为内联函数

 隐藏的“this”

//程序2.2
inline complex& complex::operator += (const complex& r)
{
    return __doapl (this, r);
}

操作符重载作为 C++ 的特点之一,有令别的语言羡慕之处,当然也有些难以理解。

实际上,这个函数的参数还有一个隐藏的 this,这个 this 就是函数调用者

  

 不能为reference的返回值

//程序2.3
inline complex operator + (const complex& x, const complex& y)
{
    return complex (real (x) + real (y), imag (x) + imag (y));
}

inline complex operator + (const complex& x, double y)
{
    return complex (real (x) + y, imag (x));
}

inline complex operator + (double x, const complex& y)
{
    return complex (x + real (y), imag (y));
}

注意,这里函数的返回值不能返回 reference,这其实是使用临时对象(typename ()),在函数体内定义变量,然后把这个变量的引用传递出去,函数结束后变量本体死亡,传出去的引用既没有意义了。

 非成员函数的操作符重载

//程序2.4
ostream& operator << (ostream& os, const complex& x)
{
    return os << ' (' << real (x) << "," << imag (x) << ')';
}

下面讲一下为什么有的操作符重载函数定义成非成员函数

我们知道,操作符重载只作用在左边的操作数上,试想一下,如果把“<<”定义为成员函数,那每次调用岂不是要这样c1 << cout

完整代码

以上就是我在学习过程中特别注意的地方,下面给出 complex 类完整代码,只不过多了几种操作运算,大体思路完全一致。

complex.h

#ifndef __MYCOMPLEX__
#define __MYCOMPLEX__

class complex; 
complex&
  __doapl (complex* ths, const complex& r);
complex&
  __doami (complex* ths, const complex& r);
complex&
  __doaml (complex* ths, const complex& r);


class complex
{
public:
  complex (double r = 0, double i = 0): re (r), im (i) { }
  complex& operator += (const complex&);
  complex& operator -= (const complex&);
  complex& operator *= (const complex&);
  complex& operator /= (const complex&);
  double real () const { return re; }
  double imag () const { return im; }
private:
  double re, im;

  friend complex& __doapl (complex *, const complex&);
  friend complex& __doami (complex *, const complex&);
  friend complex& __doaml (complex *, const complex&);
};


inline complex&
__doapl (complex* ths, const complex& r)
{
  ths->re += r.re;
  ths->im += r.im;
  return *ths;
}
 
inline complex&
complex::operator += (const complex& r)
{
  return __doapl (this, r);
}

inline complex&
__doami (complex* ths, const complex& r)
{
  ths->re -= r.re;
  ths->im -= r.im;
  return *ths;
}
 
inline complex&
complex::operator -= (const complex& r)
{
  return __doami (this, r);
}
 
inline complex&
__doaml (complex* ths, const complex& r)
{
  double f = ths->re * r.re - ths->im * r.im;
  ths->im = ths->re * r.im + ths->im * r.re;
  ths->re = f;
  return *ths;
}

inline complex&
complex::operator *= (const complex& r)
{
  return __doaml (this, r);
}
 
inline double
imag (const complex& x)
{
  return x.imag ();
}

inline double
real (const complex& x)
{
  return x.real ();
}

inline complex
operator + (const complex& x, const complex& y)
{
  return complex (real (x) + real (y), imag (x) + imag (y));
}

inline complex
operator + (const complex& x, double y)
{
  return complex (real (x) + y, imag (x));
}

inline complex
operator + (double x, const complex& y)
{
  return complex (x + real (y), imag (y));
}

inline complex
operator - (const complex& x, const complex& y)
{
  return complex (real (x) - real (y), imag (x) - imag (y));
}

inline complex
operator - (const complex& x, double y)
{
  return complex (real (x) - y, imag (x));
}

inline complex
operator - (double x, const complex& y)
{
  return complex (x - real (y), - imag (y));
}

inline complex
operator * (const complex& x, const complex& y)
{
  return complex (real (x) * real (y) - imag (x) * imag (y),
               real (x) * imag (y) + imag (x) * real (y));
}

inline complex
operator * (const complex& x, double y)
{
  return complex (real (x) * y, imag (x) * y);
}

inline complex
operator * (double x, const complex& y)
{
  return complex (x * real (y), x * imag (y));
}

complex
operator / (const complex& x, double y)
{
  return complex (real (x) / y, imag (x) / y);
}

inline complex
operator + (const complex& x)
{
  return x;
}

inline complex
operator - (const complex& x)
{
  return complex (-real (x), -imag (x));
}

inline bool
operator == (const complex& x, const complex& y)
{
  return real (x) == real (y) && imag (x) == imag (y);
}

inline bool
operator == (const complex& x, double y)
{
  return real (x) == y && imag (x) == 0;
}

inline bool
operator == (double x, const complex& y)
{
  return x == real (y) && imag (y) == 0;
}

inline bool
operator != (const complex& x, const complex& y)
{
  return real (x) != real (y) || imag (x) != imag (y);
}

inline bool
operator != (const complex& x, double y)
{
  return real (x) != y || imag (x) != 0;
}

inline bool
operator != (double x, const complex& y)
{
  return x != real (y) || imag (y) != 0;
}

#include <cmath>

inline complex
polar (double r, double t)
{
  return complex (r * cos (t), r * sin (t));
}

inline complex
conj (const complex& x) 
{
  return complex (real (x), -imag (x));
}

inline double
norm (const complex& x)
{
  return real (x) * real (x) + imag (x) * imag (x);
}

ostream&
operator << (ostream& os, const complex& x)
{
  return os << '(' << real (x) << ',' << imag (x) << ')';
}

#endif   //__MYCOMPLEX__
complex_test.cpp

#include <iostream>
#include "complex.h"

using namespace std;

int main()
{
  complex c1(2, 1);
  complex c2(4, 0);

  cout << c1 << endl;
  cout << c2 << endl;
  
  cout << c1+c2 << endl;
  cout << c1-c2 << endl;
  cout << c1*c2 << endl;
  cout << c1 / 2 << endl;
  
  cout << conj(c1) << endl;
  cout << norm(c1) << endl;
  cout << polar(10,4) << endl;
  
  cout << (c1 += c2) << endl;
  
  cout << (c1 == c2) << endl;
  cout << (c1 != c2) << endl;
  cout << +c2 << endl;
  cout << -c2 << endl;
  
  cout << (c2 - 2) << endl;
  cout << (5 + c2) << endl;
  
  return 0;
}

总结

 
作为初学者,一定要养成良好的编程习惯,正如侯捷所说:“一出手就是大家风范”。

添加新评论