Contents

C语言中的运算符

运算符

在C语言中运算符分为:算法运算、逻辑运算、位运算、赋值更新、内存访问。

算术运算

加减乘除

对CPU来说,加减法的运行速率比乘除要快。CPU进行运算主要依赖算术逻辑单元(ALU)浮点运算单元(FPU),ALU主要负责整数的加减乘除,而FPU主要负责浮点数的运算。乘除法有时候会转换成加减法或者移位操作。随着计算机的发展,现在已经有了“乘法器”的硬件支持。

  • 直接调用”乘法器”进行乘法运算
#include <stdio.h>

int main() {
    int a = 10;
    int b = a * 34;

    printf("b = %d \n", b);

    return 0;
}

汇编代码如下:

movl	$10, -8(%rbp)
movl	-8(%rbp), %eax
imull	$34, %eax, %eax
movl	%eax, -4(%rbp)

编译器也会对乘法进行优化,比如将乘法转换为移位操作:

#include <stdio.h>

int main() {
    int a = 10;
    int b = a * 33;

    printf("b = %d \n", b);

    return 0;
}

汇编代码如下:

movl	$10, -8(%rbp)
movl	-8(%rbp), %edx
movl	%edx, %eax
sall	$5, %eax      # eax = a * 32
addl	%edx, %eax      # eax = eax + a = a * 33
movl	%eax, -4(%rbp)
movl	-4(%rbp), %eax

mod(取模运算)

%运算符用于计算两个整数相除的余数,也称为mod运算。它只能用于整数类型,不能用于浮点数

#include <stdio.h>
int main() {
    int a = 10;
    int b = 3;
    int c = a % b; // c的值为1

    printf("c = %d \n", c);

    return 0;
}

位运算

移位:«、»

移位运算符用于将一个整数的二进制表示向左或向右移动指定的位数。左移运算符(«)将二进制数向左移动,右移运算符(»)将二进制数向右移动。

  • 左移运算符(«):将一个整数的二进制表示向左移动指定的位数,右侧用0填充。在没有溢出之前,每左移一位,相当于乘以2。
#include <stdio.h>

#define CALCULATE_LEN(a) (sizeof(a) * 8)

void print_binary(long long a, int n) {
    printf(": ");
    for (int i = n - 1; i >= 0; i--) {
        printf("%lld", (a >> i) & 0x01);
    }
    printf(": %lld\n", a);
}

int main() {
    char a = 1;

    for (int i = 0; i < CALCULATE_LEN(a) + 1; i++) {
        printf("a = %d, 左移 %d 位", a, i);
        print_binary(a << i, CALCULATE_LEN(a));
    }
}

输出结果:

a = 1, 左移 0 位: 00000001: 1
a = 1, 左移 1 位: 00000010: 2
a = 1, 左移 2 位: 00000100: 4
a = 1, 左移 3 位: 00001000: 8
a = 1, 左移 4 位: 00010000: 16
a = 1, 左移 5 位: 00100000: 32
a = 1, 左移 6 位: 01000000: 64
a = 1, 左移 7 位: 10000000: 128
a = 1, 左移 8 位: 00000000: 256

左移8位后,超出了char类型的表示范围,a所圈定的内存空间无法存储该值,导致溢出,结果变为0。打印结果显示为256,是因为print_binary函数中将a参数定义为long long类型,能够存储更大的值。

  • 右移运算符(»):将一个整数的二进制表示向右移动指定的位数左侧用符号位填充(对于有符号整数,复数为1,正数为0)或用0填充(对于无符号整数)。在没有溢出之前,每右移一位,相当于除以2。负数通过右移永远都不会等于0.
#include <stdio.h>

#define CALCULATE_LEN(a) (sizeof(a) * 8)

void print_binary(char a, int n) {
    printf(": ");
    for (int i = n - 1; i >= 0; i--) {
        printf("%d", (a >> i) & 0x01);
    }
    printf(": %d\n", a);
}

int main() {
    char a = 0x80;

    for (int i = 0; i < CALCULATE_LEN(a) + 1; i++) {
        printf("a = 0x%x, 右移 %d 位", a & 0xFF, i);
        print_binary(a >> i, CALCULATE_LEN(a));
    }
}

输出结果:

a = 0x80, 右移 0 位: 10000000: -128
a = 0x80, 右移 1 位: 11000000: -64
a = 0x80, 右移 2 位: 11100000: -32
a = 0x80, 右移 3 位: 11110000: -16
a = 0x80, 右移 4 位: 11111000: -8
a = 0x80, 右移 5 位: 11111100: -4
a = 0x80, 右移 6 位: 11111110: -2
a = 0x80, 右移 7 位: 11111111: -1
a = 0x80, 右移 8 位: 11111111: -1

可以看到,char类型的最高位是符号位,表示负数。右移操作时,符号位被复制到左侧,导致结果始终为负数。

main函数中的char a = 0x80;修改为unsigned char a = 0x80;,重新编译运行,输出结果如下:

a = 0x80, 右移 0 位: 10000000: -128
a = 0x80, 右移 1 位: 01000000: 64
a = 0x80, 右移 2 位: 00100000: 32
a = 0x80, 右移 3 位: 00010000: 16
a = 0x80, 右移 4 位: 00001000: 8
a = 0x80, 右移 5 位: 00000100: 4
a = 0x80, 右移 6 位: 00000010: 2
a = 0x80, 右移 7 位: 00000001: 1
a = 0x80, 右移 8 位: 00000000: 0

位与或、取反: &、|、~

操作示例如下:

// 位与(&)
1 & 1 = 1 1 & 0 = 0 0 & 1 = 0 0 & 0 = 0

// 位或(|)
1 | 1 = 1 1 | 0 = 1 0 | 1 = 1 0 | 0 = 0

// 位取反(~)
~1 = 0 ~0 = 1

位异或: ^

操作示例如下:

// 位异或(^):相同为0,不同为1
1 ^ 1 = 0 1 ^ 0 = 1 0 ^ 1 = 1 0 ^ 0 = 0

逻辑运算

条件或与:&&、||

操作示例如下:

// 逻辑与(&&)
true && true = true true && false = false false && true = false false && false = false
// 逻辑或(||)
true || true = true true || false = true false || true = true false || false = false

大小比较运算符:<、>、<=、>=、==、!=

操作示例如下:

// 小于(<)
3 < 5 = true 5 < 3 = false
// 大于(>)
3 > 5 = false 5 > 3 = true
// 小于等于(<=)
3 <= 5 = true 5 <= 5 = true 6 <= 5 = false
// 大于等于(>=)
3 >= 5 = false 5 >= 5 = true 6 >= 5 = true
// 等于(==)
3 == 5 = false 5 == 5 = true
// 不等于(!=)
3 != 5 = true 5 != 5 = false

条件取反:!

可用于判断指针是否为空、二值化处理等场景。

int *p = NULL;

if (!p) {
    // p为空指针
}

b = !!a; // 二值化处理

赋值更新:=、+=、-=、*=、/=、%=、«=、»=、&=、|=、^=

运算符等价表达式用途
+=a = a + b加法更新
-=a = a - b减法更新
*=a = a * b乘法更新
/=a = a / b除法更新
%=a = a % b取模更新
<<=a = a << b左移更新
>>=a = a >> b右移更新
&=a = a & b按位与更新
|=a = a | b按位或更新
^=a = a ^ b按位异或更新
int a = 5;

a += 3;   // a = 8
a -= 2;   // a = 6
a *= 4;   // a = 24
a /= 6;   // a = 4
a %= 3;   // a = 1

a <<= 2;  // a = 4  (1 << 2)
a >>= 1;  // a = 2

a &= 6;   // a = 2
a |= 1;   // a = 3
a ^= 2;   // a = 1