文章目录
一、操作符分类
1.算术操作符
注意:
1. 除了`%`操作符外,其他几个操作符可以作用于整数和浮点数
2. 对于`/`操作符如果两个操作数都为整数,执行整数除法。只要有浮点数执行的就是浮点数除法
3. `%`操作符的两个操作数必须为整数。返回的是整除之后的余数
2.移位操作符
移位操作符,作用在正数的内存存储二进制序列(补码形式)
正数的原码、反码、补码都是相同的;
负数的原码、反码、补码是不相同的(反码:原码除符号位外每位取反;补码:反码末尾+1)
<< 左移操作符
>> 右移操作符
注:移位操作符的操作数只能是整数
2.1 左移位操作符
移位规则 : 左边抛弃,右边补0
2.2 右移操作符
移位规则:分为两种
1)逻辑移位: 左边用0填充,右边丢弃
2)算术移位: 左边用原该值的符号位填充,右边丢弃
右移操作时,逻辑右移还是算术右移看使用的编译器,这里使用的VS2019是算术右移
int main()
{
int num = -1; //-1的补码为1111 1111 1111 1111 1111 1111 1111 1111
// 32个1
num >> 1; //error,这个会出警告(“>>”:未使用表达式结果)
//因为这一行只是做了运算,而运算结果并未赋值给任何变量或者用来做判断条件
num >>= 1;
// 1111 1111 1111 1111 1111 1111 1111 1111
//算术右移: 1111 1111 1111 1111 1111 1111 1111 1111 符号位为1,左边补1
//逻辑右移: 0111 1111 1111 1111 1111 1111 1111 1111 左边补0
return 0;
}
注:对于移位运算符,不要移动负数位,这个标准是未定义的
3.位操作符
& // 按位与, 都为1时,结果为1
| // 按位或, 一个为1时,结果为1
^ // 按位异或 相同为0,不同为1
注:操作数必须是整数
位操作符还是作用在整数的内存中二进制中(补码形式)
练习:编写代码实现:求一个整数存储在内存中的二进制中1的个数
#include<stdio.h>
int main()
{
int num = 10;
int count = 0; //计数
while(num > 0)
{
num &= (num-1); //num和num-1每一次按位与,都会去掉num右边第一个1
count++;
}
printf("%d",count);
return 0;
}
4.赋值操作符
= //赋值操作符
int weight = 20; //重量
//赋值操作符可以连续使用
int a = 10;
int b = 0;
int c = 20;
a = b = c + 1; //连续赋值
复合赋值符
+= -= *= /= %= >>= <<= &= |= ^=
5.单目操作符
! //逻辑反操作,真取假(非0取0),假取真(0取1)
~ //对一个数的二进制位按位取反(补码取反)----例如:-1取0
— //负值
+ //正值
& //取地址
sizeof //操作数的类型长度(以字节为单位),求变量(类型)所占空间大小
-- //前置、后置--
++ //前置、后置++
* //间接访问操作符(解引用操作符)
(类型) //强制类型转换
对于- -和++操作符: 前置-------->先自增(减)再使用,后置------->先使用再自增(减)
练习:求a, b, c的值
#include <stdio.h>
int main()
{
int a, b, c;
a = 5;
c = ++a; //a=5+1 , c=a=6
b = ++c, c++, ++a, a++; //a=6+1+1=8, b=c+1=7, c=c+1+1=8
b += a++ + c; //b=a+b+c=23 , c=8 , a=9
printf("a = %d b = %d c = %d\n:", a, b, c); //a=9 , b= 23 ,c=8
return 0;
}
6.关系操作符
> //大于
>= //大于等于
< //小于
<= //小于等于
!= //不等于
== //等于
7.逻辑操作符
&& //逻辑与 从左到右,两边表达式同为真,返回1;一个为假,返回0
|| //逻辑或 从左到右,一个表达式为真,返回1;同时为假,返回0
练习:
#include <stdio.h>
int main()
{
int i = 0,a=0,b=2,c =3,d=4;
i = a++ && ++b && d++;
//i = a++||++b||d++;
printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
return 0; //输出的结果为1,2,3,4,因为a为0,右边不执行,然后a再自增1
}
8.条件操作符
exp1 ? exp2 : exp3 //exp1为真时,返回exp2;为假时,返回exp3
9.逗号操作符
exp1, exp2, exp3, ....expN
逗号表达式,就是用逗号隔开的多个表达式
逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果
练习:
int a = 1;
int b = 2;
int c =(a > b,a = b + 10, a, b = a + 1);// c=13
//也可以这样使用
while(a = get_val(),count_val(a),a>0)
{
//函数体
}
//上述代码可以替代
a = get_val();
count_val(a);
while(a > 0)
{
a = get_val();
count_val(a);
}
10.下标引用、函数调用和结构成员
[] //下标引用操作符
() //函数调用操作符
. //结构体.成员名
-> //结构体指针->成员名
二、表达式求值
表达式求值的顺序一部分是由操作符的优先级和结合性决定的
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型
1.隐式类型转换
C的整型算术运算总是至少以缺省整型的精度来进行的
为了获得整个精度,表达式的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升
整型提升的意义:
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度
一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长
度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令
中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转
换为int或unsigned int,然后才能送入CPU去执行运算
eg:
char a,b,c;
...
a = b + c; //b和c的值被提升为普通整型,然后再执行加法运算
//加法运算完成之后,结果被截断再存储再a中
如何进行整型提升呢?
整型提升是按照变量的数据类型的符号位提升的
//负数的整形提升
char c1 = -1;
//变量c1的二进制位(补码)中只有8个比特位:
//1111111
//因为 char 为有符号的 char
//所以整形提升的时候,高位补充符号位,即为1
//提升之后的结果是:
//11111111111111111111111111111111
//正数的整形提升
//char c2 = 1;
//变量c2的二进制位(补码)中只有8个比特位:
//00000001
//因为 char 为有符号的 char
//所以整形提升的时候,高位补充符号位,即为0
//提升之后的结果是:
//00000000000000000000000000000001
//无符号整形提升,高位补0
int main()
{
char c = 1;
printf("%u\n", sizeof(c)); // 1
printf("%u\n", sizeof(+c)); // 4
printf("%u\n", sizeof(-c)); // 4
return 0;
}
c只要参与了表达式运算,就会发生整型提升,表达式+c
,就会发生提升,所以sizeof(+c)
就是4个字节,表达式-c
也会发生整型提升,所以sizeof(-c)
是4个字节
练习:
int main()
{
char a = 0xb6; //1011 0110
short b = 0xb600; //1011 0110 0000 0000
int c = 0xb6000000;
if (a == 0xb6)
printf("a");
if (b == 0xb600)
printf("b");
if (c == 0xb6000000)
printf("c");
return 0;
}
结果为c,因为a和b参与了表达式,发生整型提升是个负数,不执行,所以执行c
2.算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则就无法进行。下面的层次体系称为寻常算术转换
如果某个操作数的类型上面这个列表中排名较低,那么首先要转换为另一个操作数的类型后执行运算。
注:但是算术转换要合理,不然会有一些潜在的问题。
float a = 3.14 ;
int num = f; //隐式转换,会有精度丢失
三、操作符的属性
复杂表达式的求值有三个影响的因素
- 操作符的优先级
- 操作符的结合性
- 是否控制求值顺序
两个相邻的操作符先执行哪个?取决于它们的优先级,如果优先级相同,取决它们的结合性‘
1.操作符的优先级、结合性和求值顺序
从上到下,优先级递减
2.问题表达式
//表达式 1
a*b + c*d + e*f
表达式1在计算的时候,由于*
比+
的优先级高,只能保证,*
的计算比+早,但是优先级并不能决定第三个*
比第一个早执行
所以表达式的计算机顺序就可能是:
a*b
c*d
a*b + c*d
e*f
a*b + c*d + e*f
或者:
a*b
c*d
e*f
a*b + c*d
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;
}
表达式3在不同的编译器中测试结果:
值 | 编译器 |
---|---|
-128 | Tandy 6000 Xenix 3.2 |
-95 | Think C 5.02(Macintosh) |
-86 | IBM PowerPC AIX 3.2.5 |
-85 | Sun Sparc cc(K&C编译器) |
-63 | gcc,HP_UX 9.0,Power C 2.0.0 |
4 | Sun Sparc acc(K&C编译器) |
21 | Turbo C/C++ 4.5 |
22 | FreeBSD 2.1R |
30 | Dec Alpha OSF1 2.0 |
36 | Dec VAX/VMS |
42 | Microsoft C 5.1 |
//表达式4
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();
中我们只能通过操作符的优先级得知:先算乘法,再算减法,函数的调用先后顺序无法通过操作符的优先级确定
//表达式5
int main()
{
int i = 1;
int ret = (++i) + (++i) + (++i);
printf("%d\n",ret);
printf("%d\n",i);
return 0;
}
VS2013环境的结果:
同样的代码产生了不同的结果,这是为什么?
这段代码中的第一个+
在执行的时候,第三个++
是否执行,这个是不确定的,因为依靠操作符的优先级和结合性是无法决定第一个+
和第三个前置++
的先后顺序
总结: 写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的