文章目录
🐒操作符分类:
算术操作符
移位操作符
位操作符
赋值操作符
单目操作符
关系操作符
逻辑操作符
条件操作符
逗号表达式
下标引用,函数调用和结构成员
🐋算术操作符
+ - * / %
算术操作符呢就是我们生活中的加减乘除取余数,这里我们需要注意的是,C语言中的乘是用*号来表示的,除是用/来表示的,%用来取余数.
1.使用%操作符时必须是整数,其他几个操作符可以作用于浮点数和整数
2.对于/如果两个操作数都为整数,执行整数除法,一旦出现浮点数就执行浮点数除法
通过代码运行实例我们发现如果%操作符旁是小数,系统会报错同时整数除法只能得到整数,浮点数除法才能得到小数。
🛳️移位操作符
<< 左移操作符
>> 右移操作符
注 :操作符的操作数只能是整数
想要理解移位操作符,我们必须了解这里的这个位其实指的是二进制位,通俗的来说就是移动二进制位,后面跟你想要移动的位数。
🏀左移操作符
左边抛弃,右边补0
#include <stdio.h>
int main()
{
int a = 1;
int b = a<<1;
printf("%d\n",a);
printf("%d\n",b);
return 0;
}
可以看到经过向左移动一位后,a的值本身并没有发生改变。移位的值变为了2。原理画图会更加清晰明了。
这里向左移动一位前面的0销毁了,后面会补上一个,所以最后的值由1变为了2
🏀右移操作符
右移运算分为两种:
1.逻辑移位
左边用0填充,右边丢弃
2.算术移位
左边按照原值的符号位填充,右边丢弃
想要讲清右移操作符,得先给大家讲一个知识点。
整数的二进制表示有三种:
原码
反码
补码
正数的原码,反码,补码相同。
负数的原码,反码,补码需要计算。
整数在内存中存储的是补码,所以无论左移还是右移都是移动补码,所以我们在计算负数移位时需要先转化为补码移位后在转化回原码因为在输出时输出的是原码。
//负数原码反码补码的计算规则
int main()
{
//比如这里以-1为例
//-1
//(这里的1是符号位代表负数)10000000000000000000000000000001(原码)
// 11111111111111111111111111111110(符号位不变其他的按位取反得到反码)
// 11111111111111111111111111111111(反码+1得到补码)
int a = -1;
int b =a>>1;
printf("%d",b);
return 0;
}
🦝位操作符
& //按位与
| //按位或^ //按位异或
注:1.他们的操作数必须是整数
2.这个位指定d操作数的二进制位
用几个实例来给大家说明位操作符的使用:
🐻按位&
🐻按位 |
🐻按位异或 (^)
🐲赋值操作符
赋值操作符可以改掉你之前不满意的值,也就是你可以重新赋值。
int a = 10;
int b = 20;
//不满意就重新赋值
a = 30;
还有一些复合赋值符
+=
-=
*=
/=
%=
/>>=
<<=
&=
|=
^=
🐴单目操作符
🐘前置++和后置++应用理解实例
🐬关系操作符
/ >
/ >=
<
<=
!= 不相等
== 相等
注:这里的==是判断相等的,=号是用来赋值的
🐋逻辑操作符
&& 逻辑与
| | 逻辑或
逻辑与和逻辑或就是对现有的值进行操作,而按位与和按位或是对她的二进制来操作
🦍一道面试题
这是逻辑与的理解应用
这是逻辑或的理解运用
⛄条件操作符
exp1 ? exp2 : exp3
如果exp1成立就执行exp2,如果exp1不成立就执行exp3
#include <stdio.h>
int main()
{
//if else 语句
int a = 3;
int b = 0;
if(a>5)
b = 3;
else
b = -3;
//用条件表达式表示
b =(a>5?3:-3);
(a>5)?(b=3):(b=-3);
}
🐋逗号表达式
exp1, exp2, exp3, expN
逗号表达式,就是用逗号隔开的多个表达式
逗号表达式,从左到右依次执行,整个表达式的结果是最后一个表达式的结果。
#include <stdio.h>
int main()
{
//在有些时候使用逗号表达式更加简洁
a = getchar();
count_val(a);
while(a>0)
{
//进行操作
}
//如果用逗号表达式改写
while(a =gerchar(),count_val(a),a>0)
{
//进行操作
}
}
🦭下标引用,函数调用和结构体成员
🛳️[ ] 下标引用操作符
操作数:一个数组名+一个索引值
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
//[]这是操作符,操作数是arr和10
//a + b 可以写成 b + a
arr[7] = 8;
7[arr] = 10;
//这里其实也遵循交换律,通过调试可以发现数组arr下标为7的元素的值变为了10
return 0;
}
🛳️函数调用
//函数也是有操作符的
#include <stdio.h>
int Add(int a,int b)
{
return a+b;
}
int main()
{
int a = 5;
int b = 10;
//函数调用
int c = Add(a,b);
//这里的这个()就是函数调用的操作符,操作数就是Add和a,b
//函数在调用时最少有一个操作数,因为可以不传参
return 0;
}
🏀访问结构体成员
. 结构体.成员名
-> 结构体指针->成员名
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
//定义一个学生类型
struct Stu
{
char name[10];
int age;
char sex[5];
double score;
};
void set_age1(struct Stu stu)
{
stu.age = 18;
}
void set_age2(struct Stu* pStu)
{
pStu->age = 18;//结构体成员访问
}
int main()
{
struct Stu stu;
//指针结构体变量
struct Stu*pStu = &stu;//结构体成员访问
stu.age = 20;
set_age1(stu);
pStu->age = 20;//结构成员访问
set_age2(pStu);
return 0;
}
🐋表达式求值
表达式求值的顺序一部分是由操作符的优先级和结合性决定。
同样,有些表达式的操作数在求值的过程中可能需要转化为其他类型。
🏀隐式类型转换
C的整形算术运算总是至少以却省整数类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整形在使用之前都被转换为普通整形,这样转换是为了整形提升。
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度 ,一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
在表达式进行计算时,char类型或者各种长度小于int长度的整型值,都要转换为int类型的整型,然后执行运算,进行截断,在进行最后的处理。
整形提升是按照变量的数据类型的符号位来提升的
int main()
{
char a = 5;
//因为a,b要相加并且他们都是字符类型所以需要整形提升
//char类型是一个字节=8个比特位,
//这是5本来的二进制位
//00000000000000000000000000000101
//被char类型截断,只能存低8位,实际上a放的就是这个数字
//00000101 - a
//
char b = 126;
//0111 1110 -b
char c = a + b;
//00000101 - a
//0111 1110 -b
//00000000000000000000000000000101 -a
//00000000000000000000000001111110 -b
//00000000000000000000000010000011 -c
//10000011-c
printf("%d\n", c);
//%d要输出整形,c是字符类型再次发生整形提升,
//11111111111111111111111110000011 -c 补码
//11111111111111111111111110000010 反码
//10000000000000000000000001111101 原码
// 最后得到的结果是-125
// 在a+b这个表达式中,达不到一个整形大小就,这个时候就会发生整形提升。
// 整形提升是按照变量的数据类型的符号位来提升
//b和c的值都提升为普通整形,然后再执行加法运算
//加法运算完成后在被截断,然后存储于a中
return 0;
}
🏀整形提升实例
整形提升在我们学习编程中,因为它不易发现我们往往会忽略它,但其实整形提升还是非常重要的我们需要关注。
🦍算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类 型,否则操作就无法进行。下面的层次体系称为寻常算术转换。
long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。
🐢操作符的属性
复杂表达式的求值有三个影响的因素。
-
操作符的优先级
-
操作符的结合性
-
是否控制求值顺序。
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
[操作符优先级](C语言运算符优先级和结合性一览表 (biancheng.net))
一些问题表达式
int main() { int i = 1; int ret = (++i) + (++i) + (++i); printf("%d\n", ret); printf("%d\n", i); return 0; }
这串代码在VS2022的环境下和linux环境下跑出的结果是不一样的,这就说明是一个问题表达式
这串代码在vs环境下加到4然后三个i相加得到12,在linux环境下,先计算前面两个i,当i的值为3时相加得到6,最后i的值加1得到4在和前面的6加得到10.
总结:我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。
操作符优先级表一些问题表达式
int main() { int i = 1; int ret = (++i) + (++i) + (++i); printf("%d\n", ret); printf("%d\n", i); return 0; }
这串代码在VS2022的环境下和linux环境下跑出的结果是不一样的,这就说明是一个问题表达式
这串代码在vs环境下加到4然后三个i相加得到12,在linux环境下,先计算前面两个i,当i的值为3时相加得到6,最后i的值加1得到4在和前面的6加得到10.
总结:我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。