c语言中的操作符有很多种,下面我们将它们做一个列举:
算术操作符
+ - * / %
移位操作符
>> <<
位操作符
& ^ |
赋值操作符
= += -= *= /= &= ^= |= >>= <<=
单目操作符
! 逻辑反操作 - 负值 + 正值 & 取地址 sizeof 操作数的类型长度(以字节为单位) ~ 对一个数的二进制按位取反 -- 前置、后置-- ++ 前置、后置++ * 1.间接访问操作符(解引用操作符) 2.乘号 3.指针 (类型) 强制类型转换
关系操作符
> >= < <= != 用于测试“不相等” == 用于测试“相等”
逻辑操作符
&& 逻辑与 || 逻辑或
条件操作符
exp1 ? exp2 : exp3
逗号表达式
exp1, exp2, exp3, …expN
下标引用,函数调用和结构成员
[] () . ->
下面我们来看一段代码了解上述操作符(这里只将一部分操作符列举出来)
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> //C语言操作符 int main(){ //运算符中的除号 printf("%d\n",5/2);//输出结果为2 printf("%f\n",(float)(5)/2);//2.500000 printf("%f\n", (float)(5) / (float)(2));//输出结果为2.500000 printf("%f\n", (float)(5/ 2));//输出结果为2.000000 //运算符中的取余 printf("%d\n",10%3);//输出结果为1 printf("%d\n", -10 % 3);//输出结果为-1 printf("%d\n", 10 % -3);//输出结果为1 printf("%d\n", -10 % -3);//输出结果为-1 //单目操作符中的逻辑反操作 int a = 0; printf("%d\n", !!a);/*在逻辑反操作中,若a为非0数,则!a的 输出结果为0,!!a的输出结果为0 若a为0,则!a的输出结果为1*/ //单目操作符当中的++与-- int b = 10; int k = b++; int o = 10; int L = ++o; printf("%d\n", k);//输出结果为10,此时是先将b赋给k以后再加1 printf("%d\n",L);//输出结果为11,此时是先将o加1以后再赋给L return 0; }
上述代码中我们将除,取余,逻辑反,和++与--列举了出来,下面我们分块对每部分代码进行分析:
除法代码实现
//运算符中的除号 printf("%d\n",5/2);//输出结果为2 printf("%f\n",(float)(5)/2);//2.500000 printf("%f\n", (float)(5) / (float)(2));//输出结果为2.500000 printf("%f\n", (float)(5/ 2));//输出结果为2.000000
第一行输出结果为2,原因是c语言整数除法中只保留整数商,不保留小数点,所以5除2商2余1,结果为2。
第二行输出结果为2.500000,原因是此时我们对5进行了强制类型转换后成为了浮点型5.0,所以此时除以2的结果变成了2.500000
第三行输出结果为2.500000,原因是此时我们对5和2进行了强制类型转换后变成了浮点型5.0,2.0,所以此时两者相除的结果为2.500000
第四行输出结果为2.000000,原因是我们此时要先算括号内的除法,5/2的结果为2,此时再对2进行强制类型转换成浮点型2.000000
注意:浮点型float保留小数点后六位
取余代码实现
//运算符中的取余 printf("%d\n",10%3);//输出结果为1 printf("%d\n", -10 % 3);//输出结果为-1 printf("%d\n", 10 % -3);//输出结果为1 printf("%d\n", -10 % -3);//输出结果为-1
第一行输出结果为1,原因是10对3进行取余的时候看的是分子的正负,所以结果为1
第二行输出结果为-1,原因是-10对3进行取余的时候看的是分子的正负,所以结果为-1
第三行输出结果为1,原因是10对-3进行取余的时候看的是分子的正负,所以结果为1
第四行输出结果为-1,原因是-10对-3进行取余的时候看的是分子的正负,所以结果为-1
逻辑反代码实现
//单目操作符中的逻辑反操作 int a = 0; printf("%d\n", !!a);/*在逻辑反操作中,若a为非0数,则!a的 输出结果为0,!!a的输出结果为0 若a为0,则!a的输出结果为1*/
在逻辑反操作中,在逻辑反操作中,若a为非0数,则!a的输出结果为0,!!a的输出结果为0,若a为0,则!a的输出结果为1
单目操作符中的++与--代码实现
//单目操作符当中的++与-- int b = 10; int k = b++; int o = 10; int L = ++o; printf("%d\n", k);//输出结果为10,此时是先将b赋给k以后再加1 printf("%d\n",L);//输出结果为11,此时是先将o加1以后再赋给L
对于单目操作符中的++与--,分为两种情况:此处我们拿变量a来举例:1.a++表示先将变量a赋给其他变量后再加1
2.++a表示先将a加1后再赋值给其他变量
同理--也是如此:a--表示先将变量a赋给其他变量后再减1 ,--a表示先将a减1后再赋值给其他变量
移位操作符举例
在这里我们拿数字4举例,4的二进制表示为0000 0100
1.此时若我们向右移动移动一位(>>1),根据位操作符右移的规定,右移在左边补最高位的数字(即为符号位0),那么此时4向右移动一位后二进制表示为0000 0010,代表数字2,
4向右移动两位(>>2)后二进制表示为0000 0001,代表数字1
那么我们可以总结规律得出若4向右移动一位为2,向右移动两位为1,即4/2^1=2,4/2^2=1
2.若此时我们向左移动移动一位(<<1),根据位操作符左移的规定,左移统一在右边补0,那么此时4向左移动一位后二进制表示为0000 1000,代表数字8,
4向左移动两位(<<2)后二进制表示为0001 0000,代表数字16
那么我们可以总结规律得出若4向左移动一位为8,向左移动两位为16,即4*(2^1)=8,4*(2^2)=16
位操作符举例
1.&按位与
0000 0101
&0000 0011
0000 0001 规律:两个数按位与时,有0便为0,全是1才是1
2 ^按位异或
0000 0101
^0000 0011
0000 0110 规律:两个数按位异或时,两个数字相同为0,不同为1
3. |按位或
0000 0101
^0000 0011
0000 0111 规律:两个数按位或时,两个数字有一个为1变为1,全部是0才为0
单目操作符的按位取反(~ )
0000 0101
1111 1010 规律:每一位上是1的话取反为0,是0的话取反为1
逻辑操作符
1.逻辑与(&&)
1.逻辑与又称做短路与,格式为表达式1&&表达式2&&...
2.逻辑与中当所有表达式为真时才为真,当其中有一个为假,则此表达式后面的表达式便不再执行了,当表达式为真时才能继续往后执行
下面给出逻辑与的代码实现:
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> //逻辑与(&&) //逻辑与又称做短路与,格式为表达式1&&表达式2&&... //逻辑与中当所有表达式为真时才为真,当其中有一个为假,则此表达式后面的表达式便不再执行了,当表达式为真时才能继续往后执行 int main(){ //例子1 int a = 1; int b = 2; if (a == 9 && b == 20)//此时表达式a==9不符合条件,所以表达式b==20便不会再执行了,则最后的输出结果为"输入有误". { printf("%s\n","hellloworld"); } else{ printf("%s\n","输入有误"); } //例子2 int c = 1; int d = 0; int e = 2; if (c++&&d++&&e++) { printf("一:%d%d%d\n", c, d, e); } else{ printf("二:%d%d%d\n",c,d,e);/*分析:在这里我们可以看到首先是一个c++语句,由于c++语句是先看c是几然后 再递增,那么此时c的值为1(非0),为真(c语言中0为假,非0为真),那么此时 执行d++语句,由于执行d++语句时c++语句已经执行完毕,那么此时c的值为2, 由于d的值为0,为假,那么此时便不再执行e++语句,那么e的值为2,虽然d++语句虽然为假, 但是它只代表其后面的语句不再执行,但其本身仍要执行,所以d的值为1,此时执行的是else 里面的语句,执行结果为二:212*/ } //例子3 int f = 1; int g = 0; int h = 2; if (++f&&++g&&h++) { printf("一:%d%d%d\n", f, g, h);/*分析:在这里我们可以看到首先是一个++f语句,由于++f语句是先递增 那么此时f的值为2非0,为真(c语言中0为假,非0为真),那么此时 执行++g语句,与上面d++的情况不同的是,由于++g是递增加1,所以此时g的值为1(非0) 所以++g为真,则执行h++语句,由于执行h++语句时++g已经执行完毕 那么g的值为1,h++此时先看h是几再递增,此时h为2(非0)为真,则h递增加1后的值为3 由于此时if内整个表达式为真,则执行printf("一:%d%d%d\n", f, g, h);,结果为一:213*/ } else{ printf("二:%d%d%d\n", f, g, h); } //例子4 int i = 0; int j = 1; int k = 2; if (i++&&j++&&k++) { printf("一:%d%d%d\n", i, j, k); } else{ printf("二:%d%d%d\n", i, j, k);/*分析:在这里我们可以看到首先是一个i++语句,由于i++语句是先看i是几然后 再递增,那么此时c的值为0,为假(c语言中0为假,非0为真),那么此时表达式 j++以及表达式k++都不会再被执行,则j和k的值为1,2;虽然i++为假,但是仍要 执行i++语句,则i的值为1,此时执行printf("二:%d%d%d\n", i, j, k); 最终的执行结果为二:112*/ } return 0; }
下面我们来对代码进行分析:
int a = 1; int b = 2; if (a == 9 && b == 20)//此时表达式a==9不符合条件,所以表达式b==20便不会再执行了,则最后的输出结果为"输入有误". { printf("%s\n","hellloworld"); } else{ printf("%s\n","输入有误"); }
此时在if判断条件中首先判断a是否为9,此时为假,则表达式b==20便不再执行了,此时执行printf("%s\n","输入有误");
输出结果为:
int c = 1; int d = 0; int e = 2; if (c++&&d++&&e++) { printf("一:%d%d%d\n", c, d, e); } else{ printf("二:%d%d%d\n",c,d,e); }
分析:在这里我们可以看到首先是一个c++语句,由于c++语句是先看c是几然后再递增,那么此时c的值为1(非0),为真(c语言中0为假,非0为真),那么此时执行d++语句,由于执行d++语句时c++语句已经执行完毕,那么此时c的值为2,由于d的值为0,为假,那么此时便不再执行e++语句,那么e的值为2,虽然d++语句为假,但是它只代表其后面的语句不再执行,但其本身仍要执行,所以d的值为1,此时执行的是printf("二:%d%d%d\n",c,d,e);,执行结果为二:212
输出结果为:
int f = 1; int g = 0; int h = 2; if (++f&&++g&&h++) { printf("一:%d%d%d\n", f, g, h); } else { printf("二:%d%d%d\n", f, g, h); }
分析:在这里我们可以看到首先是一个++f语句,由于++f语句是先递增那么此时f的值为2非0,为真(c语言中0为假,非0为真),那么此时执行++g语句,与上面d++的情况不同的是,由于++g是递增加1,所以此时g的值为1(非0), 所以++g为真,则执行h++语句,由于执行h++语句时++g已经执行完毕,那么g的值为1,h++此时先看h是几再递增,此时h为2(非0)为真,则h递增加1后的值为3,由于此时if内整个表达式为真,则执行printf("一:%d%d%d\n", f, g, h);,结果为一:213*/
输出结果为:
int i = 0; int j = 1; int k = 2; if (i++&&j++&&k++) { printf("一:%d%d%d\n", i, j, k); } else{ printf("二:%d%d%d\n", i, j, k); }
分析:在这里我们可以看到首先是一个i++语句,由于i++语句是先看i是几然后再递增,那么此时c的值为0,为假(c语言中0为假,非0为真),那么此时表达式j++以及表达式k++都不会再被执行,则j和k的值为1,2;虽然i++为假,但是仍要执行i++语句,则i的值为1,此时执行printf("二:%d%d%d\n", i, j, k);最终的执行结果为二:112
输出结果为:
2.逻辑或(||)
1.逻辑或:符号为||,格式:表达式1||表达式2||表达式3||...
2.逻辑或中当其中有一个表达式为真时,那么整个表达式都为真,此时这个表达式后面的表达式便不再执行,当有一个表达式为假时,则继续往后执行寻找为真的表达式,当所有表达式为假时,整个表达式才为假
下面给出逻辑或的代码实现:
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> //逻辑或:符号为||,格式:表达式1||表达式2||表达式3||... /*逻辑或中当其中有一个表达式为真时,那么整个表达式都为真,此时这个表达式后面的表达式便不再执行了,当有一个表达式为假时,则继续往后执行寻找为真的表达式,当所有表达式为假时,整个表达式才为假*/ int main(){ //例子1 int c = 1; int d = 0; int e = 2; if (c++||d++||e++) { printf("一:%d%d%d\n", c, d, e);/*分析:在这里我们可以看到首先是一个c++语句,由于c++语句是先看c是几然后 再递增,那么此时c的值为1(非0),为真(c语言中0为假,非0为真),则c递增 后为2,此时后面的表达式d++与表达式e++便不再执行,则d的值为0,c的值为2,此时的 输出结果为一:202*/ } else{ printf("二:%d%d%d\n", c, d, e); } //例子2 int f = 0; int g = 1; int h = 2; if (f++||g++||h++) { printf("一:%d%d%d\n", f, g, h);/*分析:在这里我们可以看到首先是一个f++语句,由于f++语句是先看f是几然后再递增 那么此时f的值为0,为假(c语言中0为假,非0为真),那么此时继续看g++语句是否为真 g++语句先看g是几再递增,此时g为1(非0),为真,此时其后面的h++语句便不再执行了,则 h的值为2,g递增加1为2,虽然f++为假,但是仍要执行,所以f递增加1后为1,此时执行的是 printf("一:%d%d%d\n", f, g, h); 结果为一:122*/ } else{ printf("二:%d%d%d\n", f, g, h); } return 0; }
下面我们对代码进行分析:
int c = 1; int d = 0; int e = 2; if (c++||d++||e++) { printf("一:%d%d%d\n", c, d, e); } else{ printf("二:%d%d%d\n", c, d, e); }
分析:在这里我们可以看到首先是一个c++语句,由于c++语句是先看c是几然后再递增,那么此时c的值为1(非0),为真(c语言中0为假,非0为真),则c递增后为2,此时后面的表达式d++与表达式e++便不再执行,则d的值为0,c的值为2,此时的输出结果为一:202
输出结果为:
int f = 0; int g = 1; int h = 2; if (f++||g++||h++) { printf("一:%d%d%d\n", f, g, h); } else{ printf("二:%d%d%d\n", f, g, h); }
分析:在这里我们可以看到首先是一个f++语句,由于f++语句是先看f是几然后再递增,那么此时f的值为0,为假(c语言中0为假,非0为真),那么此时继续看g++语句是否为真
g++语句先看g是几再递增,此时g为1(非0),为真,此时其后面的h++语句便不再执行了,则h的值为2,g递增加1为2,虽然f++为假,但是仍要执行,所以f递增加1后为1,此时执行的是printf("一:%d%d%d\n", f, g, h); 结果为一:122
输出结果为:
条件操作符(?)
这个我们就直接上代码吧哈哈
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int main(){ int a = 1; int b = 0; int c = 1; int d = a==1? (b == 1 ? c : a) : c; printf("d的值为%d\n", d); //执行结果为1 return 0; }
此时我们对此语句进行分析:
int d = a==1? (b == 1 ? c : a) : c;
首先判断a是否为1,此时为真,则执行括号内的表达式,再判断b此时是否为1,b等于0不等于1,则输出a,a的值为1,那么d的值为1
输出结果为:
逗号表达式(,)
还是直接上代码吧:
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int main(){ int a = 10; int b = 20; int c = 30; int d = (10, 20, 30,40,50); printf("%d\n", d);//此时d的值为50,原因是整个逗号表达式的结果为逗号表达式的最后一个表达式的结果,牢记即可 return 0; }
此时d的值为50,原因是整个逗号表达式的结果为逗号表达式的最后一个表达式的结果,牢记即可。
输出结果为:
单目操作符(*)
单目操作符*的作用有三个:1.间接访问操作符(解引用操作符) 2.乘号 3.指针(指针在后面会有专题详细介绍,这里先举几个例子简单了解)下面我们来看代码
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> //*号的三个作用 int main(){ //一级指针 int a = 10; int* p = &a;/*p是指针变量,此时我们将a在内存中的地址赋给了p,此时*的作用为指针变量 并且a为整形,则必须用int类型的指针变量*/ *p = 99; printf("p的值为%d\n",*p);//此时*号的作用是间接访问操作符,访问在a的地址中所存的数字,为10 char ch = 'w'; char* q = &ch;//q是char*类型的指针变量,负责存储ch在内存中的地址,又因为ch为char类型的,那么指针变量也必须为char类型 *q = 'r';//此时我们修改q所存储的ch地址中所对应的内容 printf("ch为%c\n", *q);//此时*的作用是间接访问符,访问在ch地址中所存的字符,为'w' int h = 0x12345678; char* d = (char*)&h; printf("%x\n",*d);/*此时的输出结果为78,原因是此时d为char*类型的,那么此时我们对d进行解引用时只能访问地址中的一个字节 并且12345678进行存储时78为低数据,存放在低地址中,这种存储方式为小端存储 */ //二级指针 int k = 10; int* l = &k; int** ll = &l;/*此时我们定义一个二级指针,此二级指针存储的是l的地址,而l的地址此时存储的是k的地址,所以此时 指针变量ll存储的是k的地址*/ printf("%d\n",**ll);//此时我们用**来访问在k地址中所存的数字,为10 return 0; }
分析:1.首先是乘号,C语言中表示乘法的符号为 *,此处不再多做介绍。
2.指针与间接访问符:下面来简单介绍一级指针和间接访问符(*),我们来对上述代码进行分析
int a = 10; int* p = &a;/*p是指针变量,此时我们将a在内存中的地址赋给了p,此时*的作用为指针变量 并且a为整形,则必须用int类型的指针变量*/ *p = 99; printf("p的值为%d\n",*p);//此时*号的作用是间接访问操作符,访问在a的地址中所存的数字,为10
此处我们定义了一个int*类型的一级指针变量p,并且我们将a的地址赋给了p,并且a为整形,则必须用int类型的指针变量,注意我们在输出语句中输出时是*p,为什么是*p呢?原因是*号此时的作用是间接访问符,访问的是p所存储地址中的内容,即为a的值,所以若此时输出*p的值应为10.
此时我们若想改变输出的值,那么我们可以直接给*p赋值,如图所示,此时若要输出*p的值应该为99.
输出结果为:
char ch = 'w'; char* q = &ch;//q是char*类型的指针变量,负责存储ch在内存中的地址,又因为ch为char类型的,那么指针变量也必须为char类型 *q = 'r';//此时我们修改q所存储的ch地址中所对应的内容 printf("ch字符为%c\n", *q);//此时*的作用是间接访问符,访问在ch地址中所存的字符,为'w'
此处我们定义了一个char*类型的一级指针变量q,并且我们将ch的地址赋给了q,并且a为char类型,则必须用char类型的指针变量,注意我们在输出语句中输出时是*q,为什么是*q呢?原因是*号此时的作用是间接访问符,访问的是q所存储地址中的内容,即为ch的值,所以此时若输出*q的值应为w.
此时我们若想改变输出的值,那么我们可以直接给*q赋值,如图所示,此时若要输出*q的值应该为r
输出结果为:
int h = 0x12345678; char* d = &h; printf("%x\n",*d);
此处我们首先定义了一个整形变量h,然后定义了一个char类型的指针变量,在这里我们思考一个问题:此时我们若将h的地址赋给char*类型的指针变量的话,编译时会报错吗?
分析:当然会,如下图所示:
此时编译器会告诉我们int*到char*的类型并不包容,原因是当我们在输出*d时。会进行解引用也就是间接访问,此时d为char*类型,而&h默认为int*类型,此时等式左右两边类型并不相等,则此时会编译错误的。
此时我们修改代码:
int h = 0x12345678; char* d = (char*)&h; printf("%x\n",*d);
现在就不会报错啦,原因是我们在&h前加上了char*类型进行了强制类型转换,此时等式左右两边类型相等,编译通过。
现在我们来思考一个问题:上述代码的输出值到底是多少呢?是12345678还是78呢?
分析:在这里我们先科普下c语言中一个重要的概念:内存。
如图所示,这就是内存,计算机为了更加有效的使用内存,就把内存划分成一个个小的内存单元,每个内存单元的大小是1个字节(图中相当于每一小格是一个字节),为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址,具体如下图所示:
内存的地址为十六进制表示,但是其本质上还是一个整形数据,因为十六进制中的一位对应二进制中的四位,一共有八个数字,每个数字对应四位,一共对应32位,而32位又正好是四个字节,为int类型。
当我们对d进行解引用(*d)时,此时默认只能访问其地址中的一个字节,而其地址中存储的是h的值,也就是十六进制数字0x12345678,因为十六进制中的一位对应二进制中的四位,所以12345678对应二进制中的32位(一共有八个数字,每个数字对应四位,一共对应32位),而32位又正好是四个字节,为int类型,所以当这个十六进制数字在内存中进行存储时,12,34,56,78,各自存储在一个字节上,如下图所示:
在这里大家可能会有疑惑,为什么是这样存储的呢?原因是int类型为四个字节,那么在图中就会占四个格子,在这里需要注意的是78称为低数据,存放在低地址中(此处注意在内存中内存地址是从低往高按照从小往大的顺序排的,其取值范围为0x00000000--------0xFFFFFFFFF(在操作系统为32位的情况下),所以一个字节所占格子的开头称为低地址,末尾称为高地址,那么12便会存在高地址,这种存储方式我们简称为小端存储。
因为访问时一般从低往高进行,所以此时我们访问到了地址中的第一个字节,也就是78,所以最终的输出结果为78.
输出结果为: