C++复合类型
——————————-
写在前面
C++从C中继承的一个重要特征就是效率。
在C中我们经常把一些短并且执行频繁的计算写成宏,而不是函数,这样做的理由方式为了执行效率,宏可以避免函数调用的开销,这些都由预处理来完成。
但是在C++出现以后,使用宏处理会出现两个问题:
- 第一个在C中也会出现,宏看起来像是一个函数调用,但是会隐藏一些难以发现的错误。
- 第二个问题是C++特有的,预处理器不允许访问类的成员,也就是说预处理器宏不能用作类的成员的函数。
为了保持预处理宏的效率又增加安全性,而且能像一般成员函数那样在类里访问自如,C++引入了内联函数。
内联函数为了继承宏函数的效率,没有函数调用的开销,然后又可以像普通函数那样,可以进行参数、返回值类型的安全检查,又可以作为成员函数。
1.预处理宏的缺陷
预处理器宏存在问题的关键是我们可能认为预处理器的行为和编译器时一样的。当然也是由于宏函数调用和函数调用在外表看起来是一样的,因此也容易混淆。但是其中会有一些微妙的问题出现。
例1
#include <iostream>
using namespace std;
#define ADD(x,y) x+y
void test()
{
int ret = ADD(10, 10);
int ret0 = ADD(10, 10) * 10;//预期结果为200
cout << "ret=" << ret << endl;
cout << "ret0=" << ret0 << endl;
}
int main()
{
test();
}
输出结果如下:
在输出结果里,我们可以看到,实际输出结果为110,并不是我们想要的结果。它实际进行运算时,运算式应该是:10+10*10。
对于这个微小的问题,我们可以通过加括号的方式来解决它。源代码和输出结果如下:
#include <iostream>
using namespace std;
#define ADD(x,y) ((x)+(y))
void test()
{
int ret = ADD(10, 10);
int ret0 = ADD(10, 10) * 10;//预期结果为200
cout << "ret=" << ret << endl;
cout << "ret0=" << ret0 << endl;
}
int main()
{
test();
}
输出结果如下:
我们可以看到,加括号之后,输出结果和预期相同。
例2
为了防止出现例1中的问题,这次,我特意对三目运算符加了括号。代码如下:
#include <iostream>
using namespace std;
#define compare(a,b) ((a)<(b))?(a):(b)
void test()
{
int a = 10;
int b = 20;
int ret = compare(a, b);
cout << "ret=" << ret << endl;
int ret0 = compare(++a, b);
cout << "ret0=" << ret0 << endl;
}
int main()
{
test();
}
输出结果如下:
这次出现的问题让人有点摸不到头脑,因此++运算符表示在进行运算之前加1,但是输出结果中,实际输出为12,相当于加了两次。
除此之外,我们还应当注意的是,预定义宏函数没有作用域概念,无法作为一个类的成员函数,也就是说,预定义宏没办法表示类的范围。
2.内联函数
在C++中,预定义宏额概念是用内联函数来实现的,而内联函数本身也是一个真正的函数。内联函数具有普通函数的所有行为。唯一不同之处在于内联函数会在适当得到地方像预定义宏一样展开,所以不需要函数调用的开销。因此应该不使用宏,使用内联函数。
普通函数(非成员函数)函数前面加上inline关键字使之成为内联函数。但是必须注意函数体和声明结合在一起,否则编译器会把它当成普通函数对待。
inline void func(int a);
上面的这种写法没有任何效果,仅仅是声明函数,应该像下面这样的方式来定义。
inline void func(int a)
{
return ++;
}
注意:编译器将会检查函数参数列表使用是否正确,并返回值(进行必要的转换)。这些事预处理器是无法完成的。
内联函数的确占用空间,但是内联函数相对于普通函数的优势只是省去了函数调用是否的压栈、跳转、返回的开销。我们可以理解为内联函数是以空间换时间。
内联说明只是向编译器发出一个请求,编译器可以选择忽略这个请求。
一般来说,内联机制用于优化规模较小、流程直接、频繁调用的函数。很多编译器都不支持内联函数递归函数,而且一个75行的函数也不太可能在调用点内联地展开。