【C语言-操作符】操作符真的很简单吗?

前言

现在想,操作符确实让我刮目相看


本章内容

  1. 操作符介绍
  2. 关于表达式求值(颠覆)

1. 操作符介绍

算术操作符
移位操作符
位操作符
赋值操作符
单目操作符
关系操作符
逻辑操作符
条件操作符
逗号表达式
下标引用、函数调用和结构成员


1.1 算数操作符

+
-
*
/
%:返回整除后的余数

  1. %只能用于整形,其他几个操作符都可以用于整形或浮点数
  2. 对于 / ,如果 两个操作数都为整数 执行 整数除法只要操作数中有浮点数,就执行浮点数除法

1.2 移位操作符

<< 左移操作符:*=2
>>右移操作符: /=2
注:移位操作符的操作数只能是整数不能移动负位

1.2.1 左移操作符

规则:

左边抛弃,右边补0

请添加图片描述
修正:这里是 “ << 1 后”

1.2.2 右移操作符

规则:

1.逻辑移位:

左边补0,右边丢弃

2.算数移位

左边补原符号位,右边丢弃

在这里插入图片描述
更正:int n = -1;


1.3 位操作符

& - 按位与:有0则0
| - 按位或:有1则1
^ - 按位异或:同0异1

&

1 & 1 = 1
1 & 0 = 0
0 & 1 = 0
0 & 0 = 0

|

1 | 1 = 1
1 | 0 = 1
0 | 1 = 1
0 | 0 = 0

^

1 ^ 1 = 0
1 ^ 0 = 1
0 ^ 1 = 1
0 ^ 0 = 0
这玩意能有啥用?

来看看这道题:

不能创建临时变量(第三个变量),实现两个数的交换。

#include <stdio.h>
//这里用到: a^a = 0
//int num = 1
//00000000000000000000000000000001 ^
//00000000000000000000000000000001 = 
//00000000000000000000000000000000
int main()
{
 int a = 3;
 int b = 5;
 a = a^b;// a = 3^5
 b = a^b;// b = 3^5^5 = 3
 a = a^b;// a = 3^5^3 = 5
 printf("a = %d b = %d\n", a, b);
 return 0;
}

1.4 赋值操作符

=

a = b + 10;//把 (b+10) 赋给 a
  • 语法规定可以连续赋值(a= b = c+10),但没必要,可读性不高

1.5 单目操作符

!
逻辑反操作:0->1 1->0
-
负值
+
正值
&
取地址
sizeof
操作数的类型长度(以字节为单位)
~
对一个数的二进制按位取反

前置、后置–
++
前置、后置++
*
间接访问操作符(解引用操作符) :通过操作数,找到其中的地址对应的变量
(类型)
强制类型转换

1.5.1 sizeof 和 数组 的那些事
#include <stdio.h>
void test1(int arr[])
{
 printf("%d\n", sizeof(arr));//(2):4
}
void test2(char ch[])
{
 printf("%d\n", sizeof(ch));//(4):4
}
int main()
{
 int arr[10] = {0};
 char ch[10] = {0};
 printf("%d\n", sizeof(arr));//(1):40
 printf("%d\n", sizeof(ch));//(3):10
 test1(arr);
 test2(ch);
 return 0; 
 }

(1)和(3)这里求的是整个数组的大小
(2)和(4)这里求的是形参arr(首元素地址)的大小,取决于电脑,32位的就是4bytes ; 64位的就是8bytes

1.5.2 前/后置 ++/–

规则:

前置:先++/–(自增/自减),后使用
后置:先使用,后 ++/–(自增/自减)


1.6 关系操作符

>
>=
<
<=
!= 用于测试“不相等”
== 用于测试“相等”

  • == 和 = 需要敏感

1.7 逻辑操作符

它们只管 真假,而且还能控制求值顺序:能得出结果,后面就不计算

&& 逻辑与 : 得一个假就为假
| | 逻辑或:得一个真,就为真

来看看这段代码,输出的结果是?

#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 b=%d c=%d d=%d\n",a,b,c,d);
	return 0;
}
//&&输出的结果是 a=1 b=2 c=3 d=4
//||输出的结果是 a=1 b=3 c=3 d=4

&& : a++,先使用:此时 a=0 , &&得一个假,直接得假 , 后面不计算
| | : b++,即使先使用, b也是2 ,为真 , | | 得一个真,直接得真,后面不计算


1.8 条件操作符

exp1 ? exp2 : exp3
可以读出来:exp1为真吗?为真就执行exp2,为假就执行exp3

#include<stdio.h>
int main()
{
	int a = 10;
	int b = (a > 15) ? 1 : 0;
	printf("%d\n", b);
	return 0;
}
//输出 0
		a>15 为真吗? 为真:1 ; 为假:0
		

1.9 逗号表达式

exp1 , exp2 , … , expN
从左向右依次执行,整个表达式的结果是最后一个表达式的结果

#include<stdio.h>
int main()
{
	int a = 10;
	int b = 20;
	int c = (a + 5, b=10 , b = a + 5);
	printf("%d\n", c);
	return 0;
}
//结果是 15

1.10 下标引用、函数调用 和 结构成员

1.10.1 下标引用操作符

[ ]
操作数: 数组名 + 索引值

int arr[10];//创建数组
 arr[9] = 10;//用下标引用操作符赋值
1.10.2 函数调用操作符

( )
操作数:函数名 + x个参数

int Add(int a, int b)
{
	return a + b;
}
int main()
{
	int a = 10;
	int b = 20;
	Add(a, b);//函数调用
	return 0;
}
1.10.3 结构成员

.
结构体.成员名
->
结构体指针->成员名

struct Student
{
	char name[50];
	int age;
	double score;
};
#include<stdio.h>
int main()
{
	struct Student stu;
	struct Student * p_stu = &stu;//创建了一个 struct Student* 类型的结构体指针

	stu.age = 10;//结构体变量.结构成员
	p_stu->score = 100.0;//结构体指针->结构成员

	printf("The age of stu is %d\nThe score of student is %.2lf",stu.age,p_stu->score);

	return 0;
}

2.表达式求值

表达式求值过程中进行的操作:

隐式类型转换(可能进行)
算数转换(可能进行)
根据操作符属性计算(一定进行)

2.1 隐式类型转换

C语言是“强类型的语言”,所以不同类型相互替换使用需要经过转换

一般规则:

精度低的转换为精度高的

类型转换之一:整型提升

表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度
一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长
度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令
中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转
换为int或unsigned int,然后才能送入CPU去执行运算

说人话:整形运算器只算整形,类型转换是统一计算的条件

来看看如何进行整型提升

按照变量的数据类型的符号位提升

//负数
char c1 = -1;
c1的二进制位(补码)中只有8个比特位:
11111111
此处的 c1 是有符号的 char
高位补充符号位(111111111111111111111111111111111

//正数
char c2 = 1;
c1的二进制位(补码)中只有8个比特位:
00000001
此处的 c2 是有符号的 char
高位补充符号位(000000000000000000000000000000001

//无符号的整形提升:
高位补0

来证实一下:

int main()
{
 char c = 1;
 printf("%u\n", sizeof(c));
 printf("%u\n", sizeof(+c));//这里的+也是运算哦
 printf("%u\n", sizeof(-c));//这里的-也是运算哦
 //对此例中的c,只要发生运算,就会发生整形提升
 return 0;
}

在这里插入图片描述


2.2 算术转换

还是“强类型”的事儿

如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。

算数转换又是如何转?

一样的 , 低精度转向高精度(多占空间是小事,至少不丢失数据了嘛)

long double
double
float
unsigned long int
long int
unsigned int
int
从上到小,精度由高到低

有符号 -> 无符号
< int 就 ->int
>int 就 ->更大的类型


*2.3 赋值类型转换

如:a=b b的类型会向a转换

float f = 3.14;
int num = f;//这时候的 float 转换成 int 就会丢失数据

2.4 操作符的属性

操作符的属性有3大块

  1. 操作符的优先级(操作符相邻才讨论)
  2. 操作符的结合性(优先级相同才讨论)
  3. 是否控制求值顺序(只有 “&&” 、“| |” 、“,”控制求值顺序)

两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。

*优先级的表格会在文末奉上

看一些问题表达式

1#include<stdio.h>
int main()
{
	int a, b, c, d;
	a* b + b * c + c * d;
	return 0;
}

解析:我们知道, * 的优先级比 + 高 , 但是不能保证第三个 * 比第一个 + 先执行
导致了两种可能

int a,b,c,d;
a*b
b*c
a*b + b*c
c*d
a*b + b*c  + c*d

或者

a*b
b*c
c*d
a*b + b*c 
a*b + b*c + c*d
2:
c + --c;

解析: + 的左操作数是在 --之前求值 还是在 --之后求值?

3#include <stdio.h>
int main()
{
 int i = 1;
 int ret = (++i) + (++i) + (++i);
 printf("%d\n", ret);
 printf("%d\n", i);
 return 0;
}

解析:不同编译器结果不同,是全部 ++ 了再 + ,还是 ++ 一个 + 一个?

我们该写什么样的表达式?

通过操作符属性就能确定唯一的计算路径


今天的分享就到这里,不足之处望请斧正


这里是培根的blog,与你共同进步!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

周杰偷奶茶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值