【C语言初阶(NEW)】五、操作符详解(一)

简介: 目录前言一、操作符分类二、算术操作符三、移位操作符3.1 移位操作符简介3.2 原码、反码、补码3.3 << 左移运算符3.4 >>右移运算符3.5 警告四、位操作符4.1 & 按位与4.2 | 按位或4.3 ^ 按位异或 五、赋值操作符 六、单目操作符6.1 单目操作符介绍6.2 ! 逻辑反操作符6.3 - +(负值、正值)6.4 ~ 取反6.5 -- 运算符和 ++ 运算符6.6 & 取地址和 * 解引用6.7 sizeof 操作符6.8 (类型) 强制类型转换 七、关系操作符八、 逻辑操

目录

前言

一、操作符分类

二、算术操作符

三、移位操作符

3.1 移位操作符简介

3.2 原码、反码、补码

3.3 << 左移运算符

3.4 >>右移运算符

3.5 警告

四、位操作符

4.1 & 按位与

4.2 | 按位或

4.3 ^ 按位异或

五、赋值操作符

六、单目操作符

6.1 单目操作符介绍

6.2 ! 逻辑反操作符

6.3 - +(负值、正值)

6.4 ~ 取反

6.5 -- 运算符和 ++ 运算符

6.6 & 取地址和 * 解引用

6.7 sizeof 操作符

6.8 (类型)  强制类型转换

七、关系操作符

八、 逻辑操作符

九、条件操作符

十、逗号表达式

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

11.1 [ ] 下标引用操作符

11.2 ( ) 函数调用操作符

11.3 访问一个结构的成员


前言

       由于之前C语言写的博客很零散,写的也不够好,这里我打算重新整理关于C语言的博客,也方便自己复习C语言,C语言专栏看到旧版本的博客建议移步到新版本相关的博客上

----------------我是分割线---------------  

一、操作符分类

操作符可以分为一下几类:

  1. 算术操作符
  2. 移位操作符
  3. 位操作符
  4. 赋值操作符
  5. 单目操作符
  6. 关系操作符
  7. 逻辑操作符
  8. 条件操作符
  9. 逗号表达式
  1. 下标引用、函数调用和结构成员

二、算术操作符

算术操作符有:

+ - * / %
  1. 加减就不解释了
  2. 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数
  3. 对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法
  4. % 操作符的两个操作数必须为整数。返回的是整除之后的余数
  5. 这几个算术操作符都是由两个操作数的,比如 1 + 2 ,左操作数是 1,右操作数是 2

测试代码:

#include <stdio.h>intmain()
{
//% 取模操作符的两端必须是整数inta=7%2;//取余,结果是 1intb=7/2;
printf("%d\n", a);//1printf("%d\n", b);//3return0;

运行结果:

image.png

三、移位操作符

3.1 移位操作符简介

在C语言中,移位运算符有双目移位运算符:<<(左移)和>>(右移)

       左移运算是将一个二进制位的操作数按指定移动的位数向左移动,移出位被丢弃,右边移出的空位一律补 0


右移运算是将一个二进制位的操作数按指定移动的位数向右移动,移出位被丢弃,左边移出的空位一律补 0 ,或者补符号位,这由不同的机器而定。在使用补码作为机器数的机器中,正数的符号位为  0 ,负数的符号位为 1 vvvv

下面且听详解!!!

3.2 原码、反码、补码

在学习移位操作符之前,首先要了解原码、反码、补码

整数在内存中存储的形式是补码的二进制

整数的二进制表示:有3种(原码、反码、补码)(假设在 32 位的平台下

原码:直接根据数值写出的二进制序列就是原码(32位)

反码:原码的符号位不变,其他位按位取反就是反码

补码:反码加1,就是补码


     对于正整数的原码、反码、补码都相同;负数是存放在二进制的补码中,负整数的原码、反码、补码都不相同

例如:1(正整数的原码、反码、补码都相同)

原码:0000000000000000000000000000001反码:0000000000000000000000000000001补码:0000000000000000000000000000001

最高位为0 ,也是符号位(最左边的第一位)

例如:-1(负整数的原码、反码、补码都不相同)

原码:10000000000000000000000000000001反码:11111111111111111111111111111110(按位取反,符号位不变)补码:11111111111111111111111111111111(反码加1)

最高位为1,也是符号位(最左边的第一位)

简单了解到这里,可以继续学习移位操作符了

3.3 << 左移运算符

直接上代码(只演示负整数的,看完正整数的也会了,正整数的比较简单)

#include<stdio.h>intmain()
{
inta=-5;
intb=a<<1;
printf("%d\n", a);
printf("%d\n", b);
return0;
}

运行结果是 -10

image.png

这是为什么呢,解释如下:

规则:左移运算是将一个二进制位的操作数按指定移动的位数向左移动,移出位被丢弃,右边移出的空位一律补0

简单说就是:左边丢弃,右边补0

先写出 -5 的补码

原码:10000000000000000000000000000101(最高位为1)反码:11111111111111111111111111111010(按位取反,符号位不变)补码:11111111111111111111111111111011(反码加1)

补码向左移动一位,左边去掉,右边补0,如图:


此时得到的是补码,还要反推原码才能打印(正数原、反、补相同,补码就是原码,负数要反推回去)

补码:11111111111111111111111111110110反码:11111111111111111111111111110101(补码-1得到反码)原码:10000000000000000000000000001010(按位取反得到原码)

此时得到的原码就可以转换为十进制打印了,结果就是 -10

注意:%d 意味着打印一个有符号的整数

注:此时的 a 没有改变,依旧是 -5

3.4 >>右移运算符

直接上代码(只演示负整数的,看完正整数的也会了,正整数的比较简单)

#include<stdio.h>intmain()
{
inta=-5;
intb=a>>1;
printf("%d\n", a);
printf("%d\n", b);
return0;
}

运行结果是 -3

 image.png

解释:

       右移运算是将一个二进制位的操作数按指定移动的位数向右移动,移出位被丢弃,左边移出的空位一律补0,或者补符号位,这由不同的机器而定。在使用补码作为机器数的机器中,正数的符号位为 0 ,负数的符号位为 1

简单说就是:(分为 2 种)

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

到底是逻辑右移还是算术右移取决于编译器

我当前使用的编译器 VS2019,它采用的是算术右移

还是一样,先写出  -5 的补码

原码:10000000000000000000000000000101(最高位为1)反码:11111111111111111111111111111010(按位取反,符号位不变)补码:11111111111111111111111111111011(反码加1)

补码向右移动一位,右边丢弃,左边补符号位,如图:


此时得到的是补码,还要反推原码才能打印

补码:11111111111111111111111111111101反码:11111111111111111111111111111100(补码-1得到反码)原码:10000000000000000000000000000011(按位取反得到原码)

此时得到的原码就可以转换为十进制打印了,结果就是 -3

注:此时的 a 没有改变,依旧是 -5,说明左移和右移操作符不改变原操作数

3.5 警告

对于移位运算符,不要移动负数位,这个是标准未定义的;左移和右移运算符只适用于正数,不支持浮点数

例如:

inta=5;
intb=a>>-1//errorfloat=1.11;
floatd=f<<1;//不支持

四、位操作符

位操作符有:

&//按位与|//按位或^//按位异或//注:他们的操作数必须是整数

运行结果是:4

image.png

解释如下:

& 按位与规则:两个二进制操作数对应位同为 1 ,结果位才为 1 ,其余情况为 0  

也是先写出 5,-2 的补码

5的补码:00000000000000000000000000000101-2的原码:10000000000000000000000000000010-2的反码:11111111111111111111111111111101-2的补码:11111111111111111111111111111110

按照规则,两个二进制操作数对应位同为 1 ,结果 位 才为 1 ,其余情况为 0

5的补码:00000000000000000000000000000101-2的补码:111111111111111111111111111111105&-2的补码:00000000000000000000000000000100

此时得到的是补码,还要反推原码才能打印

1.  5 &- 2 的原码:00000000 00000000 00000000 00000100
2.  (正整数原、反、补相同)

此时得到的原码就可以转换为十进制打印了,结果就是 4

4.2 | 按位或

老样子,先上代码

#include<stdio.h>intmain()
{
inta=5;
intb=-2;
intc=a|b;
printf("%d\n", c);
return0;
}

运行结果是:-1

image.png

解释如下:

| 按位或规则:两个二进制操作数对应位只要有一个为 1 ,结果 位 就为 1 ,其余情况为 0  

也是先写出 5,-2 的补码

5的补码:00000000000000000000000000000101-2的原码:10000000000000000000000000000010-2的反码:11111111111111111111111111111101-2的补码:11111111111111111111111111111110

也是按照规则,两个二进制操作数对应位只要有一个为 1 ,结果 位 就为 1 ,其余情况为 0

5的补码:00000000000000000000000000000101-2的补码:111111111111111111111111111111105|-2的补码:11111111111111111111111111111111

此时得到的是补码,还要反推原码才能打印

5|-2的补码:111111111111111111111111111111115|-2的反码:111111111111111111111111111111105|-2的原码:10000000000000000000000000000001

此时得到的原码就可以转换为十进制打印了,结果就是 -1

4.3 ^ 按位异或

老样子,先上代码

#include<stdio.h>intmain()
{
inta=5;
intb=-2;
intc=a^b;
printf("%d\n", c);
return0;
}

运行结果是:-5

image.png

原因解释如下:

^ 按位异或 规则:两个二进制操作数对应 位 相同为 0 ,不同为 1

也是先写出 5,-2 的补码

5的补码:00000000000000000000000000000101-2的原码:10000000000000000000000000000010-2的反码:11111111111111111111111111111101-2的补码:11111111111111111111111111111110

也是按照规则来,两个二进制操作数对应 位 相同为 0 ,不同为 1

5的补码:00000000000000000000000000000101-2的补码:111111111111111111111111111111105^-2的补码:11111111111111111111111111111011

此时得到的是补码,还要反推原码才能打印

5^-2的补码:111111111111111111111111111110115^-2的反码:111111111111111111111111111110105^-2的原码:10000000000000000000000000000101

此时得到的原码就可以转换为十进制打印了,结果就是 -5

 ----------------我是分割线---------------  

五、赋值操作符

赋值操作符为:=

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

比如:

intweight=120;//体重weight=89;//不满意就赋值doublesalary=10000.0;
salary=20000.0;//使用赋值操作符赋值

但是这种代码不建议写,别人看了增加头发负担量

建议这么写:

1. x = y+1;
2. a = x;

赋值操作符也可以写成复合赋值符

  • +=
  • -=
  • *=
  • /=
  • %=
  • >>=
  • <<=
  • &=
  • |=
  • ^=

比如:

intx=10;
x=x+10;
x+=10;//复合赋值,等价于 x = x+10//其他运算符一样的道理,这样写更加简洁

 ----------------我是分割线---------------    

六、单目操作符

6.1 单目操作符介绍

单目操作符意味着只有一个操作数

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

6.2 ! 逻辑反操作符

intflag=3;
//flag为真,进入ifif (flag)
{}
//flag为假,进入ifif(!flag)
{}

C语言中0表示假,非0表示真

6.3 - +(负值、正值)

//int a = -10;//负号intb=-a;//负负得正intc=+1;//正号一般不写出来

6.4 ~ 取反

直接上代码

#include<stdio.h>intmain()
{
inta=5;
intc=~a;
printf("%d\n", c);
return0;
}

运行结果是:-6

image.png

原因解释如下:

~ 取反规则:一个二进制操作数,对应位为 0 ,结果位为 1 ;对应位为 1 ,结果位为 0(作用是将每位二进制取反)

先写出 5 的补码

5 的补码:00000000 00000000 00000000 00000101

按照规则,对应位为 0 ,结果位为 1 ;对应位为1,结果位为 0

1. 5 的补码:00000000 00000000 00000000 00000101
2.      取反:11111111 11111111 11111111 11111010

此时得到的是补码,还要反推原码才能打印

取反后(补码):11111111111111111111111111111010反码:11111111111111111111111111111001原码:10000000000000000000000000000110

此时得到的原码就可以转换为十进制打印了,结果就是 -6

6.5 -- 运算符和 ++ 运算符

-- 分前置--和后置--,++ 也分前置++和后置++

前置++,先++,后使用;后置++,先使用,再++


前置--,先--,后使用; 后置--,先使用,再--

测试代码,-- 同样道理

#include<stdio.h>intmain()
{
inta=1, b=1;
//前置++,先++,后使用//后置++,先使用,再++printf("前置:%d\n", ++a);
printf("后置:%d\n", b++);
return0;
}

运行结果

image.png

6.6 & 取地址和 * 解引用

       & 取地址操作符用于取变量的起始地址;* 叫做间接访问操作符,也叫解引用操作符,一般都是用于解引用指针,拿到指针所指向的内容

测试代码:

#include<stdio.h>intmain()
{
inta=10;
printf("%p\n", &a);//&取地址, %p是打印地址//取出的地址是变量的起始地址int*p=&a;//把 a的地址拿给指针p,指针p 可以间接使用a*p=20;//p 是a的地址,*p就拿到了地址的内容,即 a的值10,指针p间接修改了a的内容printf("%d\n", a);
return0;
}

运行结果

image.png

指针后面讲,这里简单了解

6.7 sizeof 操作符

       sizeof 操作符,可以求变量(类型)所占空间的大小,单位是字节

注意:sizeof是操作符,不是函数,strlen是库函数,是用来求字符串长度

测试代码:

#include <stdio.h>intmain()
{
inta=1;
intarr[10] = { 0 };
printf("%d\n", sizeof(a));//可以计算变量的大小    4printf("%d\n", sizeof(arr));//可以计算数组的大小,4*10printf("%d\n", sizeof(int));//可以计算变量类型的大小 4printf("%d\n", sizeof(char));//可以计算变量类型的大小 1printf("%d\n", sizeofa);//这样写行不行?ok//printf("%d\n", sizeof int);//这样写行不行?errorreturn0;
}

运行结果

image.png

来看看 sizeof 和 数组的一个面试题

#include <stdio.h>voidtest1(intarr[])
{
printf("%d\n", sizeof(arr));//(2)}
voidtest2(charch[])
{
printf("%d\n", sizeof(ch));//(4)}
intmain()
{
intarr[10] = { 0 };
charch[10] = { 0 };
printf("%d\n", sizeof(arr));//(1)printf("%d\n", sizeof(ch));//(3)test1(arr);
test2(ch);
return0;
}

问:(1)(2)两个地方分别输出多少?(3)(4)两个地方分别输出多少?

(2)(4)本质上是指针,指针在 32位平台下是 4

运行结果

image.png

6.8 (类型)  强制类型转换

测试代码:

#include <stdio.h>intmain()
{
inta= (int)3.14;//把 3.14强制类型转换成 intprintf("%d\n", a);
return0;
}

运行结果

image.png

七、关系操作符

关系操作符有:

>>=<<=!=用于测试“不相等”==用于测试“相等”

这些我们都是很熟悉,不再解释了,要注意一点:在编程的过程中 == 和 = 不小心写错,导致的错误

  ----------------我是分割线---------------    

八、 逻辑操作符

逻辑操作符有:

1. && 逻辑与
2. || 逻辑或

区分逻辑与和按位与

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

区分逻辑或和按位或

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

看一道面试题

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

运行结果

image.png

说明:&& 左边为假,右边就不计算了

继续看

#include <stdio.h>intmain()
{
inti=0, a=0, b=2, c=3, d=4;
i=a++||++b||d++;
printf(" a = %d\n b = %d\n c = %d\n d = %d\n", a, b, c, d);
return0;
}

运行结果

image.png

明:|| 左边为真,右边就不计算了

----------------我是分割线---------------    

九、条件操作符

条件操作符也叫三目操作符

exp1 ? exp2 : exp3

表达式1 为真则取 表达式2 的结果,表达式1 为假则取 表达式3 的结果

测试代码

#include <stdio.h>intmain()
{
inta=3;
intb=0;
intmax= (a>b?a : b);
printf("%d\n", max);
/*if (a > 5)b = 3;elseb = -3;*///int max = (a > b ? a : b) 等价于上面的if语句return0;
}

运行结果

image.png

十、逗号表达式

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

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

exp1, exp2, exp3, …expN

测试代码

#include <stdio.h>intmain()
{
inta=1;
intb=2;
intc= (a>b, a=b+10, a, b=a+1);//整个表达式的结果是最后一个表达式的结果printf("c=%d\n", c);
return0;
}

运行结果

image.png

再如:

//代码2a=get_val();
count_val(a);
while (a>0)
{
//代码a=get_val();
count_val(a);
}
如果使用逗号表达式,改写:while (a=get_val(), count_val(a), a>0)
{
//代码}
//代码3if (a=b+1, c=a/2, d>0)//判断条件为整个表达式的结果是最后一个表达式的结果

----------------我是分割线---------------    

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

11.1 [ ] 下标引用操作符

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

ntarr[10];//创建数组arr[9] =10;//实用下标引用操作符[ ]的两个操作数是arr和9//arr[9] --> *(arr+9) --> *(9+arr) --> 7[arr]//arr[9] 也可以写成 9[arr],但是创建数组就不可以//arr是数组首元素的地址//arr+9就是跳过9个元素,指向了第10个元素//*(arr+9) 就是第8个元素

11.2 ( ) 函数调用操作符

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

#include <stdio.h>voidtest1()
{
printf("hello\n");
}
voidtest2(char*str)
{
printf("%s\n", str);
}
intmain()
{
test1(); //实用()作为函数调用操作符test2("hello");//实用()作为函数调用操作符return0;
}

11.3 访问一个结构的成员

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

直接看代码

#include <stdio.h>structStu{
charname[10];
intage;
charsex[5];
doublescore;
};
voidset_age1(structStustu)
{
stu.age=18;
}
voidset_age2(structStu*pStu)
{
pStu->age=18;//结构成员访问}
intmain()
{
structStustu;
structStu*pStu=&stu;//结构成员访问stu.age=20;// . 结构成员访问set_age1(stu);
pStu->age=20;// -> 结构成员访问set_age2(pStu);
return0;
}

----------------我是分割线---------------    

文章就到这里,下篇即将更新

相关文章
|
4月前
|
存储 C语言 索引
【C语言篇】操作符详解(下篇)
如果某个操作数的类型在上⾯这个列表中排名靠后,那么⾸先要转换为另外⼀个操作数的类型后执⾏运算。
76 0
|
4月前
|
程序员 编译器 C语言
【C语言篇】操作符详解(上篇)
这是合法表达式,不会报错,但是通常达不到想要的结果, 即不是保证变量 j 的值在 i 和 k 之间。因为关系运算符是从左到右计算,所以实际执⾏的是下⾯的表达式。
270 0
|
2月前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
42 10
|
2月前
|
存储 编译器 C语言
【C语言】简单介绍进制和操作符
【C语言】简单介绍进制和操作符
163 1
|
2月前
|
存储 编译器 C语言
初识C语言5——操作符详解
初识C语言5——操作符详解
180 0
|
4月前
|
C语言
C语言操作符(补充+面试)
C语言操作符(补充+面试)
45 6
|
4月前
|
存储 编译器 C语言
十一:《初学C语言》— 操作符详解(上)
【8月更文挑战第12天】本篇文章讲解了二进制与非二进制的转换;原码反码和补码;移位操作符及位操作符,并附上多个教学代码及代码练习示例
60 0
十一:《初学C语言》—  操作符详解(上)
|
5月前
|
C语言
五:《初学C语言》— 操作符
本篇文章主要讲解了关系操作符和逻辑操作符并附上了多个代码示例
44 1
五:《初学C语言》—  操作符
|
5月前
|
编译器 C语言
【C语言初阶】指针篇—下
【C语言初阶】指针篇—下
|
5月前
|
存储 C语言
【C语言初阶】指针篇—上
【C语言初阶】指针篇—上