IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C语言操作符(超详细满满干货) -> 正文阅读

[C++知识库]C语言操作符(超详细满满干货)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

目录

前言

1.算术操作符

2.移位操作符

3.位操作符

4.赋值操作符

5.单目操作符

6.关系操作符

7.逻辑操作符

8.条件操作符

9.逗号表达式

10.下标引用,函数调用,结构体成员操作符

11.表达式求值

12.操作符优先级表

总结



前言

暑假漫漫,复习了一下C语言操作符部分,发现操作符像是一个在C语言中被严重低估的部分,我们每天写代码一定会用到一些操作符,可是它的原理我们真的了解的很透彻么?在复习的过程中我发现真的有好多的细节我之前没学到位,甚至整型提升这一个复杂的过程之前都没怎么听说过。写这样一篇文章留给我今后复习用,同时也分享给大家,我尽量写的详细,争取在初识C语言这里将操作符这部分内容搞透彻。欢迎大家一键三连(比心? ?比心)


提示:以下是本篇文章正文内容,下面案例可供参考

1.算术操作符

1.? +? ? -? ? ?*:

这三个操作符其实没有什么值得一提的,进行加法,减法,乘法运算

2.? /? :

? (1) 当'/'两边都是整数时,执行的是整数除法

(2)当'/'操作数有浮点数时,执行浮点数除法

? ?结果类型与操作数有关,与类型无关

int ret=9/2; //ret=4
double ret=9/2 //ret=4.000000
double ret=9/2.0 //ret=4.500000
int ret=9/2.0 //编译器会报错

?3. %

取模操作符,即取余数

ret=9%2;  //ret=1

注意'%'的操作数只能是整型

2.移位操作符

? ?<< :左移操作符

? ?>>:右移操作符

谈到移位操作符和位操作符,有必要复习一下原码,反码,补码的知识

对于整数的二进制存储有三种形式,原码,反码,补码。而在计算机中存储的数据为补码。

1.正数

正数的原码,反码,补码都是相同的,就是将该正数转换为二进制,以整型为例

10的原码,反码,补码是00000000 00000000 00000000 00001010 由于是整型,所以一共32个二进制位,第一位是符号位,所以为0,代表正数。

2.负数

负数的原码是将第一位符号位置为1,其他位按照十进制转换为二进制运算即可。

负数的反码是指符号位1不变,其他位取反。

负数的补码是由反码加一得到的。

以 -10 为例

原码:10000000 00000000 00000000 00001010

反码:11111111 11111111 11111111 11110101(0比1宽,视觉误差。。。我还查了一遍)

补码:11111111 11111111 11111111? 11110110

重要的事情说两遍,计算机存的是补码。?

1.<< :左移操作符

顾名思义,就是将二进制的补码向左移动n位,也就是将最左边n个二进制位删掉,右边补了n个0。

比如

int a=10;
int b=a<<1;

这段代码,b的值咋个算呢?

首先写出a的补码 :00000000 00000000 00000000 00001010

? ? ? ? ? ? ?然后移位 :00000000 00000000 00000000 00010100

得到b的补码,由于是正数所以也是原码。b的值就算出来了是20。

2.>> :右移操作符

右移操作符和左移操作符基本相似,但是有不同。

同样最右边的数丢弃,但是最左边补什么由你使用的编译器决定。

(1)正数最左边补的位一定是0

?(2)负数则需要看编译器支持哪种右移,支持逻辑右移则补0,支持算数右移则补1,大部分编译器支持的是算数右移

以-10为例,先写出-10的补码

补码:11111111 11111111 11111111? 11110110

逻辑右移:011111111 11111111 11111111 11111011

算数右移:11111111 11111111 11111111 11111011

两种方式算出的结果是不同的,所以当你用不同编译器算出的结果不同,请不要惊讶。

3.位操作符

&:按位与

|? :按位或

^ :? ?按位异或

这三种操作符都有两个操作数,而且都是利用操作数的补码进行运算的。

int a=3;
int b=-2;

用这两个数字举例,先写出他们的补码

a : 00000000 00000000 00000000 00000011

b : 11111111 11111111 11111111 11111110

1.&按位与?

有0则0,同1则1。

a&b=00000000 000000000 00000000 00000010

注意a和b的每一位都要对齐(1比0窄,博客不完美了,可恶啊。。。)

2.|按位或

有1则1,同0则0

a|b=11111111 11111111 11111111 11111111

由于每一位都有1存在,所以都是1,若有一位两者都是0的话,则为0

3.^按位异或

相同为0,不同为1。

a^b=11111111 11111111 11111111 11111101

值得一提的是想打出^,需要在英文模式下,中文模式下是……

注意:这三种位运算计算出的结果仍为补码。只要是用补码当操作数的,结果均为补码。

4.这三种位运算的用处

学习到这里你可能会问,这些运算在写程序的时候有什么用呢,似乎用的也不是很多。

(1)取到二进制位的最低位

只需要a&1,如果得到的结果是1,则最低位为1;如果得到的是0,则最低位是0。

(2)计算一个未知数的值

利用循环语句,每次循环将a的二进制序列向右移一位(>>),同时进行a&1,这样每一次循环都能得到a的一个二进制位,循环执行32次,则a的二进制序列便可得到,进而得到a的值。

(3)交换两个数的数值

通常情况下我们交换两个数的数值应该是这样的

int a=10;
int b=20;
int temp;
temp=a;
a=b;
b=temp;

我初学的时候一直觉得再创建一个变量挺麻烦的,不知道你们是否有同感。那有没有不用创建temp的方法呢?有,'^'呀。

int a=10;
int b=20;
a=a^b;
b=a^b;
a=a^b;

这样两个数就交换了,至于原理,你可以写一写二进制序列自己试一试。

4.赋值操作符

这个其实没什么可说的,就是一个‘=’,注意和==区分就行了

还有一些复合赋值符,我列出来吧

+=? ? -=? ? *=? ? /=? ?%=

>>=? <<=? ? &=? |=? ^=

还有注意一下字符串的赋值不能用=,要用strcpy

5.单目操作符

所谓单目,就是只有一个操作数的意思

! :逻辑反

-? ? :负

+? ?:正

&? ?:取地址

*? ?:解引用操作符

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

--? ?:前置,后置--

++? :前置,后置++

(类型):强制类型转换操作符

sizeof:操作数的类型长度

1.? !逻辑反

(1)非0数的逻辑反是0;

比如:a=10; 则!a=0;

? (2)? 0的逻辑反是1;

比如:a=0; 则!a=1;

2.? ?-与+

将某一值变成正数或者负数。

3.? ?&? *取地址操作符和解引用操作符

&取出某一个变量的地址,*通过地址找到变量的值。

int a=10;
int* p=&a;//取出a的地址放在指针p中
*p=20;//通过解引用操作符修改a的值
printf("%d",a);//a=20

这里需要一些指针的知识,注意一下(int*)代表指针的类型,这里的‘*’和后面的解引用操作符‘*’不同。

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

符号位也要取反

int a=0;//00000000 00000000 00000000 00000000
int b=~a;//11111111 11111111 11111111 11111111

用处:一般在修改单个二进制位的时候使用

5.++ --

这两个操作符一定要区分好前置和后置

int a=10;
int b=a++;
int c=++a;
printf("%d %d %d",a,b,c);//12 10 12

记住一点就可以了,前置++先计算后赋值,后置++先赋值后计算,--也是一样的

6.(类型)强制类型转换

int a=3.14;//编译器会报错
int a=(int)3.14//a=3,编译器不会报错

7.sizeof: 计算操作数的类型长度

(1)计算变量或者类型所占内存大小

int sz=sizeof(int);//sz=4,()不能省略
int a;
int sz=sizeof(a);//sz=4,()可以省略
int sz=sizeof a;//sz=4

计算变量时可以省略,但是计算类型时不能省略(),这是为什么呢?? 因为大乌龟上有小乌龟,规定上有新规定(狗头保命)

(2)注意只计算变量内存的大小,与变量中存的什么数据无关

char arr[10]="abc";
printf("%d",arr);//10

(3) sizeof内的数据不参与运算,举个例子

int a=5;
int s=10;
printf("%d",sizeof(s=a+2));//4
printf("%d",s);//10

在这里很多人会认为s应该打印的是数字7,然而并不是这样,这里的原因很容易理解

我们都知道一个程序由test.c文件要先经过“编译”变成.obj文件,再经过“链接”最后变成test.exe文件,计算的过程发生在.exe中

而在编译的过程中,sizeof内的数据的类型就已经确定了,是int型,编译器会将(s=s+a)这一模块直接看成int

既然已经是int了,s=s+a是否执行就没什么意义了,直接默认为不执行了,所以s的值并没有改变

?(4)计算数组的大小

在没有真正掌握sizeof之前,我们求数组大小的方式大部分是利用for循环,这样会很麻烦。那么有了sizeof后

int arr[]={1,2,3,4,5,6,7,8,9,0};
int a=sizeof(arr)/sizeof(arr[0]);//a=10

这里也说明当sizeof的操作数是数组时,数组名不在是首元素的地址,而变成了整个数组

6.关系操作符

>? ? <? ? >=? ? <=? ? !=? ? ?==

就这几个,有问题吗?没有问题。

7.逻辑操作符

&&? ||

1.&&:逻辑与

是并且的意思,全真则真,有假则假。

2.||? ?:逻辑或

是或者的意思,有真则真,全假则假

(1)区分一下逻辑和按位

1&2=0;//按位与后计算的结果是0
1&&2=1;//表示真的意思
1|2=3;//按位或后结果为3
1||2=1;//表示真的意思

(2)这两个操作符都控制了求值顺序(这个很重要)? 这是一道360的笔试题

? ?来看这样一段代码

int i=0,a=0,b=2,c=3,d=4;
i=a++&&++b&&d++;
printf("%d %d %d %d ",a,b,c,d);

该代码的执行结果是a=1,b=2,c=3,d=4。

但是为什么b和d两个数字没有像a一样++呢?

因为a是后置++,先赋值后++,a的值已经是0了,所以i的值一定为假(0),后面的值无论是多少也改变不了i已经是假的事实了,所以干脆不要计算后面的值了,因此++b和d++都没有参与运算。

||也是同理,这都是由于这两个操作符对求值顺序的控制。

再举一个例子

int i=0,a=0,b=2,c=3,d=4;
i=a++||++b||d++;
printf("%d %d %d %d ",a,b,c,d);

代码执行的结果是a=1,b=3,c=3,d=4,由于a的值赋的是0,无法判断表达式是否是真,所以要执行++b,而b的值已经是真了,就没有必要再去执行d++了,所以得到的是这样的结果。

8.条件操作符

exp1?exp2:exp3;

如果exp1为真,那么执行exp2,为假则执行exp3。

这个操作符主要是代提if语句使用的,但是我经常忘记使用它,现在开始要用起来。

if(a>5)
b=3;
else
b=-3;
a>5?b=3:b=5

这两个是等价的。

9.逗号表达式

exp1,exp2 ,exp3,? ……

逗号表达式中也有一些细节是我们没怎么注意到的。

(1)逗号表达式由左向右执行,整个表达式的结果是最后一个表达式的结果

    int a = 5;
	int b = 1;
	int c = (a = b + 1,b = a - 2);
	printf("%d", c);//c=0

c的结果是0,这说明逗号表达式确实从左到右执行了,而且整个逗号表达式的值是最后一个表达式的值

(2)根据逗号表达式的特性可以简化循环语句

a=get();
count(a);
while(a>0)
{
a=get();
b=count(a);
}

用逗号表达式可以这样写:

while(a=get(),count(a),a>0)
{  }

但其实不太建议这样去做,虽然很秀,但是容易把自己绕晕,而且do while语句可以达到同样的效果

10.下标引用,函数调用,结构体成员操作符

[]:下标引用操作符

():函数调用操作符

.? ->:结构体成员调用操作符

1.[]下标引用操作符

int arr[5]={1,2,3,4,5};
arr[4]=10;//将第五个元素的值变成10

这里就用到了下标引用操作符[],它的两个操作数分别是arr和4,我们甚至可以这样写

arr[4]=10;
4[arr]=10;

这两段代码是等价的。

2.()函数调用操作符

和下标引用操作符一样,我们每天都在使用,但是很少注意到它们其实也是一种操作符

例如我们调用函数strlen(),函数调用操作符的操作数就是函数名strlen和()里的内容,我们自定义的函数也是如此

3.结构体成员的引用操作符

#include<stdio.h>
struct Stu
{
char name[10];
int age;
}
void setage1(struct Stu stu)
{
stu.age=18;
}
void setage2(struct Stu* stu)
{
stu->age=18;
}
int main()
{
struct Stu stu;
struct Stu* pStu=&stu;
stu.age=20;
pStu->age=20;
setage1(stu);
setage2(pstu);
return 0;
}

这么一长串你看出了什么呢?当拿到的是结构体变量本身,修改它内部值只需要 结构体名.成员名=某个数。当拿到的是结构体变量的地址时,需要 结构体名->成员名=某个数。

11.表达式求值

1.整型提升

这个东西说起来比较复杂,但是理解起来还是挺容易的。所谓整型提升,就是把所占内存低于整型的char类型和short类型强行提升变成整型。

(1)官方术语:

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

(2)整型提升的意义

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

总结一下就是推究本源是CPU在进行运算,但是CPU只能计算整型的数据,因此例如short类型的数据得先变成int型参加运算,然后再发生截断,编译器再把它变回short型。

(3)负数的整形提升

char c1 = -1;

变量c1的二进制位(补码)中只有8个比特位: 1111111 因为 char 为有符号的 char 所以整形提升的时候,高位补充符号位,即为1 提升之后的结果是: 11111111111111111111111111111111

(4)正数的整形提升

char c2 = 1;

变量c2的二进制位(补码)中只有8个比特位: 00000001 因为 char 为有符号的 char 所以整形提升的时候,高位补充符号位,即为0 提升之后的结果是: 00000000000000000000000000000001

下面我们用三个例子讲解一下整型提升的过程

(1)

char a=3;
char b=127;
char c=a+b;
printf("%d",c);

这样一个短短的代码其实就用到了三次整型提升,我来演示一下过程

a的补码:00000011

b的补码:01111111

由于a和b要参与运算,所以要进行整型提升

a:00000000 00000000 00000000 00000011

b:00000000 00000000 00000000 01111111

计算a+b=:00000000 00000000 00000000 10000010

由于c是char类型所以发生截断 。

c:10000010

在之前发生了a与b的两次整型提升。

之后需要用整型的形式(%d)打印c,再一次发生整型提升,打印整型提升之后的数

c:11111111 11111111 11111111 10000010

注意这个是c的补码,我们打印的是原码,所以结果为-126。

(2)

int main()
{
 char a = 0xb6;
short b = 0xb600;
 int c = 0xb6000000;
 if(a==0xb6)
 printf("a");
 if(b==0xb600)
 printf("b");
 if(c==0xb6000000)
 printf("c");
 return 0;
}

这段代码打印出来的结果是什么呢?? 只有一个c ,原因是a和b参与运算时都发生了整型提升变成了负数,不与原来的值相等,而c不发生整型提升。所以打印出来的是c。

(3)

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;
}

第一个没有参与运算所以打印的是1,但是第二个和第三个参与了运算,所以打印的是整型大小4。

2. 算数转换

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

long double

double

float

unsigned? long? int

long int

unsigned? int

int

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

3.操作符的属性

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

(1)操作符的优先级

(2)操作符的结合性

(3)是否控制求值顺序

两个相邻的操作符先执行优先级高的那个,若优先级相同,取决于它们的结合性。

下面给出优先级和结合性的表格

12.操作符优先级表

优先级

运算符

名称或含义

使用形式

结合方向

说明

1

[]

数组下标

数组名[常量表达式]

左到右

()

圆括号

(表达式)
函数名(形参表)

.

成员选择(对象)

对象.成员名

->

成员选择(指针)

对象指针->成员名

2

-

负号运算符

-表达式

右到左

单目运算符

(类型)

强制类型转换

(数据类型)表达式

++

自增运算符

++变量名
变量名++

单目运算符

--

自减运算符

--变量名
变量名--

单目运算符

*

取值运算符

*指针变量

单目运算符

&

取地址运算符

&变量名

单目运算符

!

逻辑非运算符

!表达式

单目运算符

~

按位取反运算符

~表达式

单目运算符

sizeof

长度运算符

sizeof(表达式)

3

/

表达式 / 表达式

左到右

双目运算符

*

表达式*表达式

双目运算符

%

余数(取模)

整型表达式%整型表达式

双目运算符

4

+

表达式+表达式

左到右

双目运算符

-

表达式-表达式

双目运算符

5

<<

左移

变量<<表达式

左到右

双目运算符

>>

右移

变量>>表达式

双目运算符

6

>

大于

表达式>表达式

左到右

双目运算符

>=

大于等于

表达式>=表达式

双目运算符

<

小于

表达式<表达式

双目运算符

<=

小于等于

表达式<=表达式

双目运算符

7

==

等于

表达式==表达式

左到右

双目运算符

!=

不等于

表达式!= 表达式

双目运算符

8

&

按位与

表达式&表达式

左到右

双目运算符

9

^

按位异或

表达式^表达式

左到右

双目运算符

10

|

按位或

表达式|表达式

左到右

双目运算符

11

&&

逻辑与

表达式&&表达式

左到右

双目运算符

12

||

逻辑或

表达式||表达式

左到右

双目运算符

13

?:

条件运算符

表达式1? 表达式2: 表达式3

右到左

三目运算符

14

=

赋值运算符

变量=表达式

右到左

/=

除后赋值

变量/=表达式

*=

乘后赋值

变量*=表达式

%=

取模后赋值

变量%=表达式

+=

加后赋值

变量+=表达式

-=

减后赋值

变量-=表达式

<<=

左移后赋值

变量<<=表达式

>>=

右移后赋值

变量>>=表达式

&=

按位与后赋值

变量&=表达式

^=

按位异或后赋值

变量^=表达式

|=

按位或后赋值

变量|=表达式

15

,

逗号运算符

表达式,表达式,…

左到右

其实掌握了优先级表格也不能保证所写的代码就一定是合法的,比如这样一段代码

a*b+c*d+e*f ,这段代码在进行计算时,由于*比+的优先级高,只能保证*的计算比+早,但是优先级并不能决定第三个*比第一个执行地早。你可能会说似乎对结果没什么影响,但如果a,b,c,d,e,f是表达式呢?那结果就是天差地别,至于怎么运算取决于编译器,但是不同编译器执行后如果结果不同的话这段代码一定不是好代码,是问题代码。

这就需要我们多多总结一些常见的歧义代码的问题,和要更加了解你的编译器属性。

总结

经过一天的呕心沥血,终于将这篇文章呕完了,个人感觉对于我们大学生来说还是很细的,当然也感谢可以读到这里的你,本文的代码我是一一在VS2019上操作过的,如果你发现文章哪里有问题,或者你可以比我更细的话,欢迎和我来讨论。

最后,听说一键三连的人下一个七夕节会有人陪你过哦!!

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-08-15 15:19:21  更:2021-08-15 15:20:14 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/20 7:02:30-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码