操作符详解


前言

  1. 各种操作符的介绍。
  2. 表达式求值

操作符分类

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

一、算术操作符

算术操作符有哪些呢?
+、-、*、/、%
优先级和结合性如何?
在这里插入图片描述
都是双目操作符;
注意:
1、%两边的操作数只能为整数,不能为浮点数;
2、=、-、 * 、/ 两边两个操作数都为整数都为整数,才执行整数的运算法则,只要其中有一个浮点数,则执行浮点数运算;

二、移位操作符

移位操作符分为两类:
<<:左移操作符;
>>:右移操作符;
首先我们来了解一下左移操作符;
使用方法:
左移多少位,右边就补多少位0;
操作代码:

int main()
{
	int a = -4;
	printf("%d\n",a<<4);

	return 0;
}

那我们就得知道,所有的整数在内存中都是以补码进行存储的,我们的左右移操作符都是对补码进行直接操作的;
于是我们的了解-4的补码:
在这里插入图片描述
既然有了补码,那我们直接对其进行操作:
在这里插入图片描述
我们向左移动了4个位,那右边有缺失啊,补啥?无脑补0;
在这里插入图片描述
我们现在不就得到一串新的补码,我们在对其进行原码转换:
补码的补码就是原码:
补码:1111111111111111111111111000000
反码:1000000000000000000000111111
补码:1000000000000000000001000000
既:-64;
我们来看看结果对不对呢?
在这里插入图片描述
的确是这样:
我们接下来再来看看右移操作符:
使用方法:
对于有符号整型:右移对少位,左边就补多少个原符号位;(算术右移)
对于无符号整型:右移对少位,左边就补多少个0;(逻辑右移)
我们来看段代码:

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

这是-4的补码:
在这里插入图片描述
在a,c中都是一样的序列:
a>>4;
在这里插入图片描述
左边补原符号位:
补码:11111111111111111111111111111111
反码:1000000000000000000000000000
补码:1000000000000000000000000001
既-1,故b=-1;
再来看c
在这里插入图片描述
补码:00001111111111111111111111111111
既:268435455,故d=268435455;
我们来看看是不是
在这里插入图片描述
显然是的;
警告⚠ :
对于移位运算符,不要移动负数位,这个是标准未定义的。
例如:

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

三、 位操作符

主要有:
& :按位与
| :按位或
^ :按位异或
~:按位取反
注:他们的操作数必须是整数。
&运算规则:在相同比特位下:1&0=0;0&0=0;1&1=1;
比如
3:011
4:100
则3&4=000=0;
在这里插入图片描述
的确是这样;
|运算规则:在相同比特位下:1|0=1;0|0=0;1|1=1;
3:011
4:100
则3|4=111=7
在这里插入图片描述
的确如此:
再来看看^异或运算规则:相同比特位下,相同为0,相异为1;
3:011
4:100
则3^4=111=7;
在这里插入图片描述
当然根据这个操作符的规则,我们可以得出这一个公式:
a^a=0;
a^0=a;
根据这个公式我们可以出一道题:
不能创建临时变量(第三个变量),实现两个数的交换:
法一:

int main()
{
	int a = 2;
	int b = 3;
	printf("\nbefore:a=%d b=%d", a, b);
	a = a + b;
	b = a - b;
	a = a - b;
	printf("\nafter:a=%d b=%d",a,b);
	return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这方法有个弊端就是容易造成超出范围,即a,b很大,但是没有超出int范围,但是a+b就超出了,后面一系列操作就会出错;
为了解决这个问题我们可以用^来帮助我们:
法2:

int main()
{
	int a = 2;
	int b = 3;
	printf("\nbefore:a=%d b=%d", a, b);
	a = a ^ b;
	b = a ^ b;
	a = a ^ b;
	printf("\nafter:a=%d b=%d",a,b);
	return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这样不会产生进位,也就不会出现超出范围的说法;

~按位取反运算规则:按比特位逐个取反:1->0;0->1;
为了方便演识,我们写个打印二进制的函数来看看:

void print_bin(int n)//大家可以下来自行研究一下
{
	int i = 0;
	int num = sizeof(int) * 8;
	i = num - 1;
	while ((i--)>=0)
	{		if (n & (1 << i))
			printf("1");
		else
			printf("0");
}
	putchar('\n');
}

int main()
{
	int a = -1;
	printf("按位取反之前:");
	print_bin(a);
	printf("按位取反之后:");
	print_bin(~a);
	return 0;
}

在这里插入图片描述

四、 赋值操作符

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

int weight = 120;//体重
weight = 89;//不满意就赋值
double salary = 10000.0;
salary = 20000.0;//使用赋值操作符赋值。
赋值操作符可以连续使用,比如:
int a = 10;
int x = 0;
int y = 20; a = x = y+1;//连续赋值
这样的代码感觉怎么样?
那同样的语义,你看看:
x = y+1; a = x;
这样的写法是不是更加清晰爽朗而且易于调试。

当然还有升级版赋值操作符:
复合赋值符
+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=
这些运算符都可以写成复合形式;

int x = 10; 
x = x+10;
 x += 10;//复合赋值
//其他运算符一样的道理。这样写更加简洁

五、 单目操作符

! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
– 前置、后置–
++ 前置、后置++

  •         *     间接访问操作符(解引用操作符) 
    
  • (类型) 强制类型转换

逻辑取反:
在这里插入图片描述
相同的代码我们可以这么改:
在这里插入图片描述
就是在逻辑上是真,!一下就是逻辑上是假了;同理逻辑上是假,!一下逻辑上就是真了;

  • – 、 +这两就不用多做解释了吧;
    &:取地址,取出的是所有地址中最低的一个地址(对所有类型都是这样)
    在这里插入图片描述
    sizeof经常被认为是函数的关键字;求一个类型在内存中占的空间大小;
    虽然用法上与函数并无差异,但是它确实不是一个函数,下面前我们通过例子来证明:
    在这里插入图片描述
    我们可以发现sizeof 变量名是可以编译运行的,也是可以不用带括号的,也就是括号可以被省略,但是函数的括号确实不能被省略,从这一点我们证明了sizeof不是函数,是一个关键字,但是在对sizeof(类型)必须加括号,不带编译器会报错;
    – 前置、后置–
    ++ 前置、后置++
    这个的却别就是,后置- - 、++先使用后自增,如果有接收方的话,则会被先接受,在自增:
    在这里插入图片描述
    前置- -、++先自增在使用,有接受方,接收方接收的是自增过后的值,如没有,直接自增后置也是如此;
    在这里插入图片描述

*解引用操作符:


在这里插入图片描述

通过指针的方式间接访问变量;
(强制类型转换):
只是改变编译器看待数据的角度,并不会该变其内存中的二进制序列;
int a=(int)3.13;
其实最终a=3;,但是3.13在内存中二进制布局并没有改变;

六. 关系操作符

=
<
<=
!= 用于测试“不相等”
== 用于测试“相等”
这些关系运算符比较简单,没什么可讲的,但是我们要注意一些运算符使用时候的陷阱。
警告:
在编程的过程中== 和=不小心写错,导致的错误

七、逻辑操作符

逻辑操作符有哪些:&&逻辑与 ||逻辑或
区分逻辑与和按位与
区分逻辑或和按位或
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++;
    //i = a++||++b||d++;//这个读者下来可以自己测试一遍;
    printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
    return 0; }
//程序输出的结果是什么?

在这里插入图片描述
在这里插入图片描述
归最终结果就是上式;

八、 条件操作符

在这里插入图片描述

1.
if (a > 5)
        b = 3;
else
        b = -3;
转换成条件表达式,是什么样?
(a>5)?3:-3;

九、 逗号表达式

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

//代码1
int a = 1;
int b = 2;
int c = (a>b, a=b+10, a, b=a+1);/

c=13;

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

  1. [ ] 下标引用操作符
    操作数:一个数组名 + 一个索引值
    在这里插入图片描述
  2. ( ) 函数调用操作符
    接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
#include <stdio.h>
 void test1()
 {
 printf("hehe\n");
 }
 void test2(const char *str)
 {
 printf("%s\n", str);
 }
 int main()
 {
 test1();            //实用()作为函数调用操作符。
 test2("hello bit.");//实用()作为函数调用操作符。
 return 0;
 }
  1. 访问一个结构的成员
    . 结构体.成员名
    -> 结构体指针->成员名
#include <stdio.h>
struct Stu
{
 char name[10];
 int age;
 char sex[5];
 double score;
 }void set_age1(struct Stu stu) {
 stu.age = 18; }
void set_age2(struct Stu* pStu) {
 pStu->age = 18;//结构成员访问
}
int main()
{
 struct Stu stu;
 struct Stu* pStu = &stu;//结构成员访问
 
 stu.age = 20;//结构成员访问
 set_age1(stu);
 
 pStu->age = 20;//结构成员访问
 set_age2(pStu);
 return 0; }

十一、 表达式求值

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

整型提升?

C的整型算术运算总是至少以默认整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。

整型提升的意义

表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。通用CPU(general-purpose CPU是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU,C的整型算术运算总是至少以默认整型类型的精度来进行的。为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这个转换称为整型提升。整型提升的意义:去执行运算。
总的来说,只要是小于int型的整型数据在进行整数运算时,都会先提升称为int类型在进行运算;(比如char,unsigned char,short,unsigned short在进行整数运算时都会发生整型提升)

如何进行整型提升?

对于有符号整数来说:
往最高位补原符号位;
比如char a=-1;
在char中:11111111
发生整型提升就是,补符号位,符号位为1,则补1;
11111111111111111111111111111111;
对于无符号来说,直接补0
比如unsigned char a=-1;
11111111:
发生整型提升:
00000000000000000000000011111111;
整型提升的例子;

int main()
{
 char a = 0xb6;//10110110
 short b = 0xb600;//1011011000000000
 int c = 0xb6000000;
 if(a==0xb6)//会发生整型提升:
 //补码:11111111111111111111111110110110
 //反码:10000000000000000000000001001001
 //补码:10000000000000000000000001001010//-4A
 printf("a");
 if(b==0xb600)//会发生整型提升
 //补码:1111111111111111101101100000000
 //反码:1000000000000000010010011111111
 //补码:1000000000000000010010100000000//-2500
 printf("b");
 if(c==0xb6000000)//不会发生整型提升
 printf("c");
 return 0; }//最终结果只会打印c
int main()
{
 char c = 1;
 printf("%u\n", sizeof(c));
 printf("%u\n", sizeof(+c));
 printf("%u\n", sizeof(-c));
 return 0; }

在这里插入图片描述

十二、算术转换

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

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

如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。
警告:
但是算术转换要合理,要不然会有一些潜在的问题。

float f = 3.14;
int num = f;//隐式转换,会有精度丢失
  • 18
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 22
    评论
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

南猿北者

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

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

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

打赏作者

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

抵扣说明:

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

余额充值