跟着侯捷老师学C++☛从complex函数定义中学到了什么

153 阅读4分钟

作为一个初学者,看侯捷老师的视频学习C++
第一个demo讲解,很有意思

代码

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

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

分析

class属性

属性一般设为private

private:
  double re, im;

默认属性值

在构造函数中有代码double r = 0, double i = 0,表示若不传值,则默认赋值0

初始化属性

在构造函数中有代码 re(r), im(i),这就是使用了构造函数初始化列表来初始化成员。
也可以在构造函数体中对成员赋值:

complex (double r = 0, double i = 0) { 
  re = r;
  im = i
}

推荐使用构造函数初始化列表来初始化成员,因为代码更简洁,执行效率更高

pass by value vs. pass by reference

代码 complex& operator += (const complex&); 中入参就是一个complex的引用。
一般建议,能传引用就传引用,因为引用值比较小,是4个字节。而value的值是整包传,value本身多大,传过去就多大,效率低。

传入值、返回值,尽量都pass by reference

const

常量对象

代码 complex& operator += (const complex&); 中 const 表明传入的值 complex引用,它在这个函数中不会被改变,是个常量对象

常量成员函数

代码double real () const { return re; } 表明 real 函数并不会改变class的属性,所以方法加了 const,使其成为常量成员函数

示例

class complex {
  ...
  double real () { return re; }
}

// 调用
const complex c1(2,1);
cout << c1.real();

会编译报错,因为使用者说"我传入的c1是不能改变的",但定义者real的函数说"这个函数可能会改变传入的值",这是矛盾的,所以编译器会报错!

friend

代码friend complex& __doapl (complex *, const complex&); 中的friend参数表示这个 __doapl方法是个friend方法,在friend的方法里,可以被friends调用private属性。

相同class的各个objects互为友元

在代码__doapl函数的使用中,发现入参 r 是一个complex,和当前的class相同,所以互为friends。而__doapl又是一个friend函数,所以在函数体里,直接获取了r的private属性r.rer.im

代码

class 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

告诉编译器,尽量把这个函数编成内联函数

全局函数 VS 成员函数

如果是 类名::方法名,这就是成员函数,如上面的operator,否则就是全局函数如__doapl

this

类函数里默认有一个指向自己的指针this,代码 return __doapl (this, r); 中就直接使用了

引用与指针

complex& x 
complex* y 
  1. complex& x 是一个引用(reference),而 complex* y 是一个指针(pointer)。

    • 引用是对象的别名,通过引用可以直接访问原始对象。引用必须在声明时初始化,并且不能重新绑定到其他对象。
    • 指针是存储内存地址的变量,可以通过指针间接地访问目标对象。指针可以重新指向其他对象。
  2. 对象的操作方式不同:

    • 通过引用 x 可以直接操作原始对象,就像操作对象本身一样。例如,x = complex(3, 4) 将直接修改原始对象。
    • 通过指针 y 需要使用间接操作符 * 和箭头操作符 -> 来访问目标对象。例如,*y = complex(3, 4) 修改目标对象,y->real() 调用目标对象的成员函数。

返回引用

在函数__doapl返回了引用 complex&,原因在于返回的对象本来就已经存在了(这里就是入参ths),我们就是要改这个对象,所以这里可以返回引用

代码

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

分析

临时对象

该方法中返回的不是引用而是值complex,因为返回的是一个临时对象。

我们传入两个complex类型的引用x与y,但这个方法中我们并不会改x、y。这时候我们要返回一个新complex,所以需要创建一个临时对象返回回去,也就是上面的complex (real (x) + real (y), imag (x) + imag (y))

而函数执行完后,这里的临时对象就会被销毁掉。所以,如果这里返回引用,会造成内存泄漏问题!

代码

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

分析

我们看到,这里做了操作符+=的重载,接收的是一个complex的引用r,然后把自己的指针this一起传给了方法__doapl。__doapl方法里,操作结束后,返回的是 *ths。ths本身是指针,所以*ths是指针所指的东西,一个complex。但返回又要返回complex&,是一个引用,其实这并不矛盾。因为传递者无需知道接收者是以引用方式接受还是值方式接收