C语言中的操作符:了解与实践
欢迎大家来到贝蒂大讲堂
养成好习惯,先赞后看哦~
所属专栏:C语言学习
贝蒂的主页:Betty‘s blog
1. 操作符的分类
操作符又叫运算符,它在C语言中起着非常大的作用,以下是对操作符的分类:
- 算术操作符: + 、- 、* 、/ 、%
- 移位操作符: <<、 >>
- 位操作符: & 、| 、^、~
- 赋值操作符: = 、+= 、 -= 、 *= 、 /= 、%= 、<<= 、>>= 、&= 、|= 、^=
- 单目操作符:!、++、--、&、*、+、-、~ 、sizeof、(类型)
- 关系操作符:> 、>= 、< 、<= 、 == 、 !=
- 逻辑操作符: && 、||
- 条件操作符: ? :
- 逗号表达式: ,
- 下标引用:[]
- 函数调用: ()
- 结构成员访问: . 、->
2. 算术操作符
2.1 用法
算术操作符顾名思义就是参与运算的操作符
下表显示了 C 语言支持的所有算术操作符。假设变量 A 的值为 1,变量 B 的值为 2,则:
运算符 | 描述 | 实例 |
---|---|---|
+ | 把两个操作数相加 | A + B 将得到 3 |
- | 从第一个操作数中减去第二个操作数 | A - B 将得到 -1 |
* | 把两个操作数相乘 | A * B 将得到 2 |
/ | 分子除以分母 | A / B 将得到 0 |
% | 取模运算符,整除后的余数 | A%B将得到 1 |
2.2 代码实现
#include<stdio.h>
int main()
{
int a = 1;
int b = 2;
int c;
c = a + b;
printf("a+b=%d\n", c);
c = a - b;
printf("a-b=%d\n", c);
c = a * b;
printf("a*b=%d\n", c);
c = a / b;
printf("a/b=%d\n", c);
c = a % b;
printf("a%%b=%d\n", c);
return 0;
输出结果:
a+b=3
a-b=-1
a*b=2
a/b=0
a%b=1
3. 移位操作符
3.1 用法
移位操作符改变的是二进制序列,所以操作对象是整数,且移动位数不能为负数
下表显示了 C 语言支持的移位操作符。假设变量 A 的值为 1:
运算符 | 描述 | 实例 |
---|---|---|
<< | 将操作数的所有位向左移动指定的位数。运算规则:左边的二进制位丢弃,右边补0。 | A<<1=2 |
>> | 将操作数的所有位向右移动指定的位数。运算规则分两种:1. 逻辑右移:左边⽤0填充,右边丢弃 2. 算术右移:左边⽤原该值的符号位填充,右边丢弃 | A>>1=0 |
3.2 图像演示
- 左移操作符
- 右移操作符
3.3 代码实现
int main()
{
int a = 1;
printf("左移之后值为%d\n", a << 1);
int b = -1;
printf("右移之后值为%d\n",b>> 1);
return 0;
}
输出结果:
左移之后值为2
右移之后值为-1
- 右移之后为-1,说明在VS2022的环境下,右移操作符是算术右移
4. 位操作符
4.1 用法
位操作符与移位操作符一样作用对象是二进制序列,所以操作数自然只能为整数
下表显示了 C 语言支持的位操作符。假设变量 A 的值为 1,变量 B 的值为 2,则:
运算符 | 描述 | 实例 |
---|---|---|
& | 对两个操作数的每个二进制位执行逻辑与操作,如果两个相应的位都为 1,则结果为 1,否则为 0。 | (A & B) 将得到 0 |
| | 对两个操作数的每个二进制位执行逻辑或操作,如果两个相应的位都为 0,则结果为 0,否则为 1。 | (A | B) 将得到 3 |
^ | 对两个操作数的每个二进制位执行逻辑异或操作,如果两个相应的位值相同,则结果为 0,否则为 1。 | (A ^ B) 将得到 3 |
~ | 对操作数的每个二进制位执行逻辑取反操作,即将每一位的 0 变为 1,1 变为 0。 | (~A ) 将得到 -2 |
4.2 代码实现
int main()
{
int a = 1;
int b = 2;
int c;
c = a & b;
printf("a&b=%d\n", c);
c = a | b;
printf("a|b=%d\n", c);
c = a ^ b;
printf("a^b=%d\n", c);
c = ~a;
printf("~a=%d\n", c);
return 0;
}
输出结果:
a&b=0
a|b=3
a^b=3
~a=-2
4.3 具体分析
- a的原码,反码,补码:00000000000000000000000000000001
- b的原码,反码,补码:00000000000000000000000000000010
- a&b:00000000000000000000000000000000——>0
- a|b:00000000000000000000000000000011——>3
- a^b:00000000000000000000000000000011——>3
- ~a的原码:11111111111111111111111111111110
- ~a的反码:11111111111111111111111111111101
- ~a的补码:10000000000000000000000000000010——>-2
4.4 有趣的例题
题目: 不能创建临时变量(第三个变量),实现两个数的交换。
代码实现:
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("a = %d b = %d\n", a, b);
return 0;
}
分析:要想解决这个问题就要使用我们刚学的异或操作符,并且我们得知道一个数异或本身是0,因为所有二进制位都相同。并且0异或任何数都等于该数,因为所有二进制位都不同。而且异或是遵循交换率与结合率的。
- 因为a=ab,所以b=ab=abb=a^0=a
- a=ab=aba=b0=b,成功实现两个数的交换
5. 赋值操作符
5.1 用法
赋值操作符顾名思义,就是对变量进行赋值
下表列出了 C 语言支持的赋值操作符:
运算符 | 描述 | 实例 |
---|---|---|
= | 简单的赋值运算符,把右边操作数的值赋给左边操作数 | C = A + B 将把 A + B 的值赋给 C |
+= | 加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数 | C += A 相当于 C = C + A |
-= | 减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数 | C -= A 相当于 C = C - A |
*= | 乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数 | C *= A 相当于 C = C * A |
/= | 除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数 | C /= A 相当于 C = C / A |
%= | 求模且赋值运算符,求两个操作数的模赋值给左边操作数 | C %= A 相当于 C = C % A |
<<= | 左移且赋值运算符 | C <<= 2 等同于 C = C << 2 |
>>= | 右移且赋值运算符 | C >>= 2 等同于 C = C >> 2 |
&= | 按位与且赋值运算符 | C &= 2 等同于 C = C & 2 |
^= | 按位异或且赋值运算符 | C ^= 2 等同于 C = C ^ 2 |
|= | 按位或且赋值运算符 | C |= 2 等同于 C = C | 2 |
5.2 代码实现
#include <stdio.h>
int main()
{
int a = 10;
int c;
c = a;
printf(" = 运算符实例:c 的值 = %d\n", c);
c += a;
printf("+= 运算符实例:c 的值 = %d\n", c);
c -= a;
printf("-= 运算符实例:c 的值 = %d\n", c);
c *= a;
printf("*= 运算符实例:c 的值 = %d\n", c);
c /= a;
printf("/= 运算符实例:c 的值 = %d\n", c);
c = 200;
c %= a;
printf("%%= 运算符实例:c 的值 = %d\n", c);
c <<= 2;
printf("<<= 运算符实例:c 的值 = %d\n", c);
c >>= 2;
printf(">>= 运算符实例:c 的值 = %d\n", c);
c &= 2;
printf("&= 运算符实例:c 的值 = %d\n", c);
c ^= 2;
printf("^= 运算符实例:c 的值 = %d\n", c);
c |= 2;
printf("|= 运算符实例:c 的值 = %d\n", c);
return 0;
}
输出结果:
= 运算符实例:c 的值 = 10
+= 运算符实例:c 的值 = 20
-= 运算符实例:c 的值 = 10
*= 运算符实例:c 的值 = 100
/= 运算符实例:c 的值 = 10
%= 运算符实例:c 的值 = 0
<<= 运算符实例:c 的值 = 0
>>= 运算符实例:c 的值 = 0
&= 运算符实例:c 的值 = 0
^= 运算符实例:c 的值 = 2
|= 运算符实例:c 的值 = 2
6. 单目操作符
6.1 用法
单目操作符简单来说就是,操作的对象只有一个
下表列出了 C 语言支持的单目操作符:
运算符 | 描述 | 实例 |
---|---|---|
sizeof() | 返回变量的大小。 | sizeof(a) 将返回a变量的大小 |
& | 返回变量的地址。 | &a, 将给出变量的实际地址。 |
* | 指向一个变量。 | *a,将指向一个变量。 |
++ | 自增运算符,整数值增加 1 | a++等价于a=a+1 |
-- | 自减运算符,整数值减少 1 | a--等价于a=a-1 |
! | 称为逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真,则逻辑非运算符将使其为假。 | 假设a为真,!a为假 |
(类型) | 将一种类型强制转换为另一种类型 | 假设a为int型,(float)a将a转换为float型 |
6.2 代码实现
int main()
{
int a = 1;
printf("a的大小为%d\n", sizeof(a));
int* p = &a;
printf("a的地址为%p\n",p );
int b = *p;
printf("b的值为%d\n", b);
a++;
printf("a的值为%d\n",a);
a--;
printf("a的值为%d\n", a);
printf("a的值为%d\n", !a);//0为假
float c = (float)a;
return 0;
}
输出结果:
a的大小为4
a的地址为010FFC08
b的值为1
a的值为2
a的值为1
a的值为0
6.3 前置与后置++,--的区别
首先我们先来看一段代码
int main()
{
int a = 1;
int b = a++;//后置++
printf("a=%d b=%d\n", a, b);
int c = ++a;//前置++
printf("a=%d c=%d\n", a, c);
int m = a--;//后置
printf("a=%d m=%d\n", a, m);
int n = --a;//前置--
printf("a=%d n=%d\n", a, n);
return 0;
}
输出结果:
a=2 b=1
a=3 c=3
a=2 m=3
a=1 n=1
通过上述代码,我们可以总结以下结论:
- 前置++,--先执行++或--,然后对等式左边进行赋值
- 后置++,--恰好相反,先对等式左边进行赋值,然后再++或--
6.4 易错题
题目:下例代码输出结果为?
int main()
{
int a = 12;
int b = 1;
int c = a - (b--);//1
int d = (++a) - (--b);//2
printf("c=%d d=%d\n", c, d);
return 0;
}
输出结果:
c=11 d=14
代码分析:
执行语句1时,因为b后置--,所以b先使用,后--,然后进行a-b运算,结果是 11,随后b 再自减,就变成了 0;最后再将a-b的结果(也就是11)交给 c,所以 c 的值是 11。
执行语句2之前,b 的值已经变成 0。对于d=(++a)-(--b),a 会先自增,变成 13,然后 b 再自减,变成 -1,最后再计算13-(-1),结果是 14,交给 d,所以 d 最终是 14。
7. 关系操作符
7.1 用法
关系操作符就是进行关系之间的比较
下表显示了 C 语言支持的所有关系运算符。假设变量 A 的值为 1,变量 B 的值为 2,则:
运算符 | 描述 | 实例 |
---|---|---|
== | 检查两个操作数的值是否相等,如果相等则条件为真。 | (A == B) 为假。 |
!= | 检查两个操作数的值是否相等,如果不相等则条件为真。 | (A != B) 为真。 |
> | 检查左操作数的值是否大于右操作数的值,如果是则条件为真。 | (A > B) 为假。 |
< | 检查左操作数的值是否小于右操作数的值,如果是则条件为真。 | (A < B) 为真。 |
>= | 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。 | (A >= B) 为假。 |
<= | 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。 | (A <= B) 为真。 |
7.2 代码实现
int main()
{
int a = 1;
int b = 2;
if (a == b)
{
printf(" a 等于 b\n");
}
else
{
printf(" 不等于 b\n");
}
if (a < b)
{
printf(" a 小于 b\n");
}
else
{
printf(" a 不小于 b\n");
}
if (a > b)
{
printf(" a 大于 b\n");
}
else
{
printf(" a 不大于 b\n");
}
if (a <= b)
{
printf(" a 小于或等于 b\n");
}
if (b >= a)
{
printf(" b 大于或等于 a\n");
}
}
输出结果:
不等于 b
a 小于 b
a 不大于 b
a 小于或等于 b
b 大于或等于 a
8. 逻辑操作符
8.1 用法
下表显示了 C 语言支持的所有关系逻辑运算符。假设变量 A 的值为 1,变量 B 的值为 0,则:
运算符 | 描述 | 实例 |
---|---|---|
&& | 称为逻辑与运算符。如果两个操作数都非零,则条件为真。 | (A && B) 为假。 |
|| | 称为逻辑或运算符。如果两个操作数中有任意一个数非零,则条件为真。 | (A || B) 为真。 |
8.2 代码实现
int main()
{
int a = 1;
int b = 0;
if (a && b)
{
printf("a&&b条件为真\n");
}
else
{
printf("a&&b条件为假\n");
}
if (a || b)
{
printf("a||b条件为真\n");
}
else
{
printf("a||b条件为假\n");
}
}
输出结果:
a&&b条件为假
a||b条件为真
8.3 补充讲解
- 下列代码会输出什么
#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;
}
输出结果:
a=1
b=2
c=3
d=4
代码分析
为什么会出现这个结果呢?这是因为逻辑与(&&)一遇见假(0)等式就为假,不会在往后运算
所以a先使用为0,等式为假,然后再++,a为1,其他变量不会改变
- 同样的道理,下列代码输出什么
#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;
}
输出结果:
a=1
b=3
c=3
d=4
代码分析:
与逻辑与同样的道理,逻辑或(||)是一遇见真,整个表达式就为真,后面就不会执行
a先使用为0,++变为1,b先++变为3,在使用为真,后面表达式不执行
9. 条件操作符与逗号表达式
9.1 条件操作符
条件操作符又称为三目操作符,它的语法规则为:
如果条件为真 ? 则值为 X : 否则值为 Y
代码示例:
int main()
{
int a = 1;
int b = 1;
int c = a == b ? 20 : 10;
//含义:如果a=b为真,就将20赋给c,反之把10赋给c
return 0;
}
9.2 逗号表达式
逗号表达式,就是⽤逗号隔开的多个表达式。
逗号表达式,从左向右依次执⾏。整个表达式的结果是最后⼀个表达式的结果。
exp1, exp2, exp3, …expN
代码示例:
int main()
{
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1);
printf("c=%d",c);
return 0;
}
输出结果:
c=13
代码分析:
逗号表达式从左往右依次计算,a>b为假,a=b+10=12,a为12,b=a+1=13,再将13赋值给c
10. 下标引用和函数调用操作符
10.1 下标引用
下标引用一般是与数组有关,它一般有两个作用
int arr[10];//创建数组
arr[1]=10;//对数组赋值
10.2 函数调用
函数调用肯定是与函数相关,因为大家在前面学过函数,所以就简单举个例子
代码示例:
int Add(int x, int y)
{
return x + y;
}
int main()
{
int a = 1;
int b = 2;
int ret = Add(a, b);
return 0;
}
11. 结构体成员访问操作符
我们知道结构结构体访问一般有两种方式,直接访问(.)与间接访问(->)
11.1 用法
下表显示了 C 语言支持的结构体成员访问操作符。假设已定义结构体struct stu s则:
运算符 | 描述 | 实例 |
---|---|---|
直接访问(.) | 通过结构体变量直接访问其中成员 | s.age=10 |
间接访问(->) | 通过结构体变量地址间接访问其中成员 | &s->age=11 |
11.2 代码实现
struct stu
{
int age;
char name[14];
};
int main()
{
struct stu s;
s.age = 10;
(&s)->age = 11;
return 0;
}
12. 操作符的优先级与结合性
12.1 介绍
优先级指的是,如果⼀个表达式包含多个运算符,哪个运算符应该优先执⾏。各种运算符的优先级是不⼀样的。
结合性指的是如果两个运算符优先级相同,优先级没办法确定先计算哪个了,这时候就看结合性了,则根据运算符是左结合,还是右结合,决定执⾏顺序。⼤部分运算符是左结合(从左到右执⾏),少数运算符是右结合(从右到左执⾏),⽐如赋值运算符( = )。
下面是各种操作符的优先级与结合性
12.2 弊端
优先级和结合性其实是有弊端的,因为这两者都不能解决所有问题,比如说下面这段代码输出什么
#include <stdio.h>
int main()
{
int i = 1;
int ret = (++i) + (++i) + (++i);
printf("%d\n", ret);
printf("%d\n", i);
return 0;
}
这其实是道错题,因为在不同编译器下结果是不同的。
VS 2022环境下:
gcc环境下:
- 而且不得不吐槽一句,在公司里写代码哪个程序员会这样赋值写代码呀~
C语言中的操作符:了解与实践的更多相关文章
- C语言中操作符的优先级大全
C语言中操作符的优先级大全, 当然c++, Objective-C,大部分语言都试用. 下面是来自The C Programming Language 2th的总结. OperatorsAssocia ...
- dart系列之:dart语言中的特殊操作符
dart系列之:dart语言中的特殊操作符 目录 简介 普通操作符 类型测试操作符 条件运算符 级联符号 类中的自定义操作符 总结 简介 有运算就有操作符,dart中除了普通的算术运算的操作符之外,还 ...
- c语言中的副作用!!千万小心!
今天刚看完书上的副作用,博主觉得呢,副作用其实就在改变变量的值,也就是一个赋值操作!不过刚刚在知道上还是犯了错!!尴尬啊!! 大家都知道,c语言中的赋值操作符是自右向左结合的!! 下面有一个关于赋值中 ...
- 2.C语言中的关键字
1.auto 修饰局部变量,编译器默认所有局部变量都是用auto来修饰的,所以在程序中很少见到. 2.static 它作用可大了,除了可以修饰变量,还可以修饰函数,修饰变量,改变其作用域和生命周期,修 ...
- C语言中内存的申请函数
C语言跟内存申请相关的函数主要有 alloca,calloc,malloc,free,realloc,sbrk等. alloca是向栈申请内存,因此无需释放. malloc分配的内存是位于堆中的,并且 ...
- C 语言中的指针和内存泄漏
引言对于任何使用 C 语言的人,如果问他们 C 语言的最大烦恼是什么,其中许多人可能会回答说是指针和内存泄漏.这些的确是消耗了开发人员大多数调试时间的事项.指针和内存泄漏对某些开发人员来说似乎令人畏惧 ...
- C语言中的指针和内存泄漏
引言 对于任何使用C语言的人,如果问他们C语言的最大烦恼是什么,其中许多人可能会回答说是指针和内存泄漏.这些的确是消耗了开发人员大多数调试时间的事项.指针和内存泄漏对某些开发人员来说似乎令人畏惧,但是 ...
- Swift语言中为外部参数设置默认值可变参数常量参数变量参数输入输出参数
Swift语言中为外部参数设置默认值可变参数常量参数变量参数输入输出参数 7.4.4 为外部参数设置默认值 开发者也可以对外部参数设置默认值.这时,调用的时候,也可以省略参数传递本文选自Swift1 ...
- c语言中的位移位操作
先要了解一下C语言里所有的位运算都是指二进制数的位运算.即使输入的是十进制的数,在内存中也是存储为二进制形式. “<<”用法: 格式是:a<<m,a和m必须是整型表达式,要求m ...
- C语言中最常用的三种输入输出函数scanf()、printf()、getchar()和putchar()
本文给大家介绍C语言中最常用的三种输入输出函数scanf().printf().getchar()和putchar(). 一.scanf()函数格式化输入函数scanf()的功能是从键盘上输入数据,该 ...
随机推荐
- C#Socket编程详解(一)TCP与UDP简介
一.TCP与UDP(转载) 1.TCP 1.1 定义 TCP(TransmissionControl Protocol)传输控制协议. 是一种可靠的.面向连接的协议(eg:打电话).传输效率低全双工通 ...
- Kiractf
信息收集 主机发现和端口扫描只开放了80的web服务 WEB打点 访问首页有文件上传,肯定可以利用一波.language那个页面甚至文件包含都写脸上了. root@Lockly tmp/ki ...
- Windows Server 2016配置NTP客户端
前提:开通Windows Time 服务 输入services.msc进入服务管理界面,找到Windows Time 开启服务. 情况1:可以直接设置NTP时钟 控制面板--时钟和区域--设置时间和日 ...
- 善用 vscode 的批量和模板技巧来提效
vs code 其实有很多实用的技巧可以在日常工作中带来很大的提效,但可能是开发中没有相应的痛点场景,因此有些技巧接触的人不多 本篇就来介绍下多光标的批量操作和模板代码两种技巧在日常工作中的提效 涉及 ...
- libGDX游戏开发之游戏纹理精灵切割(十六)
libGDX游戏开发之游戏纹理精灵切割(十六) libGDX系列,游戏开发有unity3D巴拉巴拉的,为啥还用java开发?因为我是Java程序员emm-国内用libgdx比较少,多数情况需要去官网和 ...
- 快速批量升级 NugetPackage 版本
批量升级项目中的 Nuget 有时候我们需要升级整个解决方案中的某些Nuget版本,如果每个手动使用NuGet Package Manager 会很麻烦.经过一个周末的踩坑,我找到一个解决方案. Na ...
- 浅谈6种流行的API架构风格
前言 API在现代软件开发中扮演着重要的角色,它们是不同应用程序之间的桥梁.编写业务API是日常开发工作中最常见的一部分,选择合适的API框架对项目的成功起到了至关重要的作用.本篇文章将浅谈一下当前6 ...
- CodeForces 808G Anthem of Berland 前缀函数 KMP DP
原题链接 题意 第一行给我们一串长为s,只包含小写字母与问号的字符串A,第二行给我们一个长为t只有小写字母的字符串B, 同时满足 $ s * t \le 1e7 $ 我们可以把问号变成任意的字母,我们 ...
- 软件界旷世之架:测试驱动开发(TDD)之争
摘要:在软件行业中,神仙打架的名场面,那就不得不提的是2014年的那场--测试驱动开发(TDD)之争. 在历史上有很多精彩绝伦的神仙打架,比如数学界的牛顿和莱布尼茨关于微积分的旷世之争:比如量子物理中 ...
- OCR性能优化:从神经网络到橡皮泥
摘要:在这个算力还可以的时代,我们的研究人员一方面致力于不断地去研究各中不同的场景中的的通用网络,一方面致力于优化神经网络的学习方式,这些都是在试图化减少AI需要的算力资源. 本文分享自华为云社区&l ...