6. 单目操作符
6.1 单目操作符介绍
!(逻辑反操作)
-(负值)
+(正值)
&(取地址)
sizeof(操作数的类型长度(以字节为单位)
~(对一个数的二进制按位取反)
–(前置、后置–)
++(前置、后置++)
*(间接访问操作符(解引用操作符))
(类型)(强制类型转换)
这里对!(逻辑反操作)解释一下:
#include <stdio.h> int main() { int i = 0; if (!i)//这里的意思是明确的真假 { printf("hehe\n"); } if (i == 0)//而这里的i在判断条件中相当于一个数值 { printf("haha\n"); } return 0; }
对&(取地址操作符)和*(解引用操作符)解析:
int arr[10]; &arr;//取的是整个数组的地址 //也可以&函数;
提一句:首元素不表示首元素地址的两种情况:
- sizeof(数组名),这里的数组名是整个数组的大小空间。
- &数组名,这里的数组名是整个数组的大小。
int a = 10; int *p = &a; *p = 20; //最终a别改为20
通常解引用操作符于指针有关,后期在对其进行理解,这里不在对其过多理解。
对sizeof操作符进行解释(直接看现象):
这里在打印指针的时候不管是什么类型在32位平台下都是4个字节的大小,在64位平台下都是8个字节的大小。
再看一例;
这里错误的原因是sizeof的()应该输入表达式。
再看一例:
这里我们观察到sizeof(s = a + 2)=2;说明计算的是s的空间大小。
6.2 sizeof和数组
还是一样通过现象看本质:
再看一例:
函数传参,传数组名,这里的数组名就是地址,所以在32为平台下是4个字节的大小。
对~(对一个数的二进制按位取反)解析:
#include <stdio.h> int main() { int a = 10; //a=00000000000000000000000000001010 //~a=11111111111111111111111111110101 //最高符号位是1,所以是补码 //11111111111111111111111111110100(~a的反码) //10000000000000000000000000001011=-11(~a的原码) printf("%d\n", ~a); return 0; }
对~这个符号了解后再来看个实例:
#include <stdio.h> //把一个数的第n二进制位改为1,在改为0. int main() { int a = 10; //00000000000000000000000000001010 //假设对第四二进制位修改 //就是用1对其进行左移操作并用按位或进行操作 int n = 0; scanf("%d", &n); ///把一个数的第n二进制位改为1 a = a | (1 << (n - 1)); printf("%d\n", a); //把一个数的第n二进制位改为0 a = a & ~(1 << (n - 1)); printf("%d\n", a); return 0; }
对前置++和后置++以及前置–和后置–解析:
int main() { int a = 3; int b = ++a; //b = ++a等价于a=a+1,b=a;(先自身++后使用)) printf("b=%d\n", b);//b=4 return 0; }
int main() { int a = 3; int c = a++; //c = a++等价于c=a,a=a+1;(先使用后自身++) printf("c=%d\n", c);//c=3 return 0; }
int main() { int g = 2; int d = --g; //int d = --g;//等价于g=g-1,d=g;(先自身--后使用) printf("d=%d\n", d);//d=1 }
int main() { int g = 2; int e = g--; //e = g--等价于e=g,g=g-1;(先使用后自身--) printf("e=%d\n", e);//e=2 }
#include <stdio.h> int main() { float a = 3.14f; int b = (int)a;//float类型强制转化为int类型 return 0; }
7. 关系操作符
>,>=,<,<=,!=,==
注意:编译过程中==和=不小心写错,导致错误。
8. 逻辑操作符
&& 逻辑与 || 逻辑或
逻辑操作符就是判断真假,非0为真,0为假。
看例题:
#include <stdio.h> int main() { int i = 0, a = 0, b = 2, c = 3, d = 4; i = a++ && ++b && d++; printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d); return 0; }//最终输出结果是1234
这里的逻辑与发生短路,逻辑与要从左到右依次执行,碰到0就不用在执行其后面的,这里a++使用的时候是0,在对其进行本身++操作,所以打印a=1,a++使用的时候是0,逻辑与操作在碰到假是时,其后面不再执行,所以后面b,c,d的值都没有变。
#include <stdio.h> int main() { int i = 0, a = 0, b = 2, c = 3, d = 4; i = a++||++b||d++; printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d); return 0; }最终输出结果是1334
这里也是短路问题,从左到右一次执行,逻辑或在碰到真时不在执行其后面表达式,所以a++使用时候是0,然后就要再看++b,而++b是非0为真,所以不在看d++表达式。
总结:逻辑与符号和逻辑或符号都是从左到右依次执行,而逻辑与操作符号是看表达式是否为假,为假就不再看其后的表达式,为真则继续执行,知道碰到为假的表达式。逻辑或符号是只要碰到为真,则不在执行。
9. 条件操作符
exp1 ? exp2 : exp3
有时if else语句不如条件操作符简洁。
if(a>1) { b=3; } else { b=1; }
上面的这段if else语句其实可以用条件操作符,b=(a>1?3:1);
10. 逗号表达式
exp1,exp2,exp3,exp4…
逗号表达式从左到右依次执行,整个表达式结果是最后一个表达式结果。
#include <stdio.h> int main() { int a = 1; int b = 2; int c = (a > b, a = b + 10, a, b = a + 1); printf("%d\n", c); return 0; }//最终输出13.
11. 下标引用、函数调用、结构成员
11.1下标引用操作符([])
int arr[10];//这个[]是创建数组 arr[9]=10;//这个[]是下标引用操作符,它的操作数是9和arr。
11.2函数调用操作符
#include <stdio.h> void test1() { printf("hehe\n"); } void test2(const char* str) { printf("%s\n", str); } int main() { test1(); //实用()作为函数调用操作符。 test2("hello bit.");//实用()作为函数调用操作符。 return 0; }
11.3结构成员
12. 表达式求值
表达式求值的顺序一部分是由操作符的优先级和结合性决定。
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
12.1隐式类型转换
C的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型
提升。
整型提升的意义:
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。通用CPU(general-purposeCPU)是难以直接实现两个8比特字节直接相加运算虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
也就是char类型和short类型在运算时首先都要转化为int类型的再进行计算。
无符号整型提升,高位直接补0,有符号高位补符号位。
例如:
#include <stdio.h> int main() { char a = 5; //00000000000000000000000000000101=5 //CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度 //所以是32位,但是a变量是char类型的所以发生截断 //00000101=a char b = 126; //00000000000000000000000001111110=126 //01111110=b(发生截断) char c = a + b; //整型提升 //(a的最高符号位是0,所以在补齐的时候补0) //(b的最高符号位是0,所以在补齐的时候补0) //a+b=00000000000000000000000000000101+00000000000000000000000001111110 //00000000000000000000000010000011=c //10000011=c(发生截断) printf("%d\n", c); //%d是十进制的方式打印有符号整数 //c的最高符号位是1,所以在补齐的时候补1 //11111111111111111111111100000011(补码) //11111111111111111111111100000010(反码) //10000000000000000000000011111101=-125(原码) return 0; }
再看一例:
#include <stdio.h> int main() { char a = 0xb6; //10110110=a //整型提升后 //11111111111111111111111110110110(a最高符号位是1,所以补1) short b = 0xb600; //1011011000000000=b //整型提升后 //11111111111111111011011000000000 int c = 0xb6000000; if (a == 0xb6)//会发生整型提升不会再等于0xb6 printf("a"); if (b == 0xb600)//会发生整型提升 printf("b"); if (c == 0xb6000000)//int类型不会发生整型提升 printf("c"); //最终输出c return 0; }
#include <stdio.h> int main() { char c = 1; printf("%zu\n", sizeof(c)); printf("%zu\n", sizeof(+c)); printf("%zu\n", sizeof(-c)); //printf("%zu\n",sizeof(c+1));//最终输出也是4. return 0; } //只要short和char类型的运算,就会发生整型提升。
输出结果:
c只要参与表达式运算,就会发生整形提升,表达式 +c ,就会发生提升,所以sizeof(+c) 是4个字节.表达式 -c也会发生整形提升,所以 sizeof(-c) 是4个字节,但是 sizeof© ,就是1个字节.
12.2算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。
float f = 3.14;//由double类型转换为float int num = f;//由float类型转换为int类型
由高精度类型向低精度类型转化会有精度丢失问题的出现。在这里注意只有short类型和char类型转换为int类型时才是整型提升(原因是CPU内整型运算器(ALU)的操作数的字节长度,一般就是int的字节长度,同时也是CPU的通用寄存器的长度),而int类型转化为double等高精度不叫做整型提升,运算时也不遵循整型提升的运算规则。
举一个int类型转换为double和float类型的例子:
可见只是变为了浮点数并补0.