✅作者简介:大家好,我是小杨
📃个人主页:「小杨」的csdn博客
🔥系列专栏:小杨带你玩转C语言【初阶】
🐳希望大家多多支持🥰一起进步呀!
大家好呀!我是小杨。小杨花几天的时间将C语言中的操作符这部分知识做了一个大总结,在方便自己复习的同时也能够帮助到大家。通篇字数在一万字左右,可以算作是非常详细了,一文就可以带领大家彻底掌握操作符这部分内容,文章很长建议先收藏再看,防止下次想看就找不到啦。
文章目录
✍1,算术操作符
✍2,移位操作符
🔍2.1,左移操作符
🔍2.2,右移操作符
✨2.2.1,算术移位
✨2.2.2,逻辑移位
✍3,位操作符
🔍3.1,按位与 &
🔍3.2,按位或 |
🔍3.3,按位异或 ^
✍4,赋值操作符
🔍4.1,赋值操作符
🔍4.2,复合赋值操作符
✍5,单目操作符
🔍5.1,逻辑反操作 !
🔍5.2,取地址 &
🔍5.3,按位取反 ~
🔍5.4,sizeof
🔍5.5,前后置++,- -
🔍5.6,解引用操作符
🔍5.7,强制类型转换
✍6,关系操作符
✍7,逻辑操作符
✍8,条件操作符
✍9,逗号操作符
✍10,其他操作符
🔍10.1,下标引用操作符
🔍10.2,函数调用操作符
🔍10.3,访问结构成员操作符
✍1,算术操作符
基本的算术操作符如下:
操作符 | + | - | * | / | % |
---|---|---|---|---|---|
名称 | 加 | 减 | 乘 | 除 | 求余 |
1, / 两边的操作数都为整数(整型除法),那么输出的结果就是整数形式。
2, / 两边的操作数只要有一个是小数(浮点型除法),输出的结果就是小数形式。
3, %求的是两束整除之后的余数,两边的操作数必须都为整数,输出的结果为整数形式。
为了能够更好的理解算术操作符及其使用,小杨给大家举了下述例子:
#include<stdio.h>
int main()
{
int a = 3;
int b = 2;
int c = 0;
printf("c=%d\n", a + b);//相加
printf("c=%d\n", a - b);//相减
printf("c=%d\n", a * b);//相乘
printf("c=%d\n", a / b);//求商
printf("c=%d\n", a % b);//取模
return 0;
}
示例输出结果如下:
🚉由此返回目录
✍2,移位操作符
操作符 | << | >> |
---|---|---|
名称 | 左移操作符 | 右移操作符 |
说明:左移操作符<<和右移操作符>>移动的是整数的二进制位。
讲到这里,我就要先给大家普及一个知识,大家先考虑一个问题:C语言整数在内存中是怎么存储的?
解答:讲解整数的存储之前,首先要了解原码、反码和补码这几个概念。然而正整数和负整数的原码,反码和补码存在着不同,需要特别注意。
原码:正整数和负整数的原码都是将一个整数转换成二进制,得到就是原码,但两者的符号位不同,正整数的符号位为0,而负整数的符号为1.
反码:对于一个正整数而言,其的反码与其的原码相同(原码、反码、补码都相同);而对于负整数,其反码则是将原码中除符号位以外的每一位进行取反,得到的就是其反码。
补码:对于正整数而言,它的补码就是其原码(原码、反码、补码都相同);而对于负整数,其补码是在其反码的基础上加 1。
为了能够更好的理解这三者之间的关系以及正负整数的区别,小杨在这里分别给你们举一个例子:
说明:7为int整型(数据类型),而int整型的数据类型在内存中占4个字节,即为32bite位。一般规定:整数的二进制序列的第一位二进制位就是其符号位,正整数的符号位为0,负整数的则为1。在不知道整数是正整数还是负整数的情况下可以根据这个符号位的数值来判断。
讲到这里,大家应该对原码,反码和补码这几个概念有了深刻的理解吧,下面我们再回到最初的那个问题:在C语言中,整数在内存中是怎么存储的?
对于这个问题,我的回答是在计算机系统中,数值一律用补码来表示和存储的。
那么肯定有小伙伴要问了,负数的补码计算是比较麻烦的,那为什么要有补码这种形式?都用原码他不香吗?
在事实上,正因为有了补码才香。使用补码,可以将符号位和数值域统一处理,同时加法和减法也可以统一处理。注意哦,CPU只有加法器而没有减法器哦)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。是不是很香?
最后,再回到左移操作符<<和右移操作符>>的讲解:
🔍2.1,左移操作符
左移操作符:把整数的二进制序列向左移动
移位规则:左边抛弃,右边补0
为了能够更好的理解,小杨给大家举了下述例子:
正整数示例:
#include<stdio.h>
int main()
{
int a = 7;//a为正整数
int b = a << 1;
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
示例(正整数)思考流程:示例(正整数)输出结果:
负整数示例:
#include<stdio.h>
int main()
{
int a = -7;//a为负整数
int b = a << 1;
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
示例(负整数)思考流程:
示例(正整数)输出结果:
🔍2.2,右移操作符
右移操作符:把整数的二进制序列向右移动
右移运算分为逻辑移位和算术移位两种,使用哪种移位取决于编译器,在大多数情况下采用算术移位,而逻辑移位目前小杨还没遇见过。
✨2.2.1,算术移位
移位规则:右边抛弃,左边补原符号位
为了能够更好的理解,小杨给大家举了下述例子:
正整数示例:
#include<stdio.h>
int main()
{
int a = 7;
int b = a >> 1;
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
示例(正整数)思考流程:
示例(正整数)输出结果:
负整数示例:
#include<stdio.h>
int main()
{
int a = 7;
int b = a >> 1;
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
示例(负整数)思考流程:
示例(负整数)输出结果:
✨2.2.2,逻辑移位
移位规则:右边抛弃,左边补0
为了能够更好的理解,小杨给大家举了下述例子:
正整数示例:
#include<stdio.h>
int main()
{
int a = 7;
int b = a >> 1;
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
为了更好理解其运算规则,小杨动手总结出了示例分析流程。
示例(正整数)思考流程:
负整数示例:
#include<stdio.h>
int main()
{
int a = 7;
int b = a >> 1;
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
为了更好理解其运算规则,小杨动手总结出了示例分析流程。
示例(负整数)思考流程如下:
说明:小杨使用的VS2019进行代码书写,而VS2019编译器采用算术移位进行整数右移操作,故无法得出采用逻辑移位方式的结果,但是我们可以通过自己的分析思路流程来得出结果。
正整数分别采用算术移位和逻辑移位的比较:
说明:正整数运用右移操作符进行右移,不管是采用算术运算还是采用逻辑运算,表面看似无影响,但实际上是有区别的。
负整数分别采用算术移位和逻辑移位的比较:
说明:对于负整数运用右移操作符进行右移,采用算术运算和采用逻辑运算,从它们的逻辑思路就看出采用不同的移位规则,计算得出的结果不同。因此运用负整数右移即可作为辨别编译器采用何种移位规则的判断依据。
🚉由此返回目录
✍3,位操作符
操作符 | & | | | ^ |
---|---|---|---|
名称 | 按位与 | 按位或 | 按位异或 |
说明:因使用这三种位操作符,整数都是以二进制的形式进行运算的,故:&可称为按(二进制)位与,| 可被称为按(二进制)位或,^ 可被称为按(二进制)位异或。
🔍3.1,按位与 &
按位与&运算规则:&两边的操作数所对应的二进制序列进行一一对应,只要对应的二进制位中有0就为0,全1才为1。
为了能够更好的理解按位与&这个位操作符,小杨给大家举了下述例子:
#include<stdio.h>
int main()
{
int a = 3;
int b = -5;
printf("%d\n", a & b);
return 0;
}
为了更好理解其运算规则,小杨动手总结出了示例分析流程,分析流程如下:
示例输出结果:
🔍3.2,按位或 |
按位或 | 运算规则:| 两边的操作数所对应的二进制序列进行一一对应,只要对应的二进制位中有1就为1,全0才为0。
为了能够更好的理解按位或 | 这个位操作符,小杨给大家举了下述例子:
#include<stdio.h>
int main()
{
int a = 3;
int b = -5;
printf("%d\n", a | b);
return 0;
}
为了更好理解其运算规则,小杨动手总结出了示例分析流程,分析流程如下:
示例输出结果:
🔍3.3,按位异或 ^
按位异或 ^ 运算规则:^ 两边的操作数所对应的二进制序列进行一一对应,对应的二进制位相同s时结果为0,而不同时结果则为1。
为了能够更好的理解按位异或 ^ 这个位操作符,小杨给大家举了下述例子:
#include<stdio.h>
int main()
{
int a = 3;
int b = -5;
printf("%d\n", a ^ b);
return 0;
}
为了更好理解其运算规则,小杨动手总结出了示例分析流程,分析流程如下:
示例输出结果:
🚉由此返回目录
✍4,赋值操作符
🔍4.1,赋值操作符
赋值操作符是一个很棒的操作符,它可以将你之前得到的不满意的值重新赋值成让你心目中的值。也就是说你可以给其重新赋值。
小杨在这里向大家提醒一下:这里不仅要注意赋值操作符=和关系操作符==的区别,防止混淆;还要学会区分数值的初始化和数值的赋值这两者。
为了更好地认识赋值操作符,小杨举了下述2个小例子:
int weight = 120;//体重初始化
weight = 89;//不满意就赋值
double salary = 10000.0;//工资初始化
salary = 20000.0;//使用赋值操作符赋值。
🔍4.2,复合赋值操作符
基本的复合赋值操作符如下:
操作符 | 描述 | 示例 | 含义 |
---|---|---|---|
+= | 加法赋值运算符 | a+=c | a=a+c |
-= | 减法赋值运算符 | a-=c | a=a-c |
*= | 乘法赋值运算符 | a*=c | a=a*c |
/= | 除法赋值运算符 | a/=c | a=a/c |
%= | 取模赋值运算符 | a%=c | a=a%c |
**= | 幂赋值运算符 | a**=c | a=a**c |
//= | 取整除赋值运算符 | a//=c | a=a//c |
为了能够更好的理解上述复合赋值操作符及其使用,小杨举了下述例子。示例如下
#include<stdio.h>
int main()
{
int a = 4;
printf("%d\n", a += 2);
printf("%d\n", a -= 2);
printf("%d\n", a *= 2);
printf("%d\n", a /= 2);
printf("%d\n", a %= 2);
return 0;
}
示例输出果如下:
复杂的复合赋值操作符如下:
操作符 | 描述 | 示例 | 含义 |
---|---|---|---|
<<= | 左移赋值运算符 | a<<=c | a=a<<c |
>>= | 右移赋值运算符 | a>>=c | a=a>>c |
&= | 按位与赋值运算符 | a&=c | a=a&c |
|= | 按位或赋值运算符 | a|=c | a=a|c |
^= | 按位异或赋值运算符 | a^=c | a=a^c |
为了能够更好的理解上述复合赋值操作符及其使用,小杨举下述例子。示例如下:
#include<stdio.h>
int main()
{
int a = 4;
printf("%d\n", a <<= 2);
printf("%d\n", a >>= 2);
printf("%d\n", a &= 2);
printf("%d\n", a |= 2);
printf("%d\n", a ^= 2);
return 0;
}
示例输出果如下:
🚉由此返回目录
✍5,单目操作符
基本的单目操作符如下:
操作符 | 描述 |
---|---|
! | 逻辑反操作 |
- | 负值 |
+ | 正值 |
& | 取地址 |
sizeof | 操作符的类型长度 |
~ | 对数的二进制按位取反 |
– | 前置or后置– |
++ | 前置or后置++ |
* | 间接访问(解引用)操作符 |
(类型) | 强制类型转换 |
🔍5.1,逻辑反操作 !
逻辑反操作!
使用结构:!表达式
运算规则:若表达式为真,!表达式结果为假,输出为0;若表达式为假,!表达式结果为真,输出结果为1。
为了能够更好的理解逻辑反操作符及其使用,小杨举下述例子。示例如下:
#include<stdio.h>
int main()
{
printf("%d\n", !2);
printf("%d\n", !0);
return 0;
}
示例输出结果如下:
🔍5.2,取地址 &
取地址 &:当作用于一个对象上时,它返回了该对象的地址,比如&num所返回的是变量num的地址。
为了能够更好的理解取地址操作符及其使用,小杨举了下述例子。示例如下:
#include<stdio.h>
int main()
{
int a = 10;
int* p = &a;
printf("%p", p);
return 0;
}
示例输出结果如下:
🔍5.3,按位取反 ~
按位取反操作符~也同位操作符一样,按照二进制进行运算,可被称为按(二进制)位取反。
运算规则:将操作数的二进制序列中的每一位二进制位进行取反操作,1变为0,0变为1。
为了能够更好的理解按位取反操作符及其使用,小杨举了下述例子。示例如下:
#include<stdio.h>
int main()
{
int a = 3;
int b = ~a;
printf("%d", b);
return 0;
}
为了更好理解其其运算规则,小杨动手总结出了示例分析流程,分析流程如下:
示例输出结果如下:
🚉由此返回目录
🔍5.4,sizeof
sizeof() 是一个计算变量所占内存的大小或者计算数据类型所创建的变量所占据空间大小的运算符。
为了能够更好的理解sizeof及其使用,小杨举了下述例子。示例如下:
#include<stdio.h>
int main()
{
int a = 10;
char b = 'a';
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(b));
printf("%d\n", sizeof(int));
printf("%d\n", sizeof(char));
return 0;
}
示例输出结果如下:
说明:我们也知道,sizeof 的功能就是计算对象所占内存空间的大小。在32位系统下, int 占4个字节,a又是整型变量,所以也占4个字节;而char 在32位系统下占1个字节,b又是字符变量,所以也占1个字节。
讲到这里,大家应该知道sizeof是操作符而不是函数,sizeof操作符后边的括号可以省略,故小杨将上述变量a和变量b外边的括号去掉,结果还是可以正常进行打印输出,说明在括号去掉,系统还可以正常编译使用。
但小伙伴可能会说变量外边的括号可以省略,省略后系统程序也能够正常进行打印,不会出现报错问题。那么数据类型两边的括号能不能同变量一样,省略后代码程序也能够正常运行吗?
为了让大家彻底掌握这个问题,小杨通过举该情况的例子来进行详细说明。
示例:
#include<stdio.h>
int main()
{
int a = 10;
char b = 'a';
printf("%d\n", sizeof int);
printf("%d\n", sizeof char);
return 0;
}
示例输出结果:
看到程序代码输出结果,答案就显而易见了吧。数据类型两边的括号不能同变量一样,省略后代码程序也能够正常运行,会出现报错。
可能会有小伙伴会有疑问:" sizeof(int) “可以运行,为什么” sizeof int "又不可以呢?
这里我们要明白,万事都有其存在意义。像 unsigned + 类型 ( 表示无符号数 )和static + 类型 (表示静态变量 ),而再看 sizeof + 类型 想要表示什么意义呢?想要声明什么样的类型变量? 类型扩展吗? 所以这样思考应该就能明白它为什么是错误的表示了吧。
如果还不明白,小杨建议大家也可以这样理解:报错就是语法规则不允许了呗~因为int是类型关键字,不是一个实体,加上括号后应该做了特殊处理。
所以在计算变量空间大小时可以省略括号,而在计算类型时大小不能省略。不过在一般情况建议写着,防止出错。
🚉由此返回目录
🔍5.5,前后置++,--
前置++,先++,后打印
后置++,先打印,后++
前置- -,先- -,后打印
后置- -, 先打印,后- -
为了能够更好的理解前置or后置++及其使用,小杨举了下述例子。示例如下::
//前置++,先++,后打印
#include<stdio.h>
int main()
{
int a = 10;
printf("%d\n", ++a);
printf("%d\n", a);
return 0;
}
程序代码输出果如下:
为了能够更好的理解后置++及其使用,小杨举了下述例子。示例如下:
//后置++,先打印,后++
#include<stdio.h>
int main()
{
int a = 10;
printf("%d\n", a++);
printf("%d\n", a);
return 0;
}
示例输出结果如下:
注意:前置- -or后置- -的用法与前置++ or 后置++的用法一样哦,小伙伴们可以自己实际操作一下,加深对这些知识的理解!
🔍5.6,解引用操作符
解引用操作符 *是指“引用 (Ref)”的反操作。
为了能够更好的理解解引用操作符及其使用,小杨举了下述例子。示例如下:
#include<stdio.h>
int main()
{
int a = 10;
int* p = &a;
*p = 20;
printf("%d", a);
return 0;
}
为了更好理解其规使用,小杨动手总结出了示例分析流程,分析流程如下:
示例输出结果如下:
🔍5.7,强制类型转换
强制类型转换是把变量从一种类型转换为另一种数据类型。
为了能够更好的理解强制类型转换及其使用,小杨举了下述例子。示例如下:
如果您想存储一个double类型的值到一个简单的整型中,您需要把 double 类型强制转换为 int 类型。
#include<stdio.h>
int main()
{
double a = 3.14;
printf("%lf\n", a);
printf("%d\n", (int)a);
return 0;
}
示例输出结果如下:
🚉由此返回目录
✍6,关系操作符
基本的关系操作符如下:
操作符 | 释义 |
---|---|
> | 大于 |
>= | 大于等于 |
< | 小于 |
<= | 小于等于 |
!= | 不等于 |
== | 等于 |
小杨在这里特别给大家扩展一个小知识:
关系操作符==不是所有东西都可以进行判断是否相等操作,例如浮点数之间的是否相等判断以及字符串之间的是否相等判断。
- 利用==来判断两个浮点数是否相等会造成浮点数精度损失。
- 利用==来判断两个字符串是否相等制作说法是错误的,两个字符串之间利用==来进行比较的是两个字符串中首元素的地址,而不是用来判断两个字符串是否相等的,要想判断两个字符串是否相等以及其他的关系要利用strcmp函数进行两个字符串的比较。
为了能够更好的理解关系操作符及其使用,小杨举了下述例子。示例如下:
#include <stdio.h>
int main()
{
printf("%d\n", 1 > 2);
printf("%d\n", 1 < 2);
return 0;
}
示例输出结果如下:
小伙伴可能不理解为什么输出的结果是这个?其实啊,可以利用数学思维进行理解。原因就是1 > 2在数学上是不成立的,所以结果为0;而1 < 2在数学上是不成立的,所以结果为1。
🚉由此返回目录
✍7,逻辑操作符
基本的逻辑操作符如下:
操作符 | 释义 |
---|---|
&& | 逻辑与 |
|| | 逻辑或 |
1.&& 逻辑与 判断规则:两真则真,一假就假
2. || 逻辑或 判断规则:一真则真,两假才假
为了能够更好的理解逻辑操作符及其使用,小杨举了下述例子。示例如下:
#include <stdio.h>
int main()
{
printf("%d\n", 1 && 2);
printf("%d\n", 0 && 2);
printf("%d\n", 0 && 0);
printf("%d\n", 1 || 2);
printf("%d\n", 1 || 0);
printf("%d\n", 0 || 0);
return 0;
}
示例输出结果如下:
扩展小知识:
- &&左边操作数为假,右边不计算
- | | 左边操作数为真,右边不计算
为了能够更好的理解其使用,小杨举了下述例子。示例如下:
&&的相关示例:
#include <stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ && ++b && d++;
printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d);
return 0;
}
示例输出结果如下:
| | 的相关示例:
#include <stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++||++b||d++;
printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d);
return 0;
}
示例输出结果如下:
🚉由此返回目录
✍8,条件操作符
操作符 | 释义 |
---|---|
exp1?exp2:exp3 | 判断表达式1?表达式1:表达式2 |
解释:表达式1进行判断,若结果为真,则 exp1 ? exp2 : exp3 整体的结果为 表达式 exp2 的结果
若结果为假,则exp1 ? exp2 : exp3 整体的结果为 表达式 exp3 的结果
为了能够更好的理解条件操作符及其使用,小杨举了下述例子。示例如下:
#include<stdio.h>
int main()
{
int a = 4;
int b = 6;
int max = a > b ? a : b;
printf("%d", max);
return 0;
}
示例输出结果如下:
🚉由此返回目录
✍9,逗号表达式
exp 1,exp 2,exp 3,exp 4……exp n
逗号表达式:就是用逗号隔开的多个表达式。
逗号表达式,从左向右依次执行,整个表达式的结果是最后一个表达式的结果。
为了能够更好的理解逗号表达式及其使用,小杨举了下述例子。示例如下:
#include<stdio.h>
int main()
{
int a = 5;
int b = 3;
int c = 0;
// 5 7 2 8
c = (a +c, a + 2, b - 1, a + b);
printf("%d", c);
return 0;
}
示例输出结果如下:
🚉由此返回目录
✍10,其它操作符
🔍10.1,下标引用操作符:[ ]
操作数:一个数组名+一个索引值
比如:arr[9]:arr和9是[]的操作数
为了能够更好的理解下标引用操作符及其使用,小杨举了下述例子。示例如下:
#include<stdio.h>
int main()
{
int arr[10];//创建数组
arr[9] = 10;//实用下标引用操作符
printf("%d", arr[9]);
return 0;
}
示例输出结果如下:
🔍10.2,函数调用操作符:()
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递函数的参数
比如:Add(num1,num2):Add,num1,num2是()的操作数
为了能够更好的理解函数调用操作符及其使用,小杨举了下述例子。示例如下:
#include <stdio.h>
void test1()
{
printf("hello world!\n");
}
int main()
{
test1(); //实用()作为函数调用操作符。
return 0;
}
示例输出结果如下:
🔍10.3,访问一个结构成员
. :结构体.成员名
->:结构体指针->成员名
为了能够更好的理解结构体访问及其使用,小杨举了下述例子。示例如下:
. :结构体.成员名使用示例:
#include <stdio.h>
#include <string.h>
struct Stu
{
char name[20];
int age;
double score;
};
void set_stu(struct Stu* ps)
{
strcpy((*ps).name, "zhangsan");
(*ps).age = 20;
(*ps).score = 100.0;
}
void print_stu(struct Stu* ps)
{
printf("%s %d %lf\n", (*ps).name, (*ps).age, (*ps).score);
}
int main()
{
struct Stu s = { 0 };
set_stu(&s);
print_stu(&s);
return 0;
}
示例输出结果如下:
->:结构体指针->成员名使用示例:
#include <stdio.h>
#include <string.h>
struct Stu
{
char name[20];
int age;
double score;
};
void set_stu(struct Stu* ps)
{
strcpy(ps->name, "zhangsan");
ps->age = 20;
ps->score = 100.0;
}
void print_stu(struct Stu* ps)
{
printf("%s %d %lf\n", ps->name, ps->age, ps->score);
}
int main()
{
struct Stu s = { 0 };
set_stu(&s);
print_stu(&s);
return 0;
}
示例输出结果如下:
🚉由此返回目录
结语
小伙伴们,当你学到这里的时候,你们应该对操作符部分的内容已经有了全新的认识吧!🥳🥳🥳后续小杨会给大家总结表达式的内容,不断更新优质的内容来帮助大家,一起进步。加油,追梦人!让我们一起拥抱美好明天!🎆🎆🎆