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

【C语言- *** 作符】 *** 作符真的很简单吗?,第1张

前言

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


本章内容
  1. *** 作符介绍
  2. 关于表达式求值(颠覆)

1. *** 作符介绍

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


1.1 算数 *** 作符

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

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

1.2 移位 *** 作符

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

1.2.1 左移 *** 作符

规则:

左边抛弃,右边补0

1.2.2 右移 *** 作符

规则:

1.逻辑移位:

左边补0,右边丢弃

2.算数移位

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


1.3 位 *** 作符

& - 按位与:“0是老大”
| - 按位或:“1是老大”
^ - 按位异或:同0异1

这玩意能有啥用?

来看看这道题:

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

#include 
//这里用到: 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 
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
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
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
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
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
从上到小,精度由高到低


*2.3 赋值类型转换

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

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

2.4 *** 作符的属性

*** 作符的属性有3大块

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

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

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

看一些问题表达式

1#include
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 
int main()
{
 int i = 1;
 int ret = (++i) + (++i) + (++i);
 printf("%d\n", ret);
 printf("%d\n", i);
 return 0;
}

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

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

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


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


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

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/915249.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-16
下一篇 2022-05-16

发表评论

登录后才能评论

评论列表(0条)

保存