操作符和表达式
操作符
分类:
- 算数操作符
- 移位操作符
- 位操作符
- 赋值操作符
- 单目操作符
- 关系操作符
- 逻辑操作符
- 条件操作符
- 逗号表达式
- 下表引用、函数调用的结构成员
算数操作符
+ - * / %
- 除了%都可以用于整数和浮点数之间的运算
- 除法操作符"/":如果两个操作数都是整数,执行整数除法。只要其中有浮点数执行运算那整个表达式就是浮点数的除法。
- %的两个操作数必须是整数。
移位操作符
<< 左移位 >> 右移位
左移位 左边抛弃右边补0
右移位
- 算术右移
右边丢弃,左边补符号位(*)
- 逻辑右移
右边丢弃,左边补0
警告:对于移位运算符,不要移动负数位,这个标准没有定义。
int num = 10;
num>>-1;//错误的
位操作符
位操作符:
& 按位与 有0就为0
| 按位或 有一就为1
^ 按位异或 相同为0相异为1
操作数必须是整数。
小练习
int num1 = 1;
int num2 = 2;
printf("%d ",num1&num2);
printf("%d ",num1|num2);
printf("%d ",num1^num2);
![image-20211231115746523](https://i-blog.csdnimg.cn/blog_migrate/cd4548015443f12a13bfd6802a2be85f.png)
题:不创建临时变量实现两数的交换
方法一 思路 (加减法可能会溢出)
代码实现
int a = 2;
int b = 3;
a = a+b;
b = a-b;
a = a-b;
方法二 (思路) 一个数对同一个数两次异或回到本身。而且异或运算有交换律
代码实现
int a = 2;
int b = 3;
a = a^b;
b = a^b;
a = a^b;
printf("%d ",a);
printf("%d ",b);
练习:
一个整数存储在内存中的二进制中的1的个数。
思路:1. 短除法思想。(只能计算正数)。
- 按位与1进行计算可以得到最后一位是什么,再加上右移操作。
代码实现:
int main(){
int number = 0;
int count = 0;
scanf("%d",&number);
//不能计算负数
// while(number){
// if (number%2 == 1){
// count++;
// }
// number = number /2;
// }
for(i = 0;i<32;i++){ //32是因为int占32位
if(1==((num>>i)&1))
count++;
}
printf("%d",count);
return 0;
}
赋值操作符(=)
初始化赋值、重新赋值、连续赋值。
复合赋值操作符
+= -= *= /= %= >>= <<= &= |= ^=
单目操作符
操作符 | 作用 |
---|---|
! | 逻辑取反 |
- | 负 |
+ | 正 |
& | 取地址 |
sizeof | 操作数的类型长度(字节位单位) |
~ | 按位取反 |
– | 前置、后置自减 |
++ | 前置、后置自加 |
* | 间接访问操作符(解引用操作符) |
(类型) | 强制类型转化 |
正、负是单目运算符,加、减是双目运算符
1是真但是真不一定是1。
计算数组长度 = sizeof(arr)/sizeof(arr[0])
坑题:
int main(){
short s = 0;
int a = 10;
printf("%d\n",sizeof(s = a + 5));
printf("%d\n",s);
}
解析:sizeof(s = a+5)的大小最后取决于s的类型。
s==5是因为sizeof运算符中的表达式仅仅是表达一下,并不会真正参与运算。
sizeof和数组
void test1(int arr[]){
printf("%d ",sizeof(arr));//(3)
}
void test2(char ch[]){
printf("%d\n",sizeof(ch));//(4)
}
int main(){
int arr[10] = {0};
char ch[10] = {0};
printf("%d ",sizeof(arr));//(1)
printf("%d\n",sizeof(ch));//(2)
test1(arr);
test2(ch);
return 0;
}
解析:
(1)处 = sizeof(int)*10 = 40
(2)处 = sizeof(char)*10 = 10
(3)处 = sizeof(arr第一个元素的指针) = 8或者4 (64位电脑和32位电脑)
(4)处 = sizeof(ch第一个元素的指针) = 8或者4 (64位电脑和32位电脑)
关系操作符
关系操作符
> >= < <= != ==
逻辑操作符
&& ||
区分逻辑与和按位与 && 和&
区分逻辑或和按位或 ||和 |
逻辑是两个,按位是一个。
笔试题
程序运行结果是?
int main(){
int i = 0,a=0,b=2,c=3,d=4;
i = a++ && ++b &&d++;
//i = a++||++b||d++;
printf("a=%d b=%d c=%d d=%d",a,b,c,d);
return 0;
}
运行的结果是:a=1 b=2 c=3 d=4
解析:逻辑与左边为假右边就不计算了。在a++是先使用后运算。相当于i式第一部分就为假了。
程序运行结果是?
int main(){
int i = 0,a=0,b=2,c=3,d=4;
i = a++||++b||d++;
printf("a=%d b=%d c=%d d=%d",a,b,c,d);
return 0;
}
运行结果:a=1 b=3 c=3 d=4
解析:逻辑或左边为真。右边就不计算了。a++为假运行,++b为真运行。d++就不运行了。
条件操作符(三目操作符)
表达式1?表达式2:表达式3
逗号表达式
表达式1,表达式2,表达式3,表达式4……,表达式n
从左向右以此计算。整个表达式的结果是最后一个表达式的结果。
int a = 1;
int b = 2;
int c = (a>b,a = b+10,a,b=a+1);
printf("%d",c);//结果为13
下表引用、函数调用和结构成员
1.[ ]下标引用操作符
操作数:一个数组名+一个索引值
int arr[10];//创建数组
arr[9] = 10;//使用下标引用操作符
[]的两个操作数是arr和9.
2.( )函数调用操作符,接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
3.访问一个结构的成员
. 结构体对象.成员
-> 结构体指针->成员名
//创建了一个结构体类型 Stu C语言中叫成员就类似于面向对象中的属性
struct Stu{
char name[10];
int age;
char id[20];
};
int main(){
//使用结构体struct Stu这个类型创建了一个学生对象,并初始化
struct Stu s1 = {"张三",20,"201808520"};
printf("%s\n",s1.name);
printf("%d\n",s1.age);
printf("%s\n",s1.id);
struct Stu* ps = &s1;
printf("%s\n",ps->name);
printf("%d\n",ps->age);
printf("%s\n",ps->id);
return 0;
}
表达式求值
表达式求值的顺序一部分是由操作符的优先级和结和性决定。
同样,有些表达式的操作数在求值的过程中可能需要转化成其他类型。
隐式类型转化
C的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
整型提升的意义:
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的自己长度,同时也是CPU的通用寄存器的长度。
因此,及时两个char类型相加,在CPU执行时实际上也要先转换为CPU内存整型操作数的标准长度。
通用CPU是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加的指令)。所以表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能进入CPU去执行运算。
实例:
问:输出的结果是什么?
char a = 3;
char b = 127;
char c = a + b;
printf("%d\n",c);
//输出-126
解析:
char a = 3;
//00000000000000000000000000000011
//截断 -> 00000011 -> 存给a
char b = 127;
//00000000000000000000000001111111
//截断 -> 01111111 -> 存给b
char c= a+b;
//a:00000011 整型提升 -> (按符号位提升)
//00000000000000000000000000000011
//b:01111111 整型提升 ->
//00000000000000000000000001111111
//相加
//00000000000000000000000010000010
//截断 -> 10000010 ->存给c
printf("%d\n",c);
//a:10000010 整型提升 -> (按符号位提升)
//11111111111111111111111110000010 -补码
//11111111111111111111111110000001 -反码
//10000000000000000000000001111110 -原码
//转化为十进制:-126
b和c的值被提升为普通整型,然后执行加法运算。加法运算完成之后,结果将被阶段,然后再存储于a中。
那么,什么是整型提升呢?
整型提升是按照变量的数据类型的符号位来提升的。无符号数直接在前面补0即可。
# 负数的整型提升
char a = -1;
整型提升 11111111 (补码) 补码-1-> 反码 正数不变 负数取反
# 正数的整形提升
char = 1;
整型提升 00000001(补码) 正数的三种码都一样
整型提升的例子
//案例1
int main(){
char a= 0xb6;//10110110
short b = 0xb600;
int c = 0xb6000000;
if(a==0xb6)
printf("a");
if(b==0xb600)
printf("b");
if(c==0xb6000000)
printf("c");
return 0;
}
说明整型提升的存在。
算数转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。寻常算数转换。
long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作数的类型在上面这个列表中的排名较低,那么首先要转化为另一个操作数的类型后执行运算。
注意:算术转化要合理进行,要不然会有一些潜在的问题。
float f = 3.14;
int num = f;//因式转换,会精度丢失。
操作符属性
复杂的表达式的求值有三个影响的因素。
- 操作符的优先级
- 操作符的结核性
- 是否控制求值顺序
两个相邻的操作符先执哪个? 取决于优先级。如果相同的优先级,取决于他们的结合性。
操作符的优先级
注:图片引自 [https://blog.csdn.net/zhanghong056/article/details/76667298]
问题代码:
int main(){
int i = 1;
int ret = (++i) + (++i) +(++i);
printf("%d",ret);
printf("%d",i);
return 0;
}
//初步判定计算顺序是三个数的加法 但是是先第一个++i存储下来再计算还是 先改变值再计算。这是又歧义的。
划重点:
写表达式时如果不能通过操作符的属性确定唯一的计算路径,那个表达式就是存在问题的。