目录
1. 操作符分类:
思维导图:
2. 算术操作符
3. 移位操作符
3.1 左移操作符
3.2 右移操作符
4. 位操作符
4.1 &
4.2 |
4.3 ^
5. 赋值操作符
6. 单目操作符
6.1 单目操作符介绍
6.2 sizeof 和数组
7. 关系操作符
8. 逻辑操作符
9. 条件操作符
10. 逗号表达式
11. 结构成员
12. 表达式求值
12.1 隐式类型转换
12.2 算术转换
12.3 操作符的属性
写在最后
+ - * / %
#include
int main()
{
int a = 10 / 3;//除法操作符,两边都是整数就执行整数除法
printf("%d\n", a);
double b = 10.0 / 3;//两边只要有一个是浮点数就能计算出小数
printf("%lf\n", b);
printf("%.1lf\n", b);//在lf前面加.1能保留小数点后一位,.几就保留几位
return 0;
}
输出结果:
输出:
3
3.333333
3.3
#include
int main()
{
int ret = 10 % 3;//取模,操作符两边必须是整数
printf("%d\n,ret");
return 0;
}
输出结果:
输出:1
在讲移位前,需要知道一些基本的概念:
二进制:
二进制整数有三种表示形式:
1. 原码
2. 反码
3. 补码
而在内存中存储的是:二进制的补码
所以参数在移位时是二进的补码。
例:
int main()
{
//整形类型占四个字节(32个比特位),二进制的表现形式:
int a = 10;
//00000000000000000000000000001010 - 原码
//00000000000000000000000000001010 - 反码
//00000000000000000000000000001010 - 补码
int a = -10;
//10000000000000000000000000001010 - 原码
//11111111111111111111111111110101 - 反码
//11111111111111111111111111110110 - 补码
return 0;
}
通过观察发现正数原码、反码、补码相同,
负数反码是:原码符号位不变,其它位按位取反,补码是:反码+1。
例:
#include
int main()
{
//左移操作符:<<
//规则:左边抛弃,右边补零
int a = 10;
//a:
//00000000000000000000000000001010 - 补码
int b = a << 1;
//b:
//00000000000000000000000000010100 - 补码
printf("%d\n", b);
int c = -10;
//c:
//10000000000000000000000000001010 - 原码
//11111111111111111111111111110101 - 反码
//11111111111111111111111111110110 - 补码
int d = c << 1;
//d:
//11111111111111111111111111101100 - 补码
//11111111111111111111111111101011 - 反码
//10000000000000000000000000010100 - 原码
printf("%d\n", d);
return 0;
}
(注:printf 打印出来给我们看的是原码。)
输出结果:
输出:
20
-20
总结:
左移操作也可以看成是乘二的操作。
例:
#include
int main()
{
//右移操作符:>>
//1.算数右移:左边补符号位,右边抛弃(常用)
//2.逻辑右移:左边补零,右边抛弃
int a = 10;
//a:
//00000000000000000000000000001010 - 补码
int b = a >> 1;
//b:
//00000000000000000000000000000101 - 补码
printf("%d\n", b);
int c = -10;
//c:
//10000000000000000000000000001010 - 原码
//11111111111111111111111111110101 - 反码
//11111111111111111111111111110110 - 补码
int d = c >> 1;
//d:
//11111111111111111111111111111011 - 补码
//11111111111111111111111111111010 - 反码
//10000000000000000000000000000101 - 原码
printf("%d\n", d);
return 0;
}
输出结果:
输出:
5
-5
注:无论是右移还是左移的位数都不能为负数。
例:
ret>>-1
这样写是错误的
& 按位与
| 按位或
^ 按位异或
例:
#include
int main()
{
//& - 按二进制与
//规则:有零则零
int a = 3;
//00000000000000000000000000000011 - 补码
int b = -5;
//10000000000000000000000000000101 - 原码
//11111111111111111111111111111010 - 反码
//11111111111111111111111111111011 - 补码
int c = a & b;
//00000000000000000000000000000011 - a 补码
//11111111111111111111111111111011 - b 补码
//00000000000000000000000000000011 - c 原码
printf("%d\n", c);
return 0;
}
输出结果:
输出:3
例:
#include
int main()
{
// | - 按二进制位或
// 规则:有一则一
int a = 3;
//00000000000000000000000000000011 - 补码
int b = -5;
//10000000000000000000000000000101 - 原码
//11111111111111111111111111111010 - 反码
//11111111111111111111111111111011 - 补码
int c = a | b;
//00000000000000000000000000000011 - a补码
//11111111111111111111111111111011 - b补码
//11111111111111111111111111111011 - c补码
//11111111111111111111111111111010 - c反码
//10000000000000000000000000000101 - c原码
printf("%d\n", c);
return 0;
}
输出结果:
输出:-5
例:
#include
int main()
{
// ^ - 按二进制位异或
// 规则:相同为零,相异为一
int a = 3;
//00000000000000000000000000000011 - 补码
int b = -5;
//10000000000000000000000000000101 - 原码
//11111111111111111111111111111010 - 反码
//11111111111111111111111111111011 - 补码
int c = a ^ b;
//00000000000000000000000000000011 - a补码
//11111111111111111111111111111011 - b补码
//11111111111111111111111111111000 - c补码
//11111111111111111111111111110111 - c反码
//10000000000000000000000000001000 - c原码
printf("%d\n", c);
return 0;
}
输出结果:
输出:-8
练习:
一道编程题:
如何不创建临时变量完成两个数的交换
例:
#include
int main()
{
int a = 3;
int b = 5;
printf("%d %d\n", a, b);
a = a ^ b;
b = a ^ b;// b = a ^ b ^ b // 而 a ^ b ^ b = a 所以b就赋值为a
a = a ^ b;// a = a ^ b ^ a // 而 a ^ b ^ a = b 所以a就赋值为b
printf("%d %d\n", a, b);
return 0;
}
输出结果:
输出:
3 5
5 3
用异或操作符交换两个变量的弊端:
1. 可读性差
2. 效率没有创建临时变量高
3. 异或只能用于整数变量的交换
总结:这种方法了解即可,平时使用临时变量交换两个变量的方法更好。
赋值操作符能给变量赋值。
例:
int main()
{
int a = 10;
a = 100;// = 能将a赋值成100
return 0;
}
还有复合赋值符:
例:
+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=
这些运算符都可以写成复合的效果(规则都是一样的)
例:
int main()
{
int a = 10;
a += 10;//这个其实就是:a = a + 10
printf("%d\n", a);//输出的结果就是20
return 0;
}
! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
-- 前置、后置--
+ 前置、后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
例1:
#include
int main()
{
//! 逻辑反操作
//C语言中0表示假,非零表示真
int n = 0;
if (n)
{
printf("1\n");
}
if (!n)//!逻辑反操作,将假变为真(也能从真变假)
{
printf("2\n");
}
return 0;
}
输出结果:
输出:2
例2:
#include
int main()
{ // + -
int a = -10;
printf("%d\n", a);
printf("%d\n", -a);
printf("%d\n", +a);//‘+’几乎没有用处
return 0;
}
输出结果:
输出:
-10
10
-10
例3:
#include
int main()
{
// ~ 按位取反
//00000000000000000000000000000000
//11111111111111111111111111111111 - 补码是全1
int a = 0;
printf("%d\n", ~a);
}
输出结果:
输出:-1
例4:
#include
int main()
{
//++
int a = 10;
int b = a++;//后置++,先使用,再++
printf("%d\n", b);
printf("%d\n", a);
int c = 10;
int d = ++c;//前置++,先++,再使用
printf("%d\n", d);
printf("%d\n", c);
return 0;
}
输出结果:
输出:
10
11
11
11
注:-- 的规则与 ++ 是一样的。
#include
int main()
{
// ++ -- 是带有副作用的
// 会改变变量自身的值
//1
int a = 10;
int b = ++a;//b=11 a=11
//2
int a = 10;
int b = a + 1;//b=11 a=10
return 0;
}
例5:
#include
int main()
{ // (类型)强制类型转换
int a = (int)3.14;//这样编译器就不会报警告
srand((unsigned int)time(NULL));//将类型为time_t的time转成srand需要的无符号整形
return 0;
}
例:
#include
void test1(int arr[])//本质上传过来的是数组首元素的地址
{
printf("%d\n", sizeof(arr));//地址在32位环境占4个字节
} //在64位的环境中占8个字节
void test2(char ch[])
{
printf("%d\n", sizeof(ch));//我是32位的环境,所以输出4
}
int main()
{
int arr[10] = { 0 };
char ch[10] = { 0 };
printf("%d\n", sizeof(arr));//int类型占4个字节
printf("%d\n", sizeof(ch));//char类型占1个字节
test1(arr);
test2(ch);
return 0;
}
输出结果:
输出:
40
10
4
4
>
>=
<
<=
!= 用于测试“不相等”
== 用于测试“相等”
注:别把 “==” 和 “=” 给写错了。
&& 逻辑与 ( a&&b a,b都要满足才真)
|| 逻辑或 ( a||b a,b只要满足一个就真)
例:
#include
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ && ++b && d++;
//因为a=0,++的优先级较低,所以(a++)这个表达式值是0,而&&有零那就是零了,
//所以(++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
#include
int main()
{
int i = 0, a = 1, b = 2, c = 3, d = 4;
i = a++ || ++b || d++;
//(a++)这个表达式为真,那||左边就无须计算了
//所以(++b)(d++)都不计算了
printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d);
return 0;
}
输出结果:
输出:
a = 2
b = 2
c = 3
d = 4
exp1 ? exp2 : exp3
例:
这是一个求较大值的代码:
#include
int max(int a, int b)
{
if (a > b)
return a;
else
return b;
}
int main()
{
int a = 10;
int b = 20;
printf("%d\n", max(a, b));
return 0;
}
输出结果:
输出:20
而用条件操作符:
#include
int max(int a, int b)
{
return (a > b ? a : b);//如果a>b则取a,否则取b
}
int main()
{
int a = 10;
int b = 20;
printf("%d\n", max(a, b));
return 0;
}
输出结果:
输出:20
两种写法是一模一样的。
逗号表达式:
1. 用逗号隔开的多个表达式。
2. 从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
例:
#include
int main()
{
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1);//逗号表达式
//先算a = b + 10,算出a = 12,再算b = a + 1,算出b = 13,
//最后结果就是最后一个表达式(b = a + 1)的值
printf("c = %d\n", c);
return 0;
}
输出结果:
输出:13
结构体在C语言中一般用于描述复杂对象,比如人、书,C语言中没有这样的类型,
但是结构体能让我们创建新的类型。
CPU在进行运算的时候一般使用整形int,所以在有些时候,当一个小于整形的类型进行计算时,计算机就会先进行整形提升再进行运算,这就是隐式类型转换。
(通用CPU是难以直接实现两个非整形的直接相加运算)
例:
// char short int long ...
// 1 2 4
int main()
{
//char --> signed char
char a = 3;
//截断
//00000000000000000000000000000011
//00000011 - a
//
char b = 127;
//00000000000000000000000001111111
//01111111 - b
char c = a + b;
//00000011
//01111111
//整型提升
//00000000000000000000000000000011 - a
//00000000000000000000000001111111 - b
//00000000000000000000000010000010 - a + b =c
//截断
//10000010 - c
printf("%d\n", c);
//%d 是打印十进制的整数
//11111111111111111111111110000010 - 补码
//11111111111111111111111110000001 - 反码
//10000000000000000000000001111110 - 原码
//-126
return 0;
}
输出结果:
输出:-126
注:
char:
有符号的char的取值范围是:-128~127
无符号的char的取值范围是:0~255
不同操作类型的数进行运算时会进行算数转换:
long double
double
float unsigned long int
long int
unsigned int
int
注:算数转换不合理会产生一些问题:
#include
int main()
{
float f = 3.14;
int num = f;//隐式转换,会有精度丢失
printf("%d\n", num);
return 0;
}
输出结果:
输出:3
复杂表达式的求值有三个影响的因素:
1. 操作符的优先级
2. 操作符的结合性
3. 是否控制求值顺序
但是,我们写出的代码如果没有唯一的运算路径,就会出问题
所以,总结:平时写代码的运算路径要唯一。
以上就是本篇文章的内容了,感谢你的阅读。
如果喜欢本文的话,欢迎点赞和评论,写下你的见解。
如果想和我一起学习编程,不妨点个关注,我们一起学习,一同成长。
之后我还会输出更多高质量内容,欢迎收看。