原码、反码、补码

存储到内存中的是补码
正整数原码反码补码都相同
原码
反码:符号位不变,其他位按位取反
补码:反码+1

移位操作符

移位操作符移的是补码
32位
10000000000000000000000000010000
右移操作符:
计算机一般用的是算数右移
1.算术右移:右边丢弃,左边补符号位,右移一位有除2效果
10000000000000000000000000001000
2.逻辑右移:右边丢弃,左边补0
00000000000000000000000000001000
左移操作符:左边丢弃,右边补0

位操作符

注意:补码运算

  • 按位与&

    • 0&0=0
    • 0&1=0
    • 1&1=1

    00101101
    10110110

&00100100

  • 按位或 |
    • 0|0=0
    • 0|1=1
    • 1|1=1

00101101
10110110
|10111111

  • 按位异或^ 相同为0,相异为1

    • 0^0=0
    • 0^1=1
    • 1^1=0

    00101101
    10110110

^ 10011011

不创建临时变量,交换两个int

  • method 1
1
2
3
4
5
6
7
8
9
10
11
12
13
int main()
{
int a = 3;
int b = 5;
printf("before: a=%d b=%d\n", a, b);
//加减法
a = a + b;
b = a - b;
a = a - b;
printf("after : a=%d b=%d\n", a, b);
//缺陷:会溢出
return 0;
}
  • method 2
1
2
3
4
5
6
7
8
9
10
11
12
int main()
{
int a = 3;
int b = 5;
printf("before: a=%d b=%d\n", a, b);
//异或法
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("after : a=%d b=%d\n", a, b);
return 0;
}

a与b异或得到c
c与a异或可得到b
c与b异或可得到a
不会溢出

exercise 1

练习:求一个整数的二进制表示中“1”的个数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
//复数不行
int main()
{
int num;
int count = 0;
scanf("%d", &num);
 //统计num补码中有几个1
while (num)
{
    if (num % 2 == 1)
       count++;
num = num / 2;
}
printf("%d\n", count);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
int main()
{
    int num;
    int count = 0;
    scanf("%d", &num);
    for (int i = 0; i < 32; i++)
    {
        if (1 == ((num >> i) & 1))
            count++;
    }
    printf("%d\n", count);
    return 0;
}

赋值操作符

=
支持连续赋值(不建议)
a = x = y + 1; <=> x = y + 1; a = x;

复合赋值符

+= -= /= *= %= >>= <<= |= &= ^=

单目操作符

只有一个操作数

! 逻辑反操作
非0为真,0为假
! 是真为假,假为真
!0=1,1为真,真不一定都为1

  • 负值
  • 正值

& 取地址操作符

  • 解引用操作符(间接访问操作符)

sizeof 操作数的类型长度(以字节为单位)
32位 int 4 char 1 char* 4
可以写 sizeof(int) 数组:sizeof(int [10])
如果为变量,可以省略括号,如 sizeof a
如果为类型,不可省略括号,如 sizeof(int)
short s = 0;
int a = 10;
printf(“%d\n”, sizeof(s = a + 5); 2 s是short
printf(“%d\n”, s); 0 sizeof中表达式不会进行真实运算

~ 对一个数的二进制按位取反
内存中对补码取反
原码0000000000000000000000000000000
取反1111111111111111111111111111111(补码)
10000000000000000000000000001(原码) – -1
a = a | (1 << n) 可以使a的指定二进制位为1
a = a & ~(1 << n) 可以使a的指定二进制位位0

++ – 前置,后置++ –
a 前置表示先后使用
a
后置表示先使用后

(类型) 强制类型转换
(int) 3.14

关系操作符

= < <= != ==

逻辑操作符

&& 逻辑与
|| 逻辑或
3 && 5 = 1
0 && 5 = 0
0 || 5 = 1
0 || 0 = 0

exercise 2

程序输出的结果是什么?

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
int main()
{
    int i = 0, a = 0, b = 2, c = 3, d = 4;
    i = a++ && ++b && d++;
    //a++先调用为0,后面没有进行运算,再++,a为1
    printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d);
    i = a++||++b||d++;
    i = 0, a = 0, b = 2, c = 3, d = 4;
    printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d);
    return 0;
}

条件操作符(三目操作符)

exp1 ? exp2 : exp3
如果exp1为真,则exp2执行,且执行结果为整个表达式的结果
如果exp1为假,则exp3执行,且执行结果为整个表达式的结果

逗号表达式

exp1,exp2,exp3,…,expn
从左向右执行,整个表达式的结果是最后一个表达式的结果

下标引用操作符

[ ]

函数调用操作符

( )

结构成员操作符

. 结构体.成员名
-> 结构体指针->成员名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct stu
{
char name[20];
int age;
char id[20];
}
int main()
{
struct stu s1 = {"zhangsan",20,"2739120"};
struct stu* ps = &s1;
printf("%d\n", s1.name);
printf("%d\n", (*ps).name);
printf("%d/n", ps->name);
return 0;
}

表达式求值

  • 隐式类型转换

C的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
原因:CPU内整型运算器的操作数的字节长度一般为int长度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int main()
{
整形提升是按照变量的数据类型的符号位来提升的
符号位为0,前面全补0
符号位为1,前面全补1

char a = 3;
//00000000000000000000000000000011
//截断,取后8位
//00000011 - a
char b = 127;
//00000000000000000000000001111111
//01111111 - b
char c = a + b;
//00000000000000000000000000000011
//+
//00000000000000000000000001111111
//00000000000000000000000010000010
//截断,取后8位
//10000010 - c
//最高位为1,补1
printf("%d\n", c);
//11111111111111111111111110000010 - 补码
//11111111111111111111111110000001 - 反码
//10000000000000000000000001111110 - 原码
//-126
return 0;
}
  • 算术转换

也属于隐式转换的一种
long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。

  • 操作符属性
  1. 操作符的优先级
  2. 操作符的结合性
  3. 是否控制求值顺序