操作符详解
操作符也被叫做:运算符。
操作符的分类
- 算术操作符:
+ 、- 、* 、/ 、%
- 赋值操作符:
= 、+= 、 -= 、 *= 、 /= 、%= 、<<= 、>>= 、&= 、|= 、^=
- 移位操作符:
<< >>
- 位操作符:
& | ^ ~
- 单目操作符:
!、++、--、&、*、+、-、~ 、sizeof、(类型)
- 关系操作符:
> 、>= 、< 、<= 、== 、!=
- 逻辑操作符:
&& 、||
- 条件操作符:
? :
- 逗号表达式:
,
- 下标引用:
[]
- 函数调用:
()
- 结构成员访问:
. 、->
算数操作符
算数操作符包括 `+ - * \ %` 。2.1 `+` 和 `-`
`+` 和 `-` 用来完成加法和减法。#include<stdio.h>
int main()
{
int a = 4 + 22;
int b = 61 - 23;
printf("%d\n%d\n", a, b);
return 0;
}
2.2 `*`
`*` 用来完成乘法。#include<stdio.h>
int main()
{
int num = 5;
printf("%d", num * num);
return 0;
}
2.3 `/`
`/` 用来完成除法。除号的两端如果是整数,执行的是整数除法,得到的也是整数。
#include<stdio.h>
int main()
{
int a = 6;
int b = 4;
int c = a / b;
printf("%d\n", c);//输出 1
printf("%f\n", c);//输出 1.000000
return 0;
}
C语言中 /
执行的整数除法是整除,只会返回整数部分,丢弃小数部分。
如果想要得到浮点型的结果,两个运算数至少有一个浮点数,此时C语言会进行浮点数除法。
#include<stdio.h>
int main()
{
int a = 6;
float b = 4.0f;
float c = a / b;
printf("%f", c);//输出 1.500000
return 0;
}
printf
打印浮点数默认保留6位小数。
#include<stdio.h>
int main()
{
float a = 6.0 / 4;
printf("%f", a);
return 0;
}
直接在代码中写浮点数,默认为 double
类型。 此处的6.0为 double
类型。
2.4 `%`
`%` 表示求模运算,即返回**两个整数相除**的余值。这个运算符**只能用于整数,不能用于浮点数**。#include<stdio.h>
int main()
{
int x = 6 % 4;
printf("%d", x);//输出 2
return 0;
}
负数求模的规则是:结果的正负号由第一个运算符的正负号决定。
#include<stdio.h>
int main()
{
printf("%d\n", 11 % 5);//输出 1
printf("%d\n", 11 % -5);//输出 1
printf("%d\n", -11 % -5);//输出 -1
printf("%d\n", -11 % 5);//输出 -1
return 0;
}
在上述示例中, + - * / %
都是由有两个操作数的,位于操作符两端的就是它们的操作数,因此算术操作符也被称为双目操作符。
赋值操作符
3.1 `=`
在变量创建好的时候给一个初始值叫做**初始化**,在变量创建好后,再给一个值,称为**赋值**。int a = 3;//初始化
a = 5;//赋值
赋值语句的返回值是所赋的值。
#include<stdio.h>
int main()
{
int x = 0;
if (x = 0)
printf("hehe");//x = 0的返回值为0,因此无输出
return 0;
}
赋值操作符可以进行连续赋值。
int a = 3;
int b = 5;
int c = 0;
c = b = a + 3;//连续赋值,b = c = 6
连续赋值从右到左依次赋值。
C语言虽然支持连续赋值,但是连续赋值写出来的代码不容易理解。
3.2 复合赋值符
这些赋值符有:+= -=
*= /= %=
>>= <<=
&= |= ^=
写代码时用于简化对一个数进行自增,自减之类的操作。
int a = 10;
a += 3;// a = a + 3
a *= 3;// a = a * 3
a >>= 3;// a = a >> 3
单目操作符
单目操作符有这些:!、++、--、&、*、+、-、~ 、sizeof、(类型)
4.1 `++`和 `- -`
**前置**:先+/-1,后使用。#include<stdio.h>
int main()
{
int a = 10;
int b = ++a;//++的操作数是a,放在a前面的,就是前置++
printf("%d\n%d", a, b);//输出结果为a=11,b=11
return 0;
}
a原来是10,先+1变成11,再给b赋值
后置:先使用,后+/-1
#include<stdio.h>
int main()
{
int a = 10;
int b = a++;//++的操作数是a,放在a后面的,就是后置++
printf("%d\n%d", a, b);//输出结果为a=11,b=10
return 0;
}
a原来是10,先赋值给b,再+1变成11
4.2 `+`和 `-`
这里的`+`是正号,`-`是负号。+
对正负值没有影响,是一个完全可以省略的操作符。
-
用来改变一个值的正负号,负数的前面加上 -
会得到正数,正数的前面加上 -
就会得到负数。
#include<stdio.h>
int main()
{
int a = 1;
int b = -1;
printf("%d\n", +a);//输出 1
printf("%d\n", +b);//输出 -1
printf("%d\n", -a);//输出 -1
printf("%d\n", -b);//输出 1
return 0;
}
4.3 强制类型转换
语法形式: `(类型)`int a = 3.14;
//a的类型为int,3.14为double类型
//两边类型不一致,编译器会报警告。
int a = (int)3.14;
//将3.14强制转换为int类型
//这种强制类型转换**只取整数部分**
:::tips
不到万不得已,尽量不使用强制类型转换。
:::
关系操作符
C语言用于比较的表达式,称为 `“关系表达式”(relational expression)`,里面使用的运算符就称为`“关系运算符”(relational operator)`,主要有下面6个::::tips
>
大于运算符<
小于运算符>=
大于等于运算符<=
小于等于运算符==
相等运算符!=
不相等运算符
:::
关系表达式通常返回 0
或 1
,表示真假。
C语言中, 0
** 表示假,所有非零值表示真**。比如,20 > 12 返回1 ,12 > 20 返回0 。
关系表达式常用于 if
或 while
结构。
if (x == 3)
{
printf("x is 3.\n");
}
注意:
相等运算符 ==
与赋值运算符 =
是两个不一样的运算符,不要混淆。有时候,可能会不小心写出下面的代码,它可以运行,但很容易出现意料之外的结果。
if (x = 3)
上面示例中,原意是x == 3 ,但是不小心写成x = 3 。这个式子表示对变量x 赋值3 ,它的返回值为3 ,所以if 判断总是为真。
if (3 == x)
为了防止出现这种错误,当一个变量和一个常量比较相等时,将变量写在等号的右边。
/* 报错 */
if (3 = x)
这样的话,如果把==
误写成=
,编译器就会报错。
另一个需要避免的错误是:多个关系运算符不宜连用。
i < j < k
上面示例中,连续使用两个小于运算符。这是合法表达式,不会报错,但是通常达不到想要的结果,即不是保证变量j 的值在i 和k 之间。
因为关系运算符是从左到右计算,所以实际执行的是下面的表达式。
(i < j) < k
上面式子中,i < j 返回 0
或 1
,所以最终是 0
或 1
与变量 k
进行比较。
如果想要判断变量 j
的值是否在 i
和 k
之间,应该使用下面的写法:
i < j && j < k
比如:我们输入一个年龄,如果年龄在18岁~36岁之间,我们输出青年。
如果我们这样写:
#include <stdio.h>
int main()
{
int age = 0;
scanf("%d", &age);
if(18<=age<=36)
{
printf("⻘年\n");
}
return 0;
}
当我们输入10的时候,依然输出青年,如下图
这是因为,我们先拿 18
和 age
中存放的 10
比较,表达式 18<=10
为假, 18<=age
的结果是 0
,再拿 0
和 36
比较, 0<=36
为真,所以打印了青年,所以即使当 age
是 10
的时候,也能打印青年,逻辑上是有问题。‘
正确的写法:
#include <stdio.h>
int main()
{
int age = 0;
scanf("%d", &age);
if(age>=18 && age<=36)
{
printf("⻘年\n");
}
return 0;
}
条件操作符
条件操作符也叫**三目操作符**,需要接受三个操作数的,形式如下:exp1 ? exp2 : exp3
条件操作符的计算逻辑是:
如果 exp1
为真, exp2
计算,计算的结果是整个表达式的结果;
如果exp1
为假, exp3
计算,计算的结果是整个表达式的结果。
练习:找出两个数间的较大值
//方案一:if语句
#include<stdio.h>
int main()
{
int a = 0;
int b = 0;
scanf("%d %d",&a, &b);
if (a > b)
printf("%d", a);
else
printf("%d", b);
return 0;
}
//方案二:条件操作符
#include<stdio.h>
int main()
{
int a = 0;
int b = 0;
scanf("%d %d",&a, &b);
int c = (a > b ? a : b);
printf("%d", c);
return 0;
}
逻辑操作符
逻辑运算符提供逻辑判断功能,用于构建更复杂的表达式,主要有下面三个运算符。&& || !
i
:逻辑取反运算符(改变单个表达式的真假)。&&
:逻辑与运算符,就是并且的意思(两侧的表达式都为真,则为真,否则为假)。||
:逻辑或运算符,就是或者的意思(两侧至少有一个表达式为真,则为真,否则为假)。
7.1 逻辑取反运算符
比如,我们有一个变量叫flag ,如果flag为假,要做一个什么事情,就可以这样写代码:#include<stdio.h>
int main()
{
int flag = 0;
if (!flag)
printf("hehe");//输出hehe
else
printf("haha");
return 0;
}
flag | !flag |
---|---|
非 0 | 0 |
0 | 1 |
如果 flag
为真, !flag
就是假,如果 flag
为假, !flag
就是真。
所以上面的代码的意思就是 flag
为假,执行 if
语句中的代码。
7.2 逻辑与运算符
`&&` 就是逻辑与运算符,也是**并且**的意思, `&&` **是一个双目操作符**,使用的方式是 `a && b`。&&
两边的表达式都是真的时候,整个表达式才为真,只要有一个是假,则整个表达式为假。
a | b | a&&b |
---|---|---|
非 0 | 非 0 | 1 |
非 0 | 0 | 0 |
0 | 非 0 | 0 |
0 | 0 | 0 |
比如:如果我们说月份是3月到5月,是春天,那使用代码怎么体现呢?
#include<stdio.h>
int main()
{
int month = 0;
scanf("%d", &month);
if (month > 3 && month < 5)
printf("春天");
return 0;
}
这里表达的意思就是month既要大于等于3,又要小于等于5,必须同时满足。
7.3 逻辑或运算符
`||` 就是逻辑或运算符,也就是**或者**的意思, `||` 也是一个**双目操作符**,使用的方式是 `a || b` 。||
两边的表达式只要有一个是真,整个表达式就是真,两边的表达式都为假的时候,才为假。
比如:我们说一年中月份是12月或者1月或者2月是冬天,那么我们怎么使用代码体现呢?
#include<stdio.h>
int main()
{
int month = 0;
if (month = 1 || month = 2 || month = 12)
printf("冬季");
return 0;
}
7.4 练习:闰年的判断
输入一个年份year,判断year是否是闰年闰年判断的规则:
- 能被4整除并且不能被100整除是闰年
- 能被400整除是闰年
//标准版
#include<stdio.h>
int main()
{
int year = 0;
scanf("%d", &year);
if (year % 100 != 0 && year % 4 == 0)
printf("是闰年");
else if (year % 400 == 0)
printf("是闰年");
else
printf("不是闰年");
return 0;
}
//简洁版
#include<stdio.h>
int main()
{
int year = 0;
scanf("%d", &year);
if (year % 100 != 0 && year % 4 == 0 || year % 400 == 0)//合二为一,更加简洁
printf("是闰年");
else
printf("不是闰年");
return 0;
}
7.5 短路
C语言逻辑运算符还有一个特点,它总是**先对左侧的表达式求值,再对右边的表达式求值**,这个顺序是保证的。如果左边的表达式满足逻辑运算符的条件,就不再对右边的表达式求值。这种情况称为“短路”。
如前面的代码:
if(month >= 3 && month <= 5)
表达式中 &&
的左操作数是 month >= 3
,右操作数是 month <= 5
,当左操作数 month >= 3
的结果是 0
的时候,即使不判断 month <= 5
,整个表达式的结果也是 0
(不是春季)。
所以,对于 &&
操作符来说,**左边操作数的结果是 **0
的时候,右边操作数就不再执行。
对于||
操作符是怎么样呢?我们结合前面的代码:
if(month == 12 || month==1 || month == 2)
如果 month == 12
,则不用再判断 month
是否等于 1
或者 2
,整个表达式的结果也是 1
(是冬季)。
所以, ||
操作符的**左操作数的结果不为 **0
时,就无需执行右操作数。
像这种仅仅根据左操作数的结果就能知道整个表达式的结果,不再对右操作数进行计算的运算称为短路求值。
练习:阅读代码,计算代码输出的结果
#include <stdio.h>
int main()
{
int i = 0,a=0,b=2,c =3,d=4;
i = a++ && ++b && d++;
printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
return 0;
}
i = 0;
a = 1;
b = 2;
c = 3;
d = 4;
i = a ++;//i = 0,&&左侧为0,短路
#include <stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++||++b||d++;
printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
return 0;
}
i = 0;
a = 1;
b = 3;
c = 3;
d = 4;
i = a ++;//i = 0,||左侧为0,继续运行,b = 3 !=0,短路。
移位操作符
<<
左移操作符
>>
右移操作符
注:移位操作符的操作数只能是整数(的补码)。
8.1 左移操作符
移位规则:**左边抛弃、右边补0**#include<stdio.h>
int main()
{
int num = 10;
int n = num << 1;
printf("%d\n", num);//输出10
printf("%d\n", n);//输出20
return 0;
}
8.2 右移操作符
移位规则:首先右移运算分两种:- 逻辑右移:左边用0填充,右边丢弃
- 算术右移::左边用该值的符号位填充,右边丢弃
逻辑右移1位演示
算术右移1位演示
逻辑右移还是算数右移,取决于编译器。
目前主流的编译器通常为算数右移。
#include<stdio.h>
int main()
{
int num = -10;
int n = num >> 2;
printf("%d\n", num);//输出-10
printf("%d\n", n);//算数右移:输出-3
return 0;
}
警告:对于移位运算符,不要移动负数位,这个是标准未定义的。
int num = 10;
num>>-1;//error
位操作符
位操作符有: `& | ^ ~`注:他们的操作数必须是整数(的补码)。
9.1 `& |`
& //按位与,只要有0,则为0,两个同时为1,才为1
| //按位或,只要有1,则为1,两个同时为0,才为0
#include<stdio.h>
int main()
{
int a = 3;
int b = -5;
//按位与 -对应的二进制位进行运算,只要有0,则为0,两个同时为1,才为1
int c = a & b;
//按位或 -对应的二进制位进行运算,只要有1,则为1,两个同时为0,才为0
int d = a | b;
/*
00000000 00000000 00000000 00000011 3的原码
00000000 00000000 00000000 00000011 3的补码
10000000 00000000 00000000 00000101 -5的原码
11111111 11111111 11111111 11111011 -5的补码
00000000 00000000 00000000 00000011 3的补码
11111111 11111111 11111111 11111011 -5的补码
00000000 00000000 00000000 00000011 c的补码
00000000 00000000 00000000 00000011 c的原码
11111111 11111111 11111111 11111011 d的补码
10000000 00000000 00000000 00000101 d的原码
*/
printf("%d\n", c);//输出3
printf("%d", d);//输出-5
return 0;
}
9.2 `^`
^ //按位异或,相同为0,相异为1
#include<stdio.h>
int main()
{
int a = 3;
int b = -5;
//按位异或 -对应的二进制位进行运算,相同为0,相异为1
int c = a ^ b;
/*
00000000 00000000 00000000 00000011 3的原码
00000000 00000000 00000000 00000011 3的补码
10000000 00000000 00000000 00000101 -5的原码
11111111 11111111 11111111 11111011 -5的补码
00000000 00000000 00000000 00000011 3的补码
11111111 11111111 11111111 11111011 -5的补码
11111111 11111111 11111111 11111000 c的补码
10000000 00000000 00000000 00001000 c的原码
*/
printf("%d\n", c);//输出-8
return 0;
}
9.3 `~`
~ //按位取反,所有二进制位取反
#include<stdio.h>
int main()
{
int a = 3;
//按位取反 -对应的二进制位进行运算,所有二进制位取反
int b = ~a;
/*
00000000 00000000 00000000 00000011 3的原码
00000000 00000000 00000000 00000011 3的补码
b的补码
11111111 11111111 11111111 11111100 b的补码
10000000 00000000 00000000 00000100 b的原码
*/
printf("%d\n", b);//输出-4
return 0;
}
9.4 练习
练习1:
**不能创建临时变量(第三个变量),实现两个数的交换。**//方法一
#include<stdio.h>
int main()
{
int a = 0, b = 0;
scanf("%d %d", &a, &b);
a = a + b;
b = a - b;
a = a - b;
printf("%d %d", a, b);
return 0;
}
//缺点:有溢出的风险
//方法二
#include<stdio.h>
int main()
{
int a = 0, b = 0;
scanf("%d %d", &a, &b);
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("%d %d", a, b);a = a + b;
return 0;
}
//缺点
//1. 可读性差
//2. 只可用于整数
//3. 效率较低
原理:
a ^ a = 0 ,a ^ 0 = a;
a’ = a ^ b
b’ = a’ ^ b = a ^ b ^ b = a
a’’ = a’ ^ b’ = a ^ b ^ a = b
练习2:
**编写代码实现:求一个整数存储在内存中的二进制中1的个数。**//方法一
#include<stdio.h>
int main()
{
int num = 0;
int count = 0;
scanf("%d", &num);
while (num)
{
if (num % 2 == 1)
count++;
num /= 2;
}
printf("%d", count);
return 0;
}
//显然,若num为负数,原码不同于补码时,则这种方法会出现问题
//方法二
#include<stdio.h>
int main()
{
int num = 0;
int count = 0;
scanf("%d", &num);
int i = 0;
for (i = 0; i < 32; i++)
{
if (num & (1 << i))
//if ((num >> i) & 1 == 1)
count++;
}
printf("%d", count);
return 0;
}
//缺陷
//循环必须要运行32次
原理:
if (num & (1 << i))
:
1 << i
只有一位为1
,则 num & (1 << i )
除了1 << i
中为1
的那一位外皆为0
,可判断num
在 1 << i
中为1
的那一位是0
还是1
if ((num >> i) & 1 == 1)
:
直接判断num
的第i
位为0
或1
//方法三
#include<stdio.h>
int main()
{
int num = 0;
int count = 0;
scanf("%d", &num);
while (num)
{
num = num & (num - 1);
count++;
}
printf("%d", count);
return 0;
}
原理:
num = num &(num - 1)
可以去掉num
的补码最右侧的1
。
在num= 0
之前,num = num &(num - 1)
可以执行几次,则num
的补码中有几个1
。
衍生练习:判断n是否为2的幂次方数
//2的幂次方数的特点为二进制数中只有一个1
#include<stdio.h>
int main()
{
int num = 0;
int count = 0;
scanf("%d", &num);
int n = num;
while (n)
{
n = n & (n - 1);
count++;
}
if (count == 1)
printf("%d是2的幂次方数", num);
else
printf("%d不是2的幂次方数", num);
return 0;
}
练习3:
**二进制位置0或者置1**编写代码将13二进制序列的第5位修改为1,然后再改回0
13的2进制序列: 00000000000000000000000000001101
将第5位置为1后:00000000000000000000000000011101
将第5位再置为0:00000000000000000000000000001101
#include<stdio.h>
int main()
{
int a = 13;
a = a | (1 << 4);
printf("a是%d\n", a);
a = a ^ (1 << 4);
//a = a & ~(1 << 4);
printf("a是%d\n", a);
return 0;
}
逗号表达式
逗号表达式,就是用逗号隔开的多个表达式。exp1, exp2, exp3, …expN
逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
//代码1
int a = 1;
int b = 2;
int c = (a>b, a=b+10, a, b=a+1);//c = 13
//代码2
if (a = b + 1, c = a / 2, d > 0)
//代码3
while (a = get_val(), count_val(a), a>0)
{
}
下标访问[]、函数调用()
11.1 [ ] 下标引用操作符
操作数:**一个数组名 + 一个索引值**int arr[10];//创建数组
arr[9] = 10;//实⽤下标引⽤操作符。
//[ ]的两个操作数是arr和9。
11.2 函数调用操作符
接受**一个或者多**个操作数:**第一个操作数是函数名,剩余的操作数就是传递给函数的参数**。#include <stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
//这⾥的()就是作为函数调⽤操作符。
printf("hello,world"); //2个操作数。
Add(3, 5);//3个操作数。
return 0;
}
操作符的属性:优先级、结合性
C语言的操作符有2个重要的属性:优先级、结合性,这两个属性决定了**表达式求值的计算顺序**。:::tips
优先级与结合性:
由于圆括号**()**
的优先级最高,可以使用它改变其他运算符的优先级。
12.1 优先级
优先级指的是,如果一个表达式包含多个运算符,哪个运算符应该优先执行。**各种运算符的优先级是不一样的**。3 + 4 * 5;
上面示例中,表达式3 + 4 * 5
里面既有加法运算符+
,又有乘法运算符*
。由于乘法的优先级高于加法,所以会先计算4 * 5
,而不是先计算3 + 4
。
12.2 结合性
如果两个**运算符优先级相同**,就要根据运算符是**左结合,还是右结合**,决定执行顺序。大部分运算符是左结合(从左到右执行),少数运算符是右结合(从右到左执行),比如赋值运算符(= )。
5 * 6 / 2;
上面示例中,*
和 /
的优先级相同,它们都是左结合运算符,所以从左到右执行,先计算5 * 6 ,