C语言操作符详解
目录
一、算术操作符
二、移位操作符
三、位操作符
四、赋值操作符
五、单目操作符
六、关系操作符
七、逻辑操作符
八、条件操作符
九、逗号表达式
十、下标引用、函数调用和结构体成员操作符
十一、表达式求值
十二、 操作符查阅表
总结
C语言中的操作符有:
算术操作符、移位操作符、位操作符、赋值操作符、单目操作符、关系操作符、逻辑操作符、
条件操作符、逗号表达式、下标引用操作符、函数调用操作符、结构成员访问操作符
一、算术操作符
+(加) -(减) *(乘) /(除) %(取模)
+(加)-(减)*(乘)与数学中的类似,这里就不过多的叙述了。
(1)% : 取模(取余)得到的是相除之后的余数
(2)/ :除法 得到的是商
- 当 /(除号)两端都是整数时,执行的是整数除法,整数除法得到的结果是整数部分,小数部分直接舍去。
- 两端只要有一个浮点数,执行的就是浮点数的除法,浮点数除法默认保留6位小数。
总结:
- 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。
- 对于 /(除) 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。
- % 操作符的两个操作数必须为整数。返回的是整除之后的余数。
二、移位操作符
<<:左移操作符
>>:右移操作符
注:移位操作符的操作数只能是整数
移位操作符移动的是整数的二进制位
先了解一下C语言中的二进制位
二进制与十进制的转换
例:二进制数:1 1 1 1 ---------> 1*2^3+1*2^2+1*2^1+1*2^0 = 15 (十进制)
二进制不好理解,可以想一想十进制的: 1 2 3 ---------> 1*10^2+2*10^1+3*10^0 = 123
十进制:5 转换为二进制: 0101 ---------> 0*2^3 + 1*2^2 + 0*2^1 + 1*2^0 = 5
二进制、八进制、十进制、十六进制只是数值的表示形式
整数有三种二进制的表示形式:原码、反码、补码
正整数:原码、反码、补码相同
负整数:原码、反码、补码不同,要进行计算!
正整数:
int a = 5;
a是整型,a占4个字节 -->32bit
直接将十进制数转换成为对应的二进制数得到的是原码
a的原码:00000000000000000000000000000101
a的反码:00000000000000000000000000000101
a的补码:00000000000000000000000000000101
最高位0表示正数
负整数:
int b = -5;
b是整型,b占4个字节 -->32bit
负数原码、反码、补码的计算:
原码:直接将十进制数转换成为对应的二进制数
反码:原码的符号位(最高位)不变,其他位按位取反
补码:反码+1
b的原码:1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1
b的反码:1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 0
b的补码:1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1
最高位1表示负数
整数在内存中存储的是补码
可以看一下内存中存储是是不是补码
使用-1来验证
-1的原码:1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
-1的反码:1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0
-1的补码:1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
十六进制数使用 0 1 2 3 4 5 6 7 8 9 a b c d e f 表示
二进制 1 1 1 1 是十进制的15;十六进制的 f 是十进制的15
ff ff ff ff 写成二进制形式:
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
由此可以知道整数在内存中存储的是补码。
1、左移操作符: <<
移位规则: 左边抛弃、右边补0
正数:
#include<stdio.h>
int main()
{
int a = 5;
//将a在内存中存储的二进制位向左移动两位
int b = a << 2;
printf("%d\n", b);
printf("%d\n", a);
return 0;
}
代码分析
虽然变量a左移两位后值发生了变化,但a的值不会发生变化,就相当于 b = a+1,b的值发生了变化,a的值不会变。
运行结果
负数:
#include<stdio.h>
int main()
{
int b = -5;
//将b在内存中存储的二进制位向左移动两位
int c = b << 2;
printf("%d\n", c);
printf("%d\n", b);
return 0;
}
代码分析
运行结果
2、右移操作符: >>
(1)算术右移
规则:右边丢弃,左边补原来的符号位
(2)逻辑右移
规则:右边丢弃,左边补0(如果是负数,左边补0得到的是正数)
算术右移与逻辑右移取决于编译器,C语言中并没有规定具体使用哪种右移。我们常见的编译器下都是算术右移。
正数:
#include<stdio.h>
int main()
{
int a = 5;
//将a在内存中存储的二进制位向右移动1位
int b = a >> 1;
printf("%d\n", b);
printf("%d\n", a);
return 0;
}
代码分析
运行结果
负数:
#include<stdio.h>
int main()
{
int a = -5;
//将a在内存中存储的二进制位向右移动1位
int b = a >> 1;
printf("%d\n", b);
printf("%d\n", a);
return 0;
}
代码分析
运行结果
由代码的运行结果得到,当前编译器采用的是算术右移(左边补符号位)。
注:不要移动负数位
int a = 10;
int b = a >> -2; //标准未定义行为
这种行为属于标准未定义行为(C语言中并没有规定移动负数位)。
三、位操作符
&:按位与 |:按位或 ^:按位异或
注:他们的操作数必须是整数,操作的是整数的补码。
1、按(2进制)位与:&
规则:两个二进制数,有0则为0,全1则为1。
#include<stdio.h>
int main()
{
int a = -3;
int b = -5;
int c = a & b;
printf("%d\n", c);
return 0;
}
计算过程
运行结果
2、按(2进制)位或:|
规则:两个二进制数,有1则为1,全0则为0。
#include<stdio.h>
int main()
{
int a = 3;
int b = -5;
int c = a | b;
printf("%d\n", c);
return 0;
}
计算过程
运行结果
3、按(2进制)位异或:^
规则:两个二进制数,相同为0,相异为1。
#include<stdio.h>
int main()
{
int a = 3;
int b = -5;
int c = a ^ b;
printf("%d\n", c);
return 0;
}
计算过程
运行结果
4、位操作符练习
不创建临时变量(第三个变量),实现两个数的交换。
方法一:
#include<stdio.h>
int main()
{
int a = 3;
int b = 5;
printf("交换前:a=%d b=%d\n", a, b);
a = a + b;
b = a - b;
a = a - b;
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
运行结果
这种方法虽然可以实现不创建临时变量交换两个数的效果,但是当两个数加起来的结果超过了整型的范围就会出错,这种方法不能满足任意两个整数的交换。
方法二:
#include<stdio.h>
int main()
{
int a = 3;
int b = 5;
printf("交换前:a=%d b=%d\n", a, b);
a = a ^ b;
b = a ^ b;//b = a ^ b ^ b
a = a ^ b;//a = a ^ a ^ b
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
运行结果
这种方法可读性太差,实际中交换两个变量应用最多的方法是创建临时变量来进行交换。这里只是练习使用位操作符。
四、赋值操作符
1、= :赋值操作符
赋值操作符是一个很棒的操作符,他可以让你得到一个你之前不满意的值。也就是你可以给自己重新赋值。
int a = 10;
a = 100; //赋值操作符
赋值操作符可以连续赋值,但是不建议这样写。
#include<stdio.h>
int main()
{
int a = 10;
int b = 0;
int c = 20;
a = b = c + 1;//连续赋值
printf("%d\n", a);
return 0;
}
运行结果
上面的写法等同于
int a = 10;
int b = 0;
int c = 20;
b = c + 1;
a = b;
补充:
赋值操作符必须保证左边是变量
- 左值,是可以放在等号左边的,一般是一块空间(变量)。
- 右值,是可以放在等号右边的,一般是一个值,或者是一块空间的内容。
2、复合赋值符
+=:加等 -=:减等 *= :乘等 /=:除等 %=:取模等
>>=:右移等 <<=:左移等 &=:按位与等 |=:按位或等 ^=:按位异或等
#include<stdio.h>
int main()
{
int a = 10;
int b = -5;
int c = 0;
a += 10;//等同于:a = a + 10;
c ^= b;//等同于:c = c ^ b;
printf("%d\n", a);
printf("%d\n", c);
return 0;
}
其他运算符一样的道理。这样写更加简洁。
五、单目操作符
3 + 5:
- 3 是左操作数
- + 是一个操作符
- 5 是右操作数
+ 有两个操作数,它是双目操作符
单目操作符只有一个操作数
!:逻辑反操作 -:负值 +:正值 &:取地址
sizeof:计算操作数的类型长度(以字节为单位)
~:对一个数的二进制按位取反 --:前置、后置--(减1) ++:前置、后置++(加1)
*:间接访问操作符(解引用操作符) (类型):强制类型转换
1、!:逻辑反操作
将一个表达式的结果,真变为假,假变为真。
#include<stdio.h>
int main()
{
int flag = 0;
//flag 为假的时候,打印hehe
if (!flag)
{
printf("hehe\n");
}
return 0;
}
C语言中0表示假,非0表示真(无论是正数还是负数)。
- !真:表示假
- !0:表示真,!0 的值为 1
2、 - :负值
#include<stdio.h>
int main()
{
int i = 0;
int a = -10;
int flag = 1;
for (i = 0; i < 10; i++)
{
printf("%d ", i * flag);
flag = -flag;
}
return 0;
}
运行结果
3、+:正值
这个符号没什么用,对于正数加上+还是正数,对于负数加上+还是负数。
4、&取地址
#include<stdio.h>
int main()
{
int a = 10;
&a;//& - 取地址操作符,获取变量的地址
return 0;
}
5、sizeof:计算操作数的类型长度(以字节为单位)
#include<stdio.h>
int main()
{
int a = 10;
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(int));
printf("%d\n", sizeof a);//因为sizeof是操作符,所以不加括号也行
//下面这种写法不可以,sizeof在计算变量大小时才可以省略括号,
//sizeof在计算类型大小时不可以将括号省略。建议在写的时候都加上括号。
printf("%d\n", sizeof int);//error
//计算数组的大小:
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(int [10]));//int [10] ->是数组 arr 的类型
return 0;
}
运行结果
#include<stdio.h>
void test1(int arr[])
{
printf("%d\n", sizeof(arr));
}
void test2(char ch[])
{
printf("%d\n", sizeof(ch));
}
int main()
{
int arr[10] = { 0 };
char ch[10] = { 0 };
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(ch));
test1(arr);
test2(ch);
return 0;
}
代码分析
运行结果
6、~ 按(内存中补码的二进制)位取反
#include<stdio.h>
int main()
{
int a = 0;
int b = ~a;
printf("%d\n", b);
return 0;
}
计算过程
0的补码:00000000000000000000000000000000
按位取反(~0)后:
补码:11111111111111111111111111111111
反码:11111111111111111111111111111110
原码:10000000000000000000000000000001结果: - 1
~ 按位取反的使用:
#include<stdio.h>
int main()
{
int a = 10;
a |= (1 << 2);
printf("%d\n", a);
a &= ~(1 << 2);
printf("%d\n", a);
return 0;
}
运行结果
7、++:前置、后置++(加1)
前置++:先++,后使用
#include<stdio.h>
int main()
{
int a = 10;
int b = ++a;
printf("a=%d b=%d\n", a, b);
return 0;
}
运行结果
后置++:先使用,再++
#include<stdio.h>
int main()
{
int a = 10;
int b = a++;//先将a的值赋给b,再++
printf("a=%d b=%d\n", a, b);
return 0;
}
运行结果
#include<stdio.h>
int main()
{
int a = 10;
printf("%d\n", a++); //值为10
return 0;
}
#include<stdio.h>
int main()
{
int a = 10;
printf("%d\n", ++a); //值为11
return 0;
}
8、 --:前置、后置--(减1)
前置--:先--,后使用
#include<stdio.h>
int main()
{
int a = 10;
int b = --a;//先将a的值赋给b,再++
printf("a=%d b=%d\n", a, b);
return 0;
}
运行结果
后置--:先使用,再--
#include<stdio.h>
int main()
{
int a = 10;
int b = a--;//先将a的值赋给b,再++
printf("a=%d b=%d\n", a, b);
return 0;
}
运行结果
自增自减(++、--)不要整的太复杂
#include<stdio.h>
int main()
{
int a = 1;
int b = (++a) + (++a) + (++a);
printf("%d\n", b);
printf("%d\n", a);
return 0;
}
运行结果
同样的代码在Linux环境下使用 gcc 编译器运行的结果
由结果可以看出,这个代码是一种错误的代码,在不同的编译器下运行会得到不同的结果。
9、 *:间接访问操作符(解引用操作符)
#include<stdio.h>
int main()
{
int a = 10;
int* pa = &a;
*pa = 20;//* ->解引用操作符(间接访问操作符)
printf("%d\n", a);
return 0;
}
代码分析
运行结果
10、(类型)强制类型转换
#include<stdio.h>
int main()
{
int a =(int)3.14;//3.14 ->double类型
printf("%d\n", a);
return 0;
}
将double类型的数据强制类型转换为整型后,得到是结果是整数部分,小数部分直接舍去。
运行结果
强制类型转换不建议大量的使用,创建变量时尽量将类型匹配。
六、关系操作符
>:大于 >=:大于等于 <:小于 <=:小于等于 !=:不等于 ==:等于
关系操作符比较简单,直接使用就可以了。
要注意:在编程的过程中容易将==和=不小心写错。==是用于测试两个值相等,=是赋值。
七、逻辑操作符
&&:逻辑与 ||:逻辑或
区分逻辑操作符和按位操作符:
- 按位与 & 和按位或 | :这两个操作符是操作二进制位的。
- 逻辑操作符:只关注变量值的真和假。
1、 &&:逻辑与
- &&操作符左边为假(0),就不会计算操作符右边的表达式,整个表达式的结果为假。
- 操作符左边为真,会继续计算操作符右边的表达式。
- 操作符两边只要有一个表达式的值为假,整个表达式的结果为假,值为0。
- 只有两个同时为真,整个表达式的结果才为真,值为1。
练习
#include <stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ && ++b && d++;
printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d);
return 0;
}
代码分析
a=0,a++先返回a=0,a再自增(a的值为1)。当a返回的值为0时就不会再计算&&后面的表达式,所以,a的值为1,b的值为2,c的值为3,d的值为4。最会输出结果,a = 1,b = 2 ,c = 3,d = 4。
运行结果
2、||:逻辑或
- ||逻辑或操作符左边为真,就不会计算操作符左边的表达式,整个表达式的结果为真。
- 操作符左边为假,会继续计算操作符右边的表达式。
- 操作符两边只要有一个表达式的值为真,整个表达式的结果为真,值为1。
- 只有两个都为假,整个表达式的结果为假,值为0。
#include <stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++||++b||d++;
printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d);
return 0;
}
代码分析
a=0,a++先返回a=0,a再自增(a的值为1)。当a返回的值为0时计算 || 后面的表达式,b=2,++b先执行b自增1,再返回自增后的结果为3。当b=3时,表达式(++b)的结果为真,不会再计算||后的表达式。所以a的值为1,b的值为3,c的值为3,d的值为4。最会输出结果,a = 1,b = 3 ,c = 3,d = 4。
运行结果
总结
&&:左操作数为假,右边不计算
||:左操作数作为真,右边不计算
八、条件操作符
表达式1 ? 表达式2 : 表达式3
如果表达式1的结果为真,则执行表达式2,整个表达式的结果为表达式2执行的结果
如果表达式1的结果为假,则执行表达式3,整个表达式的结果为表达式3执行的结果
#include<stdio.h>
int main()
{
int a = 10;
int b = 20;
int max = 0;
max = (a > b ? a : b);
printf("%d\n", max);
return 0;
}
九、逗号表达式
表达式1,表达式2,表达式3 …… 表达式n
逗号表达式,就是由逗号隔开的多个表达式。
逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
#include<stdio.h>
int main()
{
int a = 3;
int b = 5;
int c = 6;
int d = (a += 2, b = a - c, c = a + 2 * b);
printf("%d\n", d);
return 0;
}
代码分析
- 先计算,a += 2,a = 3 + 2 = 5
- 再计算b = a - c,b = 5 - 6 = -1
- 再计算c = a + 2 * b,c = 5 + 2 * (-1) = 3
- d的结果为最后一个表达式的结果,所以输出d的值为3。
运行结果
逗号表达式的使用
#include<stdio.h>
int main()
{
int a = 5;
int count = 0;
while (count < 10)
{
a = a+1;
count++;
}
//简洁的写法
while (a = a + 1, count++, count < 10)
{
;
}
return 0;
}
十、下标引用、函数调用和结构体成员操作符
1、 [ ],下标引用操作符
操作数:数组名+元素下标
#include<stdio.h>
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", arr[7]);//[]就是下标引用操作符,arr 和 7是操作数
return 0;
}
运行结果
补充:
其实编译器在编译arr[7]时,是将arr[7]转换成 *(arr+7)再对数组访问。
*(arr+7) :arr是数组首元素的地址,arr+7就表示从数组首元素来说找到第8个元素(数组下标是从0开始的,所以找到的是第8个元素)。
arr[7] ---> *(arr+7) --->*(7+arr) --->7[arr]
这里只是推导一下有这种形式,但在写代码的时候还是正常写比较好。
2、 ( ) 函数调用操作符
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
无参函数,函数名就是操作数,有参函数,函数名和参数都是操作数。
3、访问一个结构的成员
- . 结构体.成员名
- -> 结构体指针->成员名
#include<stdio.h>
struct Stu
{
char name[20];
int age;
double score;
};
int main()
{
struct Stu s = { "zhangsan",20,85.5 };
// . 操作符
//结构体变量.结构体成员
printf("%s %d %.1f\n", s.name, s.age, s.score);
// -> 操作符
struct Stu* ps = &s;
printf("%s %d %.1f\n", (*ps).name, (*ps).age, (* ps).score);
//结构体指针->结构体成员
printf("%s %d %.1f\n", ps->name, ps->age, ps->score);
return 0;
}
运行结果
十一、表达式求值
表达式求值的顺序一部分是由操作符的优先级和结合性决定。
有些表达式的操作数在求值的过程中可能需要转换为其他类型。
1、隐式类型转换
C的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
(1)整型提升的意义
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度 一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长 度再计算。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令 中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转 换为int或unsigned int,然后才能送入CPU去执行运算。
(2)整型提升的计算过程
整型提升是按照变量的数据类型的符号位来提升的。
#include<stdio.h>
int main()
{
char a = 5;
char b = 126;
char c = a + b;
printf("%d\n", c);
return 0;
}
执行过程
运行结果
参与运算的数据类型是char和char计算,char和short计算,short和short计算都会发生整型提升。
整型提升的例子
(1)
#include<stdio.h>
int main()
{
char a = 0xb6;
short b = 0xb600;
int c = 0xb6000000;
if (a == 0xb6)
printf("a\n");
if (b == 0xb600)
printf("b\n");
if (c == 0xb6000000)
printf("c\n");
return 0;
}
代码分析
- 变量 a 和 b要进行整形提升,但是变量c是整型所以不需要整形提升。
- a和b整形提升之后就变成了负数,所以表达式 a==0xb6 , b==0xb600 的结果为假。
- c不发生整形提升,所以表达式 c==0xb6000000 的结果是真. 所程序输出的结果是: c
运行结果
(2)
#include<stdio.h>
int main()
{
char c = 1;
printf("%u\n", sizeof(c));
printf("%u\n", sizeof(+c));
printf("%u\n", sizeof(-c));
return 0;
}
代码分析
- 变量c只要参与表达式运算,就会发生整形提升。
- 表达式 +c 就会发生提升,所以 sizeof(+c) 是4个字节。表达式 -c 也会发生整形提升,所以 sizeof(-c) 是4个字节,但是 sizeof(c) ,就是1个字节。
- 最后输出的结果是:1 4 4
运行结果
补充:sizeof(),括号内的表达式不参与运算。
int a = 10;
int b = 20;
a + b;
- 表达式有两个属性:值属性,类型属性
- a+b 中:30就是值属性,int就是类型属性
- 当知道类型属性时,sizeof是通过类型属性判断表达式有几个字节,所以不需要计算。表达式的值。
#include<stdio.h>
int main()
{
short s = 20;
int a = 5;
printf("%d\n", sizeof(s = a + 4));
printf("%d\n", s);
return 0;
}
代码分析
- a是整型,a+4表达式的类型是int,将a+4放到变量s中,但是s是short类型,所以表达式的类型就变成了short类型。所以sizeof计算的是short类型字节的大小,结果是2。
- sizeof()括号中的表达式不会真实计算,所以s的值还是20。
- 最后输出的结果是 2 20
运行结果
2、算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类 型,否则操作就无法进行。下面的层次体系称为寻常算术转换。
- long double
- double
- float
- unsigned long int
- long int
- unsigned int
- int
- 上面这些类型之间转换是由下往上转换。
- 例:int 类型的数与flaot类型的数进行计算,最终计算的结果应该是float类型。
如果某个操作数的类型在上面这些列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。
#include<stdio.h>
int main()
{
int a = 5;
float b = 3.14;
float r = a + b;//算术转换
return 0;
}
小于4个字节的类型之间计算是整型提升,其他类型之间计算时算术转换。
注: 算术转换要合理,要不然会有一些潜在的问题。
3、操作符的属性
复杂表达式的求值有三个影响的因素。
- 操作符的优先级
- 操作符的结合性
- 是否控制求值顺序。
两个相邻的操作符取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
(1)操作符优先级
#include<stdio.h>
int main()
{
int a = 10;
int b = 20;
int c = a + b * c;
return 0;
}
乘法的优先级高于加法,所以先计算乘法再计算加法。
(2)操作符结合性
相邻操作符的优先级相同的情况下,取决于结合性。
#include<stdio.h>
int main()
{
int a = 10;
int b = 20;
int c = a + b + c;
return 0;
}
加法的结合性是从左向右计算。所以先计算a+b,再将a+b计算出的结果与c进行计算。
一些问题表达式有了优先级和结合性也不能准确的确定表达式的计算结果
(1)代码1
a * b + c * d + e * f
这个表达式没有唯一确定的计算顺序
第一种:
- 计算a * b
- 计算c * d
- 计算e * f
- 计算a * b + c*d
- 计算a * b + c * d + e * f
第二种:
- 计算a * b
- 计算c * d
- 计算a * b + c*d
- 计算e * f
- 计算a * b + c * d + e * f
如果a,b,c,d,e,f 每个都是一个表达式,那么两种计算顺序得到的结果肯定不同。
由于*比+的优先级高,只能保证,*的计算是比+早,但是优先级并不 能决定第三个*比第一个+早执行。
(2)代码2
#include<stdio.h>
int main()
{
int c = 5;
c + --c;
return 0;
}
这个代码中--的优先级高于+,但是c的取值不确定。
- 加号的左操作数先准备让c=5,先计算--c,c = 4。5 + 4=9
- 加号的左操作数不准备,先计算--c,c = 4。4 + 4 = 8
操作符的优先级只能决定自减--的运算在+的运算的前面,但是我们并没有办法得知,+操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的。
(3)代码3
#include<stdio.h>
int main()
{
int i = 10;
i = i-- - --i * ( i = -3 ) * i++ + ++i;
printf("i = %d\n", i);
return 0;
}
这段代码在不同的编译器下运行的结果是不同的。编译器都凌乱了,所以这是一个问题代码。
(4)代码4
#include<stdio.h>
int fun()
{
static int count = 1;
return ++count;
}
int main()
{
int answer;
answer = fun() - fun() * fun();
printf("%d\n", answer);//输出多少?
return 0;
}
- 虽然这段代码在大多数的编译器上求得结果都是相同的。
- 但是这个代码 answer = fun() - fun() * fun(); 中我们只能通过操作符的优先级得知:先算乘法, 再算减法。函数的调用先后顺序无法通过操作符的优先级确定。
上面这些代码都是问题代码(错误代码),在写代码的时候应该避免这种写法。
十二、 操作符查阅表
操作符 | 描述 | 用法 | 结果类型 | 结合性 | 是否控制求值 顺序 |
---|---|---|---|---|---|
() | 聚组 | (表达式) | 与表达式相同 | N/A | 否 |
() | 函数调用 | 函数名(参数1,...,参数n) | 函数返回类型 | L - R | 否 |
[ ] | 下标引用 | 数组名[下标] | 数组类型 | L - R | 否 |
. | 访问结构体成员 | lexp.member_name | lexp | L - R | 否 |
-> | 访问结构体指针成员 | rexp->member_name | lexp | L - R | 否 |
++ | 后缀自增 | lexp ++ | rexp | L - R | 否 |
-- | 后缀自减 | lexp -- | rexp | L - R | 否 |
! | 逻辑反 | ! rexp | rexp | R - L | 否 |
~ | 按位取反 | ~ rexp | rexp | R - L | 否 |
+ | 单目,表示正值 | + rexp | rexp | R - L | 否 |
- | 单目,表示负值 | - rexp | rexp | R - L | 否 |
++ | 前缀自增 | ++ lexp | rexp | R - L | 否 |
-- | 前缀自减 | -- lexp | rexp | R - L | 否 |
* | 间接访问 | * rexp | lexp | R - L | 否 |
& | 取地址 | & lexp | rexp | R - L | 否 |
sizeof | 计算类型长度, 以字节表示 | sizeof rexp sizeof(类型) | rexp | R - L | 否 |
(类型) | 类型转换 | (类型) rexp | rexp | R - L | 否 |
* | 乘法 | rexp * rexp | rexp | L - R | 否 |
/ | 除法 | rexp / rexp | rexp | L - R | 否 |
% | 整数取余 | rexp % rexp | rexp | L - R | 否 |
+ | 加法 | rexp + rexp | rexp | L - R | 否 |
- | 减法 | rexp - rexp | rexp | L - R | 否 |
<< | 左移位 | rexp << rexp | rexp | L - R | 否 |
>> | 右移位 | rexp >> rexp | rexp | L - R | 否 |
> | 大于 | rexp > rexp | rexp | L - R | 否 |
>= | 大于等于 | repx >= rexp | rexp | L - R | 否 |
< | 小于 | rexp < rexp | rexp | L - R | 否 |
<= | 小于等于 | rexp <= rexp | rexp | L - R | 否 |
== | 等于 | rexp == rexp | rexp | L - R | 否 |
!= | 不等于 | rexp != rexp | rexp | L - R | 否 |
& | 按位与 | rexp & rexp | rexp | L - R | 否 |
^ | 按位异或 | rexp ^ rexp | rexp | L - R | 否 |
| | 按位或 | rexp | rexp | rexp | L - R | 否 |
&& | 逻辑与 | rexp && rexp | rexp | L - R | 是 |
| | | 逻辑或 | rexp || rexp | rexp | L - R | 是 |
? : | 条件操作符 | rexp ? rexp : rexp | rexp | N/A | 是 |
= | 赋值 | lexp = rexp | rexp | R - L | 否 |
+= | 以...加 | lexp += rexp | rexp | R - L | 否 |
-= | 以...减 | lexp -= rexp | rexp | R - L | 否 |
*= | 以...乘 | lexp *= rexp | rexp | R - L | 否 |
/= | 以...除 | lexp /= rexp | rexp | R - L | 否 |
%= | 以...取模 | lexp %= rexp | rexp | R - L | 否 |
<<= | 以...左移 | lexp <<= rexp | rexp | R - L | 否 |
>>= | 以...右移 | lexp >>= rexp | rexp | R - L | 否 |
&= | 以...与 | lexp &= rexp | rexp | R - L | 否 |
^= | 以...异或 | lexp ^= rexp | rexp | R - L | 否 |
|= | 以...或 | lexp |= rexp | rexp | R - L | 否 |
, | 逗号 | rexp , rexp | rexp | L - R | 是 |
表中的操作符由上至下,优先级依次递减,N/A表示无结合性,L - R表示从左至右,R - L表示从右至左。
总结
这篇文章详细的叙述了C语言中的操作符!
熟练的掌握这些操作符对后期的C语言的学习有很大的帮助!