一、头文件的防御式声明(video2)

#ifndef __COMPLEX__
#define __COMPLEX__ //内容 #endif

二、初步感受模板(video2)

定义的时候:

//复数的实部和虚部可能是int,float等不同类型,使用模板来统一兼容他们
template <typename T> class Complex{
public:
Complex(T re, T im) {} private:
T re, im;
};

使用的时候:

Complex<int>(, );
Complex<float>(5.0, 4.0);

三、Inline方法(video3)

当方法在class定义的body中实现,就是Inline方法。

class Complex{
public:
Complex(double re, double im) {} //Inline方法
double real(){ return re}
double imag(){ return im} private:
double re, im;
};

理论上来说,将所有的方法都在Body中实现,全部变为Inline方法是最好的,因为Inline方法的执行速度最快。

但是对于编译器来说,是否将该方法变为真正的Inline方法,要看方法是否复杂,编译器是否有能力将其变为Inline方法。

四、访问级别public和private(video3)

class Complex{
//一般将一些提供给外部访问的方法写在public中
public:
Complex(double re, double im) {} //Inline方法
double real(){ return re}
double imag(){ return im}
//一般将一些处理内部事务的方法和数据变量等写在private中
private:
double re, im;
//public和private可以交错着写,不用死板的写为两段
private:
//Todo
public:
//Todo
};

五、构造函数(video3)

class Complex {
public:
//构造函数
Complex(double r = , double i = ) :re(r), im(i) {} double real() { return re; }
double imag() { return im; }
private:
double re, im;
};

构造函数中得re(r),im(i)相当于把实参赋值给变量re和im,这是构造函数特有的特性,其他普通函数是没有的。

建议使用这种方式来赋值,虽然也可以在函数体中使用赋值的方式来传入参数,但不建议。

实例化方式:

//生成Compelx对象,返回对象
Complex c = Complex(5.4, 4.5);
//同上
Complex c2(2.1, 3.9);
//生成Compelx对象,返回对象的指针
Complex *c3 = new Complex(5.6, 5.6);

六、函数的重载(video3)

class Complex {
public:
//两个构造函数实现不同方式创建对象
Complex(double r, double i) :re(r), im(i) {}
Complex() :re(), im() {}
//函数重载,两个real函数虽然名字相同,但参数和返回值都不同
double real() { return re; }
void real(double r) { re = r; } double imag() { return im; }
private:
double re, im;
};

函数对于编译器来说,名字回转换成人类无法阅读的形式,例如 ?real@Complex@@QBENXZ

所以,当函数参数、返回值等不同的时候,实际上对于编译器来说是两个不同的函数。

七、函数中使用const(video4)

class Complex {
public:
Complex(double r, double i) :re(r), im(i) {} double real() const { return re; }
void real(double r) { re = r; }
private:
double re, im;
};

在real()方法中使用了const,表示该方法内部不会对数据进行修改。如果不使用const,则表示在方法内部可能对数据进行修改。

在使用的时候:

//对象c2被定义为const,即不可修改
const Complex c2(2.1, 3.9);
cout << c2.real() << endl; //调用成功
c2.real(5.0); //调用失败

c2被定义为const,即值不可修改。

那么使用c2.real()打印re的值时,由于定义real()方法时指定方法为const,所以编译器认为该方法不会对数据进行修改,是安全的,调用成功。

使用c2.real(5.0)调用另外一个赋值的real方法,在定义时未指定为const,所以编译器认为该方法可能修改数据,而c2又是const的,所以产生冲突,调用失败。

结论:当我们明确一个方法不会对数据进行修改时,尽量将方法指定为const的。

这里补充一个指针使用const:(2020-3-3)

const int *p = ;  # 表示值不能被修改
*p = ; # 错误
int a = ;
p = &a; # 正确 int * const p = ; # 表示p指针不能重新定向
*p = ; # 正确
int a = ;
p = &a; # 错误

如果const在"*"的左边,则"左定值"。如果const在"*"的右边,则"右定向"

八、参数传递(传值或引用)和值的返回(返回值或引用)(video4)

传值:pass by value

传引用:Pass by reference

传引用但不能修改:Pass by reference with const

尽量使用传引用的方式才传递参数,因为引用的底层是指针(C语言中指针作为参数传递类似),传递的数据大小为4个字节,速度会很快。

同样,值的返回也尽量返回引用(如果可以的话)。

class Complex {
public:
Complex(double r, double i) :re(r), im(i) {}
//参数为一个Complex对象的引用,并且是const的,表示不能修改
void add(const Complex& c) {
this->re += c.real();
this->im += c.imag();
c.real(9.0); //报错,由于传进来的c的引用是const的,不能修改
} double real() const { return re; }
void real(double r) { re = r; }
double imag() const { return im; }
private:
double re, im;
};

使用:

Complex c1(2.1, 3.9);
Complex c2(4.5, 3.6);
//将c2的引用传入c1.add(),但是add不能c2造成影响
c1.add(c2);
cout << c1.real() << endl;

注意:当使用引用作为参数传递时,如果形参指明是const的,则在函数内部不能对其进行数据修改,如果进行修改,编译器报错。如果未指明const,则函数对其进行修改,会影响传入对象的本体。

九、友元friend(video4)

使用关键字friend将方法设置为友元方法,友元方法可以直接读取私有数据。

class Complex {
public:
Complex(double r, double i) :re(r), im(i) {} double real() const { return re; }
void real(double r) { re = r; }
double imag() const { return im; }
private:
double re, im;
//将friend_func函数定义为该类的友元,友元函数可以直接访问私有数据
friend void friend_func(const Complex& c);
}; //使用友元直接访问数据
inline void friend_func(const Complex& c){
cout << c.re << endl;
}

使用:

Complex c1(2.1, 3.9);
//使用友元直接访问数据
friend_func(c1);

另一种友元的场景:

class Complex {
public:
Complex(double r, double i) :re(r), im(i) {} double real() const { return re; }
void real(double r) { re = r; }
double imag() const { return im; }
//该方法传入另一个Complex,但是在函数内部可以直接访问私有数据
void func(const Complex& c) {
cout << c.re << endl;
cout << c.im << endl;
}
private:
double re, im;
};

使用:

Complex c1(2.1, 3.9);
//使用友元直接访问数据
Complex c2(4.5, 4.3);
//将c2传入c1.func()中
c1.func(c2);

为什么会有这种情况呢,按理说应该无法直接访问c2的私有数据的。

解释:用一句话解释,同一class所衍生出来的对象互为友元(friend)。

十、返回值什么时候返回引用(video4)

inline Complex& complex_add(Complex& c1, const Complex& c2) {
//c1的re值发生了改变,c2的re值未改变
c1.real(c2.real());
//c1是已经存在的空间,所以可以返回引用
return c1;
} inline Complex complex_add2(const Complex& c1, const Complex& c2) {
//重新生成一个Complex对象
Complex c_res = Complex(, );
c_res.real(c1.real() + c2.real());
c_res.imag(c1.imag());
//由于c_res是一个local变量,函数结束,它的生命周期就结束了,所以不能使用引用返回,而必须返回值
return c_res;
}

使用:

Complex c1(2.1, 3.9);
Complex c2(4.5, 4.3);
//使用引用接收
Complex& rp = complex_add(c1, c2);
//使用值接收
Complex c = complex_add2(c1, c2);
cout << rp.real() << endl;
cout << c.real() << endl;

结论:当返回的东西是已经存在的,即已经在外部分配了地址空间,则直接返回引用,效率会更高。如果返回的东西是新建的局部变量,那么必须以值的形式返回,否则函数结束后,该变量会被回收。

十一、操作符重载(成员函数)(video5)

当我们定义的类之间需要进行某些运算,但普通的操作符并不支持,那么我们可以自定义这些操作符。例如复数相加,我们可以自己定义“+=”的操作。

class Complex {
public:
Complex(double r, double i) :re(r), im(i) {} double real() const { return re; }
void real(double r) { re = r; }
double imag() const { return im; }
void imag(double i) { im = i; }
//重载+=操作符,实现复数相加
Complex& operator += (const Complex& c) {
return __doapl(this, c);
} private:
double re, im;
//将+=的实现细节抽取出来单独一个方法实现
inline Complex& __doapl(Complex* ths, const Complex& c) {
ths->re += c.re;
ths->im += c.im;
return *ths;
}
};

对于成员函数 Complex& operator += (const Complex& c);实际上有有两个参数(this, const Complex& c),this是一个指针,指向调用该函数的对象。

所以对于__doapl函数来说,就有两个参数,是将c2加到ths上去,并返回ths。

使用:

Complex c1(2.1, 3.9);
Complex c2(4.5, 4.3);
c1 += c2;
cout << c1.real() << endl;

__doapl中得ths就是c1。

引申思考:当我们仅执行 c1+=c2 时,我们的 Complex& operator += (const Complex& c);函数的返回值是可以用void代替的,已经完成了加法,无需返回Complex&。

但是当我们 c3+=c1+=c2 这样连续使用呢?当c1+=c2完成操作后,并未返回Complex&,则c3就无法进行+=操作了。

十二、操作符重载(非成员函数)(video5)

//两个复数相加,实部加实部,虚部加虚部
inline Complex operator + (const Complex& c1,const Complex& c2) {
return Complex(c1.real() + c2.real(), c1.imag() + c2.imag());
}
//复数加一个double数,将double数加到实部
inline Complex operator + (const Complex& c1, const double c2) {
return Complex(c1.real() + c2, c1.imag());
}
//一个double数加复数,将double数加到实部
inline Complex operator + (const double c1, const Complex& c2) {
return Complex(c2.real() + c1, c2.imag());
}

使用:

Complex c1(2.1, 3.9);
Complex c2 = c1 + 5.2;
Complex c3 = 6.2 + c1;
cout << c2.real() << endl; //输出7.3
cout << c3.real() << endl; //输出8.3

十三、将+、-号重载为复数的正负号(video5)

//+作为正号
inline Complex operator + (Complex& x) {
return x;
}
//-作为负号
inline Complex operator - (Complex& x) {
return Complex(-x.real(), -x.imag());
}

使用:

Complex c1(2.1, 3.9);
cout << (-c1).real() << endl;
cout << (+c1).real() << endl;

十四、重载<<操作符输出复数到屏幕(video5)

//重载<<,让cout可以直接输出复数
inline ostream& operator << (ostream& os,const Complex& x) {
return os << '(' << x.real() << ',' << x.imag() << ')';
}

当然,该重载函数也可以作为成员函数写到Complex类的定义中去。

使用:

Complex c1(2.1, 3.9);
cout << c1 << endl;

1-6 video总结:

1.习惯使用初始化列表,构造函数中,Complex(double r = 0, double i = 0) : re(r), im(i) {}

2.成员函数是否指明const,取决于该方法是否修改数据,若不修改数据,则尽可能指明const。因为可能会影响后续调用(特别是合作开发,其他人使用该接口)。当对象定义为const,则该对象无法调用非const成员方法。

3.参数传递和结果返回,在允许的情况下,尽量使用传递和返回引用。效率更高。

4.数据都放到private下,对外提供的函数接口放在public,内部自己用的方法放到private。public和private不是强制写成两段的,可以分成多段。

5.同一个类衍生出的对象之间都互为友元,可以直接调用对方的私有变量。

6.操作符重载都是作用在左边的变量上的(即调用该操作符函数的对象),注意连续调用的情况(返回不要为void),操作符重载可以是成员方法,也可以是全局方法。

7.成员方法在类定义中实现,就可能成为inline方法(视编译器的决定)。如果是全局函数,则需要在函数最前面加inline关键字,让编译器尽量的将其变为inline函数。

最新文章

  1. angular2系列教程(十)两种启动方法、两个路由服务、引用类型和单例模式的妙用
  2. python 数据类型 ----字典
  3. 【CentOS】LAMP相关2
  4. shell编程之变量
  5. No module named yum错误的解决办法
  6. DOM解析XML练习
  7. YUM软件管理
  8. RedirectFromLoginPage和FormsAuthenticationTicket的区别
  9. Swift - 17 - 数组的初始化
  10. Centos6.5安装与配置Tomcat-8的方法
  11. final 关键字与安全发布 多线程中篇(十三)
  12. Intellij Idea 2016创建web项目
  13. Halcon 17与 c# 混合编程
  14. PID control
  15. Python - 去除list中的空字符
  16. Luogu4774 NOI2018 屠龙勇士 ExCRT
  17. OO课程第四次总结
  18. 23.纯 CSS 创作一个菜单反色填充特效
  19. linux 源码编译php的参数
  20. UIWebView 获取网页标题

热门文章

  1. WPF中使用TranslateTransform3D修改CAD的3D旋转中心
  2. HTML5 随手记(4)
  3. 常见信号的模拟仿真(matlab)(spike signal)
  4. ASP .NET 返回Json操作结果
  5. 将QuickReport报表保存为图片(使用TMetaFile和TMetafileCanvas)
  6. PMC另类阐述
  7. SQL Server 游标运用:查看所有数据库所有表大小信息(Sizes of All Tables in All Database)
  8. 【备忘】C#语言基础-1
  9. JavaScript API for Office Outlook Add-in - “一页纸文档“
  10. How to Capture the Integer-Divide-By-Zero Error in C++(提前定义信号)