提出公共子表达式
在某些情况下,C++编译器不能从浮点表达式中提出公共的子表达式,因为这意味着相当于对表达式重新排序。需要特别指出的是,编译器在提取公共子表达式前不能按照代数的等价关系重新安排表达式。这时,程序员要手动地提出公共的子表达式(在VC.net里有一项“全局优化”选项可以完成此工作,但效果就不得而知了)。
推荐的代码
float a, b, c, d, e, f;
...
e = b * c / d;
f = b / d * a;
float a, b, c, d, e, f;
...
const float t(b / d);
e = c * t;
f = a * t;
推荐的代码
float a, b, c, e, f;
...
e = a / c;
f = b / c;
float a, b, c, e, f;
...
const float t(1.0f / c);
e = a * t;
f = b * t;
结构体成员的布局
很多编译器有“使结构体字,双字或四字对齐”的选项。但是,还是需要改善结构体成员的对齐,有些编译器可能分配给结构体成员空间的顺序与他们声明的不同。但是,有些编译器并不提供这些功能,或者效果不好。所以,要在付出最少代价的情况下实现最好的结构体和结构体成员对齐,建议采取这些方法:
按类型长度排序
把结构体的成员按照它们的类型长度排序,声明成员时把长的类型放在短的前面。
把结构体填充成最长类型长度的整倍数
把结构体填充成最长类型长度的整倍数。照这样,如果结构体的第一个成员对齐了,所有整个结构体自然也就对齐了。下面的例子演示了如何对结构体成员进行重新排序:
不好的代码,普通顺序 推荐的代码,新的顺序并手动填充了几个字节
struct
{
char a[5];
long k;
double x;
} baz;
struct
{
double x;
long k;
char a[5];
char pad[7];
} baz;
这个规则同样适用于类的成员的布局。
按数据类型的长度排序本地变量
当编译器分配给本地变量空间时,它们的顺序和它们在源代码中声明的顺序一样,和上一条规则一样,应该把长的变量放在短的变量前面。如果第一个变量对齐了,其它变量就会连续的存放,而且不用填充字节自然就会对齐。有些编译器在分配变量时不会自动改变变量顺序,有些编译器不能产生4字节对齐的栈,所以4字节可能不对齐。下面这个例子演示了本地变量声明的重新排序:
不好的代码,普通顺序 推荐的代码,改进的顺序
short ga, gu, gi;
long foo, bar;
double x, y, z[3];
char a, b;
float baz;
double z[3];
double x, y;
long foo, bar;
float baz;
short ga, gu, gi;
避免不必要的整数除法
整数除法是整数运算中最慢的,所以应该尽可能避免。一种可能减少整数除法的地方是连除,这里除法可以由乘法代替。这个替换的副作用是有可能在算乘积时会溢出,所以只能在一定范围的除法中使用。
不好的代码 推荐的代码
int i, j, k, m;
m = i / j / k;
int i, j, k, m;
m = i / (j * k);
把频繁使用的指针型参数拷贝到本地变量
避免在函数中频繁使用指针型参数指向的值。因为编译器不知道指针之间是否存在冲突,所以指针型参数往往不能被编译器优化。这样是数据不能被存放在寄存器中,而且明显地占用了内存带宽。注意,很多编译器有“假设不冲突”优化开关(在VC里必须手动添加编译器命令行/Oa或/Ow),这允许编译器假设两个不同的指针总是有不同的内容,这样就不用把指针型参数保存到本地变量。否则,请在函数一开始把指针指向的数据保存到本地变量。如果需要的话,在函数结束前拷贝回去。 不好的代码 推荐的代码
?/ 假设 q != r
void isqrt(unsigned long a, unsigned long* q, unsigned long* r)
{
*q = a;
if (a >; 0)
{
while (*q >; (*r = a / *q))
{
*q = (*q + *r) >;>; 1;
}
}
*r = a - *q * *q;
}
// 假设 q != r
void isqrt(unsigned long a, unsigned long* q, unsigned long* r)
{
unsigned long qq, rr;
qq = a;
if (a >; 0)
{
while (qq >; (rr = a / qq))
{
qq = (qq + rr) >;>; 1;
}
}
rr = a - qq * qq;
*q = qq;
*r = rr;
}
赋值与初始化
先看看以下代码:
class CInt
{
int m_i;
public:
CInt(int a = 0):m_i(a) { cout <;<; ";CInt"; <;<; endl; }
~CInt() { cout <;<; ";~CInt"; <;<; endl; }
CInt operator + (const CInt&; a) { return CInt(m_i + a.GetInt()); }
void SetInt(const int i){ m_i = i; }
int GetInt() const{ return m_i; }
};
不好的代码 推荐的代码
void main()
{
CInt a, b, c;
a.SetInt(1);
b.SetInt(2);
c = a + b;
}
void main()
{
CInt a(1), b(2);
CInt c(a + b);
}
这两段代码所作的事都一样,但那一个更好呢?看看输出结果就会发现,不好的代码输出了四个";CInt";和四个";~CInt";,而推荐的代码只输出三个。也就是说,第二个例子比第一个例子少生成一次临时对象。Why? 请注意,第一个中的c用的是先声明再赋值的方法,第二个用的是初始化的方法,它们有本质的区别。第一个例子的";c = a + b";先生成一个临时对象用来保存a + b的值,再把该临时对象用位拷贝的方法给c赋值,然后临时对象被销毁。这个临时对象就是那个多出来的对象。第二个例子直接用拷贝构造函数的方法对c初始化,不产生临时对象。所以,尽量在需要使用一个对象时才声明,并用初始化的方法赋初值。
尽量使用成员初始化列表
在初始化类的成员时,尽量使用成员初始化列表而不是传统的赋值方式。
不好的代码 推荐的代码
籧lass CMyClass
{
string strName;
public:
CMyClass(const string&; str);
};
CMyClass::CMyClass(const string&; str)
{
strName = str;
}
class CMyClass
{
string strName;
int i;
public:
CMyClass(const string&; str);
};
CMyClass::CMyClass(const string&;str)
: strName(str)
{
}
不好的例子用的是赋值的方式。这样,strName会先被建立(调用了string的默认构造函数),再由参数str赋值。而推荐的例子用的是成员初始化列表,strName直接构造为str,少调用一次默认构造函数,还少了一些安全隐患。