C++面向对象高级开发课程(第一周)
0. 内存分区
计算机中的内存在用于编程时,被人为的进行了分区(Segment),分为:
-“栈区”(Stack)
-“堆区”(Heap)
-全局区(静态 区,Static)
-文字常量区和程序代码区
在前面的课程中,我们主要直接涉及到的是栈区的内存,在你的程序中,函数的参数值,局部变量的值等都被存在 了“栈区”,这部分的内存,是由系统来帮助你来管理的,没有特殊情况的时候,你是不需要对其进行特别处理的。计算机中内存的分配如下图。
而针对堆区的内存,一般由程序员进行分配和释放, 使用堆内存的原因一般是
-“栈上内存比较小,不够用”
-“系统管理内存的方式死板,不方便用”
对于堆上的内存,被程序员手动分配后,若程序员 不释放就可能会出现“内存泄漏”。很多企业级的应用,都因为内存泄漏而在“正常”运转很长时间后,轰然“坍塌”。在后面的入门课程中,我们会简单的对这块 的知识进行介绍。
全局区、文字常量区和程序代码区在我们入门阶段,暂时还可以不去过多理解(甚至看不懂也无妨),只需要知道他们的大致作用即可——全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 程序结束后由系统释放;文字常量区是用于储存常量字符串的, 程序结束后由系统释放;程序代码区用于存放函数体的二进制代码。
1. C++类的写法
C++类的两大经典写法:
Class without pointer member(s) 比如 课程中定义的 Complex 类。
and
Class with pointer member(s) 比如 系统中的 string 类。
2. C++正规、大气的写法
2.1 使用“防卫式声明”
#ifndef _COMPLEX_ #define _COMPLEX_ class Complex { //some declaration } #endif
2.2 使用“构造函数初始值列表”
尽量在声明构造函数时使用“构造函数初始值列表”,因为这样在变量的声明阶段就赋予了值,有助于提高效率。
2.3 使用“const”
为了代码在以后被其他程序员复用中,在“顶层”能使用const关键字限定行为,所以我们在做“底层”工作时,要充分考虑到使用const限定函数行为。
同时注意C++语法的“顶层const”原则。
另外,在类body的定义中,也尽量使用“const”语法。
2.4 使用”引用“
尽量考虑使用“引用”这一语法。因为在传递的过程中开销比较小—— 一个4字节的指针。
但是引用也有一些缺点,比如引用的不安全性——引用的母体的值可能会通过子体被修改。所以我们可以在引用的地方考虑使用const。
3. inline
函数若在 class body 内完成定义,该函数便自动成为 inline “候选人”。
太复杂的函数无法成为“真正的” inline function,即便是用 inline 声明。
4. 参数传递
两种方式:
pass by value 程序栈内数据直接拷贝,开销比较大。
and
pass by reference (to const) “引用”语法的底层实现是指针,所以传4字节的指针开销比较小。形式美观。
inline complex& complex::operator += (const complex& r) { return __doapl (this, r); }
6. 友元
6.1 提高性能
在一些情况下,为了提高性能,请尽量通过友元实现数据存取。
因为通过使用友元语法,可以省略掉一步多余的——通过类共有方法实现存取类私有数据——操作,因此可以提高性能。但也破坏了封装。
6.2 特性
同一类的各个对象互为友元。例如下面代码:
class complex { public: Complex (double r = 0, double i = 0) : re(r), im(i) { } int func(const complex& param) { return param.re + param.im; } private: double re, im; }; int main() { complex c1(2,1 ); complex c2(); c2.func(c1); }
7. “引用”的不适用场景
inline complex& __doapl (complex* ths, const complex& r) { ths->re += r.re; //第一参数将会被改动 ths->im += r.im; //第二参数将不会被改动 return *ths; }
因为 r 是一个引用,当函数执行完成,空间被回收以后,引用这一实体将不存在。
8. 如何实现C++高性能
侯杰认为,好的C++程序应该具备
- 数据尽可能放在 private 区
- 参数尽可能使用 reference 传递
- 返回值尽可能使用 reference 传递
- class body 中尽可能使用 const 语法
- 尽可能使用 构造函数初始值列表
9. 运算符重载
9.1 成员函数
作为类的成员函数,运算符重载要写成如下的形式。“+=”运算符右边的操作数作为函数的参数被传递;“+=”运算符左边的操作数作为类内部的 this 指针。
inline complex& complex::operator += (const complex& r) { return __doapl (this, r); }
为了便于理解,可以写成如下形式(语法错误,但是便于理解)。
// 语法有错误,但是便于理解。注意第一个参数:this inline complex& complex::operator += (this, const complex& r) { return __doapl (this, r); }
9.2 非成员函数
作为非成员函数的重载,函数定义时operator关键字前面不应该有 “类名::”限定符。代码如下:
inline complex operator + (const complex& x, const complex& y) { return complex (real (x) + real (y), imag (x) + imag (y)); }
其中,return complex(......)表示声明了一个临时对象,并将其直接返回。这样的写法——返回临时对象——在STL中很常见。
如果作为“成员函数”的重载,代码如下:
inline complex& complex::operator += (const complex& r) { return __doapl (this, r); }
10. return by reference 语法分析
传递者无需知道接收者是从reference形式接收。
11. STL :: complex类中的优良写法
- initialization list (构造函数初始化列表)
- 函数 const
- 参数传递尽量 pass by reference
- 函数返回值尽量 return by reference
- 数据声明在 private
12. 关于引用的讨论
引用传递的底层实现是一根4字节的指针,所以开销相比较而言较小。
但是对于“基本内置类型”比如 bool, double, char[4]以内, int直接传递其本身与传递引用效率是一样的,这就需要程序员根据编程规范自己取舍。
13. 成员函数声明时const
在声明成员函数时加上const关键字,如代码所示:
class complex { public: double real () const { return re; } };
表示该函数不被允许修改类中的成员变量。
14. C++组成部分
C++由两部分组成:C++语言本身 和 C++标准库。
15. STL complex类代码区域划分
主要分成三部分:前置声明、类声明、类定义。
#ifndef __COMPLEX__ #define __COMPLEX__ /* 0 前置声明 */ #include <cmath> class ostream; class complex; complex& __doapl ( complex* ths, const complex& ); /* end 0 前置声明 */ /* 1 类声明 */ class complex { }; /* end 1 类声明 */ /* 2 类定义 */ complex::function.... /* end 2 类定义 */ #endif // __COMPLEX__
16. 附录
complex 类代码如下:
#ifndef __MYCOMPLEX__ #define __MYCOMPLEX__ 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 { 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&); 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&); }; 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 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); } 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); } 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) { 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, 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, 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)); } complex operator / (const complex& x, double y) { return complex (real (x) / y, imag (x) / 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) && imag (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; } #include <cmath> 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); } #endif //__MYCOMPLEX__