Cpp pro tip 1

从源文件到可执行文件

  • 预处理(pre-processing)E
    .c.i。编译器将C源代码中的包含的头文件如stdio.h编译进来,替换宏。
  • 编译(Compiling)S
    .i.s。gcc首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,gcc把代码翻译成汇编语言。
  • 汇编(Assembling) c
    把编译阶段生成的.s文件转成二进制目标代码.o文件。
  • 链接 (Linking)
    链接到库中,生成可执行文件。

绘制这里的图(https://blog.csdn.net/Meteor_s/article/details/85208589)

这里更详细

1
2
3
4
gcc -E hello.c -o hello.i
gcc –S hello.i –o hello.s
gcc –c hello.s –o hello.o
gcc hello.o –o hello

生成.o文件,可以重定向,方便其他应用使用。

一条命令从源文件到可执行文件:

1
gcc hello.c –o hello

1 虚析构函数

补充内容(tip 7):

任何时候都应该为待多态性质的基类(父类)声明virtual析构函数。如果一个class含有任何virual函数,就一定要有一个virtual析构函数。

而如果这个class的设计不是作为基类使用,或者不准备让这个class具备多态性,就不该声明virtual析构函数。

含有多态性质的base class的设计目的是为了通过base class的接口来使用derived class。所以通常使用base class类的指针指向derived class的对象。

并不是所有的base class都是为了多态用途,比如STL容器的设计不是用作base class的。

virtual同名函数的目的是允许derived class的实现可以客制化

2 C++帮你实现了的函数

(tip 5)

当定义了一个class,又没有实现任何成员函数时,编译器会为这个class声明4个函数:

  • 一个default构造函数
  • 一个copy构造函数
  • 一个析构函数
  • 一个copy操作符

这些函数都是publicinline的。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Myclass{
public:
Myclass(){...}
Myclass(const Myclass& rhs){...}
~Myclass(){...}
Myclass& operator=(const Myclass& rhs){...}
};

// 实例化这个class,并使用
Myclass e1; //default 构造函数
Myclass e2(e1); //copy构造函数
e2=e1; //copy符号操作符
//析构函数

其中

  1. 编译器给出的析构函数是个non-virtual函数,除非这个class的base class含有virtual析构函数。此时这个class的虚拟性来自其base class
  2. 对于构造函数,如果我声明了一个构造函数,那么编译器不再创建default构造函数
  3. copy构造函数,和copy操作符只是单纯的将来源对象的每一个non-static成员变量拷贝到目标对象。所以:
    1
    Myclass e2(e1)
    是使用e1的成员变量来初始化e2的成员变量。

3 为classes实现赋值操作符

使用赋值操作时,通常可以写成如下形式:

1
2
int x, y, z;
x=y=z=2;

上述实际上的赋值行为是

1
x=(y=(z=2));

其行为是将2赋值给z,再将z赋值给y,最后将y赋值给x

为了实现上述的连锁赋值,赋值操作符必须返回一个引用指向操作符的左侧:

1
2
3
4
5
6
7
class Myclass{
public:
Myclass& operator=(const Myclass& rhs){
...
return* this;
}
};

同样的上述的形式也适用于所有相关的操作符,如+=-=×=

这是一条协议,被所有内置类型和标准库函数共同遵守,所以,遵守它吧。

4 将成员变量声明为private

为什么要把classes的成员变量声明为private:因为这体现了封装

类成员变量应该只被这个类的成员方法可见,protected成员变量同public成员变量一样缺乏封装性

从封装的角度讲,访问权限只有提供封装(private)和不提供封装两种。

5 使用non-member non-friend函数而非再定义一个member函数

假设由一个类含有若干个清理函数,只是清理的对象不同:

1
2
3
4
5
6
7
8
class WebBrowser{
public:
...
void clearHistory();
void clearCache();
void clearCookies();
...
};

其实很多时候,用户想要一次性执行这些动作,如何一次性完成,两种方案:

  1. 给这个class中添加一个member函数clearAll(),它调用上述三个清理函数:

    1
    2
    3
    4
    5
    6
    class WebBrowser{
    public:
    ...
    void clearAll();
    ...
    };
  2. 使用一个non-member,non-friend函数:

    1
    2
    3
    4
    5
    void clearBrowser(WebBrowser& wb){
    wb.clearHistory();
    wb.clearCache();
    wb.clearCookies();
    }

    pro tip告诉你使用后者。后者更体系那封装性。后者保护了类的包裹性,进而有较低的编译依赖,增强了类的可延展性

对于封装性,可以访问private成员变量的只有member函数friend函数。上述两种方式提供了相同的功能,而后者提供了较强的封装性,因为它不能增加访问private变量的能力

P.S. friend函数和member函数对于类private成员的访问能力相同。

在C++ 中自然的做法是将类WebBrowser和函数clearBrowser()放置于同一个namespace中:

1
2
3
4
5
namespace Web{
class WebBrowser{...};
void clearBrowser(WebBrowser& wb);
...
}

namespace 可以跨越多个源码文件,而class不能。就是说相同的namespace中可以有多个头文件,这正是C++标准库的组织方式:std::vector, std::sort, std::map, … C++ 将不同的部分,不同container放在不同的头文件中,每个声明了std的某部分功能,这样当使用vector,时只用include <vector>,而不必将所有的std内容include进来。就是说,用户只用对所用的部分进行编译。而class必须整体定义,不能分割。

想要扩充这个namespace的能力,只需要在这个namespace中添加更多的non-member,non-friend函数。即增强了功能,又没有破坏封装性,由没有增加编译依赖。

敲黑板两个角度:

  • 增强封装性
  • 减小编译依赖