侯捷 面向对象高级编程(上)第一章源码学习

前言

看完侯捷C++面向对象高级编程(上)的前五节课,侯老师提供了2份源码:complex.h和complex_test.cpp。此文章解读complex.h,而complex_test.cpp相当于提供了测试用例。

1、complex.h代码前半段

1.1防卫式声明和构造函数

在课程中,侯老师首先给出了C++程序的一般实例框架:

// my_header.h

#ifndef MY_HEADER_H // 如果 MY_HEADER_H 这个宏没有被定义
#define MY_HEADER_H // 定义 MY_HEADER_H 这个宏

// 此处放置头文件的内容

#endif // 结束防卫式声明

在头文件中,经常使用这种防卫式声明来避免头文件的多次包含,避免重复定义。

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是对类进行前向声明,但是并没有提供具体的定义。这通常在头文件中使用,以便在文件中引用这个类,而不需要包含完整的类定义。前向声明的一个典型应用场景是解决循环依赖的问题。例如,如果两个类相互引用对方,可以使用前向声明来解决:

// File A.h
class B;

class A {
    B* ptrB;
    // ...
};

// File B.h
class A;

class B {
    A* ptrA;
    // ...
};

在上述例子中,A和B相互引用,但是通过前向声明,它们可以正确地编译。

声明了__doapl、__doami、__doaml三个函数,用来对复数进行具体的操作。

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&);

  // 函数内部没有修改成员数据,所以加上const
  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&);
};

complex类是创建复数,并对其进行操作的类。在private中定义的两个成员参数re, im分别表示复数中的实部和虚部。

构造函数(constructor)的作用就是创建一个与class相同名称、相同类型的实例。*记忆tips:不可能在一个程序中的任何地方调用构造函数,而是只有在创建类的实例时,构造函数会自然而然地被调用。

以下是上述代码中构造函数的两种写法:

// 方法一
complex (double r = 0, double i = 0): re (r), im (i) { }

// 方法二
complex(double r = 0, double i = 0){re = r; im = i;}

编写complex类的构造函数时,侯老师说方法一的写法会让人觉得你是受过专业训练的程序员,代码风格具有大家风范。方法一的正式名称是“初始化列表”,比在构造函数内部对成员进行赋值更高效。

  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; }

前四句代码是对 '+=', '-=', '*=', '/='运算符进行重载。

  • complex& 是返回类型,表示该函数将返回一个对complex对象的引用。

  • operator += 是一个运算符重载函数,它重载了+= 运算符。+=运算符通常用于对对象进行自增操作。

  • (const complex&) 是函数的参数列表,表示它接受一个常量引用作为参数。这里的const complex&表示传递进来的参数是一个复数对象,并且在函数内部将被视为一个不可修改的对象。

后两句代码的作用分别是返回复数的实部与虚部,且函数被标记为const,表示它不会修改任何成员变量。

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

将函数 __doapl、__doami、__doaml设置为complex类的友元函数,它们可以访问complex类的私有成员。特别的“相同class的各个objects互为friends(友元)”。

1.2__doapl函数和 '+='运算符重载(成员函数,有ths)
inline complex& __doapl (complex* ths, const complex& r)
{
  ths->re += r.re;
  ths->im += r.im;
  return *ths;
}

inline是函数内联关键字,这个关键字告诉编译器,这个函数可能会被多次调用,因此可以考虑在调用点直接展开这个函数的代码,以提高性能。

所有成员函数都有一个隐藏的参数this,指向调用者。__doapl,do assignment plus,标准库的代码。

__doapl函数接受两个参数,ths是一个指向complex类型的指针,表示被操作的复数对象,r是一个常量引用,表示另外一个复数对象。

__doapl函数的功能是:

                                    r的实部 + ths的实部,并将结果赋值给ths的实部

                                    r的虚部 + ths的虚部,并将结果赋值给ths的虚部

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

因为普通的 '+=' 运算符无法运用在复数上,所以要对 '+=' 运算符进行重载,return时会调用__doapl函数,传递了两个参数:一个是指向当前对象的指针'this',另一个是右侧的复数对象r。函数会执行赋值加法操作,然后返回结果。

1.3__doami函数和 '-=' 运算符重载
// __doami函数
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);
}

和__doapl函数相同,只不过将 '+=' 运算符换成了 '-='。

1.4__doaml函数和 '*=' 运算符重载
// __doaml函数
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);
}

__doaml函数内部的代码一眼望去有点抽象,不妨设出两个复数,进行相乘运算:

a+bi , c + di

(a+bi)*(c+di)=ac+adi+bci-bd=ac-bd+(ad+bc)i

通过该公式可以很好地理解代码部分,首先用double f来接受两个复数相乘后的实部(ac-bd),其次使用ths所指虚部来接受两个复数相乘后的虚部((ad+bc)i)。

2、complex.h代码后半段

2.1应对不同需求的运算符重载
inline double imag (const complex& x)
{
  return x.imag ();
}

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

 首先设计提取复数实部、虚部的函数imag、real。

2.1.1 '+'运算符的重载(非成员函数,无ths)
// 不将operator+设计为类的成员函数,就是因为加法的左右值类型是多样变化的
// + 运算符的重载一
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));
}

+运算符的重载一:传入的参数类型为(复数, 复数),表示两个复数的相加,即实部+实部, 虚部+虚部。

+运算符的重载二:传入的参数类型为(复数, double型实数),表示复数的实部+实数,虚部保持不变。

+运算符的重载三:传入的参数类型为(double型实数, 复数),表示实数+复数的实部,虚部保持不变。

在课程中,侯老师讲到这部分内容的时候,强调了返回值传递:return by value vs return by reference。上面的三个关于 '+'运算符重载的 函数绝不可能return by reference,因为它们返回的必定是个local object,因为返回的结果仅仅是两个对象相加后的结果,不像 '+='运算符,可以将运算结果存入 a += b中的a,相加得到的新值会寻找另一块空间去存储,自然不能return by reference。

2.1.2 '-'运算符的重载
// - 运算符的重载一
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));
}

- 运算符的重载一:传入的参数类型为(复数, 复数),表示两个复数的相减,即实部-实部, 虚部-虚部。

- 运算符的重载二:传入的参数类型为(复数, double型实数),表示复数的实部-实数,虚部保持不变。

- 运算符的重载三:传入的参数类型为(double型实数, 复数),表示实数-复数的实部,0-复数的虚部。

2.1.3 '*'运算符的重载
// * 运算符的重载一
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));
}

* 运算符的重载一:传入的参数类型为(复数, 复数),表示两个复数的相乘,即ac-bd+(ad+bc)i

* 运算符的重载二:传入的参数类型为(复数, double型实数),表示复数的实部*实数,虚部*实数。

* 运算符的重载三:传入的参数类型为(double型实数, 复数),表示实数*复数的实部,实数*虚部。

2.1.4'/'运算符的重载
complex operator / (const complex& x, double y)
{
  return complex (real (x) / y, imag (x) / y);
}

/ 运算符的重载:传入的参数类型为(复数,double型实数),表示复数的实部/实数,虚部/实数。

2.1.5 取正、取逆运算符重载
inline complex operator + (const complex& x)
{
  return x;
}

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

取正运算符的重载:传入的参数类型为(复数),返回原数。

取逆运算符的重载:传入的参数类型为(复数),返回取逆后的复数(-a + -bi)。

2.1.6 '=='运算符重载
// == 运算符的重载一
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;
}

== 运算符的重载一:传入的参数类型为(复数, 复数),分别判断两个复数的实部、虚部是否都相等,若相等则返回True。

== 运算符的重载二:传入的参数类型为(复数, double型实数),判断复数的实部是否等于该实数、且虚部是否为0,若满足则返回True。

== 运算符的重载三:传入的参数类型为(double型实数, 复数),判断实数是否等于复数的实部,且复数的虚部是否为0,若满足则返回True。

2.1.7'!='运算符重载
// !=运算符的重载一
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;
}

!= 运算符的重载一:传入的参数类型为(复数, 复数),分别判断两个复数的实部、虚部是否都不相等,若不相等则返回True。

!= 运算符的重载二:传入的参数类型为(复数, double型实数),判断复数的实部是否不等于该实数、或者虚部是否不为0,若满足则返回True。

!= 运算符的重载三:传入的参数类型为(double型实数, 复数),判断实数是否不等于复数的实部,或者复数的虚部是否不为0,若满足则返回True。

2.2 其他功能函数
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);
}

 polar函数:接受的参数有两个,r表示极坐标的半径,t表示极坐标的角度,函数作用是接受一个极坐标形式的复数参数,然后返回一个对应的复数对象。代码中返回了一个complex类型的对象,它是根据极坐标形式(r, t)计算得到的。具体地,使用了三角函数cos 和sin 分别计算了实部和虚部。

conj函数:表示这个函数用于计算复数的共轭,这行代码返回了一个complex类型的对象,它是根据输入复数对象x计算得到的共轭。具体地,使用了real(x)获取实部, -imag(x)获取虚部。

norm函数:作用是接受一个复数对象x,然后计算并返回其模长,即实部的平方加上虚部的平方的和。这行代码返回了一个double类型的值,它表示了输入复数对象x的模长。具体地,计算了实部的平方加上虚部的平方。


总结

还未结束,再接再厉。


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值