加载中...
从零接触C语言(初览)-VIII: 操作符

从零接触C语言(初览)-VIII: 操作符

周四 8月 07 2025
3816 字 · 17 分钟

操作符

认识条件分支和循环语句的文章中, 介绍了逻辑运算符&& || !和关系运算符== != >= <= > <

这两类运算符是C语言操作符的一部分

什么是操作符

操作符(Operator) 是C语言中用于对数据(变量、常量等)执行特定操作的符号或关键字

简单来说, C语言中的操作符就是用来操作数据的, 特定的操作符可以对数据实现特定的操作

操作符介绍

C语言存在许多操作符, 本篇文章只做简单介绍, 某些操作符的具体使用方法, 在之后的文章中会涉及

算术运算符

C语言提供的算术运算符有:

运算符功能示例
+加, 实现两数相加1 + 1 => 2
-减, 实现两数相减1 - 1 => 0
*乘, 实现两数相乘2 * 2 => 4
/除, 实现两数相除2 / 2 => 1
%模, 取模, 即对整数求余数5 % 2 => 1

移位运算符

移位运算符, 顾名思义是操作在数据的二进制层面的, 用于对数据二进制位进行左移和右移

运算符功能示例
<<左移位操作符, 实现将数据二进制位向左移动1 << 2 => 4
>>右移位操作符, 实现将数据二进制位向右移动8 >> 2 => 2

移位操作符, 就是将数据 在二进制位的层面上 向左或向右移动n

移位操作符可能有一些抽象, 如果对二进制不是很了解, 可能不容易理解

不过也可以通过例子简单理解一下:

C
/*
 *    以 4(int) 为例
 *    4 的 二进制是 00000000 00000000 00000000 00000100 (32位)
 *    4 << 5, 就是 二进制位左移5位, 得:
 *    00000000 00000000 00000000 00000000 10000000, 128
 *
 *    4 >> 2, 就是 二进制位右移2位, 得:
 *    00000000 00000000 00000000 00000000 00000001, 1 
 */ 

但要注意, <<左移时 移动的位数只能小于数据类型大小, 否则就是C语言标准未定义的行为, 会出现未知的问题

C
// 左移的错误例子
#include <stdio.h>

int main() {
    int age = 2;
    printf("%u\n", age << 32);

    return 0;
}

如果左移位数不小于数据类型的大小, 编译时也会提醒

位运算符

位操作符, 也是操作在数据的二进制层面的, 但并不是移动:

运算符功能示例
&按位与, 对两数二进制位执行与操作, 对应位置均为1, 结果位才为12 & 3 => 2
|按位或, 对两数二进制位执行或操作, 对应位置均为0, 结果位才为02 | 4 => 6
~按位取反, 对数据二进制位执行取反操作, 对数据每位进行取反操作, 即 1变0, 0变1~1 => -2 (有符号)
^按位异或, 对两数二进制数据执行异或操作, 对应位置不同, 结果位才为12 ^ 3 => 1

位操作符, 比移位操作符要更加抽象一些, 不过依旧可以通过例子进行理解:

C
/*
 *  以 4(int) 和 7(int) 为例
 *  4: 00000000 00000000 00000000 00000100
 *  7: 00000000 00000000 00000000 00000111
 *
 *  4 & 7 (与):
 *  00000000 00000000 00000000 00000100
 *  00000000 00000000 00000000 00000111
 *  -----------------------------------
 *  00000000 00000000 00000000 00000100     (对应位均为1, 结果位才为1)
 *
 *  4 | 7 (或):
 *  00000000 00000000 00000000 00000100
 *  00000000 00000000 00000000 00000111
 *  -----------------------------------
 *  00000000 00000000 00000000 00000111     (对应位均为0, 结果位才为0)
 *
 *  4 ^ 7 (异或):
 *  00000000 00000000 00000000 00000100
 *  00000000 00000000 00000000 00000111
 *  -----------------------------------
 *  00000000 00000000 00000000 00000011     (对应位不同, 结果位才为1)
 *
 *  ~4 (取反);
 *  00000000 00000000 00000000 00000100
 *  -----------------------------------
 *  11111111 11111111 11111111 11111011     (对应位取反)
 */

仔细观察一下, 比较容易理解的

赋值运算符

C语言提供了赋值运算符, 可以给变量赋值: =

用法很简单:

C
#include <stdio.h>

int main() {
    int age;
    age = 20;
    printf("%d\n", age);

    return 0;
}

这段代码, age = 20;age变量赋值20

除此之外, C语言还提供了一些赋值运算符的扩展, 被称为复合赋值运算符:

运算符功能示例等价形式
=赋值, 将右侧的值 赋给左侧变量a = 5;a = 5;
+=加后赋值, 左侧变量 加 右侧值 再赋给左侧a += 3;a = a + 3;
-=减后赋值, 左侧变量 减 右侧值再赋给左侧a -= 2;a = a - 2;
*=乘后赋值, 左侧变量 乘 右侧值 再赋给左侧a *= 4;a = a * 4;
/=除后赋值, 左侧变量 除以 右侧值 再赋给左侧a /= 2;a = a / 2;
%=取模后赋值, 左侧变量 对 右侧值取模 再赋给左侧a %= 3;a = a % 3;
&=按位与后赋值, 左侧变量 与右侧值按位与 再赋给左侧a &= b;a = a & b;
`=`按位或后赋值, 左侧变量 与右侧值按位或 再赋给左侧`a
^=按位异或后赋值, 左侧变量 与右侧值按位异或 再赋给左侧a ^= b;a = a ^ b;
<<=左移后赋值, 左侧变量 左移 右侧值位 再赋给左侧a <<= 2;a = a << 2;
>>=右移后赋值, 左侧变量 右移 右侧值位 再赋给左侧a >>= 1;a = a >> 1;

在不考虑编译器额外优化的情况下, 使用复合赋值运算符要比正常的计算后赋值, 运行效率要高一点

单目操作符

单目操作符, 从名字来看 看不出是什么意思

事实上, 单目操作符就是操作单个对象的操作符

什么是操作单个对象呢?

以之前用过的计算变量大小的操作符为例: sizeof

sizeof的用法是这样的:

C
sizeof(int);    // 操作类型
sizeof(1.23f);    // 操作数据
long long age = 10;
sizeof(age);    // 操作变量

可以看到, 操作的对象只有一个

上面已经介绍过的算术、移位、位、赋值运算符都是双目操作符, 他们操作的都是两个对象:

C
var1 + var1;
var1 - 1;
var1 << 2;
var1 & var2;
var1 += 1;
...

C语言最常用的单目操作符有:

操作符功能示例
++自增, 即 自增1. 但 区分前置和后置var++++var
--自减, 即 自减1. 但 区分前置和后置var----var
sizeof求变量、类型等大小sizeof(int)sizeof int
&取地址, 可以取到变量空间在内存中地址值&var
+正值+var
-负值-var
!逻辑取反, 属于逻辑运算符!var
~按位取反, 属于位运算符~var
*解引用, 可以解引用指针, 按特定类型访问指针所指向空间的值
指针定义声明符, 用于声明 定义指针变量
*addr(解引用)
char*(声明指针)
(type)强制类型转换, 可以将变量、常量值强制转换为目标类型(int)23.33

单目操作符中, 有几个需要特别介绍一下:

  1. ++--

    自增和自减操作符

    如果不区分前置和后置, 从功能上很容易理解, 就是自增1, 它与+= 1是等价的

    但自增和自减操作符是区分前置和后置的, 且在使用上有很大的区别, 即 i++++i是存在区别的

    可以直接通过代码来介绍:

    C
    #include <stdio.h>
    
    int main() {
        int index = 0;
        index++;
        printf("index: %d\n", index);
        ++index;
        printf("index: %d\n", index);
    
        return 0;
    }

    这段代码的运行结果很明显, 只是index变量两次自增1

    从结果来看好像没有区别

    实际上, 要观察区别, 应该这样观察:

    C
    #include <stdio.h>
    
    int main() {
        int index = 0;
        int res1 = index++;
        int res2 = index;
        int res3 = ++index;
        printf("res1: %d\n", res1);
        printf("res2: %d\n", res2);
        printf("res3: %d\n", res3);
    
        return 0;
    }

    这段代码的运行结果是什么呢?

    自增的执行顺序还是 先index++++index

    但从结果可以看到res1 = 0; (index++) res2 = 1; (index) res3 = 2; (++index)

    为什么res1 = 0? 不是自增了吗?

    res = 1可以看出来index确实自增了

    为什么res3 = 2? 按照res1的结果推测, res3不应该是1吗?

    造成这个结果的原因就是 前置++和后置++存在的区别了

    ++前后置调用的区别是:

    前置++, 先执行自增1的操作, 然后将自增后的结果返回

    后置++, 先保存原值, 再执行自增1的操作, 然后返回原值

    所以, 从没有优化的角度来看, 前置++要比后置++性能稍微好一点点


    --自减操作符, 与++自增操作符相同, 同样区分前置使用和后置使用

  2. &* (简单介绍)

    C语言中, &取地址操作符 和 *解引用操作符 是两个密切关联的操作符

    &取地址操作符, 作用于变量, 作用是 取变量在内存中占用空间的地址

    *解引用操作符, 作用于指针, 作用是 按类型访问指针所指向地址的空间

    这两个操作符的使用通常是相关的:

    C
    #include <stdio.h>
    
    int main() {
        int index = 10;
        int* pIndex = &index;
    
        printf("index在内存中的地址: %p, index的值: %d\n", pIndex, *pIndex);
    
        return 0;
    }

    简单介绍一下这段代码具体做了什么操作:

    1. int index = 10;, 定义一个整型变量index并初始化为10

    2. int* pIndex = &index;, 定义一个int*类型变量pIndex并初始化为 变量index的地址

      &取地址操作符, 作用于变量, 可以取出这个变量在内存中的地址

      上面列表中介绍了*可以用来声明定义指针变量, int*就是一个int类型的指针变量

      指针(pointer)变量是用来存储地址的一种变量, 在此例中pIndex这个变量中存储的就是index的地址

    3. 访问pIndex

      pIndex中存储的是index这个变量的地址, 可以看作pIndex指针变脸实际指向index的地址空间

      那么, 访问pIndex就能获取到index的地址

    4. 访问*pIndex

      *解引用操作符, 始终作用于指针, 用于按指针类型访问指针所指向的空间

      pIndex是一个int类型的指针, 存储的是变量index的地址

      那么*pIndex就会按照int类型去访问pIndex的存储的地址, 实际访问到的就是变量index的空间

      所以*pIndex就能访问到index的值

  3. (类型)

    C语言可以对数据进行强制类型转换, 即 将非目标类型的数据强制转换为目标类型

    使用上也非常简单:

    C
    #include <stdio.h>
    
    int main() {
        float varF = 12.333;
        printf("%d %d\n", (int)varF, varF);
    
        return 0;
    }

    这段代码的执行结果:

    同样用%d打印整型数据的格式化字符, (int)varF正确打印出了12, 而varF却是0

    在C语言标准中, 使用特定类型的格式化字符 尝试去 格式化特定类型的行为, 是没有被定义的, 即 是一种未定义行为

    所以使用%d去格式化浮点数据, 是一种未定义行为, 结果通常是未知的

    但可以通过 (int)floatData将浮点类型数据强制转换为整型数据, 这样再使用%d进行格式化, 就不是未定义行为了

条件操作符

C语言标准中定义了一个唯一一个三目操作符, 同时也被称作条件操作符: condition ? result1 : result2

condition表示条件表达式, result1result2表示两个结果

条件操作符的用法为: 条件满足时, 执行并返回result1, 否则 执行并返回result2

以一个简单的例子做介绍:

C
(a > b) ? (a - b) : (b - a)

例子的意思为:

如果a > b成立, 则执行a - b并返回结果, b - a不会被执行

如果a > b不成立, 则执行b - a并返回其结果, a - b不会被执行

可以将? :当作一个简写的if-else条件语句, 逻辑上是完全等价的

逗号表达式

C语言标准中提供了一个特殊的操作符,

用法是这样的:

C
expression1, expression2, expression3, expression4, expression5, expression6, expression7, expression8, ...

可以通过,组成不限制长度的逗号表达式

逗号表达式的规则为: 从左到右按顺序执行所有的表达式, 但整个逗号表达式的结果为最后一个表达式的执行结果

以简单的代码为例:

C
#include <stdio.h>

int main() {
    int index = 0;
    index = ++index, ++index, ++index, ++index, ++index;
    printf("index: %d\n", index);

    return 0;
}

执行结果为:

可以看到, index的最终结果为5, 即为最后一次执行++index的结果

特殊操作符

除了上述的操作符, C语言标准中还存在几个特殊的操作符, 具体功能需要结合具体的情况进行使用:

  1. []数组下标访问操作符
  2. ()函数调用操作符
  3. .结构体成员访问操作符
  4. ->结构体成员访问操作符

[]()没有什么需要特别注意的

.->在介绍结构体时再进行介绍

操作符的运算优先级

C语言操作符的使用, 也是有优先级的

就像小学就学过的”先乘除后加减”一样, C语言也一样

不过, C语言中这么多的操作符, 运算优先级也都不尽相同:

优先级操作符描述结合性
1() [] -> .函数调用、数组下标、结构体成员访问左到右
2++ --后缀自增、后缀自减左到右
3++ -- + - ! ~ (type) * & sizeof前缀自增、前缀自减、正号、负号、逻辑非、按位取反、类型转换、解引用、取地址、求字节大小右到左
4* / %乘法、除法、取模左到右
5+ -加法、减法左到右
6<< >>左移、右移左到右
7< <= > >=关系比较左到右
8== !=相等比较左到右
9&按位与左到右
10^按位异或左到右
11``按位或
12&&逻辑与左到右
13``
14?:条件操作符(三目运算符)右到左
15= += -= *= /= %= <<= >>= &= ^= |=赋值操作符右到左
16,逗号表达式左到右

C语言操作符的优先级是需要牢记的, 序号越小 运算优先级越高

当然, 如果你在使用操作符时不确实不确定哪个优先级高, 你可以用()进行包裹, 被()包裹的表达式总是优先计算


Thanks for reading!

从零接触C语言(初览)-VIII: 操作符

周四 8月 07 2025
3816 字 · 17 分钟