作为一个初学者,看侯捷老师的视频学习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.re和r.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
-
complex& x是一个引用(reference),而complex* y是一个指针(pointer)。- 引用是对象的别名,通过引用可以直接访问原始对象。引用必须在声明时初始化,并且不能重新绑定到其他对象。
- 指针是存储内存地址的变量,可以通过指针间接地访问目标对象。指针可以重新指向其他对象。
-
对象的操作方式不同:
- 通过引用
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&,是一个引用,其实这并不矛盾。因为传递者无需知道接收者是以引用方式接受还是值方式接收