第五节-操作符详解(上)
本章重点
1. 操作符分类:
算术操作符
移位操作符
位操作符
赋值操作符
单目操作符
关系操作符
逻辑操作符
条件操作符
逗号表达式
下标引用、函数调用和结构成员
2. 算术操作符
+ - * / %
⭐️1. 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。
⭐️2. 对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。
⭐️3. % 操作符的两个操作数必须为整数。返回的是整除之后的余数。
3.移位操作符
<< 左移操作符
>> 右移操作符
注:移位操作符的操作数只能是整数。
操作的对象是补码。
移动的是数的二进制位
数在计算机中运算是利用补码
但是 在输出时会转化为原码输出
二进制说明
正数的二进制有三种表示方式:原码,反码,补码。
✏️ 例:
a = 2
00000000 00000000 00000000 00000010 原码
00000000 00000000 00000000 00000010 反码
00000000 00000000 00000000 00000010 补码正整数 原码、反码、补码相同
✏️例:
a = -2
10000000 00000000 00000000 00000010 原码
11111111 11111111 11111111 11111101 反码
11111111 11111111 11111111 11111110 补码
负数原码 --> 反码
符号位不变,其他位按位取反。
反码 --> 补码
反码加一,得到补码。
(记忆:补码需要补上一)计算机在计算中运算使用的是补码
3.1左移操作符
⭐️移位规则:
左边抛弃、右边补0
✏️例:
a = -2; b = a << 1;
10000000 00000000 00000000 00000010 原码
11111111 11111111 11111111 11111101 反码
11111111 11111111 11111111 11111110 补码
移动的是补码,然后再转换成原码输出
3.2右移操作符
⭐️移位规则:
首先右移运算分两种:
逻辑移位
左边用0填充,右边丢弃
算术移位
左边用原该值的符号位填充,右边丢弃
具体用哪一种方式,由编译器决定
(vs编译器用的是算术移位)
⭐️警告⚠ :
对于移位运算符,不要移动负数位,这个是标准未定义的。
例如:
int num = 10;
num>>-1;//error
✏️例:
a = -2; b = a >> 1;
10000000 00000000 00000000 00000010 原码
11111111 11111111 11111111 11111101 反码
11111111 11111111 11111111 11111110 补码
⭐️ 1. 负数移位,不要移动符号位!
⭐️ 2. 向右移动后,补上符号位。
⭐️ 3. 无符号整数也看成正数。
⭐️ 4. 移位操作符的操作数必须是正整数。
4.位操作符
位操作符有:
& //按位与
| //按位或
^ //按位异或
注:他们的操作数必须是整数。
⭐️ 操作数依然为 数的二进制的补码。
⭐️ & 按(二进制)与 :二进制位都为1才是1
⭐️ | 按(二进制)或:二进制位至少有一个是1为1
⭐️ ^ 按(二进制)异或 :二进制位不同则为1
✏️ 练习一下:
#include <stdio.h>
int main()
{
int num1 = -1;
int num2 = 2;
printf("%d\n", num1 & num2);//2
printf("%d\n", num1 | num2);//-1
printf("%d\n", num1 ^ num2);//3
return 0;
}
⭐️注意以%d的形式打印,需要将补码转换成原码!
11111111 11111111 11111111 11111111 -1的补码
00000000 00000000 00000000 00000010 2的补码
00000000 00000000 00000000 00000010 -1&2的补码
11111111 11111111 11111111 11111111 -1|2的补码
以%d的形式打印,需要将补码转换成原码
观察符号位,发现 -1&2 为负数
负数 补码—>反码—>原码
补码—>反码 :补码减一
反码—>原码 :除符号位以外,按位取反
⭐️1. 符号位为0,所以为正数,原码、反码、补码大小相同。
⭐️2. 只要放在内存中就是补码。
❓一道变态的面试题:
不能创建临时变量(第三个变量),实现两个数的交换。
方法一:
#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", b, a);
return 0;
方法一 :当a+b的结果足够大时,可能会出现溢出的现象。
方法二:
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
a = a ^ b; a=10^20
b = a ^ b; 10^20^20 ···> b=10
a = a ^ b; 10^20^20 ···> a=20
printf("a = %d b = %d\n", b, a);
return 0;
例 : 3 ^ 3 和 3 ^ 0
3 的原码: 00000000000000000000000000000011
0 的原码: 000000000000000000000000000000003 ^ 3结果:00000000000000000000000000000000
3 ^ 0结果:00000000000000000000000000000011存在规律:
a ^ a = 0
a ^ 0 = a
方法二 :更加安全,不会存在溢出的现象。但可读性不够好,方法一操作效率更高,而且异或只能用于整形 。实践中多用方法一。
✏️例子 练习:
999
编写代码实现:求一个整数存储在内存中的二进制中1的个数。
5.赋值操作符
赋值操作符是一个很棒的操作符,他可以让你得到一个你之前不满意的值。也就是你可以给自己重新赋值。
int weight = 120; 体重
weight = 89; 不满意就赋值
double salary = 10000.0;
salary = 20000.0; 使用赋值操作符赋值。
赋值操作符可以连续使用,比如:
int a = 10;
int x = 0;
int y = 20; a = x = y+1; 连续赋值
这样的代码感觉怎么样?
那同样的语义,你看看:
x = y+1;
a = x;
这样的写法是不是更加清晰爽朗而且易于调试。
复合赋值符
+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=
这些运算符都可以写成复合的效果。
比如:
int x = 10;
x = x+10;
x += 10; 复合赋值
其他运算符一样的道理。这样写更加简洁。
6.单目操作符
6.1单目操作符介绍
弹幕操作符:只有一个操作数
! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
-- 前置、后置--
++ 前置、后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
6.1.1 !
! :逻辑反操作
C语言中,0表示假,非0表示真。
int main()
{
int flag = 3;
//flag为真,进入if
if (flag)
{}
//flag为假,进入if
if (!flag)
{}
return 0;
}
6.1.2 + / -
int main()
{
int a = -10;
int b = +10;
printf("%d", a);//10
printf("%d", b);//10
return 0;
}
6.1.3 &
int main()
{
int a = 10;
printf("%p", &a);//10
return 0;
}
-
⭐️取出的是变量在内存中的起始地址
-
⭐️%p :取地址
int *p = &a;
- p是指针变量,是专门用来存放地址的
- p的类型是int *
6.1.4 sizeof
计算的是变量所占内存空间的大小,单位是字节
计算类型所创建的变量所占据空间的大小,单位是字节。
6.1.5 ~
按(二进制)位取反
int a = 0;
00000000 00000000 00000000 00000000 补码
11111111 11111111 11111111 11111111 ~0 (还是补码,需要转化成原码才能打印出来)
11111111 11111111 11111111 11111110 反码
10000000 00000000 00000000 00000001 原码
❓用途
✏️例 :把二进制某一位由0改为1 或者由1
把从低到高第四位由0改成1
00000000 00000000 00000000 00000001 1 00000000 00000000 00000000 00001101 a 00000000 00000000 00000000 00010000 1 << 4 1. 1 << 4 把1向左移动四位 00000000 00000000 00000000 00010000 2. a |= (1 << 4) 00000000 00000000 00000000 00011101
把从低到高第四位由1改成0
00000000 00000000 00000000 00011101 a 00000000 00000000 00000000 00000001 1 00000000 00000000 00000000 00010000 1 << 4 1. 1 << 4 把1向左移动四位 00000000 00000000 00000000 00010000 2. ~(1 << 4) 按位取反 11111111 11111111 11111111 11101111 00000000 00000000 00000000 00011101 a 3. a &= ~(1 << 4) 00000000 00000000 00000000 00001101
6.1.6 ++/–
b = ++a
-
前置++
a先加一,再赋值给b。
-
后置++
a先赋值给b,再加一。
-
同理可得前置 --和后置 –
6.1.7 *
int main()
{
int a = 10;
int *p = &a;
*p = 20;
printf("%d\n" ,a);
return 0;
}
*p 通过地址找到原来的数
6.1.8 ()
int main()
{
int a = (int)3.14;
printf("%d", a);
return 0;
}
✏️
srand((int)time(NULL));
time 函数的返回值类型是 time_t 实际上又是long long int 类型,利用(int)强制类型转换
6.2 sizeof 和数组
- sizeof是一个操作符。
- 计算的是变量所占内存空间的大小。
- 计算类型所占空间的大小,单位是字节。
int a = 10;
int n = sizeof(int);//4
int m = sizeof(a);//4
演示代码:
#include <stdio.h>
int main()
{
int a = -10;
int* p = NULL;
printf("%d\n", !2);//0
printf("%d\n", !0);//1
a = -a;
p = &a;
printf("%d\n", sizeof(a));//4
printf("%d\n", sizeof(int));//4
printf("%d\n", sizeof a);//这样写行不行? //ok
printf("%d\n", sizeof int);//这样写行不行?//err
return 0;
}
⭐️1. sizeof是操作数,不是函数。
⭐️2. strlen是库函数,仅用来求字符串长度。
#include <stdio.h>
void test1(int arr[])
{
printf("%d\n", sizeof(arr));//(2)
传过去的是指针,是地址,用指针变量接受
}
void test2(char ch[])
{
printf("%d\n", sizeof(ch));//(4)
传过去的是指针,是地址,用指针变量接受
}
int main()
{
int arr[10] = { 0 };
char ch[10] = { 0 };
printf("%d\n", sizeof(arr));//(1)
printf("%d\n", sizeof(ch));//(3)
test1(arr); 传过去的是arr数组的首元素的地址
test2(ch); 传过去的是arr数组的首元素的地址
return 0;
}
问:
(1)、(2)两个地方分别输出多少?
(3)、(4)两个地方分别输出多少?
40
4
10 实际上把十个元素的第一个元素的地址传过去了
4 同上,并且指针不管是什么类型,他的大小都是四个字节或者八个字节。
7.关系操作符
关系操作符
>
>=
<
<=
!= 用于测试“不相等”
== 用于测试“相等”
这些关系运算符比较简单,没什么可讲的,但是我们要注意一些运算符使用时候的陷阱。
⭐️警告:
在编程的过程中== 和=不小心写错,导致的错误。
int main()
{
if ("abc" == "abcdef");
这样写是比较两个字符串首字符的地址
{
}
return 0;
}
⭐️ ⭐️ ⭐️
两个字符串比较大小
- 不能用 == 比较
- 应该用库函数 strcmp 比较
str cmp :
str : string (字符串)
cmp : compare (比较)
8.逻辑操作符
逻辑操作符有哪些:
&& 逻辑与
|| 逻辑或
区分逻辑与和按位与
区分逻辑或和按位或
1&2----->0
1&&2---->1
1|2----->3
1||2---->1
int main()
{
int a = 3;
int b = 5;
int c = a && b; 并且
int d = a || b; 或者
printf("%d\n", c);
printf("%d\n", d);
return 0;
}
✏️ 例:判断 是否为闰年
int is_leap_year(int y)
{
if((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0))
{
return 1;
}
else
{
return 0;
}
}
✏️360笔试题
#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;
}
//程序输出的结果是什么?
1 2 3 4
#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;
}
//程序输出的结果是什么?
1 3 3 4
- 或运算符 | 当遇到 真 时就不再向下进行
- 与运算符 & 当遇到 假 时就不再向下进行
- a 不论前置++,还是后置++,a本身都会加一
- 前置++/-- :先自身加一,再代入运算。
- 后置++/-- :先代入运算,再自身加一。