预备知识:C/C++编译过程
step1:预处理
——由预处理器(一个简单的程序)将程序员通过预处理器指令(参考博客:C/C++预处理器指令C/C++预处理指令)定义好的模式代替源代码中的模式。
step2:第一遍编译
——对预处理过的代码进行语法分析,编译器把源代码分解成小的单元并把它们按树形结构组织起来。比如表达式“A+B”,被分解成语法分析树的叶子节点“A”、“B”和“+”。类型检查也在这个阶段进行,由于是在编译阶段而不是在运行时进行内存检查,所以也称为“静态类型检查”。
step3:第二遍编译
——有代码生成器遍历语法分析树,把树的每个节点转化成汇编语言(再由汇编器对其进行汇编成机器码)或者直接转换成机器代码,生成目标模块(.o或.obj文件)。
step4:连接
——将汇编生成的目标代码进行连接,生成可执行文件。
1. 内联函数与宏定义
在C中,常用预处理语句#define来代替一个函数定义。例如:
#define MAX(a,b) ((a)>(b)?(a):(b))
该语句使得程序中每个出现MAX(a,b)函数调用的地方都被宏定义中后面的表达式((a)>(b)?(a):(b))所替换。
宏定义语句的书写格式有过分的讲究, MAX与括号之间不能有空格,所有的参数都要放在括号里。很容易出错,比如:
int a=1,b=0;
MAX(a++,b); //a将被增值2次,与初衷不一致
MAX(a,"Hello"); //错误地比较int和字符串,原因是宏定义没有参数类型检查
这样定义的MAX( )函数会对两个参数值产生不同的副作用。
MAX(a++,b)的值为2,同时a的值为3;
MAX(a++,b+10)的值为10,同时a的值为2。
如果是普通函数,则MAX(a,"Hello")会受到函数调用的检查,但此处不会因为两个参数类型不同而被编译拒之门外。幸运的是,通过一个内联函数可以得到所有宏的替换效能和所有可预见的状态以及常规函数的类型检查:
inline int MAX(int a,int b)
{
return a>b?a:b;
}
两者的比较:
由于省去了参数压栈、生成汇编语言的 CALL调用、返回参数、执行return等过程,从而提高了速度;都能节省输入,提高代码的可读性。
宏定义容易引起不必要的错误。
用内联取代宏具有如下优势:
1).内联可调试;
2).可进行类型安全检查或自动类型转换;
3).可访问成员变量;
4.) 内联函数的使用提高了效率(省去了很多函数调用汇编代码如:call和ret等)。
另外,定义在类声明中的成员函数自动转化为内联函数。
值得注意的是,内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间(如存在循环和递归调用),相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。以下情况不宜使用内联:
(1)如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。
(2)如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。
注:要使函数成为内联函数,在其定义处申明为inline才能凑效。
2.内联函数的使用:
C++把所有在类的声明中定义的函数自动认为是内联函数。
class A()
{
void c();// not a inline function;
void d(){ print("d() is a inline function.");}
};
如果想将一个全局函数定义为内联函数可用,inline 关键字。
inline a(){print("a() is a inline function.");}
以下内容引自lchen_fhhls的博客:http://blog.csdn.net/lchen_fhhls/article/details/4123408
一些心得体会
C++ 语言中的重载、内联、缺省参数、隐式转换等机制展现了很多优点,但是这些优点的背后都隐藏着一些隐患。正如人们的饮食,少食和暴食都不可取,应当恰到好 处。我们要辨证地看待C++的新机制,应该恰如其分地使用它们。虽然这会使我们编程时多费一些心思,少了一些痛快,但这才是编程的艺术。
第9章 类的构造函数、析构函数与赋值函数
构造函数、析构函数与赋值函数是每个类最基本的函数。它们太普通以致让人容易麻痹大意,其实这些貌似简单的函数就象没有顶盖的下水道那样危险。
每个类只有一个析构函数和一个赋值函数,但可以有多个构造函数(包含一个拷贝构造函数,其它的称为普通构造函数)。对于任意一个类A,如果不想编写上述函数,C++编译器将自动为A产生四个缺省的函数,如
A(void); // 缺省的无参数构造函数
A(const A &a); // 缺省的拷贝构造函数
~A(void); // 缺省的析构函数
A & operate =(const A &a); // 缺省的赋值函数
这不禁让人疑惑,既然能自动生成函数,为什么还要程序员编写?
原因如下:
(1)如果使用“缺省的无参数构造函数”和“缺省的析构函数”,等于放弃了自主“初始化”和“清除”的机会,C++发明人Stroustrup的好心好意白费了。
(2)“缺省的拷贝构造函数”和“缺省的赋值函数”均采用“位拷贝”而非“值拷贝”的方式来实现,倘若类中含有指针变量,这两个函数注定将出错。
对于那些没有吃够苦头的C++程序员,如果他说编写构造函数、析构函数与赋值函数很容易,可以不用动脑筋,表明他的认识还比较肤浅,水平有待于提高。
本章以类String的设计与实现为例,深入阐述被很多教科书忽视了的道理。String的结构如下:
class String
{
public:
String(const char *str = NULL); // 普通构造函数
String(const String &other); // 拷贝构造函数
~ String(void); // 析构函数
String & operate =(const String &other); // 赋值函数
private:
char *m_data; // 用于保存字符串
};