0
点赞
收藏
分享

微信扫一扫

第五站:操作符

扒皮狼 2022-03-11 阅读 114

1. 各种操作符的介绍。

2. 表达式求值

目录

1. 操作符分类:

2. 算术操作符

3. 移位操作符

3.1 左移操作符

3.2 右移操作符

4. 位操作符

5. 赋值操作符

6. 单目操作符

6.1 单目操作符介绍

6.2 sizeof 和 数组

7. 关系操作符

8. 逻辑操作符

9. 条件操作符

10. 逗号表达式

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

1. [ ] 下标引用操作符

2. ( ) 函数调用操作符

3. 访问一个结构的成员

12. 表达式求值

12.1 隐式类型转换

12.2 算术转换

12.3 操作符的属性


1. 操作符分类:

算术操作符

移位操作符

位操作符

赋值操作符

单目操作符

关系操作符

逻辑操作符

条件操作符

逗号表达式

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

2. 算术操作符

+ - * / %

1. 除了%操作符之外,其他的几个操作符可以作用于整数和浮点数。

2. 对于/操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。

3. %操作符的两个操作数必须为整数。返回的是整除之后的余数。

3. 移位操作符

移动的是二进制补码

<< 左移操作符

>> 右移操作符

注:移位操作符的操作数只能是整数。

3.1 左移操作符

移位规则:左边抛弃、右边补0

左移,左边丢弃,右边补零
整数在内存中存储的是补码的二进制序列
//int main()
//{
//	//整数的二进制表示有三种形式
//	//原码 反码 补码
//	//正整数得原反补是相同的
//	//负整数得原反补需要计算
//
//	int a = 5;// 5 (5)2=0101;
//	//因为a是整型 4个字节 32个bite位
//	//0000 0000 0000 0000 0000 0000 0000 0101 原码
//	//0000 0000 0000 0000 0000 0000 0000 0101 反码
//	//0000 0000 0000 0000 0000 0000 0000 0101补码
//
//	int c = -5;
//	//-5;
//	//1000 0000 0000 0000 0000 0000 0000 0101 -5的原码
//	//1111 1111 1111 1111 1111 1111 1111 1010 -5的反码
//	// 由原码到反码,符号位不变,其他位按位取反
//	//1111 1111 1111 1111 1111 1111 1111 1011 -5的补码
//	// 由反码到补码,需要加一
//
//	int b = a << 1;//a本质是不变的
//	//移动的是二进制位的补码;
//	//000 0000 0000 0000 0000 0000 0000 01010补码
//	int d = c << 1;
//	//111 1111 1111 1111 1111 1111 1111 10110 -5左移后的补码
//	//111 1111 1111 1111 1111 1111 1111 10101 -5左移后的反码
//	//100 0000 0000 0000 0000 0000 0000 01010 -5左移后的原码
//	//-10
//
//	printf("%d\n", a);//5
//	printf("%d\n", b);//10
//	printf("%d\n", d);//-10
//	//打印出来的值是原码
//	return 0;
//}

3.2 右移操作符

移位规则:

首先右移运算分两种:

1. 逻辑移位左边用0填充,右边丢弃

2. 算术移位左边用原该值的符号位填充,右边丢弃

》》当前编译器在右移时,采用算术右移
》》到底是算术右移还是逻辑右移取决于编译器的!!!
 
右移操作符有两种规则:
1.算术右移:右边丢弃,左边补原符号位
2.逻辑右移:右边丢弃,左边补零
//int main()
//{
//	int a = 5;
//	//0000 0000 0000 0000 0000 0000 0000 0101 5的补码
//	//5是正数,符号位为0,算术右移或逻辑右移都一样
//	int b = a >> 1;
//	//00000 0000 0000 0000 0000 0000 0000 010 5右移后的补码
//
//	int c = -5;
//	//1111 1111 1111 1111 1111 1111 1111 1011 -5的补码
//	int d = c >> 1;
//	//11111 1111 1111 1111 1111 1111 1111 101 -5右移后的补码
//	//11111 1111 1111 1111 1111 1111 1111 100 -5右移后的反码
//	//10000 0000 0000 0000 0000 0000 0000 011 -5右移后的原码
//	printf("%d\n", a);//5
//	printf("%d\n", b);//2
//	printf("%d\n", c);//-5
//	printf("%d\n", d);//-3
//	return 0;
//}

警告⚠ :对于移位运算符,不要移动负数位,这个是标准未定义的。

例如:

int num = 10;num>>-1;//error

4. 位操作符

位操作符有:

& //按位与

| //按位或

^ //按位异或

//位操作符有三种:
//1. & 按(二进制)位与        // a&b——双目操作符
//2. | 按(二进制)位或		   // &a——单目操作符	
//3. ^ 按(二进制)位异或
int main()
{
	int a = 3;
	int b = -5;
	int c = a & b;
	//0000 0000 0000 0000 0000 0000 0000 0011 3的补码
	//1111 1111 1111 1111 1111 1111 1111 1011 -5的补码
	//按位与——两个同时为1,结果才为1;
	//0000 0000 0000 0000 0000 0000 0000 0011  3 & -5的补码
	printf("%d\n", c);//3

	int d = a | b;
	//0000 0000 0000 0000 0000 0000 0000 0011 3的补码
	//1111 1111 1111 1111 1111 1111 1111 1011 -5的补码
	//按位与——  一个为1,结果就为1;
	//1111 1111 1111 1111 1111 1111 1111 1011  3 | -5的补码
	//1000 0000 0000 0000 0000 0000 0000 0101 原码
	printf("%d\n", d);//-5

	int e = a ^ b;
	//0000 0000 0000 0000 0000 0000 0000 0011 3的补码
	//1111 1111 1111 1111 1111 1111 1111 1011 -5的补码
	//按位异或,相同为0,相异为1;
	//1111 1111 1111 1111 1111 1111 1111 1000  3 ^ -5的补码
	//1000 0000 0000 0000 0000 0000 0000 1000  3 ^ -5的原码
	printf("%d\n", e);//-8
	return 0;
}

//总结
//a ^ a = 0;  两个对应的二进制完全相同,异或为0;
//0 ^ a = a;  
//例如:0000 0000 0000 0000 0000 0000 0000 0000  0的补码
//      1111 1111 1111 1111 1111 1111 1111 1011 -5的补码
//0^-5  1111 1111 1111 1111 1111 1111 1111 1011 仍是-5  
//数组 arr1[100] = {1,2,3,4,5}
//数组 arr2[100] = {1,2,3,4}
//有两个数组,数组中的元素个数未知,但是数组元素
//都是成对出现的,只有一个元素出现了一次,找出
//只出现一次的数字
//答案:
// arr1[100] 异或 arr2[100]
//因为相同的两个元素异或结果是0;
//而 0 和 非0 异或 结果为它本身

注:他们的操作数必须是整数。

一道变态的面试题:

//不能创建临时变量,实现两个整数的交换
int main()
{
	int a = 3;
	int b = 5;
	printf("a = %d,b = %d\n", a, b);
	//a = a + b;
	//b = a - b;
	//a = a - b;
	//如果a b值过大 相加则会溢出
	a = a ^ b;//011 ^ 101 = 110 
	b = a ^ b;//110 ^ 101 = 011
	//b = a ^ b—带入— b = a ^ b ^ b = a ^ 0 = a
	a = a ^ b;//110 ^ 011 = 101
	// a = a ^ b ^ a = 0 ^ b = b
	printf("a = %d,b = %d\n", a,b);
	return 0;
}
//练习
//求一个整数存储在内存中二进制中1的个数
//5
//0000 0000 0000 0000 0000 0000 0000 0101
//按位与
//0000 0000 0000 0000 0000 0000 0000 0001
//=0000 0000 0000 0000 0000 0000 0000 0001     1
//5&1==1
//右移
int main()
{
	int i = 0;
	int count = 0;
	int num = 0;
	scanf("%d", &num);
	int sz = sizeof(num) * 8;
	for (i = 0; i < 32; i++)
	{
		if (((num >>i)& 1) == 1)
			count++;
	}
	printf("%d\n", count);
	return 0;
}

5. 赋值操作符

赋值操作符是一个很棒的操作符,他可以让你得到一个你之前不满意的值。也就是你可以给自己重新赋值。

复合赋值符

+

=

-=

*=

/=

%=

>>=

<<=

&=

|=

^=

这些运算符都可以写成复合的效果

//赋值操作符
//int main()
//{
//	int a = 10;
//	int x = 0;
//	int y = 20;
//	a = x = y + 1;
//	a = a + 2;
//	a += 2;
//	printf("%d %d\n", a, x);
//	return 0;
//}

6. 单目操作符

6.1 单目操作符介绍

!          逻辑反操作

-          负值

+          正值

&          取地址

sizeof     操作数的类型长度(以字节为单位)

~          对一个数的二进制按位取反

--         前置、后置--

++         前置、后置++

*          间接访问操作符(解引用操作符)

(类型)      强制类型转换

//单目操作符
//C语言中C99之前没有表示真假的类型
//C99中引入了布尔类型
//#include<stdbool.h>
//int main()
//{
//	_Bool flag1 = false;
//	bool flag2 = true;
//	if (flag2)
//	{
//		printf("hehe\n");
//	}
//	//int num = 10;
//	//if (num)
//	//{
//
//	//}
//	//if (!num)//num为假,做事情
//	//{
//
//	//}
//	return 0;
//}
struct S
{
	char name[20];
	int age;
};
int main()
{
	//&——取地址操作符
	//*——解引用操作符(间接访问操作符)

	int a = 10;
	int* pa = &a;
	*pa = 20;//解引用操作符
	int arr[10] = { 0 };
	&arr;//取出数组的地址,数组的地址应该放到[数组指针]中
	struct S s = { 0 };
	struct S* ps = &s;
	return 0;
}
// ~ 按二进制位按位取反
int main()
{
	int a = 0;
	//0000 0000 0000 0000 0000 0000 0000 0000
	//1111 1111 1111 1111 1111 1111 1111 1111 ~a的补码
	//1000 0000 0000 0000 0000 0000 0000 0001 ~a的原码
	printf("%d\n", ~a);//-1
	return 0;
}

6.2 sizeof 和 数组

//struct S
//{
//	char name[20];
//	int age;
//};
//int main()
//{
//	//&——取地址操作符
//	//*——解引用操作符(间接访问操作符)
//
//	int a = 10;
//	int* pa = &a;
//	*pa = 20;//解引用操作符
//	// *&a ==>a
//	int arr[10] = { 0 };
//	&arr;//取出数组的地址,数组的地址应该放到[数组指针]中
//	struct S s = { 0 };
//	struct S* ps = &s;
//	return 0;
//}

//sizeof()
//计算类型创建的变量所占内存的大小
int main()
{
	//int a = 10;
	//printf("%d\n", sizeof a);
	sizeof是一个操作符,是c语言的关键字,不是函数
	//printf("%d\n", sizeof(a));
	//printf("%d\n", sizeof(int));

	/*int arr[10] = { 0 };
	printf("%u\n", (unsigned int)sizeof(arr));*/

	int a = 10;
	short s = 0;
	printf("%d\n",(unsigned int)sizeof(s = a + 2));//2
	//sizeof()中的表达式不参与计算;
	//sizeof()是在编译期间处理的
	printf("%d\n", s);//0
	return 0;
}
//sizeof()和数组
void test1(int arr[])
{
	printf("%d\n", sizeof(arr));//4
}
void test2(char* ch)
{
	printf("%d\n", sizeof(ch));//4
}
int main()
{
	int arr[10] = { 0 };
	char ch[10] = { 0 };
	printf("%d\n", sizeof(arr));//40
	printf("%d\n", sizeof ch);//10
	test1(arr);
	test2(ch);
	return 0;
}
// ~ 按二进制位按位取反
int main()
{
	//int a = 0;
	0000 0000 0000 0000 0000 0000 0000 0000
	1111 1111 1111 1111 1111 1111 1111 1111 ~a的补码
	1000 0000 0000 0000 0000 0000 0000 0001 ~a的原码
	//printf("%d\n", ~a);//-1

	int a = 11;
	//0000 0000 0000 0000 0000 0000 0000 1011  11的补码
	//0000 0000 0000 0000 0000 0000 0000 0100   11|4
	//0000 0000 0000 0000 0000 0000 0000 1111   15
	//0000 0000 0000 0000 0000 0000 0000 0001   1
	// 1<<2;
	a |= (1<<2);
	printf("%d\n", a);//15
	//a ^= (1 << 2);
	a &= (~(1 << 2));
	printf("%d\n", a);//11
	return 0;
}

7. 关系操作符

关系操作符

>

>=

<

<=

!=      用于测试“不相等”

==     用于测试“相等”

int main()
{
	//比较的不是字符串,而是首字符串的地址
	if ("abcdef" == "abbq")
	{

	}
	return 0;
}

这些关系运算符比较简单,没什么可讲的,但是我们要注意一些运算符使用时候的陷阱。

警告:

在编程的过程中== 和=不小心写错,导致的错误。

8. 逻辑操作符

逻辑操作符有哪些:

&&    逻辑与

||         逻辑或

区分逻辑与和按位与

区分逻辑或和按位或

1&2----->0                        1&&2---->1

1|2----->3                          1||2---->1

#include <stdio.h>
int main()
{
	int i = 0,a=0,b=2,c =3,d=4;
	//i = a++ && ++b && d++;//a=1,b=2,c=3,d=4,i=1
	//对于逻辑与操作符,左边的左值为假,后面全部就不计算了
	
	//若 int a = 1;
	//i = a++ && ++b && d++;//a=2;b=3;c=3;d=5

	i = a++||++b||d++;//a=1;b=3;c=3;d=4
	//a先使用,后++;b先++,后使用
	//i = 0 || 3 一真为真,后面都不计算
	
	//若 int a = 1;
	//i = a++ || ++b || d++;//a=2;b=2;c=3;d=4

	printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d); 
	return 0;
}

9. 条件操作符

exp1 ? exp2 : exp3

10. 逗号表达式

exp1, exp2, exp3, …expN

逗号表达式,就是用逗号隔开的多个表达式。

逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。


int main()
{
	int a = 1;
	int b = 2;
	int c = (a > b, a = b + 10, b = a + 1); //(0,a=12,b=13)
	printf("%d\n", c);//13
	return 0;
}

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

1. [ ] 下标引用操作符

操作数:一个数组名 + 一个索引值 ———————拥有两个操作数 arr 和 4

//下标引用操作符【】
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("%d\n", arr[4]);//5   //arr[4]-->*(arr+4)
	printf("%d\n", 4[arr]);//5   
	//arr
	//arr[4] = 5;  //[]就是下标引用操作符
	//
	return 0;
}

由此可知,【】就是相当于一个操作符,arr和4只是两个操作数,例如a+b和b+a是等价的

2. ( ) 函数调用操作符

接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。

//()函数调用操作符
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int ret = Add(2, 3);//操作数是:Add,2,3
	printf("%d\n", ret);//5
	return 0;
}

3. 访问一个结构的成员

.         结构体.成员名

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

//结构体成员访问操作符
#include<string.h>
typedef struct 
{
	char name[20];
	int age;
	float score;
}stu;
//struct stu
//{
//	char name[20];
//	int age;
//	float score;
//};
void print1(stu s)
{
	printf("name:%s age:%d score:%f\n", s.name, s.age, s.score);

}
void print2(stu* pc)
{
	//printf("name:%s age:%d score:%f\n", (*pc).name, (*pc).age, (*pc).score);
	// 结构体成员操作符 优先级 高于 解引用操作符
	printf("name:%s age:%d score:%f\n", pc->name, pc->age, pc->score);

}
int main()
{
	stu s = { "张山",20,90.5f };
	//s.name="张山峰";//数组名是地址无法直接更改
	strcpy(s.name,"张三丰");
	//scanf("%s", s.name);
	print1(s);
	print2(&s);
	return 0;
}

12. 表达式求值

表达式求值的顺序一部分是由操作符的优先级和结合性决定。

同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。

12.1 隐式类型转换

C的整型算术运算总是至少以缺省整型类型的精度来进行的。

为了获得这个精度,表达式中的字符短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升

整型提升的意义:

表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。

因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。

通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。

如何进行整体提升呢?

整形提升是按照变量的数据类型的符号位来提升的

int main()
{
	char c1 = 3;
	//整型 存入 字符型,4个字节存入1个字节中,会发生截断
	//0000 0000 0000 0000 0000 0000 0000 0011
	//c1 = 0000 0011
	char c2 = 127;
	//0000 0000 0000 0000 0000 0000 0111 1111
	//c2 = 0111 1111
	char c3 = c1 + c2;
	//当c1 和 c2 计算时先进行整型提升,才能相加
	//c1 = 0000 0000 0000 0000 0000 0000 0000 0010
	//c2 = 0000 0000 0000 0000 0000 0000 0111 1111
	//c1+c2 = 0000 0000 0000 0000 0000 0000 1000 0010
	//c3 = c1 + c2;进行截断
	//c3 = 1000 0010
	printf("%d", c3);
	//打印的又是整形,进行整型提升
	//1111 1111 1111 1111 1111 1111 1000 0010;补码
	//1000 0000 0000 0000 0000 0000 0111 1110;原码 = -(2^7-1)
	return 0;
}
int main()
{
	char a = 0xb6;
	//1011 0110
	short b = 0xb600;
	//1011 0110 0000 0000
	int c = 0xb6000000;
	//1011 0110 0000 0000 0000 0000 0000 0000
	if (a == 0xb6)
	//a是字符型,转为整型,需要进行整型提升
	//1111 1111 1111 1111 1111 1111 1011 0110  a
	//0000 0000 0000 0000 0000 0000 1011 0110 //不相等
	{
		printf("a");
	}
	if (b == 0xb600)
	//b是短整型,与整型比较,需要进行整型提升
	//1111 1111 1111 1111 1011 0110 0000 0000 b
	//0000 0000 0000 0000 1011 0110 0000 0000 //不相等
	{
		printf("b");
	}
	if (c == 0xb6000000)
	//c是整形,整型与整型数据进行比较,不需要进行整型提升
	{
		printf("c");
	}
	return 0;
	//打印c
}
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;
}

12.2 算术转换

如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。

 由下至上进行转换

如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。

警告:

但是算术转换要合理,要不然会有一些潜在的问题。

12.3 操作符的属性

复杂表达式的求值有三个影响的因素。

1. 操作符的优先级

2. 操作符的结合性

3. 是否控制求值顺序。

两个相邻的操作符先执行哪个?        

取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。

操作符优先级

 

 一些问题表达式

注释:代码1在计算的时候,由于*比+的优先级高,只能保证,*的计算是比+早,但是优先级并不能决定第三个*比第一个+早执行。

所以表达式的计算机顺序就可能是:

注释:

同上,操作符的优先级只能决定自减--的运算在+的运算的前面,但是我们并没有办法得知,+操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的。

//代码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.1 R

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();中

我们只能通过操作符的优先级得知:先算乘法,再算减法。

函数的调用先后顺序无法通过操作符的优先级确定。

总结:我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。

举报

相关推荐

0 条评论