当前位置: 首页 > article >正文

【参考资料 II】C 运算符大全:算术、关系、赋值、逻辑、条件、指针、符号、成员、按位、混合运算符

目录

C 语言运算符大全

1 算术运算符

1.1 运算符说明

1.2 案例演示

2 关系运算符

2.1 运算符说明

2.2 关系表达式 

2.3 案例演示

3 赋值运算符

3.1 运算符说明

3.1.1 基本赋值运算符

3.1.2 复合赋值运算符

3.2 案例演示

4 逻辑运算符

4.1 运算符说明

4.2 逻辑表达式

4.3 逻辑表达式的求值顺序

4.3.1 短路求值规则

4.3.2 示例分析

4.4 案例演示

5 条件运算符

5.1 运算符说明

5.2 案例演示

6 指针运算符

6.1 运算符说明

6.2 案例演示

7 符号运算符

7.1 运算符说明

7.2 案例演示

8 结构和联合运算符

8.1 成员运算符

8.2 间接成员运算符

8.3 等效写法说明 

8.3.1 ptrst->code

8.3.2 item.code

8.3.3 (*ptrst).code

8.3.4 等效性分析

9 按位运算符

9.1 运算符说明

9.2 案例演示

9.3 左移和右移运算符特性

9.3.1 数学意义

9.3.2 注意事项与常见问题

10 混合运算符

10.1 大小运算符(sizeof)

10.2 对齐要求运算符(_Alignof)

10.3 强制类型转换运算符(type)

10.4 逗号运算符(,)


C 语言运算符大全

        C 语言有大量的运算符。表 B.2.1 按优先级从高至低的顺序列出了 C 运算符,并给出了其结合性。除非特别指明,否则所有运算符都是二元运算符(需要两个运算对象)。

        注意,一些二元运算符和一元运算符的表示符号相同,但是其优先级不同。例如,*(乘法运算符)和 *(间接运算符)。

提示:

        C 语言运算符优先级不用刻意地去记忆,大致上:一元运算符  >  算术运算符  >  关系运算符  >  逻辑运算符  >  条件运算符  >  赋值运算符

        

        常见的运算符优先级关系可简单记忆为: 逻辑非 !  >  算术运算符  >  关系运算符  >  逻辑与 &&  >  逻辑或 ||  >  条件运算符  >  赋值运算符


1 算术运算符

1.1 运算符说明

运算符类型描述
+二元运算符(加法)把右边的值加到左边的值上。
+一元运算符(正号)生成一个大小和符号都与右边值相同的值。
-二元运算符(减法)从左边的值中减去右边的值。
-一元运算符(负号)生成一个与右边值大小相等但符号相反的值。
*乘法运算符把左边的值乘以右边的值。
/除法运算符把左边的值除以右边的值;如果两个运算对象都是整数,则结果将被截断,只保留整数部分
%求余运算符计算左边值除以右边值时的余数。
++前缀自增运算符在使用变量之前,先将变量的值加 1
++后缀自增运算符先使用变量的当前值,之后再将该变量的值加 1
--前缀自减运算符在使用变量之前,先将变量的值减 1
--后缀自减运算符先使用变量的当前值,之后再将该变量的值减 1

1.2 案例演示

#include <stdio.h>

int main()
{
    // 二元加法运算符:左结合属性,把右边的值加到左边的值上。
    int a = 5 + 3;
    printf("a (5 + 3) = %d\n", a); // 8

    // 一元正号运算符:右结合属性,生成一个大小和符号都与右边值相同的值。
    int b = +5;
    printf("b (+5) = %d\n", b);      // 5
    b = +-5;                         // 根据正负号运算符的右结合属性,等价于 +(-5)
    printf("b (+-5) = %d\n", b);     // -5
    b = +(-5);                       // 使用括号来强调运算优先级,使运算结构更加清晰
    printf("b (+(-5)) = %d\n\n", b); // -5

    // 二元减法运算符:左结合属性,从左边的值中减去右边的值。
    int c = 5 - 3;
    printf("c (5 - 3) = %d\n", c); // 2

    // 一元负号运算符:右结合属性,生成一个与右边值大小相等但符号相反的值。
    int d = -5;
    printf("d (-5) = %d\n", d); // -5
    // d = --5;
    printf("d = --5 是错误的写法,--会被当成前缀自减运算符,前置递减运算符只能作用于左值。\n");
    printf("\t左值是指可以出现在赋值表达式左侧的对象,通常是具有内存地址的变量。\n");
    printf("\t数字字面量(如 5)是一个右值,它没有内存地址,因此不能作为前置递减运算符的操作数。\n");
    // 正确的写法如下所示:
    d = -(-5);
    printf("d (-(-5)) = %d\n\n", d); // 5

    // 乘法运算符:左结合属性,把左边的值乘以右边的值。
    int e = 5 * 3;
    printf("乘法:e (5 * 3) = %d\n", e); // 15

    // 除法运算符:左结合属性,把左边的值除以右边的值。
    // 如果两个运算对象都是整数,则结果将被截断为整数部分。
    int f1 = 10 / 3;
    printf("整型除法:f1 (10 / 3) = %d\n", f1); // 结果不是 3.333333,而是 3,小数部分会被截断

    float f2 = 10.0 / 3.0;
    printf("浮点型除法:f2 (10.0 / 3.0) = %f\n", f2); // 结果是 3.333333

    // 求余运算符:左结合属性,计算左边值除以右边值时的余数。
    int g = 10 % 3;
    printf("求余:g (10 %% 3) = %d\n\n", g); // 1,注意这里需要使用转义字符 %% 来表示一个 %

    // 前缀自增运算符:右结合属性,在使用变量之前,先将变量的值加 1。
    int h = 5;
    int i = ++h;                            // 先将变量 h 的值加 1(为 6),然后再将值(6)赋值给变量 i。
    printf("i (++h) = %d, h = %d\n", i, h); // h 和 i 都将是 6

    // 后缀自增运算符:左结合属性,
    int j = 5;
    int k = j++;                            // 先将变量 j 的值(为 5)赋值给变量 k,然后再将变量 j 的值加 1(为 6)。
    printf("k (j++) = %d, j = %d\n", k, j); // k 将是 5, 之后 j 变为 6

    // 前缀自减运算符:右结合属性,在使用变量之前,先将变量的值加 1。
    int l = 5;
    int m = --l;                            // 先将变量 l 的值减 1(为 4),然后再将值(4)赋值给变量 m。
    printf("m (--l) = %d, l = %d\n", m, l); // l 和 m 都将是 4

    // 后缀自减运算符:左结合属性,
    int n = 5;
    int o = n--;                            // 先将变量 n 的值(为 5)赋值给变量 o,然后再将变量 n 的值减 1(为 4)。
    printf("o (n--) = %d, n = %d\n", o, n); // o 将是 5,之后 n 变为 4

    return 0;
}

        程序在 VS code 中的运行结果如下所示:


2 关系运算符

2.1 运算符说明

        下面的每个运算符都把左边的值与右边的值相比较。

运算符类型描述
<小于左边的值小于右边的值时为真 (1)
<=小于或等于左边的值小于或等于右边的值时为真 (1)
==等于左边的值等于右边的值时为真 (1)
>大于左边的值大于右边的值时为真 (1)
>=大于或等于左边的值大于或等于右边的值时为真 (1)
!=不等于左边的值不等于右边的值时为真 (1)

2.2 关系表达式 

        简单的关系表达式由关系运算符及其两侧的运算对象组成。

  • 如果关系为真,则关系表达式的值为 1;
  • 如果关系为假,则关系表达式的值为 0

        下面是两个例子:

  • 5 > 2 关系为真,整个表达式的值为 1。
  • (2 + a) == a 关系为假,整个表达式的值为 0。 

2.3 案例演示

#include <stdio.h>

int main()
{
    int a = 66;
    int b = 99;

    // 使用大于运算符 > 比较 a 和 b
    // 如果 a 大于 b,则返回 1(真),否则返回 0(假)
    printf("a > b 的值:%d\n", a > b); // 输出为 0,因为 66 不大于 99

    // 使用大于等于运算符 >= 比较 a 和 b
    // 如果 a 大于等于 b,则返回 1(真),否则返回 0(假)
    printf("a >= b 的值:%d\n", a >= b); // 输出为 0,因为 66 不大于等于 99

    // 使用小于运算符 < 比较 a 和 b
    // 如果 a 小于 b,则返回 1(真),否则返回 0(假)
    printf("a < b 的值:%d\n", a < b); // 输出为 1,因为 66 小于 99

    // 使用小于等于运算符 <= 比较 a 和 b
    // 如果 a 小于等于 b,则返回 1(真),否则返回 0(假)
    printf("a <= b 的值:%d\n", a <= b); // 输出为 1,因为 66 小于等于 99

    // 使用等于运算符 == 比较 a 和 b
    // 如果 a 等于 b,则返回 1(真),否则返回 0(假)
    printf("a == b 的值:%d\n", a == b); // 输出为 0,因为 66 不等于 99

    // 使用不等于运算符 != 比较 a 和 b
    // 如果 a 不等于 b,则返回 1(真),否则返回 0(假)
    printf("a != b 的值:%d\n", a != b); // 输出为 1,因为 66 不等于 99

    return 0;
}

        程序在 VS code 中的运行结果如下所示:


3 赋值运算符

3.1 运算符说明

        C 语言有一个基本赋值运算符,= 运算符是基本的形式。

3.1.1 基本赋值运算符

运算符类型描述
=基本赋值将右边表达式的值赋给左边的操作数(左值、变量)

3.1.2 复合赋值运算符

        C 语言不仅提供了一个基本的赋值运算符,还提供了多个复合赋值运算符,它们可以简化某些操作。

        每个复合赋值运算符都是基本赋值运算符(=)和另一个二元运算符的组合。

        下面的每个赋值运算符都根据它右边的值更新其左边的左值

运算符类型描述
+=加法赋值将右边的值加到左边变量上,并将结果存回左边变量
-=减法赋值从左边变量中减去右边的值,并将结果存回左边变量
*=乘法赋值将左边变量乘以右边的值,并将结果存回左边变量
/=除法赋值将左边变量除以右边的值,并将结果存回左边变量
%=求余赋值计算左边变量除以右边值的余数,并将结果存回左边变量
&=按位与赋值对左边变量和右边值执行按位与操作,并将结果存回左边变量
|=按位或赋值对左边变量和右边值执行按位或操作,并将结果存回左边变量
^=按位异或赋值对左边变量和右边值执行按位异或操作,并将结果存回左边变量
>>=右移赋值将左边变量右移指定的位数(由右边值决定),并将结果存回左边变量
<<=左移赋值将左边变量左移指定的位数(由右边值决定),并将结果存回左边变量

        使用复合赋值运算符可以减少代码量并提高可读性。例如:

  • rabbits *= 1.6; 与 rabbits = rabbits * 1.6 效果相同。

3.2 案例演示

#include <stdio.h>

int main()
{
    // 基本赋值运算符
    int a = 10;
    int b = 3;
    // 展示初始值
    printf("初始值: a = %d, b = %d\n", a, b);

    // 加法赋值
    a += b;                               // 等同于 a = a + b;
    printf("a += b 后,左值 a = %d\n", a); // 10 + 3 = 13

    // 减法赋值
    a -= b;                               // 等同于 a = a - b;
    printf("a -= b 后,左值 a = %d\n", a); // 13 - 3 = 10

    // 乘法赋值
    a *= b;                               // 等同于 a = a * b;
    printf("a *= b 后,左值 a = %d\n", a); // 10 * 3 = 30

    // 除法赋值
    a /= b;                               // 等同于 a = a / b;
    printf("a /= b 后,左值 a = %d\n", a); // 30 / 3 = 10

    // 求余赋值
    a %= b;                                  // 等同于 a = a % b;
    printf("a %%= b 后,左值 a = %d\n\n", a); // 10 % 3 = 1

    // 按位与(有 0 出 0)赋值
    int c = 5;                        //  5 的二进制为 0101
    c &= 3;                           //  3 的二进制为 0011, 按位与结果是 0001 (1)
    printf("c &= 3 后: c = %d\n", c); // 1

    // 按位或(有 1 出 1)赋值
    c |= 2;                           // 2 的二进制为 0010, 1 的二进制  0001, 按位或结果是 0011 (3)
    printf("c |= 2 后: c = %d\n", c); // 3

    // 按位异或(相同为 0,不同为 1)赋值
    c ^= 6;                           // 6 的二进制为 0110, 3 的二进制 0011, 结果是 0101 (5)
    printf("c ^= 6 后: c = %d\n", c); // 5

    // 右移赋值
    int d = 8;                         // 8 的二进制为 0000 1000
    d >>= 1;                           // 右移 1 位(相当于除 2 的 1 次方),结果是 0000 0100 (4)
    printf("d >>= 1 后: d = %d\n", d); // 4

    // 左移赋值
    d <<= 2;                           // 4 的二进制为 0000 0100, 左移 2 位(相当于乘 2 的 2 次方)结果是 0001 0000 (16)
    printf("d <<= 2 后: d = %d\n", d); // 16

    return 0;
}

        程序在 VS code 中的运行结果如下所示:


4 逻辑运算符

4.1 运算符说明

        逻辑运算符通常以关系表达式作为运算对象。! 运算符只需要一个运算对象,其他运算符需要两个运算对象,运算符左边一个,右边一个。

运算符类型描述
逻辑非

对操作数逻辑取反。

如果操作数为真(非零),结果为假(0)

如果操作数为假(0),结果为真(1)

&&逻辑与

如果两个操作数都为真(非零),结果为真(1);否则结果为假(0)【一假则假】

短路求值:如果第一个操作数为假,则不计算第二个操作数

||逻辑或

如果至少一个操作数为真(非零),结果为真(1)【一真则真】

只有当两个操作数都为假(0)时,结果为假(0)

短路求值:如果第一个操作数为真,则不计算第二个操作数

4.2 逻辑表达式

        逻辑表达式是由操作数和逻辑运算符(如 &&、|| 和 !)组成的表达式,用于执行布尔逻辑运算并返回真(1)或假(0)的结果。

逻辑非 (!)

  • 如果操作数的值为假(0),则 !操作数 的值为真(1);
  • 如果操作数的值为真(非零),则 !操作数 的值为假(0)
  • 示例:!0 的值为 1,而 !5 的值为 0。

逻辑与 (&&)

  • 当且仅当两个操作数都为真(非零)时,操作数 1 && 操作数 2 的值才为真(1)。否则,结果为假(0)【一假则假】
  • 示例:5 && 0 的值为 0,因为第二个操作数为假。

逻辑或 (||)

  • 当两个操作数中至少有一个为真(非零)时,操作数 1 || 操作数 2 的值为真(1)【一真则真】
  • 只有当两个操作数都为假(0)时,结果为假(0)
  • 示例:5 || 0 的值为 1,因为第一个操作数为真。

4.3 逻辑表达式的求值顺序

        逻辑表达式的求值顺序是从左到右依次进行

        在某些情况下,编译器会根据逻辑运算符的特性进行短路求值(Short-Circuit Evaluation)。也就是说,当已经能够确定整个表达式的结果时,后续的操作数将不再被计算。

4.3.1 短路求值规则

逻辑与 (&&):

        如果左侧操作数为假(0),则整个表达式必定为假,右侧操作数不会被计算

逻辑或 (||):

        如果左侧操作数为真(非零),则整个表达式必定为真,右侧操作数不会被计算

4.3.2 示例分析

示例 1:

6 > 2 && 3 == 3
  • 求值过程:
    • 首先计算 6 > 2,结果为真(1)。
    • 然后计算 3 == 3,结果也为真(1)。
  • 最终结果:真(1)。

示例 2:

! (6 > 2 && 3 == 3)
  • 求值过程:
    • 先计算括号内的 6 > 2 && 3 == 3,结果为真(1)。
    • 然后对结果取反,!1 的值为假(0)。
  • 最终结果:假(0)。

示例 3:

x != 0 && 20 / x < 5
  • 求值过程:
    • 首先计算 x != 0。如果 x 为 0,则整个表达式为假,右侧的 20 / x < 5 不会被计算,从而避免了除以零的错误。
    • 如果 x 非零,则继续计算 20 / x < 5。
  • 最终结果:取决于 x 的具体值。

4.4 案例演示

#include <stdio.h>

int main()
{
    int x = 0, y = 10;

    // 示例 1:逻辑与 (&&) 的短路性质
    // 如果第一个条件为假,则不会计算第二个条件
    printf("示例 1: 逻辑与 (&&) 短路性质\n");
    if (x != 0 && y / x > 5)
    { // 第一个条件 x != 0 为假,因此不会计算 y / x > 5
        printf("条件成立: x != 0 && y / x > 5\n");
    }
    else
    {
        printf("条件不成立,短路发生。\n"); // 输出此行,避免了除以零错误
    }

    // 示例 2:逻辑或 (||) 的短路性质
    // 如果第一个条件为真,则不会计算第二个条件
    printf("\n示例 2: 逻辑或 (||) 短路性质\n");
    if (y > 5 || ++x > 0)
    { // 第一个条件 y > 5 为真,因此不会计算 ++x > 0
        printf("条件成立: y > 5 || ++x > 0\n");
    }
    printf("x 的值:%d\n", x); // 输出 x 的值仍为 0,因为 ++x 没有被执行

    // 示例 3:逻辑非 (!) 的使用
    // 对条件取反,判断 x 是否为 0
    printf("\n示例 3: 逻辑非 (!) 的使用\n");
    if (!x)
    { // !x 为真,因为 x == 0
        printf("x 的值为 0\n");
    }
    else
    {
        printf("x 的值不为 0 \n");
    }

    // 示例 4:结合逻辑运算符的复杂条件判断
    // 使用逻辑与和逻辑或组合,体现优先级和短路特性
    printf("\n示例 4: 结合逻辑运算符的复杂条件判断\n");
    printf("此时的 x = %d, y = %d\n", x, y); // x = 0, y = 10

    if ((x == 0 || y > 20) && !(y == 10))
    // x == 0 为真,根据逻辑或的短路性质,右侧的 y > 20 不会被计算,结果:(x == 0 || y > 20) 的值为真(1)
    // y == 10 的值为真,结果: !(y == 10) 的值为假(0)
    // 最终:(真 && 假) 结果为假(0)
    {
        printf("复杂条件成立。\n");
    }
    else
    {
        printf("复杂条件不成立。\n");
    }

    return 0;
}

        程序在 VS code 中的运行结果如下所示:


5 条件运算符

5.1 运算符说明

运算符类型描述
?:条件运算符

?: 有三个运算对象,每个运算对象都是一个表达式。

根据条件表达式的结果选择两个表达式中的一个。

语法为:expression1 ? expression2 : expression3

        如果 expression1 为真(非零),则返回 expression2 的值;

        否则返回 expression3 的值

5.2 案例演示

#include <stdio.h>

int main()
{
    // 示例 1:基本用法
    int result1 = (5 > 3) ? 1 : 2;                   // 如果 5 > 3 为真,则返回 1;否则返回 2
    printf("(5 > 3) ? 1 : 2 的值为:%d\n", result1); // 输出:1

    int result2 = (3 > 5) ? 1 : 2;                   // 如果 3 > 5 为假,则返回 2
    printf("(3 > 5) ? 1 : 2 的值为:%d\n", result2); // 输出:2

    // 示例 2:比较两个数的大小
    int a = 10, b = 20;
    int max = (a > b) ? a : b;                // 如果 a > b 为真,则返回 a;否则返回 b
    printf("a 和 b 中较大的值是:%d\n", max); // 输出:20

    // 示例 3:嵌套使用条件运算符
    int x = 5, y = 10, z = 15;
    int largest = (x > y) ? ((x > z) ? x : z) : ((y > z) ? y : z);
    // 解释:先比较 x 和 y,取较大者;再与 z 比较,最终得到最大值
    printf("x, y, z 中最大的值是:%d\n", largest); // 输出:15

    return 0;
}

        程序在 VS code 中的运行结果如下所示:


6 指针运算符

6.1 运算符说明

运算符类型描述
&地址运算符

当它作用于一个变量时,返回该变量的内存地址

语法:&variable。

*间接或解引用运算符

当它作用于一个指针时,返回指针所指向内存地址中存储的值

语法:*pointer。

6.2 案例演示

#include <stdio.h>

int main()
{
    int nurse = 22; // 定义一个整型变量 nurse,并赋值为 22
    int *ptr;       // 定义一个指针变量 ptr,用于存储地址
    int val;        // 定义一个变量 val,用于存储解引用后的值

    // 使用地址运算符 & 获取 nurse 的地址
    ptr = &nurse;                                // 将 nurse 的地址赋值给指针 ptr
    printf("nurse 的地址是:%p\n", (void *)ptr); // 输出 nurse 的地址

    // 使用解引用运算符 * 获取指针指向的值
    val = *ptr;                          // 解引用 ptr,获取 nurse 的值并赋值给 val
    printf("ptr 指向的值是:%d\n", val); // 输出 ptr 指向的值

    // 修改指针指向的值
    *ptr = 33;                                   // 通过解引用修改 nurse 的值
    printf("修改后,nurse 的值是:%d\n", nurse); // 输出修改后的 nurse 值

    return 0;
}

        程序在 VS code 中的运行结果如下所示:


7 符号运算符

7.1 运算符说明

运算符类型描述
-负号运算符

反转运算对象的符号,将其从正数变为负数,或从负数变为正数

语法:-expression。

+正号运算符

不改变运算对象的符号,通常用于显式表示正值

语法:+expression。

7.2 案例演示

#include <stdio.h>

int main()
{
    int a = 10;
    int b = -5;

    // 使用负号运算符
    int neg_a = -a; // 将 a 的符号取反,结果为 -10
    int neg_b = -b; // 将 b 的符号取反,结果为 5
    printf("负号运算符:\n");
    printf("-a = %d\n", neg_a); // 输出:-10
    printf("-b = %d\n", neg_b); // 输出:5

    // 使用正号运算符
    int pos_a = +a; // 不改变 a 的符号,结果仍为 10
    int pos_b = +b; // 不改变 b 的符号,结果仍为 -5
    printf("\n正号运算符:\n");
    printf("+a = %d\n", pos_a); // 输出:10
    printf("+b = %d\n", pos_b); // 输出:-5

    return 0;
}

        程序在 VS code 中的运行结果如下所示:


8 结构和联合运算符

        结构和联合使用一些运算符标识成员。

  • 成员运算符与结构和联合一起使用;
  • 间接成员运算符与指向结构或联合的指针一起使用。

8.1 成员运算符

运算符类型描述
.成员运算符

与结构名或联合名一起使用,用于访问结构或联合中的成员

语法:struct_or_union_name.member_name。

        成员运算符 (.) 主要用于直接访问结构体或联合体中定义的成员变量。当你有一个结构体或联合体的实例时,可以通过这个运算符来访问其内部的成员。 如下代码所示:

#include <stdio.h>

int main()
{
    // 定义一个结构体模板
    struct Product
    {
        int code;   // 商品编号
        float cost; // 商品价格
    };

    // 定义结构体变量
    struct Product item;

    // 使用成员运算符 (.)访问并修改结构体 item 中的成员
    item.code = 1265;  // 将值 1265 赋给结构体变量 item 的成员 code
    item.cost = 99.99; // 将值 99.99 赋给结构体变量 item 的成员 cost

    printf("商品编号: %d, 商品价格: %.2f\n", item.code, item.cost);

    return 0;
}

        程序在 VS code 中的运行结果如下所示:

8.2 间接成员运算符

运算符类型描述
->

间接成员运算符

结构指针运算符

与一个指向结构或联合的指针一起使用,用于通过指向结构或联合的指针访问成员

语法:pointer_to_struct_or_union->member_name。

        间接成员运算符 (->) 用于通过指向结构体或联合体的指针访问其成员变量。当你有一个指向结构体或联合体的指针时,使用这个运算符可以方便地访问该结构体或联合体中的成员。如下代码所示:

#include <stdio.h>

int main()
{
    // 定义一个结构体模板
    struct Product
    {
        int code;   // 商品编号
        float cost; // 商品价格
    };

    // 定义结构体变量和指针
    struct Product item;
    struct Product *ptrst;

    // 使用间接成员运算符 (->)
    ptrst = &item;        // 将指针指向结构体变量 item
    ptrst->code = 3451;   // 使用间接成员运算符访问结构体成员 code
    ptrst->cost = 199.99; // 使用间接成员运算符访问结构体成员 cost

    printf("商品编号: %d, 商品价格: %.2f\n", ptrst->code, ptrst->cost);

    // 等效写法验证
    (*ptrst).code = 5678;   // 使用解引用运算符和成员运算符访问并修改成员 code
    (*ptrst).cost = 299.99; // 使用解引用运算符和成员运算符访问并修改成员 cost

    printf("\n等效写法验证:\n");
    printf("商品编号: %d, 商品价格: %.2f\n", item.code, item.cost);

    return 0;
}

        程序在 VS code 中的运行结果如下所示:

8.3 等效写法说明 

        在 C 语言中,结构体成员可以通过不同的方式访问,具体取决于你是直接操作结构体变量还是通过指针操作。如下代码所示:

#include <stdio.h>

int main()
{
    // 定义结构体模板
    struct Product
    {
        int code;
        float cost;
    };

    // 定义结构体变量和指针
    struct Product item;
    struct Product *ptrst;

    // 将指针指向结构体变量
    ptrst = &item;

    // 方法 1: 使用间接成员运算符 ->
    ptrst->code = 3451;
    printf("方法 1 (ptrst->code): %d\n", ptrst->code);

    // 方法 2: 使用成员运算符 .
    item.code = 5678;
    printf("方法 2 (item.code): %d\n", item.code);

    // 方法 3: 使用解引用运算符和成员运算符
    (*ptrst).code = 91011;
    printf("方法 3 ((*ptrst).code): %d\n", (*ptrst).code);

    return 0;
}

        以上三种写法是等效的,最终效果都是将值赋给结构体 item 的成员 code。 

        程序在 VS code 中的运行结果如下所示:

8.3.1 ptrst->code

  • 使用间接成员运算符 -> 访问指针所指向的结构体成员
  • 这是最常见的方式,尤其当使用指针时非常直观。
ptrst->code = 3451;
// ptrst 是一个指向结构体 item 的指针
// ptrst->code 表示通过指针访问 item 的成员 code

8.3.2 item.code

  • 使用成员运算符 . 直接访问结构体变量的成员
  • 这种方式适用于你已经有一个结构体实例(而不是指针)的情况。
item.code = 3451;
// item 是一个结构体变量,item.code 表示直接访问 item 的成员 code

8.3.3 (*ptrst).code

  • 使用解引用运算符 * 和成员运算符 . 组合访问结构体成员
  • 这种方式显式地解引用指针,然后访问结构体成员。
(*ptrst).code = 3451;
// *ptrst 表示解引用指针 ptrst,得到它指向的结构体变量(即 item)
// 然后通过成员运算符 . 访问成员 code

8.3.4 等效性分析

        这三种写法之所以等效,是因为它们最终都访问了同一个内存区域——即结构体 item 中的 code 成员。让我们逐步分析它们的关系:

  1. ptrst->code 和 (*ptrst).code 的等效性

    • 在 C 语言中,ptrst->code 实际上是 (*ptrst).code 的语法糖。
    • 编译器会将 ptrst->code 自动转换为 (*ptrst).code
    • 因此,这两种写法完全等效,只是 -> 更简洁、更常用。
  2. (*ptrst).code 和 item.code 的等效性

    • 根据代码 ptrst = &item;,我们知道 ptrst 指向结构体变量 item。
    • 解引用指针 *ptrst 得到的就是 item 本身
    • 因此,(*ptrst).code 等价于 item.code。
  3. 总结等效关系

    • ptrst->code  ⇔  (*ptrst).code  ⇔  item.code
    • 它们最终都修改了 item 结构体中的 code 成员。

扩展:

        语法糖(Syntactic Sugar) 是编程语言中的一种术语,指的是某些语法设计上的简化或便利写法,目的是让代码更易读、更简洁,而不会改变程序的功能或语义。换句话说,语法糖是一种对已有功能的 “美化” 或 “简化”,但其底层实现和效果是完全等价的。

总结:

  • ptrst->code:最简洁的方式,推荐用于指针操作。
  • item.code:直接访问结构体成员,适用于非指针场景。
  • (*ptrst).code:显式解引用指针后访问成员,本质上与 ptrst->code 等效。

9 按位运算符

9.1 运算符说明

运算符类型描述
~按位取反

翻转运算对象的每一位(0 变 1,1 变 0)得到一个值

语法:~expression。

&按位与

对两个运算对象的对应位进行逻辑与操作,只有当两个位都为 1 时,结果位才为 1(有 0 为 0)

语法:expression1 & expression2。

|按位或

对两个运算对象的对应位进行逻辑或操作,只要有一个位为 1,结果位就为 1(有 1 为 1)

语法:expression1 | expression2。

^按位异或

对两个运算对象的对应位进行异或操作,只有当两个位中只有一个为 1 时,结果位才为 1(相同位为 0,不同位为 1)

语法:expression1 ^ expression2。

<<左移运算符

将运算对象的二进制位向左移动指定的位数,右侧空出的位用 0 填充

数学意义:左移 n 位相当于将数值乘以 2^n

语法:expression << n。

注意事项:

        左移可能会导致溢出(超出数据类型的最大范围)
        对于负数,结果可能依赖于具体的编译器实现

>>右移运算符

将运算对象的二进制位向右移动指定的位数,左侧空出的位用 0 填充(对于无符号整数或正整数)

数学意义:右移 n 位相当于将数值除以 2^n 并向下取整

语法:expression >> n。

注意事项:

        对于负数,右移的结果可能依赖于具体实现(算术右移或逻辑右移)

                算术右移:算术右移保留符号位(最高位),即用符号位填充高位,用于有符号整数
                逻辑右移:逻辑右移不保留符号位,高位总是补 0,用于无符号整数
        如果是无符号整数,则始终使用逻辑右移

9.2 案例演示

#include <stdio.h>

int main()
{
    int x = 2; // 二进制表示为 0000 0010
    int y = 3; // 二进制表示为 0000 0011

    // 按位取反 (~):0 变 1,1 变 0
    int not_x = ~x;
    // 二进制位:0000 0010
    // 操作:~
    // 结果:    1111 1101(此补码表示一个负数)
    // 我们对这个补码 1111 1101 进行按位取反,末尾加一
    // 得到 0000 0011,即为该负数对应的绝对值数:3
    // 所以,原补码表示的数据就是 -3
    printf("~x 的值为:%d\n", not_x); // 输出:-3(二进制补码表示:1111 1101)

    // 按位与 (&):有 0 为 0
    int and_result = x & y;
    //   0000 0010
    // &
    //   0000 0011
    //   0000 0010,这是 2 的二进制表示
    printf("x & y 的值为:%d\n", and_result); // 输出:2(二进制:0000 0010)

    // 按位或 (|)
    int or_result = x | y;
    //   0000 0010
    // |
    //   0000 0011
    //   0000 0011,这是 3 的二进制表示
    printf("x | y 的值为:%d\n", or_result); // 输出:3(二进制:0000 0011)

    // 按位异或 (^)
    int xor_result = x ^ y;
    //   0000 0010
    // ^
    //   0000 0011
    //   0000 0001,这是 1 的二进制表示
    printf("x ^ y 的值为:%d\n", xor_result); // 输出:1(二进制:0000 0001)

    // 左移运算符 (<<)
    int left_shift_result = y << x;
    //   0000 0011
    // << 2
    //   0000 1100,这是 12 的二进制表示
    printf("y << x 的值为:%d\n", left_shift_result); // 输出:12(二进制:0000 1100)

    // 右移运算符 (>>)
    int right_shift_result = y >> x;
    //   0000 0011
    // >> 2
    //   0000 0000,这是 0 的二进制表示
    printf("y >> x 的值为:%d\n", right_shift_result); // 输出:0(二进制:0000 0000)

    return 0;
}

        程序在 VS code 中的运行结果如下所示:

9.3 左移和右移运算符特性

        在 C 语言中,左移运算符 (<<) 和 右移运算符 (>>) 是按位运算符,它们通过对二进制位的移动来实现高效的数据操作。

9.3.1 数学意义

        左移 n 位相当于对数值乘以 2^n

#include <stdio.h>

int main()
{
    unsigned int num = 5; // 5 的 32 位二进制表示为 0000 0000 0000 0000 0000 0000 0000 0101
    int shift = 2;        // 需要左移的位数

    // 左移操作
    unsigned int result = num << shift; // 将 num 的二进制位向左移动 2 位
    printf("原始值: %u (二进制: ", num);
    for (int i = 31; i >= 0; i--)
    {
        // 将 num 向右移动 i 位,使第 i 位移到最低位
        // 对结果进行按位与操作,提取最低位(0 或 1)
        printf("%d", (num >> i) & 1); // 打印每一位
    }
    printf(")\n");

    printf("左移 %d 位后: %u (二进制: ", shift, result);
    for (int i = 31; i >= 0; i--)
    {
        printf("%d", (result >> i) & 1); // 打印每一位
    }
    printf(")\n");

    // 数学验证
    // 左移 shift 位等价于乘以 2^shift
    printf("数学验证: %u * 2^%d = %u\n", num, shift, num * (1 << shift));

    return 0;
}

        程序在 VS code 中的运行结果如下所示:

        右移 n 位相当于对数值除以 2^n 并向下取整(对于无符号整数或正整数)

#include <stdio.h>

int main()
{
    unsigned int num = 20; // 20 的 32 位二进制表示为 0000 0000 0000 0000 0000 0000 0001 0100
    int shift = 2;         // 需要右移的位数

    // 右移操作
    unsigned int result = num >> shift; // 将 num 的二进制位向右移动 2 位
    printf("原始值: %u (二进制: ", num);
    for (int i = 31; i >= 0; i--)
    {
        // 将 num 向右移动 i 位,使第 i 位移到最低位
        // 对结果进行按位与操作,提取最低位(0 或 1)
        printf("%d", (num >> i) & 1); // 打印每一位
    }
    printf(")\n");

    printf("右移 %d 位后: %u (二进制: ", shift, result);
    for (int i = 31; i >= 0; i--)
    {
        printf("%d", (result >> i) & 1); // 打印每一位
    }
    printf(")\n");

    // 数学验证
    // 左移 shift 位等价于乘以 2^shift
    printf("数学验证: %u / 2^%d = %u\n", num, shift, num / (1 << shift));

    return 0;
}

        程序在 VS code 中的运行结果如下所示:

9.3.2 注意事项与常见问题

  • 溢出问题

    • 左移可能导致数值超出数据类型的范围。例如,对于 unsigned int 类型,左移过多位会导致高位丢失。
  • 负数的右移

    • 对于负数,右移的行为可能因编译器不同而异:
      • 算术右移:算术右移保留符号位(最高位),即用符号位填充高位,用于有符号整数
        • 如果整数是正数,高位补 0,因为符号位是 0。
        • 如果整数是负数,高位补 1,因为符号位是 1。
        • int num = -8; // 二进制: 1111 1111 1111 1111 1111 1111 1111 1000 (假设 32 位)
          int result = num >> 2; // 右移 2位: 1111 1111 1111 1111 1111 1111 1111 1110
          // result 为 -2,因为高位补 1,保持符号。
      • 逻辑右移:逻辑右移不保留符号位,高位总是补 0,用于无符号整数。在某些编译器和硬件平台上,有符号整数也可能执行逻辑右移。
        • 无论整数是正数还是负数,高位总是补 0。
        • unsigned int num = 8; // 二进制: 0000 0000 0000 0000 0000 0000 0000 1000
          unsigned int result = num >> 2; // 右移 2 位: 0000 0000 0000 0000 0000 0000 0000 0010
          // result 为 2,因为高位补 0。
  • 适用于无符号整数

    • 左移和右移运算符在处理无符号整数时最为直观和可靠。

总结:

  • 左移 (<<) 相当于对数值乘以 2^n,是一种高效的乘法操作。
  • 右移 (>>) 相当于对数值除以 2^n 并向下取整,是一种高效的除法操作。
  • 在 C 语言标准中,右移操作的行为对于有符号整数是实现定义的,这意味着不同的编译器和硬件平台可能会有不同的实现方式:
    • 一些平台对有符号整数执行算术右移。
    • 一些平台对有符号整数执行逻辑右移。
  • 对于无符号整数,C 语言标准要求执行逻辑右移
  • 在实际编程中,左移和右移常用于优化性能,尤其是在嵌入式开发和底层编程中。

10 混合运算符

10.1 大小运算符(sizeof)

  • 用途:sizeof 是一个编译时运算符用于获取数据类型或变量在内存中所占的字节数。单位是 char 的大小,通常,char 类型的大小是 1 字节。
  • 语法
    • sizeof(type):返回指定类型的大小。
    • sizeof(expression) :返回表达式的大小。
    • sizeof 字面量/变量名/数组名 :返回字面量/变量名/数组名的大小。
  • 括号的可选性
    • 计算基本数据类型的大小,必须使用括号将数据类型关键字包裹起来。

    • 对于字面量和变量,sizeof 运算符可以直接作用于它们,括号是可选的,可以省略括号

    • 一般建议在使用 sizeof 运算符时主动加上后面的小括号,这样能使代码更加清晰明了。

  • 返回值类型size_t,这是一个无符号整数类型,格式占位符可用 %zu
#include <stdio.h>

int main()
{
    // 计算基本数据类型的大小,必须使用括号将数据类型关键字包裹起来
    printf("char:%zu \n", sizeof(char));               // char:1,表示 char 类型占用 1 个字节
    printf("short:%zu \n", sizeof(short));             // short:2 ,表示 short 类型占用 2 个字节
    printf("int:%zu \n", sizeof(int));                 // int:4 ,但这也可能因编译器和平台而异
    printf("long:%zu \n", sizeof(long));               // long:4,但这也可能因编译器和平台而异
    printf("long long:%zu \n", sizeof(long long));     // long long:8,表示 long long 类型占用 8 个字节
    printf("float:%zu \n", sizeof(float));             // float:4 ,表示 float 类型占用 4 个字节
    printf("double:%zu \n", sizeof(double));           // double:8 ,表示 double 类型占用 8 个字节
    printf("long double:%zu \n", sizeof(long double)); // long double:16 ,但这也可能因编译器和平台而异
    printf("\n");

    // 计算字面量数据的大小
    // 对于字面量,sizeof 运算符可以直接作用于它们,括号是可选的,可以省略括号
    // 字符字面量(如'a')在大多数情况下被视为 int 类型(但 sizeof 会返回其实际存储所需的大小)
    // 整数和浮点数字面量的大小取决于它们被解释为的类型
    printf("%zu \n", sizeof('a')); // 输出为 4,因为字符字面量被隐式提升为 int
    printf("%zu \n", sizeof(431)); // 输出为 4,因为 431 是 int 类型的字面量
    printf("%zu \n", sizeof 4.31); // 输出为 8,因为 4.31 默认为 double 类型的字面量
    printf("\n");

    // 计算变量的大小
    // 对于变量,sizeof 运算符同样可以直接作用于它们,括号是可选的,可以省略括号
    char a = 'A';
    int b = 90;
    long long c = 100;
    double d = 10.8;
    printf("a: %zu \n", sizeof(a)); // 输出为 1,表示 char 变量 a 占用 1 个字节
    printf("b: %zu \n", sizeof b);  // 输出为 4,表示 int 变量 b 占用 4 个字节
    printf("c: %zu \n", sizeof c);  // 输出为 8,表示 long long 变量 c 占用 8 个字节
    printf("d: %zu \n", sizeof(d)); // 输出为 8,表示 double 变量 d 占用 8 个字节
    printf("\n");

    // 获取数组的大小
    int arr[5] = {1, 2, 3, 4, 5};
    printf("sizeof(arr): %zu 字节\n", sizeof(arr));       // 输出数组 arr 的总大小
    printf("sizeof(arr[0]): %zu 字节\n", sizeof(arr[0])); // 输出数组第一个元素的大小

    return 0;
}

        程序在 VS code 中的运行结果如下所示:

10.2 对齐要求运算符(_Alignof)

  • 用途:_Alignof 是 C11 标准引入的一个运算符,用于获取数据类型的对齐要求。对齐要求是指该类型的数据需要存储在内存地址的某个倍数上。(一些系统要求以特定值的倍数在地址上储存特定类型,如 4 的倍数。这个整数就是对齐要求。)
  • 语法
    • _Alignof(type):返回指定类型的对齐要求。
  • 返回值类型size_t,这是一个无符号整数类型,格式占位符可用 %zu
#include <stdio.h>

int main()
{
    // 获取基本数据类型的对齐要求
    printf("_Alignof(char): %zu 字节\n", _Alignof(char));     // 输出 char 类型的对齐要求
    printf("_Alignof(int): %zu 字节\n", _Alignof(int));       // 输出 int 类型的对齐要求
    printf("_Alignof(float): %zu 字节\n", _Alignof(float));   // 输出 float 类型的对齐要求
    printf("_Alignof(double): %zu 字节\n", _Alignof(double)); // 输出 double 类型的对齐要求

    return 0;
}

        程序在 VS code 中的运行结果如下所示:

10.3 强制类型转换运算符(type)

  • 用途:强制类型转换运算符 (type) 用于将一种类型的值显式地转换为另一种类型。需要注意的是,这种转换可能会导致精度损失或溢出
  • 语法
    • (type)expression:将表达式转换为圆括号中关键字指定的类型。
#include <stdio.h>

int main()
{
    // 整数到浮点数的转换
    int num = 42;
    float float_num = (float)num; // 将整数 42 转换为浮点数 42.0
    printf("强制类型转换: %d -> %.1f\n", num, float_num);

    // 浮点数到整数的转换
    float f = 3.14f;
    int int_f = (int)f; // 将浮点数 3.14 转换为整数 3
    printf("强制类型转换: %.2f -> %d\n", f, int_f);

    // 指针转换(不推荐)
    int *ptr = &num;
    void *void_ptr = (void *)ptr;           // 将指针转换为 void* 类型
    int *back_to_int_ptr = (int *)void_ptr; // 将 void* 指针转换回 int* 类型
    printf("指针转换: ptr = %p, back_to_int_ptr = %p\n", (void *)ptr, (void *)back_to_int_ptr);

    return 0;
}

        程序在 VS code 中的运行结果如下所示: 

10.4 逗号运算符(,)

  • 用途:逗号运算符 , 它把两个表达式链接成一个表达式,并保证先对最左端的表达式求值整个表达式的值是最右边表达式的值。该运算符通常在 for 循环头中用于包含更多的信息。
  • 语法
    • expression1, expression2:先计算 expression1,然后计算 expression2,整个表达式的值为 expression2 的值。
#include <stdio.h>

int main()
{
    // 简单的逗号运算符示例
    int a = (1, 2); // 计算 1 和 2,结果为 2
    printf("a = %d\n", a);

    // 在 for 循环中使用逗号运算符
    int step, fargo;
    for (step = 2, fargo = 0; fargo < 1000; step *= 2)
    {
        fargo += step;
        printf("循环过程中: step = %d, fargo = %d\n", step, fargo);
    }
    printf("循环结束: step = %d, fargo = %d\n", step, fargo);

    return 0;
}

        程序在 VS code 中的运行结果如下所示:


http://www.kler.cn/a/599165.html

相关文章:

  • 多线程编程
  • 模糊数学 | 模型 / 集合 / 关系 / 矩阵
  • endnote相关资料记录
  • V8引擎源码编译踩坑实录
  • vue3 如何清空 let arr = reactive([])
  • React Native集成到现有原生Android应用
  • WebGPU实战:Three.js性能优化新纪元
  • SpringMVC请求和响应
  • 练习题:101
  • 腾讯云大模型知识引擎x deepseek:打造智能服装搭配新体验
  • 详解Spark executor
  • vue中keep-alive组件的使用
  • DeepSeek 助力 Vue3 开发:打造丝滑的表格(Table)之添加导出数据功能示例14,TableView15_14多功能组合的导出表格示例
  • C++——权限初识
  • 炫酷的3D卡片翻转画廊实现教程
  • 使用ES支持树状结构查询实战
  • 蓝桥杯 - 中等 - 智能停车系统
  • 大数据(2)Hadoop架构深度拆解:HDFS与MapReduce企业级实战与高阶调优
  • 《TCP/IP网络编程》学习笔记 | Chapter 21:异步通知 I/O 模型
  • MQ 消息幂等性保证