您的位置:首页 > 编程语言 > C语言/C++

C语言注意事项——数据类型、操作符和表达式

2014-12-18 10:09 399 查看
疑问一、操作符优先级?

A.记住C语言中运算符的优先级是有益的。 (语句摘自《C陷阱与缺陷》)

——引申:算术运算符优先级讲解及记法在《C陷阱与缺陷》2.2节中讲的简约又透彻,遗忘时可以翻看这一节同时对照优先级表进行阅读,也要理解并记住操作符的结合性。

疑问二、操作符求值顺序?



A.C语言中只有四个运算符(&& || ?: ,)存在规定的求值顺序。C语言中其他所有运算符对其操作数的求值顺序是未定义的。 (语句摘自《C陷阱与缺陷》,《C和指针》一书中也着重讲解了优先级和求值顺序关系)

——引申:在一些语句中可以利用有求值顺序的运算符(&& || ?: ,)实现一些功能,同时写一些复杂语句时切勿把未定义求值顺序的运算符按照自己想象的求值顺序进行运算(特别是语句内部出现多个自增、自减运算符(含副作用)时),否则由于编译器不同可能会得到不同结果,导致程序不可移植。

疑问三、可移植性问题:移位操作符?

A1.编程者如果关注向右移位时空出的位,那么可以将操作的变量声明为无符号类型,那么空出的位都会被设置为0。 (语句摘自《C陷阱与缺陷》)

A2.标准说明无符号值执行的所有移位操作都是逻辑移位,但对于有符号值,到底是采用逻辑移位还是算术移位取决于编译器。

(语句摘自《C和指针》)

A3.如果被移位的对象长度是n位,那么移位计数必须大于或等于0,而严格小于n。 (语句摘自《C陷阱与缺陷》)

——引申:移位操作符带来的可移植性问题在《C陷阱与缺陷》和《C和指针》中均被提及,为了保证移植性,使用操作符时,最好把操作数先强制转换为无符号类型或直接声明为无符号类型。在用于位操作和高效的通过移位代替乘除2运算中尤其要注意。同时,移位操作符优先级也要注意,它在算术运算符优先级之后

// 实现除以2功能,
int width = 124;
int half_offset_width =  12 + ((unsigned int)width >> 2);


疑问四、可移植性问题:除法运算符?
A1.更好的做法是:程序在设计时就应该避免n的值为负这样的情形,并且声明n为无符号数。(语句摘自《C陷阱与缺陷》)

——引申:数学上的整数除法(除数,被除数均存在正负号)是有规定的,《C陷阱与缺陷》一书中有记录,但是实际上C编译器可能采取不同的措施,带来一个移植性的问题,对于求余(%)语句也是。应当避免 / 和 % 操作符两边的操作数出现负值的情况。另一个注意事项是被除数为零的情况,一种可能的情况是,CPU会产生0中断信号,系统终止程序,实际上程序员也要避免被除数为0的情况。
int a = 53;
int b = 12;
int c = (unsigned int)a / (unsigned int)b; // 无符号
// 被除数为零 a b c 为 整型;
if (b != 0) {
c = a / b;
} else {
PRINT_ERROR;  // 调试信息
assert(0);  // 调试信息
}
// 被除数为0,a b c为浮点double型(其他)
<pre name="code" class="cpp" style="font-weight: bold;">c = a / b;
if (c >= DBL_MAX || c <= -DBL_MAX) { PRINT_ERROR; // 调试信息 assert(0); // 调试信息}


疑问五、可移植性问题:有符号字符和无符号字符?字符集?

A1.缺省的char要么是signed char,要么是unsigned char,这取决于编译器。(语句摘自《C和指针》,《C陷阱与缺陷》中也有提及)

A2.最妥协的方案就是把存储于char型变量的值限制在signed char和unsigned char的交集内,这可以获得最大程度的可移植性,同时又不牺牲效率。并且只有当char型变量显示声明为signed或unsigned时,才对它执行算术运算。(语句摘自《C和指针》)

A3.只有在我们需要把一个字符值转换为一个较大的整数时,这个问题才变得重要起来。.... 如果编程者关注一个最高位是1的字符其数值究竟是正还是负,可以将这个字符声明为无符号字符。(语句摘自《C陷阱与缺陷》)

A4.如果C是一个字符变量,转换为与C等价的无符号整数使用语句:(unsigned char)c ,而不是(unsigned)c(c将首先被转换为int型整数,可能得到非预期结果)。(语句总结自《C陷阱与缺陷》)

——引申:考虑到编译器,对于字符串a,用char型进行存储,存储的值最好位于0-127值之间,而执行运算操作和整型提升前需先利用强制转换(unsigned char)a。

char a = '4';
int b = 32;
char c = b + (unsigned char)a;
B1.假定:在机器的字符集中数字是顺序排列、没有间隔的。这种假定,对ASCII字符集和EDCDIC字符集是正确的,对符合ANSI的C实现也是正确的,但对某些机器却有可能出错。要避免这个问题,解决办法是使用一张代表数字的字符表。(语句摘自《C陷阱与缺陷》)

——引申:对于整型值与字符之间的转换,获得最大移植性的办法就是采用字符表。

// 将数字n转换为字符n
int i = 3;
char c = "0123456789"[i % 10]


疑问六、可移植性问题:整型大小和溢出

A1.长整型至少应该和整型一样长,而整型至少应该和短整型一样长。(语句摘自《C和指针》)

A2.short int至少为16位,long int至少为32位。至于缺省的int究竟是16位还是32位,或者其他值,则由编译器设计者决定。通常这个选择的缺省值是这种机器最为自然(高效)的位数。(语句摘自《C和指针》)

A3.整数溢出:一种正确的方式是将a和b都强制转换为无符号整数。(语句摘自《C陷阱与缺陷》)

A4.整数大小:可移植性最好的办法,定义一个”新的“类型无疑更为清晰。(语句摘自《C陷阱与缺陷》)

——引申:ANSI标准没有规定各自整型的字节数,因此也无法确切知道各个类型的整型范围。各个编译器的<limits.h>和<float.h>定义了各个整型和浮点型的范围,在编程实现时可以参考。利用typedef定义新的类型,当移植时(同一个编译器,不同的CPU)只需要稍微改动下类型定义,编程时只用相应的位数类型(UINT8
UINT16 UNIT32 UINT64)。


<pre name="code" class="cpp">/// Macro and Const
typedef unsigned char uint8;
typedef unsigned char UINT8;
typedef unsigned char byte;
typedef unsigned char BYTE;
typedef signed char int8;
typedef signed char INT8;

typedef unsigned short uint16;
typedef unsigned short UINT16;
typedef unsigned short word;
typedef unsigned short WORD;
typedef short int16;
typedef short INT16;

typedef unsigned int uint32;
typedef unsigned int UINT32;
typedef unsigned int dword;
typedef unsigned int DWORD;
typedef int int32;
typedef int INT32;

typedef unsigned long uint64;
typedef unsigned long UINT64;
typedef long int64;
typedef long INT64;



                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: