源程序中,但凡可以出现某种类型的数据的地方都可以用同类型的表达式替换。一个表达式的类型,取决于多个方面.
操作符有单目操作符和双目操作符, 还有三目操作符(? : 是C语言唯一的一个三目操作符)。"目"指的是操作数的个数!
C语言没有指定同一操作符的多个操作数的计算顺序(除了&& || ? : 和,)。所以语句x = f() +
g();的计算结果依赖不同的编译器。
1 /* 2 * 文件名: 3mu.c 3 * 描述:(1)一个表达式的类型取决于多个方面(表达式的操作数类型) 4 * (2)C语言没有指定同一操作符的多个操作数的计算顺序, 5 * 除了: 逻辑与(&&) 逻辑或(||) 三目操作符(? : ) 逗号操作符(,) 6 * 她们的操作数计算顺序是自左而右,当可以得到计算结论时就停止计算,返回计算结果 7 * 8 * 9 * */ 10 #include <stdio.h> 11 12 int main() 13 { 14 char c = ‘a‘; 15 double d = 0.0; 16 17 printf("c是%c, d是%f\n", c, d);// a, 0.000000 18 c ? c = ‘A‘ : (d = 10.99); // 三目操作符号的运算从左至右,得出计算结论就不再计算,该行代码会导致c被赋值‘A‘,不会导致d被赋值 19 20 //c ? c = ‘A‘ : d = 10.99; //ERROR 赋值操作符号优先级低于三目操作符,三目操作符的表达式运算结果是右值! 也就是说该行语句等价于(c ? c = ‘A‘ : d) = 10.99; 21 /*编译器gcc报错如下: 22 * 3mu.c: 在函数‘main’中: 23 * 3mu.c:9:28: 错误:赋值运算的左操作数必须是左值 24 * c ? c = ‘A‘ : d = 10.99 25 * ^ 26 */ 27 28 printf("c是%c, d是%f\n", c, d);// A, 0.000000 29 printf("表达式 c ? c = ‘A‘ : (d = 10.99) 的类型是double, 表达式的值将占有空间 %d Byte\n", sizeof(c ? c = ‘A‘ : (d = 10.99)));//sizeof()操作符计算表达式值占有的空间大小时候会全面考虑表达式所有的操作数 30 31 32 return 0; 33 }
逗号操作符,可以连接多个独立的表达式, 最后一个表达式的计算结果将作为逗号操作运算的结果。
自增自减操作符
++是自增操作符,--是自减操作符。他们可以对变量进行操作,可以实现把变量内部的数字加一或者减一。这两个操作符写在变量前面的时候被称为"前缀操作
符",写在后面是被称为"后缀操作符"。这两种情况下,其效果都是将变量的值加一或者减一。但是,它们之间有一点不同。作为前缀操作符,会先完成自增或者
自减,然后再供其他操作使用,作为后缀操作符,会在完成自增或者自减的同时,提供一个自增自减前的值给其他操作使用。
实际上可以进一步理解后++/后--
int i = 1;
int j = 0;
j = i++;//OK.
++i = 200;// 在C中,前加加表达式计算结果是右值,该条语句ERROR。但是在C++中,前加加表达式计算结果是个左值,该条语句OK。
i++ = 100;//ERROR.错误:赋值运算的左操作数必须是左值。 而无论C还是C++中,表达式i++的计算结果是个右值.
/*
* 对于表达式i++;编译器将按照如下步骤处理:
* step1:制作一个i的副本(临时的匿名的变量,这里临时记作i_bak.)
* step2:给i增加1
* step3:将i的副本i_bak返回作为表达式i++的计算结果
*
* 结论:(1)C/C++中,基本类型的临时匿名变量是不可更改的,是个典型的右值。所以表达式的计算结果一般都是个右值。但是在C++中,前加加表达式计算结果是个左值
* (2)C/C++中,后置++/后置--计算结果导致操作数增加或者减少1。
*/
1 #include <stdio.h> 2 3 int main(int argc, char *argv[]) 4 { 5 char i = 0x80;// -128 6 ++i = 100;//C++编译器不会报错。C++规范里面认定前加加表达式计算结果为左值。 7 // C编译器会报左值错误。C规范里无论前加加还是后加加表达式计算结果都是右值 8 9 // i++ = 127; //ERROR 10 11 return 0; 12 }
逻辑运算(逻辑运算是指计算结果只有真假两种结果的运算)操作符:
== > < >= <= !=
逻辑非(!) 逻辑与(&&) 逻辑或(||)
按位求反(~) 按位与AND(&) 按位或OR(|) 按位异或XOR(^)
/*请理解这条语句 3 > 5 && ++num;*/
3 > 5 && ++num;//计算机中逻辑运算(逻辑与和逻辑或)存在短路特性。当已经判定逻辑表达式运算结果,那么不管逻辑表达式是否全部判定结束都停止判定。
printf("num是%d\n", num);//num的打印结果是0
3 < 5 || ++num;//逻辑或的短路特性
printf("num是%d\n", num);//num的打印结果依然是0
对于逻辑运算,要知道C语言中任何数据信息都可以参与逻辑运算。参与的规则:只有零才是假,其他都是真"
位运算。位运算操作符:
按位取反(~)
按位与AND(&)
按位或OR(|)
按位异或XOR(^)
左移<<
右移>>
(特别注意:机器数采用的二进制编码方式是补码,补码参与运算,计算结果依然是补码)
&是按位与,通常可以用按位与运算把某个机器数中特定的位清零。举个例子:判断一个int
integer 的奇偶性可以这样做 (integer & 0x1),这样做的好处是计算速度快。
|是按位或(OR), 通常可以用按位或运算把某个机器数中特定位置为1。
^是按位异或(XOR), 通常可以用按位异或运算把某个机器数特定位求反.举个例子:求一个int integer的相反数可以这样做 (integer ^ 0x11111111) + 1, 这样做的好处是计算速度快。
~是按位求反,按位求反实现了对某个机器数按位求反。
<<是按位左移,左移会给右边补零,左移一位数值扩大一倍。
>>是按位右移,对于无符号类型,右移会给左边补零;对于有符号类型,右移会给左边补符号位;右移一位数值缩小1/2倍
1 /* 2 * 文件名: swap.c 3 * 描述:不引入第三个变量,交换两个变量的值 4 * */ 5 #include <stdio.h> 6 7 int main(void) 8 { 9 char a = ‘A‘, b = ‘B‘; 10 printf("a是%c, b是%c\n", a, b); 11 a = a ^ b; 12 b = b ^ a; 13 a = a ^ b; 14 printf("a是%c, b是%c\n", a, b); 15 }
C语言中可以使用强制类型转换把任何一种数据类型的数据当成另外一种类型的数据来使用。
隐式类型转换会把占地小的数据类型转换为占地大的数据类型,如果数据类型占地大小一样则会把有符号的数据类型转换成无符号类型的数据。例如:
/*请理解如下代码*/
int num = -10;
unsigned int num2 = 3;
num = (num + num2) > 0 ? 1 : -1;//隐身类型转换原则:把占有空间小的调整为占有空间大的,如果占有空间一样,则把有符号的数据转换为无符号数据
printf("(num + num2) > 0 ? 1 : -1 是%d\n", num);//num是1
1 /* 2 * 若char c = 0xFF, 则 输出 c 十进制是-1, 十六进制是ffffffff 3 * 若 强转c为unsigned char, 即(unsigned char)c, 则 输出 c 十进制是255, 十六进制是ff 4 * 5 */ 6 7 #include <stdio.h> 8 9 int main(int argc, char *argv[]) 10 { 11 char c = 0xFF; 12 printf("若char c = 0xFF, 则 输出 c 十进制是%d, 十六进制是%0x\n", c, c); 13 printf("若 强转c为unsigned char, 即(unsigned char)c, 则 输出 c 十进制是%d, 十六进制是%0x\n", (unsigned char)c, (unsigned char)c); 14 return 0; 15 }
所以大概可以看出来:C是向下泛型的,而我们知道C++是向上泛型的。
范例学习:
1 /* 2 *操作符练习 操作符的优先级和结合性练习 3 */ 4 5 #include <stdio.h> 6 int num3 = 0;//这里安排这个变量,意图在于测试语句num2 = num3++ + ++num3; 7 main() 8 { 9 int num = 0, num2 = 0; 10 num = (3, 7); 11 printf("num是%d\n", num); 12 //num++; 13 //++num; 14 //num--; 15 --num; 16 printf("num是%d\n", num); 17 18 //num2 = ++num;//前置加加操作符优先级非常高,基本上属于最高(优先级只低于 () [] -> 和 .) 19 num2 = num++;//后置加加操作符 20 printf("num是%d, num2是%d\n", num, num2);//num是7 num2是6 21 22 23 num2 = num++ + ++num;/*不要这么写语句,这样的语句具体执行结果依靠编译器,不同编译器编译时处理方式不一。 例如:num在局部变量和全局变量两种情况下是不同编译器处理方式就不一样*/ 24 printf("num是%d, num2是%d\n", num, num2);//num是9 num2是16 25 26 num3 = 7; 27 num2 = num3++ + ++num3;/*num3为全局变量。同样的语句当num3为局部变量时候,运算结果可能就不一样,这取决于编译器在这种情况下的处理方案*/ 28 printf("num2是%d, num3是%d\n", num2, num3);//num2是16 num3是9 29 }
1 /* 2 * 逻辑操作符练习 3 * 重点掌握操作符的结合性和优先级 4 * 逻辑运算是指计算结果只有真假两种结果的运算。 5 * 逻辑运算操作符:== > < >= <= != 6 * 逻辑非(!) 逻辑与(&&) 逻辑或(||) 7 * 按位求反(~) 按位与AND(&) 按位或OR(|) 按位异或XOR(^) 8 * C语言中任何数据信息,都可以参与逻辑运算。规则:只有零才是假,其他都是真 9 * */ 10 11 #include <stdio.h> 12 13 main() 14 { 15 int num = 0; 16 17 printf("3 == 5是%d\n", 3 == 5); 18 printf("3 != 5是%d\n", 3 != 5); 19 printf("3 < 5是%d\n", 3 < 5); 20 printf("3 > 5是%d\n", 3 > 5); 21 printf("3 >= 5是%d\n", 3 >= 5); 22 printf("3 <= 5是%d\n", 3 <= 5); 23 24 printf("3 < 7 < 5是%d\n", 3 < 7 < 5);//操作符<的结合性是"自左而右" 逻辑表达式3 < 7 < 5计算结果是1 25 printf("3 < 7 && 7 < 5是%d\n", 3 < 7 && 7 < 5); 26 printf("3 < 7 || 7 < 5是%d\n", 3 < 7 || 7 < 5); 27 28 29 /*请理解这条语句 3 > 5 && ++num;*/ 30 3 > 5 && ++num;//计算机中逻辑运算(逻辑与和逻辑或)存在短路特性。当已经判定逻辑表达式运算结果,那么不管逻辑表达式是否全部判定结束都停止判定。 31 printf("num是%d\n", num);//num的打印结果是0 32 3 < 5 || ++num;//逻辑或的短路特性 33 printf("num是%d\n", num);//num的打印结果依然是0 34 35 printf("!6是%d\n", !6); 36 printf("3 + 5 >= 1是%d\n", 3 + 5 >= 1);//双目操作符+ -优先级高于逻辑操作符>= 37 38 return 0; 39 }
1 /* 2 * 位运算。位运算操作符: 3 * 按位取反(~) 4 * 按位与AND(&) 5 * 按位或OR(|) 6 * 按位异或XOR(^) 7 * 8 * 左移<< 9 * 右移>> 10 * */ 11 12 13 #include <stdio.h> 14 15 main() 16 { 17 printf("3 & 5是%d\n", 3 & 5); 18 printf("0x0003 & 0x0005是%x\n", 0x0003 & 0x0005); 19 /*通常可以用按位与运算把某个机器数中特定的位清零*/ 20 printf("0x00FE & 0x00C9 是%X\n", 0x00FE & 0x00C9);//表达式0xFE & 0xC9实现了把特定位(第2.3.5.6位)置零 21 22 printf("3 | 5是%d\n", 3 | 5); 23 printf("0x0003 | 0x0005是%X\n", 0x0003 | 0x0005);//表达式0x0003 | 0x0005实现了把特定位(第1.3位)置1 24 25 printf("3 ^ 5是%d\n", 3 ^ 5); 26 printf("0x005A ^ 0x0026是%X\n", 0x005A ^ 0x0026);//表达式0x005A ^ 0x0026实现把特定位(第2.3.6位)求反 27 printf("0x005A相反数是%X\n", (0x005A ^ 0xFFFF) + 1);//用按位异或XOR巧妙地获得了0x005A的相反数 28 29 printf("~0xffff是%X\n", ~0xffff); 30 31 printf("0xffffffff<<2是%X\n", 0xffffffff<<2); 32 printf("2<<1是%d\n", 2<<1);//左移会给右边补零,左移一位数值扩大2倍 33 printf("0x00000002<<1是0x%X\n", 0x00000002<<1); 34 35 printf("0xffffffff>>2是%X\n", 0xffffffff>>2);//对于源程序中出现的整型常量,编译器默认会将其认定为unsigned int型 36 printf("2>>1是%d\n", 2>>1);//对于无符号类型数据,右移会给左边补零,对于有符号类型数据,右移会给左边补符号位,右移一位数值缩小1/2倍 37 printf("0x00000002>>1是0x%X\n", 0x00000002>>1); 38 }
1 /* 2 * 隐式类型转换练习 3 * */ 4 #include <stdio.h> 5 main() 6 { 7 int num = -10; 8 unsigned int num2 = 3; 9 num = (num + num2) > 0 ? 1 : -1;//隐身类型转换原则:把占有空间小的调整为占有空间大的,如果占有空间一样,则把有符号的数据转换为无符号数据 10 printf("(num + num2) > 0 ? 1 : -1 是%d\n", num);//num是1 11 12 printf("3 > 4 ? 1 : 0.9占有的存储空间是%d\n", sizeof(3 > 4 ? 1 : 0.9)); // 输出8, 因为sizeof根据类型判定表达式结果所占存储空间, // 所以根据类型隐式转换规则,最终应该以double所占空间为准。 // 注意:源程序中的文字 0.9 编译器默认为double类型。 13 14 }
1 /* 2 * 强制类型转换 3 * */ 4 #include <stdio.h> 5 6 main() 7 { 8 int num = 300; 9 printf("num是%d,地址是0x%p\n", num, &num); 10 printf("num是%d,地址是0x%p\n", (unsigned char) num, & num); 11 printf("num是%d,地址是0x%p\n", num, &num); 12 13 }