目录
1 求值顺序
运算符优先级: 运算符优先级定义了在表达式中不同运算符的执行顺序。例如乘法 * 和除法 / 的优先级高于加法 + 和减法 -,在表达式 a + b * c 中,b * c 会先于 a + 进行计算,因为乘法的优先级高于加法。
求值顺序: 求值顺序指的是在表达式中各个操作数或子表达式的计算顺序。例如下面的表达式:
a < b & c < d
语言的定义a<b应当首先被求值,如果a确实小于b,此时必须进一步对c<d求值,以确定整个表达式的值。但是,如果a大于或等于b,则无需对c<d求值,表达式肯定为假。另外,要对a<b求值,编译器可能先对a求值,也可能先对b求值,在某些机器上甚至有可能对它们同时并行求值。
C语言中只有四个运算符(&&、||、?: 和 ,)存在规定的求值顺序。运算符&&和运算符 || 首先对左侧操作数求值,只在需要时才对右侧操作数求值。运算符 ?: 有三个操作数:在a ? b:c中,操作数a首先被求值,根据a的值再求操作数b或c的值。而逗号运算符,首先对左侧操作数求值,然后该值被“丢弃”,再对右侧操作数求值。
C语言中其他所有运算符对其操作数求值的顺序是未定义的。特别地,赋值运算符并不保证任何求值顺序。
下面这种从数组x中复制前个元素到数组y中的做法是不正确的,因为它对求值顺序作了太多的假设:
i = 0;
while(i < n)
y[i] = x[i++];
问题出在哪里呢?上面的代码假设 y[i] 的地址将在 i 的自增操作执行之前被求值,这一点并没有任何保证!在C语言的某些实现上,有可能在ⅰ自增之前被求值:而在另外一些实现上,有可能与此相反。可以写成如下的表达形式:
i = 0;
while(i < n){
y[i] = x[i];
i++;
}
for(i = 0; i < n; i++)
y[i] = x[i];
2 &和|不同于&&和||
在C语言中,按位与运算符&和按位或运算符|是位操作符号,&&和 || 是逻辑运算符号,在实际编程中,位运算符通常用于底层硬件操作或位字段的设置和清除,而逻辑运算符则用于控制程序的流程。区别如下:
- 位运算符&:它对两个数的二进制表示进行逐位比较,如果两个相应的位都为1,则结果位为1,否则为0。
- 逻辑与运算符&&:用于布尔逻辑。当两个操作数都为真时,结果才为真。在C语言中,逻辑运算符通常用于控制流程语句,如 if 和 while。逻辑与运算符具有短路特性,即如果第一个操作数为假,那么整个表达式的结果就是假,第二个操作数将不会被求值。
- 位运算符|:它对两个数的二进制表示进行逐位比较,只要两个相应的位中有一个为1,结果位就为1。
- 逻辑或运算符||:用于布尔逻辑。只要有一个操作数为真,结果就为真。与 && 一样,|| 也具有短路特性,即如果第一个操作数为真,那么整个表达式的结果就是真,第二个操作数将不会被求值。
int a = 0b1010; // 二进制表示为1010
int b = 0b1100; // 二进制表示为1100
int c = a & b; // c 的值为 0b1000,即8
int d = a | b; // d 的值为 0b1110,即14
int a = 5;
int b = 0;
if (a > 0 && b != 0) {
// 这个分支不会执行,因为 b 为 0
}
if (a > 0 || b != 0) {
// 这个分支会执行,因为 a > 0
}
3 &&和&不能混用
考虑下面的代码段,其作用是在表中查询一个特定的元素:
int x = 5;
int y = 3;
// 错误使用:按位与运算符 & 被误用
if (x < y & y > 2) {
}
在这个示例中,if 语句中的条件 x < y & y > 2 使用了按位与运算符 & 而不是逻辑与运算符 &&。由于 y > 2 总是为真,& 运算符实际上将 y 的值与 x < y 的结果进行按位与操作。无论 x < y 的结果如何,由于 y 的二进制表示中至少有一个位是1,所以 x < y & y > 2 的结果总是非零的,这意味着条件总是被认为是真。
4 词法分析中的贪心法
在C语言中,词法分析在编译过程中将源代码分解成一系列的词素。贪心法则对于每个可能的词素模式,它总是尽可能地匹配更多的字符。这通常通过使用正则表达式或状态机来实现。以下是贪心法在C语言词法分析中的一些关键步骤:
x = y /* z;
/之后紧接着*,那么无论上下文如何,这两个字符都将被当作一段注释的开始。可能本意是用x除以z所指向的值,把所得的商再赋给y,将上面的语句重写如下才是所表示的原意。
y = x/(*z) /*z指向除数*/
5 单引号和双引号的区别
5.1 单引号和双引号数据的区别
单引号' ':用于表示单个字符常量,例如 'A' 表示大写字母A,'0' 表示数字0,单引号内的字符可以是任何有效的字符,包括字母、数字、空格、标点符号等,如果单引号内包含多于一个字符,或者没有字符,编译器会报错。
双引号" ":双引号用于表示字符串常量,例如 "Hello, World!" 是一个字符串常量,字符串常量必须以双引号开始和结束,并且通常以空字符 '\0' 结尾,这个空字符是由编译器自动添加的,不需要程序员显式写出。
下面是一些示例来说明单引号和双引号的区别:
char a= 'A'; // 使用单引号定义字符常量
char b = '\n'; // 定义一个换行符字符常量
char* c = "Hello, World!"; // 使用双引号定义字符串常量
在这个例子中,a 和 b 是字符常量,而 c 是一个指向字符串的指针。注意,字符常量在内存中只占用一个字节,而字符串常量实际上占用的内存大小是字符串长度加1(用于存储空字符'\0')。
5.2 单引号和双引号数据的本质
单引号:一个字符实际上代表一个整数,整数值对应于该字符在编译器采用的字符集中的序列值,因此,对于采用ASCⅡ字符集的编译器而言,'a' 的含义与0141(八进制)或者97(十进制)严格一致。
双引号:双引号引起的字符串,代表的是一个指向数组起始字符的指针,该数组被双引号之间的字符以及一个额外的二进制值为零的字符 'a' 初始化。
因为用单引号括起的一个字符代表一个整数,而用双引号括起的一个字符代表一个指针,如果两者混用,那么编译器的类型检查功能将会检测到这样的错误。例如:
char *slash = '/';
在编译时将会生成一条错误消息,因为并不是一个字符指针。然而,某些C编译器对函数参数并不进行类型检查,特别是对printf函数的参数。因此,如果用
printf ('\n');
来代替正确的
printf ("\n");
则会在程序运行的时候产生难以预料的错误,而不会给出编译器诊断信息。不过现在的编译器一般能够检测到在函数调用时混用单引号和双引号的情形,但不推荐单引号和双引号混用。
6 左值与右值
左值:指的是具有持久存储位置的对象或函数。这意味着左值表示的是内存中的一个位置,该位置可以存储数据,并且可以通过该位置来访问和修改数据。左值有如下特性:
- 可以出现在赋值语句的左侧。
- 可以取地址(即可以使用&运算符)。
- 通常是一个变量(包括数组元素、结构体成员等)。
右值:指的是表达式计算后得到的结果值,这个值通常没有持久的存储位置,或者说是一个临时值。右值不能作为赋值的对象。
- 通常出现在赋值语句的右侧。
- 不能取地址(即不能使用&运算符)。
- 可以是常量、函数返回值、算术表达式的结果等。
通过下面的示例理解左值和右值:
int x = 10;
int y = x; // x 是左值,y 是左值,x 的值赋给了 y
x + y = 20; // 错误:x + y 是右值,不能出现在赋值语句的左侧
int *p = &x; // 正确:x 是左值,可以取地址
int *q = &(x + y); // 错误:x + y 是右值,不能取地址