1.算术操作符
+ - * / %
- +、-、*、/均可用于操作数是整数和浮点数,而%只能用于操作数是整数。
-
/ 除法:
1.整数除法(除号两端都是整数)
2.浮点数除法(除号的两端只要有一个小数)
#include<stdio.h> int main() { int a = 3 / 2; printf("%d\n", a); double b = 3 / 2; printf("%lf\n", b); double c = 3.0 / 2; printf("%lf\n", c); double d = 3 / 2.0; printf("%lf\n", d); double e = 3.0 / 2.0; printf("%lf\n", e); return 0; }
运行结果:
1
1.000000
1.500000
1.500000
1.500000
-
第二组的结果产生的原因是除号两边都是整数,因此进行整数除法。然后将结果赋予double类型。因此,会显示屏幕上的结果。使用浮点数除法必须有一位小数存在。
2.移位操作符
<< 左移操作符
>> 右移操作符
注:操作符的操作数只能是整数,且移位操作符移动的是二进制
1.在进行移位操作符的学习之前,我们先回忆一下整数的二进制表示形式
整数的二进制表示形式:原码、反码、补码
(整数的存储类型是整型,1个整型是4个字节,32个比特位。第一位是符号位,1代表负数,0代表正数。数据在内存中是以二进制补码的形式进行存储的。)
1.正整数的原码、反码、补码是相同的
原码:根据正整数直接写出二进制序列
2.负整数的原码、反码、补码需要计算
原码:根据正整数直接写出二进制序列
反码:符号位不变,其他位按位取反
补码:反码+1
2.<< 左移操作符
1. 移位移动的是补码的二进制序列
2.移位规则:左边抛弃,右边补0
int main()
{
int a = 9;
//原码:00000000000000000000000000001001
//反码:00000000000000000000000000001001
//补码:00000000000000000000000000001001
int b = a << 1;
//补码:000000000000000000000000000010010
//反码:000000000000000000000000000010010
//原码:000000000000000000000000000010010
//2的1次方+2的4次方=18
printf("%d\n", b);
printf("%d\n", a);
return 0;
}
运行结果:
18
9
3.>> 右移操作符
1. 移位移动的是补码的二进制序列
2.移位规则:
1.逻辑移位:左边用0补充,右边丢弃。
2.算术移位:左边补原来的符号位,右边丢弃。
C语言没有明确规定到底是逻辑移位还是算术移位。一般编译器采用的是算术移位。
int main()
{
int a = 9;
//原码:00000000000000000000000000001001
//反码:00000000000000000000000000001001
//补码:00000000000000000000000000001001
int b = a >> 1;
//右移一位是0000000000000000000000000000100
//补符号位:000000000000000000000000000000100
//补码:000000000000000000000000000000100
//反码:000000000000000000000000000000100
//原码:000000000000000000000000000000100
//2的2次方=4
printf("%d\n", b);
printf("%d\n", a);
return 0;
}
运行结果:
4
9
4.错误写法:
int main()
{
int a = 2;
int b = a << -2;
return 0;
}
注意:对于移位操作符,不能随便移动负数位,这属于标准未定义行为。
3.位操作符
& 按位与
| 按位或
^ 按位与或
注意:操作数必须是整数,且是操作二进制。
1.& 按位与
& ——对应二进制位有0则为0,两个同时为1,才是1
int main()
{
int a = 2;
//原码:00000000000000000000000000000010
//反码:00000000000000000000000000000010
//补码:00000000000000000000000000000010
int b = -7;
//原码:10000000000000000000000000000111
//反码:11111111111111111111111111111000
//补码:11111111111111111111111111111001
int c = a & b;
//& ——对应二进制位有0则为0,两个同时为1,才是1
//00000000000000000000000000000010
//11111111111111111111111111111001
//00000000000000000000000000000000
printf("%d", c);
return 0;
}
运行结果:
0
2.| 按位或
| ——对应二进制位有1则为1,两个同时为0,才是0
int main()
{
int a = 2;
//原码:00000000000000000000000000000010
//反码:00000000000000000000000000000010
//补码:00000000000000000000000000000010
int b = -7;
//原码:10000000000000000000000000000111
//反码:11111111111111111111111111111000
//补码:11111111111111111111111111111001
int c = a | b;
//| ——对应二进制位有1则为1,两个同时为0,才是0
//00000000000000000000000000000010
//11111111111111111111111111111001
//11111111111111111111111111111011
//补码:11111111111111111111111111111011
//反码:11111111111111111111111111111010
//原码:10000000000000000000000000000101
//-5
printf("%d\n", c);
return 0;
}
运行结果:
-5
3.^ 按位与或
^ ——对应二进制位相同为0,相反为1
int main()
{
int a = 2;
//原码:00000000000000000000000000000010
//反码:00000000000000000000000000000010
//补码:00000000000000000000000000000010
int b = -7;
//原码:10000000000000000000000000000111
//反码:11111111111111111111111111111000
//补码:11111111111111111111111111111001
int c = a ^ b;
//^ ——对应二进制位相同为0,相反为1
//00000000000000000000000000000010
//11111111111111111111111111111001
//11111111111111111111111111111011
//补码:11111111111111111111111111111011
//反码:11111111111111111111111111111010
//原码:10000000000000000000000000000101
//-5
printf("%d\n", c);
return 0;
}
运行结果:
-5
4.经典例题
不能使用临时变量,实现两个数字的交换
法一:
int main()
{
int a = 8;
int b = 9;
a = a + b;
b = a - b;
a = a - b;
printf("a = %d ,b = %d\n", a, b);
return 0;
}
//当a、b过大时,可能会有溢出的情况;
法二:
int main()
{
int a = 8;
int b = 9;
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("a = %d ,b = %d\n", a, b);
return 0;
}
运行结果:
a = 9 , b = 8
具体解释如下:
int main()
{
int a = 3;
int b = 5;
//a^a = 0
// 011
// 011
// 000
//a^0 = a
// 011
// 000
// 011
//a^b^a = 5
//011
//110
//101即a^b
//101
//011
//110即a^b^a = b
//a^a^b = 0^b=b
//按位异或支持交换律
}
即
int main()
{
int a = 8;
int b = 9;
a = a ^ b;
b = a ^ b;//b = a ^ b =(a ^ b)^ b = a ^ b ^ b = b ^ b ^ a = 0 ^a = a
a = a ^ b;//a = a^ b ^a =a ^ a ^ b = 0 ^ b = b
printf("a = %d ,b = %d\n", a, b);
return 0;
}
4.赋值操作符
赋值操作符是一个很棒的操作符,他可以让你得到一个你之前不满意的值,也就是你可以给自己重新赋值。
int main()
{
int weight = 120;//体重
weight = 89;//不满意就赋值
double salary = 2400;//薪资
salary = 20000;//使用赋值操作符赋值
}
赋值操作符可以连续使用
int main()
{
int a = 10;
int x = 100;
int y = 180;
//a = x = y + 1;连续赋值操作
x = y + 1;
a = x;//这样的方法更加清晰爽朗且易于调试
return 0;
}
//一般不推荐连续赋值
复合赋值符
+= 加等于
-= 减等于
*= 乘等于
/= 除等于
%= 取余等于
>>= 右移等于
<<= 左移等于
&= 按位与等于
|= 按位或等于
^= 按位异或等于
这些运算符都可以写成复合的效果。
int x = 10;
x = x + 10;
x += 10;//复合赋值与上一行意思相同
//其他运算符一样的道理。这样写更加简洁。
5.单目操作符
单目操作符只有一个操作数。
a+b
a&b
3^5
这些均属于双目操作符
! 逻辑反操作
- 负值
+ 正值
& 取地址
* 间接访问操作符(解引用操作符)
sizeof 操作符的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
-- 前置、后置--
++ 前置、后置++
(类型) 强制类型转换
1.! 逻辑反操作
!可以把假变为真,把假变成真
int main()
{
int flag = 0;
if (flag == 0)
{
printf("hehe\n");
}
if (!flag)//flag为假 打印hehe
{
printf("hehe\n");
}
if (flag)//flag为真 打印haha
{
printf("haha\n");
}
return 0;
}
2. - 负值 + 正值
int main()
{
int a = 5;
int b = -a;
printf("%d\n", b);
return 0;
}
3. & 取地址 * 间接访问操作符(解引用操作符)
& * 应用于指针
int main()
{
int a = 10;
&a;//&-取地址操作符——取出a的地址
int* pa = &a;//a的地址是一个序列,存在pa变量中,pa的类型叫做int*
//*告诉我们pa是指针变量
//int告诉我们pa指向的对象a是int
//此处*表示的是指针类型定义里面的*,这个*表示pa是指针变量
*pa = 20;//*表示解引用操作符——单目操作符
//通过pa中存放的地址,找到指向的空间(内容)
//*pa其实就是a的空间
int c = *pa;
//此处*是解引用操作符,又称间接访问操作符
return 0;
}
4.sizeof 操作符的类型长度(以字节为单位)
1.sizeof 不是函数,是操作符
sizeof 计算的是创建类型变量的大小
int main()
{
int a = 10;
printf("%d\n", sizeof(int));
//printf("%d\n", sizeof int);//sizeof不能直接后面跟类型
printf("%d\n", sizeof(a));
printf("%d\n", sizeof a);//可以证明sizeof不是函数
return 0;
}
运行结果:
4
4
4
2.sizeof可以计算数组大小
int main()
{
int arr[10] = { 0 };
printf("%d\n", sizeof(arr));//计算整个数组的大小,单位是字节
printf("%d\n", sizeof(int [10]));//去掉arr就是数组类型
}
运行结果:
40
40
3.sizeof和数组
void test1(int arr[])//int* 指针
{
printf("%d\n", sizeof(arr));//指针4/8个大小字节
}
void test2(char ch[])//char* 指针
{
printf("%d\n", sizeof(ch));//指针4/8个大小字节
}
int main()
{
int arr[10] = { 0 };
char ch[10] = { 0 };
printf("%d\n", sizeof(arr));//计算数组大小,10个整型
printf("%d\n", sizeof(ch));//计算数组大小,10个字符
test1(arr);//数组传参传的是首元素地址arr[0]
test2(ch);//数组传参传的是首元素地址ch[0]
return 0;
}
运行结果:
40
10
8
8
5.~ 对一个数的补码二进制按位取反
int main()
{
int a = 0;
printf("%d\n", ~a);
//原码00000000000000000000000000000000
//反码00000000000000000000000000000000
//补码00000000000000000000000000000000
//按位取反
//补码:11111111111111111111111111111111
//反码:11111111111111111111111111111110
//原码:10000000000000000000000000000001
//答案为-1
return 0;
}
运行结果:
-1
经典例题:一次输入多组数据
法一:
int main()
{
int a = 0;
while (scanf("%d", &a) == 1)
{
printf("%d\n", a);
}
return 0;
}
法二:
int main()
{
int a = 0;
//scanf 读取失败返回的是EOF
while (scanf("%d", &a) != EOF)
{
printf("%d\n", a);
}
return 0;
}
法三:
int main()
{
int a = 0;
//假设scanf读取失败,返回EOF EOF = -1
//-1的补码:11111111111111111111111111111111
//按位取反:00000000000000000000000000000000
//全0就是假
//while循环停止
while (~scanf("%d", &a) )
{
printf("%d\n", a);
}
return 0;
}
6. -- 前置、后置-- ++ 前置、后置++
1.前置--
int main()
{
int a = 1;
int b = --a;//前置--:先--,后使用
// a = a - 1 ,b = a ;
printf("a=%d,b=%d\n", a, b);
return 0;
}
运行结果:
a = 0 , b = 0
2.后置--
int main()
{
int a = 1;
int b = a--;//前置--:先使用,后--
// b = a ,a = a - 1 ;
printf("a=%d,b=%d\n", a, b);
return 0;
}
运行结果:
a = 0 , b = 1
3.前置++
int main()
{
int a = 1;
int b = ++a;//前置++:先++,后使用
// a = a + 1 ,b = a ;
printf("a=%d,b=%d\n", a, b);
return 0;
}
运行结果:
a = 2, b = 2
4.后置++
int main()
{
int a = 1;
int b = a++;//后置++:先使用,后++
//b = a ; a = a + 1 ;
printf("a=%d,b=%d\n", a, b);
return 0;
}
运行结果:
a = 2 , b = 1
5.经典例题
int main()
{
int a = 10;
printf("%d\n", ++a);//a=a+1
printf("%d\n", a);
return 0;
}
运行结果:
11
11
7.(类型) 强制类型转换
int main()
{
int a = (int)3.14;//()里面放的是类型
//int a = int (3.14);
printf("%d\n", a);
return 0;
}
运行结果:
3
6.关系操作符
> 大于
>= 大于等于
< 小于
<= 小于等于
!= 用于测试“不相等”
== 用于测试“相等”
只能应用于适合的类型上
7.逻辑操作符
&& 逻辑与(并且)
|| 逻辑或(或者)
结果是真,用1表示;
结果是假,用2表示。
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
//a和b都是5,打印hehe
if (a == 5 && b == 5)
{
printf("hehe\n");
}
//a或者b是5,打印haha
if (a == 5 || b == 5)
{
printf("haha\n");
}
return 0;
}
运行结果:
a = 0 , b = 0
经典例题:
&&操作符,左边为假,右边就不用计算了
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ && ++b && d++;
//a后置++:先使用 后++
//0&&++b即为0,因此++b并未计算
//0&&d++也为0,因此d++并未计算
printf("a=%d\nb=%d\nc=%d\nd=%d\n", a, b, c, d);
return 0;
}
运行结果:
a=1
b=2
c=3
d=4
||操作符,左边为真,右边不再计算
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ || ++b || d++;
//a后置++:先使用 后++
//0&&++b,计算++b,即为3,为真,即为1
//1||d++,前面不为0,后面就不用计算
printf("a=%d\nb=%d\nc=%d\nd=%d\n", a, b, c, d);
return 0;
}
运行结果:
a=1
b=3
c=3
d=4
8.条件操作符
exp1 ? exp2 : exp3
又称三目操作符(有三个操作数)
表达式1为真,计算表达式2,不计算表达式3,表达式2的结果就是整个表达式的结果
表达式2为真,不计算表达式2,计算表达式3,表达式3的结果就是整个表达式的结果
1.if else语句与条件操作符的相互转换
#include<stdio.h>
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
if (a > 5)
b = 3;
else
b = -3;
return 0;
}
用条件操作符替换
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
(a > 5) ? (b = 3) : (b = -3);//第一种写法
b = (a > 5) ? (3) : (-3);//第二种写法
return 0;
}
2.例题:求较大值
法一:
int main()
{
int a = 0;
int b = 0;
int m = 0;
scanf("%d %d", &a, &b);
if (a > b)
m = a;
else
m = b;
printf("%d", m);
return 0;
}
法二:
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
printf("%d", (a>b)?(a):(b));
return 0;
}
9.逗号表达式
exp1 , exp2 , exp3 , ... expN
逗号表达式,就是用逗号隔开的多个表达式。
逗号表达式,从左向右依次计算,整个表达式的结果是最后一个表达式的结果!
1.举例
int main()
{
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1);//逗号表达式
//a>b只是一个判断真假,并不会产生什么
//a = b + 10 = 12
//a = 12
//b = a + 1 = 13
//即c = 13
printf("%d\n", c);
return 0;
}
运行结果:
13
2.替换,解决代码冗余
a=get_val();
count_val(a);
while(a>0)
{
//业务处理
a=get_val();
count_val(a);
}
//用逗号表达式
while(a=get_val(),count_val(a),a>0)
{
//业务处理
}
10.下标引用、函数调用和结构成员
[ ] 下标引用
( ) 函数调用
. 结构成员操作符
-> 结构成员操作符
1.下标引用操作符 [ ]
操作数:一个数组名+一个索引值
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
// 0 1 2 3 4 5 6 7 8 9
//数组的起始是有下标的,下标是0开始的
printf("%d\n", arr[2]);//打印第三个元素
//[ ] 下标引用操作符
//操作数是arr和2
return 0;
}
2.函数调用操作符 ( )
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
int main()
{
int len = strlen("abc");//( )函数调用操作符
//操作数是函数名(strlen)和参数(abc)
printf("%d\n", len);
return 0;
}
对于函数调用操作符来说,最少有1个操作数(即有函数名,没有操作数)
3.结构成员访问操作符 . ->
. 结构体.成员名
-> 结构体指针->成员名
1 . 结构体.成员名
int main()
{
//运用结构体创建变量b1,b2
struct Book b1 = {"数据结构","杭哥",88};//进行结构体初始化
struct Book b2 = {"C语言第一课","鹏哥",66};
printf("%s %s %f\n", b1.name, b1.author, b1.price);
printf("%s %s %f\n", b2.name, b2.author, b2.price);
return 0;
}
2. -> 结构体指针->成员名
void Print(struct Book* p)
{
printf("%s %s %f\n", (*p).name, (*p).author, (*p).price);//法一
printf("%s %s %f\n", p->name, p->author,p->price);//法二
}
int main()
{
//运用结构体创建变量b1,b2
struct Book b1 = { "数据结构","杭哥",88 };//进行结构体初始化
struct Book b2 = { "C语言第一课","鹏哥",66 };
Print(&b1);
Print(&b2);
return 0;
}
11.表达式求值
表达式求职的顺序一部分是由操作符的优先级和结合性决定。
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
1.隐式类型转换
- C的整型算术运算总是至少以缺省(默认)整型类型的精度来进行的。
- 为了获得这个精度,表达式中的字符(char)和短整型(short)操作数在使用之前被转换为普通整型,这种转换称为整型提升。
- 整型提升的意义:
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU )的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
整型提升是按照变量的数据类型的符号位来提升的。
int main()
{
//负数的整型提升
char c1 = -1;
//变量c1的二进制(补码)中只有8个比特位
//11111111
//因为char是有符号的char
//因此整型提升的时候,高位补充符号位,即为1
//提升后的结果就是
//11111111111111111111111111111111
//正数的整型提升
char c2 = 1;
//变量c2的二进制(补码)中只有8个比特位
//00000001
//因为char是有符号的char
//因此整型提升的时候,高位补充符号位,即为0
//提升后的结果就是
//00000000000000000000000000000001
//无符号整型提升,高位补0
return 0;
}
例题1:
int main()
{
char c1 = 5; //char——1个字节,8个比特位
//00000101——c1
//00000000000000000000000000000101整型
char c2 = 127;//char——1个字节,8个比特位
//01111111——c2
//00000000000000000000000001111111整型
char c3 = c1 + c2;
//00000101——c1
//01111111——c2
//进行整型提升
//00000000000000000000000000000101整型
//00000000000000000000000001111111整型
//00000000000000000000000010000100补码
//进行截断(因为c3是char类型,只有1个字节,8个比特位)
//10000100——c3 char
//c3进行整型提升 (char类型最高位为符号位)即1代表c3是负数
//高位补的全是1
//11111111111111111111111110000100补码
//11111111111111111111111110000011反码
//10000000000000000000000001111100原码
//2的2次方+2的3次方+2的4次方+2的5次方+2的六次方=124
//符号位为1,即为负数
//即打印结果为-124
printf("%d\n", c3);//%d以十进制的形式打印有符号的整数
return 0;
}
运行结果:
-124
例题2:
int main()
{
char a = 0xb6;//0x开头代表十六进制数字
//b的二进制序列是1011,6的二进制序列是0110
//即10110110
short b = 0xb600;
int c = 0xb6000000;
if (a == 0xb6)//0xb6就是一个数字
printf("a");
if (b == 0xb600)//0xb600就是一个数字
printf("b");
if (c == 0xb6000000)
printf("c");
return 0;
}
//a,b没有打印的原因是:进行整型提升以后,数字会发生变化。
运行结果:
c
例题3:
//%d——10进制的形式打印有符号的整数
//%u——10进制的形式打印无符号的整数
int main()
{
char c = 1;
printf("%u\n", sizeof(c));//1个字节
printf("%u\n", sizeof(+c));//进行整型提升
printf("%u\n", sizeof(-c));//进行整型提升
return 0;
}
运行结果:
1
4
4
2.算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行,下面的层次体系称为寻常算术转换。
long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。
警告:但是算术转换要合理,要不然会有一些潜在的问题。
3.操作符的属性
复杂表达式的求值有三个影响的因素:
- 操作符的优先级
- 操作符的结合性
- 是否控制求值顺序
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
3.1操作符的优先级
表格里面优先级由高到低
操作符 | 描述 | 用法示例 | 结果类型 | 结合性 | 是否控制求值顺序 |
() | 聚组 | (表达式) | 与表达式相同 | N/A | 否 |
() | 函数调用 | rexp ( rexp , ... , rexp ) | rexp | L-R | 否 |
[ ] | 下标引用 | rexp [ rexp ] | lexp | L-R | 否 |
. | 访问结构成员 | lexp.->member_name | lexp | L-R | 否 |
-> | 访问结构指针成员 | rexp->member_name | lexp | L-R | 否 |
++ | 后缀自增 | lexp ++ | rexp | L-R | 否 |
-- | 后缀自减 | lexp -- | rexp | L-R | 否 |
! | 逻辑反 | ! lexp | rexp | R-L | 否 |
~ | 按位取反 | ~ lexp | rexp | R-L | 否 |
+ | 单目,表示正值 | + lexp | rexp | R-L | 否 |
- | 单目,表示负值 | - lexp | rexp | R-L | 否 |
++ | 前缀自增 | ++ lexp | rexp | R-L | 否 |
-- | 前缀自减 | -- lexp | rexp | R-L | 否 |
* | 间接访问 | * lexp | 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 | 否 |
>= | 大于等于 | 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 | 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 | 是 |
//相邻操作符优先级高的先算,低的后算
//相邻操作符的优先级相同的情况下,结合性起作用
int main()
{
int a = 2 + 3 + 5;
return 0;
}
一些问题表达式
表达式的求值部分由操作符的优先级决定。
//代码1
//a * b +c * d + e * f
在计算的过程中,由于 * 比 + 的优先级高,只能保证 * 的计算比 + 早,但是优先级并不能决定第三个 * 比第一个 + 早执行。
//代码2
// c + -- c
操作符的优先级只能决定自减 -- 的运算在 + 的运算的前面,但是我们并没有办法得知,+ 操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的。
//代码3(非法表达式)
int main()
{
int i = 10;
i = i -- - --i * ( i = -3 ) * i ++ + ++i;
printf(" i = %d\n ", i );
return 0;
}
//不同编译器答案不同
// 代码4
int fun()
{
static int count = 1;
return ++count;
}
int main()
{
int answer;
answer = fun() - fun() * fun();
printf("%d\n", answer);//输出多少?
return 0;
}无法确定先调用哪个位置的函数
总结:我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。