前言
看完侯捷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函数内部的代码一眼望去有点抽象,不妨设出两个复数,进行相乘运算:
通过该公式可以很好地理解代码部分,首先用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的模长。具体地,计算了实部的平方加上虚部的平方。
总结
还未结束,再接再厉。