自学内容网 自学内容网

十、操作符详解

目录

1、操作符分类

2、二进制转换

2.1二进制转十进制

2.1.1、十进制转二进制

2.2、二进制转八进制和十六进制

2.2.1、二进制转八进制

2.2.2、二进制转十六进制

3、原码、反码、补码

4、移位操作符(移动的是二进制位)

4.1、左移操作符

4.2、右移操作符

5、位操作符:&、|、^、~

6、单目操作符

7、逗号表达式

8、下标访问[]、函数调用()

8.1、[]下标引用操作符

9、结构成员访问操作符

9.1、结构体

9.1.1结构的声明

10、操作符的属性: 优先级、结合性

10.1、优先级

10.2、结合性

11、表达式求值

11.1、整型提升

11.2、算术转换

11.3、问题表达式解析

11.3.1、表达式1

11.3.2、表达式2

11.3.3、表达式3

11.3.4、表达式4

11.3.5、表达式5

11.4、总结


1、操作符分类

(1)算术操作符:+、-、*、/、%、

(2)移位操作符:<<、>>、(移动的是二进制位)

(3)位操作符:&、|、^、(也是使用二进制位进行计算)

(4)赋值操作符:=、+=、-=、*=、/=、%=、<<=、>>=、&=、|=、^=、

(5)单目操作符:!、++、--、&、*、+、-、~、sizeof、(类型)、

(6)关系操作符:>、>=、<、<=、==、!=、

(7)逻辑操作符:&&、||、

(8)条件操作符:?=、

(9)逗号表达式:,、

(10)下标引用:[]、

(11)函数调用:()、

(12)结构成员访问:,、->、

上述操作符,我们已经学过算术操作符、赋值操作符、逻辑操作符、条件操作符和部分的单目操作符,今天继续介绍一部分,操作符中有一些操作符和二进制有关系。

2、二进制转换

我们经常能听到:2进制、8进制、10进制、16进制,其实2进制、8进制、10进制、16进制是数值的不同表示形式,比如数值15的各种进制的表示形式:

①15的2进制:1111

②15的8进制:17

③15的10进制:15

④15的16进制:F

//16进制的数值之前写:0x

//8进制的数值之前写:0

我们重点介绍一下2进制:

首先要讲二进制先从十进制讲起:

十进制

(1)十进制中满10进1

(2)十进制的数字每一位都是0~9的数字组成,其实二进制也差不多

二进制

(1)二进制中满2进1

(2)二进制的数字每一位都是0~1的数字组成

那么1101就是二进制的数字了

十进制123组成:

相加之后得123

二进制1111转十进制:

相加之后得15

八进制17转十进制:

相加之后得15

十六进制转十进制:

2.1二进制转十进制

十进制的123表示的值为123,每一位都是有权重的,每一位权重为10^0、10^1、10^2  ……

二进制同理若有1101(二进制)

2.1.1、十进制转二进制

比如十进制125转二进制:

把余数从下到上拿出来就是125的二进制数1111101

2.2、二进制转八进制和十六进制

2.2.1、二进制转八进制

八进制的数字每一位是0~7的,0~7的数字各自写成二进制,最多有3个二进制位就足够了,比如7的二进制是111,所以在二进制转八进制数的时候,从二进制序列中右边低位开始向左每3个二进制会换算下一个八进制位,剩余不够三个二进制位的直接换算。

若二进制数01101011

2.2.2、二进制转十六进制

3、原码、反码、补码

当我们要把一个数转换成二进制表示时

整数的二进制有3中表示形式(暂时不考虑浮点数)

有符号整数的三种表示方法,均有符号位和数值为两部分,二进制序列中,最高位的1位被当作符号位,剩余的都是数值位

int的长度时四个字节,等于32个bit位

对于unsigned  int来说没有符号位,全都为数值位

正整数的原码、反码、补码都相同;

负整数的三种表示方法各不相同。

原码:直接将数值按照正负数的形式翻译成二进制得到的就是原码。

反码:将原码的符号位不变,其他位一次按位取反就可以得到反码。

补码:反码+1得到补码。

补码得到原码也可以使用取反+1的操作:

比如说-10:

整数在内存中存储的是补码的二进制序列,原因在于,使用补码,可以将符号位和数值位统一处理,同时(CPU只有加法器)此外,补码与原码相互转换,其运算过程相同,不需要额外的硬件电路。

在计算机系统中,数值一律用补码来表示和存储。

4、移位操作符(移动的是二进制位)

<<左移操作符

>>右移操作符

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

4.1、左移操作符

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

  int main()
{
int a = 10;//移动的是存储在内存中的二进制位(补码)
int b = a << 1;//左移一位
printf("b = %d\n",b);
printf("a = %d\n",a);
return 0;
}

 int main()
{
int a = -1;
int b = a << 1;
printf("b = %d\n",b);
printf("a = %d\n",a);
return 0;
}

左移一位有乘二的效果。

4.2、右移操作符

移位规则:首先右移运算分两种:

①逻辑右移:左边用0填充,右边丢弃。

②算术右移:左边用原该值的符号位填充,右边丢弃。

int main()
{
int a = -10;
int b = a >> 1;
printf("a = %d\n",a);
printf("b = %d\n",b);
return 0;
}

右移到底采用算术右移还是逻辑右移,是取决于编译器的!

通常采用的都是算术右移

浮点数不能移位

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

例如:

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

5、位操作符:&、|、^、~

① & //按位与

② | //按位或

③ ^ //按位异或

④ ~ //按位取反

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

& --- 按(二进制)位与                &&  --- 逻辑与

| ---按(二进制)位或                   || ---逻辑或

 &(按位与):

int main()
{
int a = 6;
int b = -7;
int c = a & b;//a和b的补码的二进制序列进行运算
printf("c = %d\n",c);
return 0;
}

00000000 00000000 00000000 00000110   -- 6的补码

11111111 11111111 11111111 11111001  -- -7的补码

00000000 00000000 00000000 00000000  -- 6 & -7 = 0

规则:二进制位对应的补码如果有0则为0,同时为1才为1;

记忆技巧:&(按位与)长得像&&(并且)所以都为1才为1,有一个0就为0。

|(按位或):

int main()
{
int a = 6;
int b = -7;
int c = a | b;
printf("c = %d\n",c);
return 0;
}

6的补码 00000000 00000000 00000000 00000110

- 7的补码 11111111 11111111 11111111 111111001

c的补码 11111111 11111111 11111111 11111111

c的原码  1000000 00000000 00000000 00000001

规则:只要有1就为1,两个同时位0才为0。

记忆技巧:|(按位或)长得像||(或者)所以有一个1就为1,有两个0才为0。

^(按位异或)

int main()
{
int a = 6;
int b = -7;
int c = a ^ b;
printf("c = %d\n",c);
return 0;
}

6的补码 00000000 00000000 00000000 00000110

-7的补码 11111111 11111111 11111111 11111001

c的补码 11111111 11111111 11111111 11111111

c的原码 10000000 00000000 00000000 00000001

规则:a和b补码的二进制位进行运算,相同为0,相异为1。

~(按位取反)

int main()
{
int a = 0;
printf("~a = %d\n",~a);
return 0;
}

~按二进制位取反:

0的补码 00000000 00000000 00000000 00000000

不管符号位,全部取反:11111111 11111111 11111111 11111111


小汇总:

&(按位与)-- 有0为0,同1为1

|(按位或)-- 有1为1,同0为0

^(按位异或)-- 相同为0,相异为1

~(按位取反)-- 不管符号位,全部取反

重点:

0 ^ a = a        3 ^ 3 ^ 5 = 5        3 ^ 5 ^ 3 = 5

异或是支持交换律的:

a ^ b ^ a = b        b ^ a ^ b = a

不创建新的变量实现a和b的值互换:

方法一:

int main()
{
int a = 3;
int b = 5;
printf("a = %d\n,b = %d\n",a,b);
a = a + b;
b = a - b;
a = a - b;
printf("a = %d\n,b = %d\n",a,b);
return 0;
}

方法二:

int main()
{
int a = 3;
int b = 5;
printf("a = %d\n,b = %d\n",a,b);
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("a = %d\n,b = %d\n",a,b);
return 0
}

 练习1:编写代码实现:求一个整数存储在内存中的二进制中1的个数

int count_bit_one(int n);

int main()
{
int input = 0;
scanf("%d",&input);
int ret = count_bit_one(input);
printf("%d",ret);
return 0;
}

int count_bit_one(int num)
{
int count = 0;
 while(num)
 {
 if((num % 2)== 1)
  count++;
 num /= 2;
 }
return count;
}

这样只能计算正整数,如果给一个负数就会出错。

这里我们可以把输入的值,在传参是转换成无符号整型就可以解决当下的问题。

我们换一种思路,是否可以不考虑正负数的问题,比如 -1:

-1:11111111 11111111 11111111 11111111

1:00000000 00000000 00000000 00000001

-1 & 1 :00000000 00000000 00000000 00000001

若n&1 == 1则其最低位原本就是1

若n&1 == 0则其最低位原本就是0

如果想知道这个数最低位的前一位就可以使用右移符,若想知道全部,可以右移32位

请看如下代码:

int count_bit_one(int num)
{
 int i = 0;
 int count = 0;
 for(i = 0; i < 32; i++)
 {
  if(((num >> i)& 1)== 1)
  {
 count++;
  }
 }
return count;
}

int main()
{
int input = 0;
scanf("%d",&input);
int ret = count_bit_one(input);
printf("%d",ret);
return 0;
} 

n & (n - 1)

若 n = 11        n = n&(n - 1)

可以发现每执行一次n = n&(n - 1),n就去掉一个1,这个表达式执行了几次这个n就有几个1.

把n的二进制序列中最右边的1去掉了,下面看代码:

int count_bit_one(int x)

int main()
{
int input = 0;
scanf("%d",&input);
int ret = count_bit_one(input)
printf("%d\n",ret);
return 0;
}

int count_bit_one(int num)
{
int count = 0;
while(num)
 {
  n = n & (n - 1);
  count++;
 }
return 0;
}

写一个代码判断n是否是2的次方数:

void count_bit_one(int x)

int main()
{
int input = 0;
scanf("%d",&input);
count_bit_one(input);
return 0;
}

void count_bit_one(int n)
{
if(n &(n - 1)== 0)
printf("yes\n");
}

练习二:二进制位置0或置1:编写代码将13的二进制序列的第5位修改为1,然后再改回0。

int main()
{
int input = 0;
int n = 0;
printf("请输入数字,以及要改为1的位数\n");
scanf("%d%d",&input,&n);
input = input | (1 << (n -1));
printf("%d\n",input);
input = input & (~(1 << (n - 1)));
printf("%d\n",input);
return 0;
}

6、单目操作符

!、++、--、&、*、+、-、~、sizeof、(类型)、

单目操作符的特点是只有一个操作数,在弹幕操作符中只有&和*没有介绍,这两个操作符,我们放在学习指针的时候学习。

& -- 取地址操作符               

* -- 解引用操作符

7、逗号表达式

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

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

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

8、下标访问[]、函数调用()

8.1、[]下标引用操作符

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

int main()
{
printf("hello\n");
return 0;
}

上述代码的括号就是函数调用操作符,操作数为printf、“hello\n”

int Add(int x, int y)
{
return x + y;
}

int main()
{
int ret = Add(3,4);
printf("%d\n"ret);
return 0;
}

上述代码中Add函数的操作数是Add、3、4。

函数调用操作符最少一个操作数。

9、结构成员访问操作符

9.1、结构体

C语言已经提供了内置类型,如:char、short、int、long、float、double等,但是只有这些内置类型还是不够的,假设想描述学生,描述一本书,这时单一的内置类型是不行的。

结构是一些值的集合,这些值称为成员变量,结构的每个成员可以是不同的变量,如:标量、数组、指针,甚至是其他结构体。

9.1.1结构的声明
struct tag //tag自定义
{
member - list;//成员列表
}variable - list;//变量列表
struct Student
{
//成员变量
char name[20];
int age;
float score;
} s4,s5,s6;

struct Student s3;//全局变量

int main()
{
int a = 0;
struct Student s1;//局部变量
struct Student s2 = {"张三",20,97.0};
return 0;
}

 

//结构体变量.结构体成员名

//->这个是依赖指针的,所以在后面给大家介绍

10、操作符的属性: 优先级、结合性

C语言的操作符有2个重要的属性:优先级、结合性,这两个属性决定了表达式求值的计算顺序。

10.1、优先级

优先级指的是,如果一个表达式包含多个运算符,哪个操作符优先执行。各种运算符的优先级是不一样的。

//相邻的操作符,优先级高的先执行

比如说 int a = 3 + 4 * 5

那么就会限制性4 * 5,而不是 3 + 4,是因为 *的优先级更高。

10.2、结合性

相邻的操作符的优先级相同的情况下,结合性说了算。

比如说 int b = 3 + 4 + 5,优先级相同,加号的结合性从左到右,所以先3 + 4,而不是4 + 5。

11、表达式求值

11.1、整型提升

C语言中整型算术运算重视至少以默认整型类型的精度来进行的。

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

就比如:

char a = 10;
char b = 20;
char c = a + b;

这样就会发生整型提升。

整型提升的意义:

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

如何进行整型提升?

1.有符号整数提升是按照变量的数据类型的符号位来提升的。

2.无符号整数提升,高位补0。

11.2、算术转换

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

整型提升讨论的是:

表达式中char和收人头类型的值。

算术转换讨论的是:

类型大于等于整形类型的类型

比如:

int a;        double b;

a + b;

a就会进行算术转换,转换成double类型,如果某个操作数在上表排名靠后,那就首先要转换为另外一个操作数的类型后才执行运算。

11.3、问题表达式解析

11.3.1、表达式1
a * b + c * d + e * f

由于*比+的优先级高,只能保证*的计算比+早,但优先级并不能决定第三个*比第一个+早执行。

所以运算顺序可能是:

 

11.3.2、表达式2
c + --c;
11.3.3、表达式3
{
int i = 10;
i = i-- - --i *(i = -3)* i++ + ++i;
}
11.3.4、表达式4
int fun()
{
static int count = 1;
return ++count;
}

int main()
{
int answer;
answer = fun() - fun() * fun();
printf("%d\n",answer);
return 0;
}
11.3.5、表达式5
int i = 1;
ret = (++i)+(++i)+(++i)
printf("%d\n",ret);
printf("%d\n",i);

我们用稿纸计算结果位9、4,而使用VS2022计算结果位12、4,结果不一致,使用反汇编来看一下计算过程。

int ret = (++i) + (++i) + (++i)

mov eax,dword ptr [i]

把i的值放到eax中(eax是寄存器)

add eax,1

eax自增1

这时用监视器看到eax由1变为2

mov dword ptr [i],eax

上面代码其实就是++i

mov ecx dword ptr [i]

add ecx,i

mov dword ptr [i],ecx

mov edx,dword ptr [i]

有完成一次++i

add edx,i

这时监视器中

eax = 2        i = 3        ecx = 3        edx = 4

mov dword ptr [i],edx

edx值放到i中

mov eax,dword ptr [i]

add eax,dword ptr [i]

add eax,dword ptr [i]

mov dword ptr [ret],eax

三次++i放到eax中去了,监视器中此时eax = 12

ret = 12

其实VS中先完成三次(++i)再把三个i相加 

11.4、总结

即使有了操作符的优先级和结合性,写出的表达式依然不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在风险的,建议不要写出特别复杂的表达式。


原文地址:https://blog.csdn.net/2202_75978977/article/details/140545318

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!