第五节 操作符

2023-12-19 21:48:39

第五节 操作符





本章重点:
各种操作符的介绍
表达式求值


一. 操作符分类

算术操作符 + - * / %
移位操作符 >> <<
位操作符 & | ^
赋值操作符 = += -= *= /=
单目操作符 ! size of ++ --
关系操作符 > < >= <= == !=
逻辑操作符 && ||
条件操作符 ? :
逗号表达式 ,
下标引用、函数调用和结构成员 [] () . ->


二. 算术操作符

+ - * / %

  1. 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。
  2. 对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。
  3. % 操作符的两个操作数必须为整数。返回的是整除之后的余数。


三. 移位操作符

<< 左移操作符
>> 右移操作符

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

1. 二进制位

整数的二进制表现形式一共有三种,原码、反码和补码。
原码:按照数值的正负,直接写出的二进制序列就是原码。
反码:原码的符号位不变,其他位按位取反。
补码:反码的二进制+1就得到了补码。

对于有符号的整数来说,最高位的1位是符号位。
符号位是1表示负数
符号位是0表示正数。
对于无符号整数来说,没有符号位,所有位都是有效位。

对于正的整数,原码、反码和补码相同,无需计算。
对于负的整数,原码、反码和补码是需要计算的。

整数在内存中存储的都是补码的二进制序列。
整数在计算的时候也使用的是补码!


2. 左移操作符

位移规则:左边抛弃,右边补零

代码如下:

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

运行结果如下:

在这里插入图片描述


#include<stdio.h>
int main() {
	int m = -7;
	int n = m << 1;
	printf("%d\n",m);
	printf("%d",n);
	return 0;
}

运行结果如下:

在这里插入图片描述


m在没被赋值的情况下自身的值不会被改变。


多写几组代码进行比对。发现,对于正整数而言,左移一位操作运算就是乘2。再次深入研究,发现正整数左移运算的效果就是将原整数的值乘以2的指定次幂。例如,将一个整数n左移k位,相当于将n乘以2的k次方。对于负整数而言,左移一位操作运算也是乘2。


3. 右移操作符

右移操作符分两种方式:一种是算数右移,另一种是逻辑右移
逻辑右移:右边直接丢弃,左边补0
算数右移:左边用原该值的符号位填充,右边丢弃

不同的编译器,采用的右移方式也不同。绝大部分编译器采用的是算数右移。


vs2019采用的是算数右移,代码如下:

#include<stdio.h>
int main()
{
	int a = -10;
	int b = a >> 1;
	printf("b=%d\n", b);
	printf("a=%d\n", a);
	return 0;
}

运行成功,结果如下:

在这里插入图片描述


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

例如:

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


四. 位操作符

位操作符有:
按位与 &
按位或 |
按位异或 ^

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


1. 按位与

代码如下:

#include<stdio.h>
int main()
{
	int a = 3;
	int b = -5;
	int c = a & b;//按(2进制)位与
	//00000000000000000000000000000011 --- 3的补码
	//10000000000000000000000000000101 
	//11111111111111111111111111111010
	//11111111111111111111111111111011 --- -5的补码
	//00000000000000000000000000000011 --- 3的补码
	//00000000000000000000000000000011
	printf("%d\n", c);
	return 0;
}

运行代码成功,结果如下:

在这里插入图片描述


我们可以利用按位与得出二进制位的最后一位,代码如下:

#include<stdio.h>
int main()
{
	int a = 3;
	//a&1
	//00000000000000000000000000000011 --- 3的补码
	//00000000000000000000000000000001
	return 0;
}

要是要用到其他二进制位,可以先位移,再按位与。


2. 按位或

代码如下:

#include<stdio.h>
int main()
{
	int a = 3;
	int b = -5;
	//00000000000000000000000000000011 --- 3的补码
	//10000000000000000000000000000101 
	//11111111111111111111111111111010
	//11111111111111111111111111111011 --- -5的补码
	int c = a | b;
	//00000000000000000000000000000011 --- 3的补码
	//11111111111111111111111111111011 --- -5的补码
	//11111111111111111111111111111011 
	//11111111111111111111111111111010
	//10000000000000000000000000000101   -5
	//11111111111111111111111111111011
	//10000000000000000000000000000100
	//10000000000000000000000000000101
	printf("%d\n", c);
	return 0;
}

运行结果成功,结果如下:

在这里插入图片描述


3. 按位异或

相同为0,相异为1

代码如下:

#include<stdio.h>
int main()
{
	int a = 3;
	int b = -5;
	//00000000000000000000000000000011 --- 3的补码
	//10000000000000000000000000000101 
	//11111111111111111111111111111010
	//11111111111111111111111111111011 --- -5的补码
	int c = a ^ b;
	//00000000000000000000000000000011
	//11111111111111111111111111111011
	//11111111111111111111111111111000
	//10000000000000000000000000000111
	//10000000000000000000000000001000
	printf("%d\n", c);
	return 0;
}

运行代码成功,结果如下:

在这里插入图片描述


注意:
a^a = 0
0^a = a


有一道很有意思的面试题,题目如下:

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

#include<stdio.h>
int main() {
	int a = 3, b = 5;
	a = a ^ b;
	b = a ^ b;
	a = a ^ b;
	printf("a=%d,b=%d\n", a, b);
	return 0;
}

运行代码成功,结果如下:

在这里插入图片描述


五. 赋值操作符

使用赋值操作符需要注意一些编码规范,例如:

a = x = y+1; 

这样的连续赋值代码感觉怎么样?

那同样的语义,你看看:

x = y+1;
a = x;

这样的写法是不是更加清晰爽朗而且易于调试。


六. 单目操作符

1. 概述

! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
– 前置、后置–
++ 前置、后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换


2. sizeof是操作符而不是函数

代码证明如下:

#include <stdio.h>
int main()
{
	int a = 10;
	printf("%d\n", sizeof(a));
	printf("%d\n", sizeof a);//后面的括号在括号中写的不是类型的时候,括号可以省略,这样就说sizeof不是函数。
	return 0;
}

代码运行成功,结果如下:

在这里插入图片描述


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

代码如下:

#include<stdio.h>
int main()
{
	int a = 0;
	printf("%d\n", ~a);
	//0
	//00000000000000000000000000000000
	//11111111111111111111111111111111
	//10000000000000000000000000000000
	//10000000000000000000000000000001
	//-1
	return 0;
}

运行成功,结果如下:

在这里插入图片描述


4. ++与- -

++ 操作是是一种自增1的操作。
前置++:计算口诀:先+1,后使用
前置--:先-1,后使用
后置++:计算口诀:先使用,后+1
后置--:先使用,后-1

非常规自增自减:https://danbaku.blog.csdn.net/article/details/131756318


5. sizeof和数组

代码如下:

#include<stdio.h>
void test1(int arr[])
{
	printf("%d\n", sizeof(arr));//(2)
}
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));//(1)
	printf("%d\n", sizeof(ch));//(3)
	test1(arr);
	test2(ch);
	return 0;
}

运行成功,结果如下:

x86环境

在这里插入图片描述

x64环境

在这里插入图片描述


七. 关系操作符

>
>=
<
<=
!=
==

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


八. 逻辑操作符

&& 逻辑与
|| 逻辑或

注意:区分逻辑与和按位与,区分逻辑或和按位或

1&2------>0
1&&2---->1
1|2------->3
1||2------>1


360面试题一道,代码如下:

#include<stdio.h>
int main()
{
    int i = 0, a = 0, b = 2, c = 3, d = 4;
    i = a++ && ++b && d++;
    printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
    return 0;
}

运行成功,结果如下:

在这里插入图片描述

&&左边为假(0),右边就不算了。


#include <stdio.h>
int main()
{
    int i = 0, a = 1, b = 2, c = 3, d = 4;
    i = a++||++b||d++;
    printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
    return 0;
}

代码运行成功,结果如下:

在这里插入图片描述

||左边为真(1),右边就不算了。


短路操作:
&& 左边操作数如果为假,右边无需计算
|| ?? 左边操作数如果为真,右边无需计算
详见博客:https://danbaku.blog.csdn.net/article/details/135054294


九. 条件操作符

exp1 ? exp2 : exp3
条件操作符也叫三目运算符,常常用于两个数中找较大值。


十. 逗号表达式

exp1, exp2, exp3, …expN

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

代码如下:

#include<stdio.h>
int main()
{
	int a = 1;
	int b = 2;
	int c = (a > b, a = b + 10, a, b = a + 1);
	printf("c=%d\n", c);
	return 0;
}

运行代码成功,结果如下:

在这里插入图片描述


逗号表达式的实际应用:

可以将:

a = get_val();
count_val(a);
while (a > 0)
{   
	//业务处理
	a = get_val();
	count_val(a);
}

改写为:

while (a = get_val(), count_val(a), a > 0)
	{
		//业务处理
	}


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

1. [ ] 下标引用操作符

操作数:一个数组名 + 一个索引值


2. ( ) 函数调用操作符

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


3. 访问一个结构的成员

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

代码如下:

#include<stdio.h>
struct Book
{
	char name[20];
	int price;
};

//结构体变量.成员
//结构体指针->成员

void Print(struct Book* pb)
{
	printf("%s %d\n", (*pb).name, (*pb).price);
	printf("%s %d\n", pb->name, pb->price);
}

int main()
{
	struct Book b = {"C语言指南", 55};
	printf("%s %d\n", b.name, b.price);
	Print(&b);
	return 0;
}

代码运行成功,结果如下:

在这里插入图片描述


十二. 表达式求值

表达式求值的顺序一部分是由操作符的优先级和结合性决定。
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。

1. 隐式类型转换

整形提升详见博客:https://danbaku.blog.csdn.net/article/details/135055042


2. 算术转换

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

long double
double
float
unsigned long int
long int
unsigned int
int

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


3. 操作符的属性

复杂表达式的求值有三个影响的因素。
操作符的优先级
操作符的结合性
是否控制求值顺序

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

一些问题表达式详见博客:https://danbaku.blog.csdn.net/article/details/135083672



本篇博客为本人学习C语言时的详细笔记,如有错误之处,还望各位指正。
文章为原创,如要转载请注明出处

文章来源:https://blog.csdn.net/m0_59188912/article/details/135084853
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。