文章目录
一、算术操作符
+ — * / %
1. 除了 % 操作符之外,其他几个操作符可以作用于整数和浮点数。
2. 对于 / 操作符,如果操作符两边都是整数,则执行整数除法。而只要有一边是浮点数,则执行浮点数除法。
3. % 操作符的两个操作数必须是整数。返回的是整数之后的余数。
二、位移操作符
<< 左移操作符
>> 右移操作符
功能
位移操作符移动的是二进制,操作数只能是整数,整数在内存中存储的是二进制的补码,位移操作符移动的是存储在内存中的补码。
2.1、左移操作符
代码如下(示例):
#include <stdio.h>
int main() {
int a = 4;
int b = a << 1;
printf("%d %d", a, b); // 4 8
return 0;
}
--------------------------
int main() {
int a = -4;
int b = a << 1;
printf("%d %d", a, b); // -4 -8
return 0;
}
左移:左边丢弃,右边补0
2.2、左移操作符
代码如下(示例):
#include <stdio.h>
int main() {
int a = 4;
int b = a >> 1;
printf("%d %d", a, b); // 4 2
return 0;
}
-----------------------
#include <stdio.h>
int main() {
int a = -4;
int b = a >> 1;
printf("%d %d", a, b); // -4 -2
return 0;
}
1. 逻辑右移:右边丢弃,左边补0
2. 算术右移:右边丢弃,左边补原符号位
注意:采用逻辑右移与算术右移,取决于编译器,但是大部分编译器都采用的是算术右移,此实例也是采用算术右移
注意事项:
对于位移运算符,不要移动负数位,标准位定义。
num>>-1 // error
三、位操作符
& 按位与
| 按位或
^ 按位异或
注意:他们的操作数必须是整数
3.1、& 按位与
代码如下(示例):
#include <stdio.h>
int main() {
int a = 3;
int b = -5;
int c = a & b;
//00000000 00000000 00000000 00000011 -- 3的补码
//11111111 11111111 11111111 11111011 -- -5的补码
//00000000 00000000 00000000 00000011 -- 3
printf("%d", c); // 3
return 0;
}
按二进制位,有0为0,同1为1。
3.2、| 按位或
代码如下(示例):
#include <stdio.h>
int main() {
int a = 3;
int b = -5;
int c = a | b;
//00000000 00000000 00000000 00000011 -- 3的补码
//11111111 11111111 11111111 11111011 -- -5的补码
//11111111 11111111 11111111 11111011 -- 3|-5的补码
//11111111 11111111 11111111 11111010 -- 3|-5的反码
//10000000 00000000 00000000 00000101 -- 3|-5的原码 -5
printf("%d", c); // -5
return 0;
}
按二进制位,有1为1。
3.3、^ 按位异或
代码如下(示例):
#include <stdio.h>
int main() {
int a = 3;
int b = -5;
int c = a ^ b;
//00000000 00000000 00000000 00000011 -- 3的补码
//11111111 11111111 11111111 11111011 -- -5的补码
//11111111 11111111 11111111 11111000 -- 3^-5的补码
//11111111 11111111 11111111 11110111 -- 3^-5的反码
//10000000 00000000 00000000 00001000 -- 3^-5的原码 -8
printf("%d", c); // -8
return 0;
}
按二进制位,相同为0,相异为1。
3.4、扩展练习
不能创建临时变量(第三个变量),实现两个数的交换
代码如下(示例):
#include <stdio.h>
int main() {
int a = 3;
int b = 5;
a = a + b;
b = a - b;
a = a - b;
printf("%d %d", a, b);
return 0;
}
此方法若a和b两个数非常大,但没有超过整型大小,a+b超过了整型大小,所以存在缺陷。
代码如下(示例):
#include <stdio.h>
int main() {
int a = 3;
int b = 5;
a = a ^ b;
b = a ^ b; // b = a ^ b ^ b --> b = a
a = a ^ b; // a = a ^ b ^ a --> a = b
printf("%d %d", a, b); // 5 3
return 0;
}
异或是支持交换律:a ^ a = 0,0 ^ a = a
四、赋值操作符
=
代码如下(示例):
#include <stdio.h>
int main() {
int a = 10;
int b = 0;
int c = 30;
a = b = c + 1; // 连续赋值
printf("%d %d %d", a, b, c); // 31 31 30
return 0;
}
赋值操作符连续赋值的前提是变量要初始化
4.1、复合赋值符
+= -+ *= /= %=
>>= <<= &= |= ^=
代码如下(示例):
#include <stdio.h>
int main() {
int a = 10;
a += 10; // --> a = a + 10
printf("%d", a); // 20
return 0;
}
这些运算符都可以写成复合的效果
五、单目操作符
! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(字节单位)
~ 对一个数的二进制按位取反
– – 前、后置- -
++ 前、后置++
* 间接访问操作符
(类型) 强制类型转换
代码如下(示例):
#include <stdio.h>
int main() {
int a = 10; // 00000000 00000000 00000000 00001010
int n = 3;
a = a | (1 << (n-1)); // 按二进制数,把n位改为1
printf("%d", a); // 14
return 0;
}
------------------------------------
#include <stdio.h>
int main() {
int a = 10; // 00000000 00000000 00000000 00001010
int n = 2;
a = a & ~(1 << (n - 1)); // 按二进制数,把n位改为0
printf("%d", a); // 8
return 0;
}
++和- -运算符
代码如下(示例):
#include <stdio.h>
int main() {
int a = 10;
int b = 10;
int c = 10;
int d = 10;
printf("%d\n", ++a); // 11
printf("%d\n", --b); // 9
printf("%d\n", c++); //10
printf("%d\n", c); //11
printf("%d\n", d--); //10
printf("%d\n", d); // 9
return 0;
}
解析:
先对a进行自增,然后使用a,也就是表达式的值是a自增之后的值。a为11
先对b进行自减,然后使用b,也就是表达式的值是b自减之后的值。b为9
先对c使用,再增加,这样c的值是10;之后c变成11
先对d使用,再自减,这样d的值是10;之后d变成9
六、关系操作符
> >= < <= != ==
注意:
= 是赋值操作符, == 是等于操作符
七、逻辑操作符
&& 逻辑与
|| 逻辑或
如何区分按位与和逻辑与
1 & 2 --------- 0
1 && 2 ------- 1
如何区分按位或和逻辑或
1 | 2 --------- 3
1 || 2 -------- 1
代码如下(示例):
#include <stdio.h>
int main()
{
int i1 = 0, a1 = 0, b1 = 2, c1 = 3, d1 = 4;
int i2 = 0, a2 = 0, b2 = 2, c2 = 3, d2 = 4;
i1 = a1++ && ++b1 && d1++;
i2 = a2++ || ++b2 || d2++;
printf("a1 = %d\nb1 = %d\nc1 = %d\nd1 = %d\n", a1, b1, c1, d1); // 1 2 3 4
printf("a2 = %d\nb2 = %d\nc2 = %d\nd2 = %d\n", a2, b2, c2, d2); // 1 3 3 4
return 0;
}
解析:
1、a1 = 0,则a1++为假,由于是&&,所以b1、c1、d1的值不变
2、a2 = 0,则a2++为假,由于是||,所以b2的值变为3,由于a2++ || ++b2 为真,则d2的值不变
八、条件操作符
exp1 ? exp2 : exp3
代码如下(示例):
#include <stdio.h>
int main()
{
int a = 4;
int b = 0;
/*if (a > 5)
b = 3;
else
b = -3;*/
b = a > 5 ? 3 : -3;
printf("%d", b); // -3
return 0;
}
九、逗号表达式
exp1,exp2,exp3……expN
代码如下(示例):
#include <stdio.h>
int main()
{
int a = 1;
int b = 2;
int c = (a > b, a = a + 10, b = a + 1);
printf("%d", c); // 12
return 0;
}
注意:
逗号表达式,从左向右依次执行,整个表达式的结果是最后一个表达式的结果
十、下标引用、函数调用和结构体成员
[ ] 下标引用操作符
( ) 函数调用操作符
. 结构体 . 结构体成员
-> 结构体 -> 结构体成员
10.1、[ ]下标引用操作符
操作数:一个数组名+一个索引值
int arr[10];
arr和10都是操作数
10.2、( )函数调用操作符
代码如下(示例):
#include <stdio.h>
void test1()
{
printf("hehe\n");
}
void test2(const char* str)
{
printf("%s\n", str);
}
int main()
{
test1(); //实用()作为函数调用操作符。
test2("hello word");//实用()作为函数调用操作符。
return 0;
}
10.3、访问一个结构体成员
代码如下(示例):
#include <stdio.h>
struct Book
{
char name[20];
int price;
};
int main()
{
struct Book sb = { "C语言",60 };
printf("%s %d", sb.name, sb.price); // C语言 60
struct Book* ps = &sb;
printf("%s %d", ps->name, ps->price); // C语言 60
return 0;
}
十一、表达式求值
11.1隐式类型转换
C的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
整形提升的意义:
表达式的运算要在CPU的相应运算器件中执行,CPU内整形运算器(ALU)的操作的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行是实际也要先转换为CPU内整形操作数的标准长度。
通用CPU是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整形值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
代码如下(示例):
#include <stdio.h>
int main()
{
char a = 5;
char b = 126;
char c = a + b;
// 00000000 00000000 00000000 00000101 -- 5
// 00000101 -- a
// 00000000 00000000 00000000 01111110 -- 126
// 01111110 -- b
//整形提升
// 00000000 00000000 00000000 00000101 -- a
// 00000000 00000000 00000000 01111110 -- b
//+00000000 00000000 00000000 10000011
// 10000011 -- c
printf("%d", c); // -125
// %d以十进制的方式打印有符号整数
// 11111111 11111111 11111111 10000011 -- c的补码
// 11111111 11111111 11111111 10000010 -- c的反码
// 10000000 00000000 00000000 01111101 -- c的原码 -125
return 0;
}
注意:
1、负数的整形提升,高位补充符号位,即为1
2、正数的整形提升,高位补充符号位,即为0
3、无符号的整形提升,高位补0
代码如下(示例):
#include <stdio.h>
int main()
{
char a = 5;
printf("%d\n", sizeof(a)); // 1
printf("%d\n", sizeof(a+1)); // 4
printf("%d\n", sizeof(+a)); // 4
printf("%d\n", sizeof(-a)); // 4
return 0;
}
只要参与表达式运算,就会发生整形提升
实例代码:
#include <stdio.h>
int main()
{
char a = 0xb6;
// 10110110
// 整形提升
// 11111111 11111111 11111111 10110110 -- a的补码
// 11111111 11111111 11111111 10110101 -- a的反码
// 10000000 00000000 00000000 01001010 -- a的原码
printf("%d ", a); // -74
short b = 0xb600;
// 10110110 00000000
// 整形提升
// 11111111 11111111 10110110 00000000 -- b的补码
int c = 0xb6000000;
// 10110110 00000000 00000000 00000000
if (a == 0xb6)
printf("a");
if (b == 0xb600)
printf("b");
if (c == 0xb6000000)
printf("c"); // c
return 0;
}
解析:
实例中a、b要进行整形提升,但是c不用整形提升
a、b 整形提升后为负数,所以表达式 a == 0xb6、b == 0xb600 的结果为假,c 不发生整形提升,所以 c == 0xb6000000的结果为真。
11.2、算术转换
某个操作符的各个操作数属于不同的类型,那么排名低的类型向排名高的类型转换
类型由低到高依次为:
int --> unsigned int --> long int --> unsigned long int --> float --> double --> long double
11.3、操作符的属性
复杂表达式求值的三个影响:
1、操作符的优先级
2、操作符的结合性
3、是否控制求值顺序