系列文章目录
C语言学习入门第五节————操作符详解(上)
操作符详解
- 操作符分类
- 算术操作符
- 位移操作符
- 位操作符
- 赋值操作符
- 单目操作符
- 关系操作符
- 逻辑操作符
- 条件操作符
- 逗号表达式
- 下标引用、函数调用和结构成员
- 表达式求值
一、操作符分类
算术操作符: + —— - —— * —— / —— %
移位操作符: << —— >>
位操作符: & —— | —— ^
赋值操作符: = —— += —— -= —— *= —— /= ……
单目操作符: ! —— sizeof —— + —— - —— ~ —— & —— *
关系操作符: > —— < —— >= —— <= —— == —— !=
逻辑操作符: && —— ||
条件操作符: ? —— :
逗号操作符: ,
下标引用、函数调用和结构成员: [] —— () —— . —— ->
二、算术操作符
+ : 加法运算
- :减法运算
* :乘法运算
/ :除法运算(除数不能为0)
% : 取模操作符,得到的是整除后的余数
(注:除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数)
/ :除法运算,除数不能为0
- 整数除法(除号两端都是整数)
- 浮点数除法(除号两端只要有一个小数就执行浮点数除法)
整数除法(除号两端都是整数)
#include <stdio.h>
int main()
{
int r = 7 / 2;// 两端都是整数,是整数除法
printf("%d\n", r);
double d = 7 / 2;
printf("%lf\n", d);//3.5?
//用浮点数类型变量存放整数除法结果,结果是不能够精确到小数位的。
//所以结果是3.00000
return 0;
###浮点数除法(除号两端只要有一个小数就执行浮点数除法)
#include <stdio.h>
int main()
{
double d = 7.0 / 2.0;
printf("%lf\n", d);//3.500000
return 0;
}
除数不能为0
#include <stdio.h>
int main()
{
int n = 0;
int d = 7 / n;
printf("%lf\n", d);
return 0;
}
%:取模操作符,得到的是整除后的余数
#include <stdio.h>
int main()
{
int d = 7 % 3;//%得到的是整除后的余数。
printf("%d\n", d);
return 0;
}
%取模操作符的两个操作数必须是整数
#include <stdio.h>
int main()
{
int d = 7 % 3.0;//%取模操作符的两个操作数必须都是整数。
int c = 7.0% 3.0;
printf("%d\n", d);
return 0;
}
三、移位操作符( * * * )
移位操作符的操作数只能是整数,对于一位运算符,不要移动负数位,这是标准未定义的,计算结果将取决于编译器,结果不可预料.
整数的二进制表示形式 ( * * * * *)
有三种表现形式:
原码 // 反码 // 补码
-
正整数的原码、反码、补码是相同的
-
负整数的原码、反码、补码是要计算的
-
不管是正整数还是负整数都可以写出二进制原码
-
根据正负直接写出的二进制序列就是原码
-
移位移动的是二进制信息
-
整数在内存中存储的是补码
-
计算的时候也是使用补码计算的
#include <stdio.h>
//不管是正整数还是负整数都可以写出二进制原码
//根据正负直接写出的二进制序列就是原码
int main()
{
//1个整型是4个字节 = 32个bit位
//符号位是 1 表示负数
//符号位是 0 表示正数
int a = 15;
//正整数的原码、反码、补码是相同的
//00000000000000000000000000001111 -- 原码
//00000000000000000000000000001111 -- 反码
//00000000000000000000000000001111 -- 补码
int b = -15;
//负整数的原码、反码、补码是要计算的
//10000000000000000000000000001111 -- 原码
//11111111111111111111111111110000 -- 反码(原码的符号位不变,其它位按位取反得到的就是反码)
//11111111111111111111111111110001 -- 补码(反码 +1 就是补码)
return 0;
}
<< : 左移操作符 – 补码 左边丢弃,右边补0 ( * * * )
#include <stdio.h>
int main()
{
//正整数:
int a = 6;
// 00000000000000000000000000000110 -- 正整数的 原码、反码、补码 相同
// 00000000000000000000000000000110 -- 补码 (移位操作的是 补码)
//左移后:
// 0 00000000000000000000000000001100 -- 补码 (原码 、 反码)
// 因为正整数的 原码、反码、补码 相同,所以这也是原码
int b = a << 1;
printf("%d\n", b);
printf("%d\n", a);
//负整数:
int c = -6;
// 10000000000000000000000000000110 -- 原码
// 11111111111111111111111111111001 -- 反码 (原码按位取反)
// 11111111111111111111111111111010 -- 补码 (反码 +1 )
int d = c << 1;
//左移后:
// 1 11111111111111111111111111110100 -- 补码
// 11111111111111111111111111110011 -- 反码 (补码 -1)
// 10000000000000000000000000001100 -- 原码 (符号位不变,反码按位取反)
// -12
printf("%d\n", d);
printf("%d\n", c);
a = a << 1;
// 也可以写成:
a <<= 1; //符合符号 <<= :左移等于 ,类似 a += 1
return 0;
}
>> : 右移操作符 ( * * * * )
- 算术 右移 (补码 右边 丢弃, 左边 补原来的符号位)
- 逻辑 右移 (补码 右边 丢弃, 左边 直接补0)
- 有类似 除以2向下取整 的效果
C语言没有明确规定到底是算术右移还是逻辑右移,一般编译器上采用的是算术右移
#include <stdio.h>
int main()
{
//正整数:
int a = 15;
//00000000000000000000000000001111 -- 补码
int b = a >> 1;
// 0 0000000000000000000000000000111 1 -- 补码(移位后)
//C语言没有明确规定到底是算术右移还是逻辑右移,一般编译器上采用的是算术右移
printf("%d\n", b); //算术右移
// b 得到的是 a 移位后的结果
// 算术右移(右边丢弃,左边补原来的符号位)
// 从 二进制的1111 变成 二进制的111 ,从 十进制的 15 变成了 十进制的 7
printf("%d\n", a);
// a >> 1,进行移位后,a没有发生变化,
// 表达式不会影响 a 的 值
//负整数:
int c = -15;
//11111111111111111111111111110001 -- 补码 (符号位是 1 表示负数)
int d = c >> 1;
// 1 1111111111111111111111111111000 1 -- 补码 (移位后)
// 空出的位置补的是 1 ,说明是算术右移(右边丢弃,左边补原来的符号位)
// 实际值是用 原码 表示的
printf("%d\n", d); //算术右移
// 反码 +1 就是补码 ,补码 -1 就是反码:
// 11111111111111111111111111110111 -- 反码
// 原码的符号位不变,反码按位取反得 原码
// 10000000000000000000000000001000 -- 原码 (-8)
printf("%d\n", c);
return 0;
}
四、位操作符( * * * * *)
-
整数在内存中存储的是补码
-
计算的时候也是使用补码计算的
注:位操作符的操作数必须是整数
位操作符有:
& —— 按位与
| —— 按位或
^ —— 按位异或
& : 按(二进制)位与 ----- 对应二进制位有0则为0,两个 同时为1,才为1( * * * * *)
#include <stdio.h>
int main()
{
int a = 3;
// 00000000000000000000000000000011 -- 补码(正整数的原码、反码、补码相同)
int b = -5;
// 10000000000000000000000000000101 -- 原码
// 11111111111111111111111111111010 -- 反码
// 11111111111111111111111111111011 -- 补码
int c = a & b;
// & -- 对应二进制位有0则为0,两个同时为1,才为1
// 00000000000000000000000000000011 -- a补码
// 11111111111111111111111111111011 -- b补码
// 得:
// 00000000000000000000000000000011 -- 补码(两个补码计算出来的也是补码)
// 这里计算出来的结果符号位是 0 ,说明是 正整数,三个码相同,所以这也是原码
printf("%d\n", c); // 所以结果是3
return 0;
}
| : 按(二进制)位或 ----- 对应二进制位有1则为1,两个同时为0,才为0( * * * * *)
//位操作符
#include <stdio.h>
int main()
{
int a = 3;
// 00000000000000000000000000000011 -- 补码(正整数的原码、反码、补码相同)
int b = -5;
// 10000000000000000000000000000101 -- 原码
// 11111111111111111111111111111010 -- 反码
// 11111111111111111111111111111011 -- 补码
int c = a | b;
// | -- 按(二进制)位或 -- 对应二进制位有1则为1,两个同时为0,才为0
// 00000000000000000000000000000011 -- a补码
// 11111111111111111111111111111011 -- b补码
// 得:
// 11111111111111111111111111111011 -- 补码(两个补码计算出来的也是补码)
//
// 这里计算出来的结果符号位是 1 ,说明是 负整数,还要计算 反码 和 补码
// 11111111111111111111111111111010 -- 反码
// 10000000000000000000000000000101 -- 原码
printf("%d\n", c); // 所以结果是-5
return 0;
}
^ : 按(二进制)位异或 -----对应二进制位相同为0,相异为1( * * * * *)
#include <stdio.h>
int main()
{
int a = 3;
// 00000000000000000000000000000011 -- 补码(正整数的原码、反码、补码相同)
int b = -5;
// 10000000000000000000000000000101 -- 原码
// 11111111111111111111111111111010 -- 反码
// 11111111111111111111111111111011 -- 补码
int c = a ^ b;
// ^ -- 按(二进制)位异或 -- 对应二进制位相同为0, 相异为1
// 00000000000000000000000000000011 -- a补码
// 11111111111111111111111111111011 -- b补码
// 得:
// 11111111111111111111111111111000 -- 补码(两个补码计算出来的也是补码)
//
// 这里计算出来的结果符号位是 1 ,说明是 负整数,还要计算 反码 和 补码
// 10000000000000000000000000000111 -- 反码
// 10000000000000000000000000001000 -- 原码
printf("%d\n", c); // 所以结果是-8
return 0;
}
^(按位异或) 的一些规律:
- 变量a ^ 变量a = 0
- 0 ^ 变量a = 变量a
- 满足交换律
#include <stdio.h>
int main()
{
int a = 3;
int b = 5;
printf("%d\n", a ^ a); // 0 (每一位都是相同的,32位全是0)
printf("%d\n", a ^ 0); // a = 3
// 011 - 3 (a)
// 000 - 0
// 011 -- 异或结果 :a = 3
printf("%d\n", a ^ b ^ a); // b = 5
// 011 - 3 (a)
// 101 - 5 (b)
// 110 -- 异或结果
// 011 - 3 (a)
// 101 -- 异或结果 : b = 5
printf("%d\n", a ^ a ^ b); // b = 5
// a ^ a --> 0
// 0 ^ b --> b = 5
return 0;
}
不使用临时变量,交换两个数的值
方法一(有缺陷)
当a,b特别大时a + b会超出整形能表示的最大值
#include <stdio.h>
int main()
{
int a = 3;
int b = 5;
printf("交换前:a=%d b=%d\n", a, b);
a = a + b;
b = a - b;
a = a - b;
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
方法二( * * * *)
(^异或 不会造成进位,所以不会产生栈溢出,但可读性不高,不易理解)
#include <stdio.h>
int main()
{
int a = 3;
int b = 5;
printf("交换前:a=%d b=%d\n", a, b);
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
求一个整数存储在内存中的二进制中1的个数( * * )
#include <stdio.h>
int main() {
int input = 0;
int count = 0; //统计 1 的个数
//输入
scanf("%d", &input);
int i = 0;
for (i = 0; i < 32; i++)
{ // 共32位,判断32次
int test = input & 1; //按位与1 取出32位最低位
//判断是不是1
if (test == 1) {
count++; //是的话,统计加1
}
//判断下一位:
input >>= 1; //向右移一位,继续判断,使用循环
}
//输出:
printf("%d", count);
return 0;
}
五、赋值操作符
=: 赋值操作符 – 可以让你得到一个你之前不满意的值。也就是你可以给自己重新赋值
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; //选择赋值
//先把 y+1 赋给 x ,再把 x 赋给 a
//更好的写法:
x = y + 1;
a = x;
//这样写更加清晰而且方便一步步调试
复合赋值符:
+= : 加等于
-= : 减等于
*= : 乘等于
/= : 除等于
%= : 模等于
>>= : 右移等于
<<= : 左移等于
&= : 按位与等于
|= : 按位或等于
^= : 按位异或等于
六、单目操作符
1.单目操作符介绍( * * * *)
! 逻辑反操作(if语句、while语句中、表达式中)
- 负值
+ 正值
& 取地址(应用于指针)
sizeof操作数的类型长度(以字节为单位)(不是函数,是操作符;计算的是类型创建变量的大小) ~ 对一个数的二进制按位取反(对补码32个bit位按位取反,1变成0,0变成1,取反后还是补码,要转换为原码)
- - 前置、后置- -
++ 前置、后置++
* 间接访问操作符(解引用操作符)
(类型)强制类型转换(应用于指针,告诉我们哪个变量是指针变量;解引用:通过指针变量中存放的地址,找到指向的 空间/内容 )
2.sizeof 和 数组( * * * )
#include <stdio.h>
void test1(int arr[])
//传过来的首元素指针 ,一个 int*类型 大小是 4个字节
{
printf("%d\n", sizeof(arr));
}
void test2(char ch[])
//传过来的首元素指针 ,一个 char*类型 大小是 4个字节
{
printf("%d\n", sizeof(ch));
}
int main()
{
//整型数组:
int arr[10] = { 0 };
printf("%d\n",sizeof(arr)); //使用 数组名
//40 - 计算整个数组的大小、单位字节
printf("%d\n", sizeof(int[10])); //使用数组的字符类型,包括[]和元素个数
test1(arr);
//字符数组
char ch[10] = { 0 };
printf("%d\n", sizeof(ch)); //使用 数组名
//10 -- 字符数组中 sizeof(数组名) 计算 的是 字符个数
printf("%d\n", sizeof(int[10])); //使用数组的字符类型,包括[]和元素个数
//40 -- 一个 char类型元素 是 4个字节,10个就是40
test2(ch);
return 0;
}
3.单目操作符运用
~ 按位取反的运用,结合 & (按位与) 和 | (按位或):( * * * *)
//将 13 的 32bit位从右往左第5位,换为 1 ,再换为 0
#include <stdio.h>
int main()
{
int a = 13;
// 00000000000000000000000000001101 - 13 的 原、反、补码
// 按位或:有1就是1
// 00000000000000000000000000010000 - 1 << 4
// 00000000000000000000000000011101 -- 29 的 原、反、补码
a |= (1 << 4);
// 这样就把第五位换成了 1 , 13 变成了 29
printf("%d\n", a);
// 再把第五位换成 0
// 00000000000000000000000000011101 - 29 的 原、反、补码
// 00000000000000000000000000010000 - 1 << 4
// 11111111111111111111111111101111 - ~(1 << 4) 进行按位取反
// 按位或:两个都是 1 才是 1
// 00000000000000000000000000001101 -- 13 的 原、反、补码
a &= (~(1 << 4));
printf("%d", a);
return 0;
}
运用 ~按位取反 实现 多组输入
#include <stdio.h>
int main()
{
int a = 0;
// scanf 读取失败返回的是EOF
// 假设 scanf 读取失败,返回EOF ,即 -1
// -1 的补码:
// 11111111111111111111111111111111
// ~ 按位取反后:
// 00000000000000000000000000000000
// 得0,条件即为假
while (~scanf("%d", &a))
{
printf("%d\n", a);
}
return 0;
}