C++实用经验(四)

C++实用经验(四)

  • 运算符引发的混乱
  • 尽量使用C++转换操作符
  • 表达式求值顺序不要想当然
  • swtch-case陷阱
  • 悬挂else
  • goto真的一无是处吗
  • 条件操作符和逗号操作符
  • 同魔鬼数字说再见
  • 关于循环语句的讨论

声明:以下内容总结自《C++程序员不可不知的101条实用经验》

运算符引发的混乱

  1. 粗心导致的混乱:为防止=和使用混乱,将 if(b10)写成if(10==b),这样当因为粗心写出if(10=b)时,编译器报错,能够及时发现问题
  2. 优先级导致的混乱:三元运算的条件操作符exp1?exp2:exp3优先级较低,可通过括号强制提高优先级cout << (exp1?exp2:exp3);
  3. 结合性导致的混乱:a=b=c等价于a=(b=c),具有向右结合特性
  4. 优先级决定表达式中各种不同运算符起作用的优先次序,而结合性则在相邻两个运算符具有同等优先级时,决定表达式的结合方向
  5. 最好使用括号设置期望的优先级,防止引入不确定性

尽量使用C++转换操作符

为了解决C语言中旧式类型转换存在的问题,C++中引入了4种新类型转换操作符:static_cast、const_cast、dynamic_cast、reinterpret_cast

  1. static_cast:(1)用于类层次结构中基类和子类之间指针或引用的转换 (2)进行上行转换是安全的 (3)把空指针转换成目标类型的空指针 (4)把任何类型的表达式转换为void类型 (5)可将一个void*类型的指针强制转换为原来的指针类型 (6)进行下行转换(把基类指针或引用转换成子类指针或引用)没有动态类型检查,所以不安全
  2. dynamic_cast:(1)用于类层次间的上行转换和下行转换以及类之间的交叉转换 (2)下行转换时,dynamic_cast具有类型检查的功能,比static_cast更加安全 (3)dynamic_cast要求派生类有虚拟函数,否则编译不通过
  3. reinterpret_cast:(1)比static_cast更接近C的强制类型转换,但比static_cast更加危险 (2)除非有必要,否则在C++编程中尽量避免使用reinterpret_cast
  4. const_cast:把常量指针或引用转换为非常量指针或引用,并仍然指向原来的对象

表达式求值顺序不要想当然

  1. 操作数的求值顺序
int x = 1;
int y = (x + 2) * (++ x);
//x+2和x++的顺序无法确定,所以y的值可能为6或8

//p(), q(), r()为三个函数, 都对同一个全局变量进行操作
int a = p() + q() + r(); //三个函数的运行顺序无法确定,导致a值无法确定
//使用指定中间变量的方式解决
int b = p(), c = q(), d = r();
int a = b + c + d;
  1. 函数参数的求值顺序
//p(), q(), r()为三个函数, 都对同一个全局变量进行操作
print("%d %d %d", p(), q(), r()); //三个函数返回值作为参数,但三个函数的执行顺序不确定

int a = 10;
int &aa = a;
int res = Func(a, aa *= 2); //无法确定时Func(10, 20)还是Func(20, 20)
int res = Func(a, a * 2); //正确

swtch-case陷阱

  1. 使用switch-case时,最好在每个标号后提供一个break语句,即使最后一个标号也不例外,如果因为某种特殊需要在switch的最后一个标号后面又要添加一个新的标号,则不用在前面加break语句了
  2. 故意省略break是特别罕见的做法,因此在这种形式的代码附近,务必添加一下注释说明运行逻辑
  3. 在switch-case结构中,只能在最后一个case标号或default中定义内部变量,避免出现代码跳过变量定义和初始化的情况
  4. case的标号必须是整型常量表达式

悬挂else

if(0 == x)
		if(0 == y) printf("abc");
else
		printf("ddd");
//else会和最近的if(0 == y)匹配,使得程序逻辑出现问题

解决方式:使用良好的编程风格,在if和else后都加上{}

goto真的一无是处吗

反对goto的论点:(1)含有goto的代码很难安排好格式 (2)使用goto会破坏编译器的优化特性 (3)使用goto会使运行速度变慢,代码更大 (4)使用goto难以维护代码
适合使用goto的两种情况:(1)跳出多层嵌套循环 (2)错误处理以及资源释放

条件操作符和逗号操作符

  1. 条件操作符:(1)最好为每个子表达式添加括号(expression1) ? (expression2) : (expression3) (2)提高代码简洁性
  2. 逗号操作符:
int a = expression1, expression2, expression3, expression4; //每个表达式都会被求值,整个表达式的值是最后一个表达式的值,即a=expression4 

同魔鬼数字说再见

  1. 魔鬼数字:上下文出现的字符常量,虽然不会影响程序功能,但没有抽象语义,影响程序的可读性
  2. 为了避免代码难以理解,应将数字定义为名称有意义的常量,使得代码更易理解

关于循环语句的讨论

  1. for循环:(1)不可在for循环体内修改循环变量,防止for循环失去控制,最终导致死循环 (2)建议for循环使用半闭半开的写法,更加直观 for(int i = 0; i < N; ++i)
  2. do{}while():(1)do{}while()是指向效率最低的循环语句,但它的特点是一定会执行一次 (2)do{}while(0)被用于宏定义中,使用do{…}while(0)构造后的宏定义不会受到大括号、分号等的影响,总是会按你期望的方式调用运行,比如:
#define foo() {p();q();}
if(true == k)
		foo(); 
else
		printf("sss");
等价于
if(true == k){
		p();
		q();
}; //多了一个分号
else
		printf("sss");
改进:
#define foo() do{p();q();}while(0)
if(true == k)
		do{
				p();
				q();
		}while(0);
else
		printf("sss");

(3)在多重循环中,应当将最长的循环放在最内层,最短的循环放在最外层,以减少CPU跨切循环层的次数,这样可以提高cache命中率,降低因为循环跳转造成cache的miss以及流水线的flush造成的延时
(4)当循环体内存在逻辑判断,并且循环次数很大时,宜将逻辑判断放到循环体的外面
(5)++i比i++效率更高,i!=N比i

你可能感兴趣的:(C++)