以下主要是关于候捷老师的c++面向对象开发的笔记。通过实现complex的基本功能,可以实现一个单一的不带指针的类的声明和定义,对面向对象的思想有了初步的了解。
头文件的基本结构
// 防卫式声明
#ifndef _COMPLEX_
#define _COMPLEX_
// 前置声明
...
// 类 - 声明
...
// 类 - 定义
...
// 非成员函数的定义, 全局函数
#endif // !_COMPLEX_
Header的防卫式声明
#ifndef _COMPLEX_ // 第一次include时定义
#define _COMPLEX_ // 第二次则跳过
#endif // !_COMPLEX_
类的声明
class complex {
public: // 希望被外界使用的函数
complex(double r = 0, double i = 0) : re(r), im(i) {}
complex& operator += (const complex&);
double real() const { return re; } // 函数如果直接在class body内定义完成, 自动成为inline候选人
double imag() const { return im; }
private: // 只有class里可以看到, 希望将数据封装
double re, im;
friend complex& _doapl(complex*, const complex&);
};
public与private
希望被外界使用的函数应放在public内声明;不希望被外界“看见”的函数和变量应在private内声明,只有在class里可以看见,起到封装的作用。
{ // public和private成员access level的不同
complex c1(2, 1);
// ×
// cout << c1.re << c1.im;
// √
cout << c1.real() << c1.imag();
}
inline函数
将函数声明为inline,表示要求编译器在每个函数调用点上,将函数的内容展开。inline仅仅是一种请求,决定权在编译器手上。
默认参数,default argument
关于默认参数的设置有两个不直观的规则:
- 默认值的解析是从最右边开始的,具有默认值的参数应在右边;
- 默认值只能指定一次,却不能在声明和定义两处都指定。
构造函数, ctor
构造函数就是用来初始化对象的成员变量的,有以下特点:
- ctor的名字一定要与class的名字相同
- ctor没有返回类型
- 创建对象时,会自动调用ctor
- ctor可以有很多个 ,可以overload
比较以下两种构造方式的差异
// 1
complex (double r = 0, double i = 0) : re(r), im(r) {}
// 2
complex (double r = 0, double i = 0) { re = r; im = i; }
构造方式1中的变量经过了初始化的过程,而方式2意味着变量放弃了初始化阶段选择直接赋值,这样做的效率比较差。编写代码时应该采用前者,是一种大气、正规的写法。
思考这样一种构造方式
// ctor
complex () : re(0), im(0) {}
// 实例化对象
complex c1;
complex c2();
运行程序会报错,原因在于构造函数没有提高默认实参,default argument
把ctor放在private区域
这样就意味着不能被外界调用,也就无法完成初始化。
// 下面的实例化均会报错
complex c1(2, 1);
complex c2;
常量成员函数,const member functions
在成员函数声明之后加上const,就成了常量成员函数,确保函数内变量不会被修改。const对象只能调用const成员函数。在设计class时,如果函数内变量不会被修改,最好加上一个const。
// 1
complex c1(2, 1);
cout << c1.real() << c1.imag();
// 2
const complex c2(2, 1);
cout << c2.real() << c2.imag();
参数传递
传值就是说把数据复制一份再传递, 速度更慢。
引用就是变量的别名,传引用不会涉及再分配内存,直接对原始数据进行处理,速度更快。
除少部分情况外,最好所有的参数传递都采用传引用方式。
引用和指针
指针可能不会指向某个实际对象,所以在使用指针时必须要判断是否为null。引用就是别名,不会分配内存,肯定会代表某个对象,不需要做此检查。
返回值传递
可以把传递理解为赋值,返回值传递即为用返回值赋给返回类型对应的对象。
由于函数内定义的对象只存在于函数执行期间,如果将这些局部对象的地址返回,就会导致运行错误。
提供者不需要知道接受者是否以reference形式接收
思考下面的返回值类型能否为void?
inline complex& _doapl(complex* ths, const complex &r){
...
return *ths;
}
inline complex& complex::operator += (const complex &r){
return _doapl(this, r);
}
考虑到连串赋值操作时,这里的返回值类型一定要为complex&。
this指针
所有的成员函数均含有一个隐藏参数,this pointer,指向调用者本身。
this指针不能在参数列表中写出,在成员函数内部可以直接使用
友元,friend
友元提供了一种普通函数访问另一个类中的私有成员的方法
相同class的各个成员互为友元
操作符重载,operator overloading
编译器根据参数类型和数量进行判断,而不是返回类型
inline complex operator + (const complex& x, const complex& y) // 非成员函数, 全局global
{
return complex(x.real() + y.real(),
x.imag() + y.imag());
}
inline complex operator + (const complex& x, double y)
{
return complex(x.real() + y, x.imag());
}
inline complex operator + (double x, const complex& y)
{
return complex(x + y.real(), y.imag());
}
complex源码
#pragma once
#ifndef _COMPLEX_
#define _COMPLEX_
#include <math.h>
class complex;
complex& _doapl(complex* ths, const complex& r);
class complex {
public: // 希望被外界使用的函数
complex(double r = 0, double i = 0) : re(r), im(i) {}
complex& operator += (const complex&);
double real() const { return re; } // 函数如果直接在class body内定义完成, 自动成为inline候选人
double imag() const { return im; }
private: // 只有class里可以看到, 希望将数据封装
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;
}
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) // 非成员函数, 全局global
{
return complex(x.real() + y.real(),
x.imag() + y.imag());
}
inline complex operator + (const complex& x, double y)
{
return complex(x.real() + y, x.imag());
}
inline complex operator + (double x, const complex& y)
{
return complex(x + y.real(), y.imag());
}
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)
{
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) && 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;
}
inline complex conj(const complex& x)
{
return complex(real(x), -imag(x));
}
inline double modulus(const complex& x)
{
return sqrt(real(x) * real(x) + imag(x) * imag(x));
}
#endif // !_COMPLEX_