c/docs/notes/01_c-basic/07_xdx/index.md

23 KiB
Raw Blame History

第一章:相关概念

1.1 运算符、表达式和操作数

  • 运算符是一种特殊的符号,用于数据的运算、赋值和比较等。
  • 表达式指的是一组运算数、运算符的组合,表达式一定具有值,一个变量或一个常量可以是表达式,变量、常量和运算符也可以组成表达式,如:

  • 操作数指的是参与运算或者对象,如:

1.2 运算符的分类

  • 根据操作数个数,可以将运算符分为:
    • 一元运算符(一目运算符)。
    • 二元运算符(二目运算符)。
    • 三元运算符(三目运算符)。
  • 根据功能,可以将运算符分为:
    • 算术运算符。
    • 关系运算符(比较运算符)。
    • 逻辑运算符。
    • 赋值运算符。
    • 逻辑运算符。
    • 位运算符。
    • 三元运算符。

Note

掌握一个运算符,需要关注以下几个方面:

  • ① 运算符的含义。
  • ② 运算符操作数的个数。
  • ③ 运算符所组成的表达式。
  • ④ 运算符有无副作用,即:运算后是否会修改操作数的值。

第二章:算术运算符(

2.1 概述

  • 算术运算符是对数值类型的变量进行运算的,如下所示:
运算符 描述 操作数个数 组成的表达式的值 副作用
+ 正号 1 操作数本身
- 负号 1 操作数符号取反
+ 加号 2 两个操作数之和
- 减号 2 两个操作数之差
* 乘号 2 两个操作数之积
/ 除号 2 两个操作数之商
% 取模(取余) 2 两个操作数相除的余数
++ 自增 1 操作数自增前或自增后的值
-- 自减 1 操作数自减前或自减后的值

Note

自增和自减:

  • ① 自增、自减运算符可以写在操作数的前面也可以写在操作数后面,不论前面还是后面,对操作数的副作用是一致的。
  • ② 自增、自减运算符在前在后,对于表达式的值是不同的。 如果运算符在前,表达式的值是操作数自增、自减之后的值;如果运算符在后,表达式的值是操作数自增、自减之前的值。
  • 变量前++:变量先自增 1 ,然后再运算;变量后++:变量先运算,然后再自增 1 。
  • 变量前--:变量先自减 1 ,然后再运算;变量后--:变量先运算,然后再自减 1 。
  • ⑤ 对于 i++i-- 各种编程语言的用法和支持是不同的例如C/C++、Java 等完全支持Python 压根一点都不支持Go 语言虽然支持 i++i-- ,却只支持这些操作符作为独立的语句,并且不能嵌入在其它的表达式中。

2.2 应用示例

  • 示例:正号和负号
#include <stdio.h>

int main() {

    int x  = 12;
    int x1 = -x, x2 = +x;

    int y  = -67;
    int y1 = -y, y2 = +y;

    printf("x1=%d, x2=%d \n", x1, x2); // x1=-12, x2=12
    printf("y1=%d, y2=%d \n", y1, y2); // y1=67, y2=-67

    return 0;
}
  • 示例:加、减、乘、除(整数之间做除法时,结果只保留整数部分而舍弃小数部分)、取模
#include <stdio.h>

int main() {

    int a = 5;
    int b = 2;

    printf("%d + %d = %d\n", a, b, a + b); // 5 + 2 = 7
    printf("%d - %d = %d\n", a, b, a - b); // 5 - 2 = 3
    printf("%d × %d = %d\n", a, b, a * b); // 5 × 2 = 10
    printf("%d / %d = %d\n", a, b, a / b); // 5 / 2 = 2
    printf("%d %% %d = %d\n", a, b, a % b); // 5 % 2 = 1

    return 0;
}
  • 示例:取模(运算结果的符号与被模数也就是第一个操作数相同。)
#include <stdio.h>

int main() {

    int res1 = 10 % 3;
    printf("10 %% 3 = %d\n", res1); // 10 % 3 = 1

    int res2 = -10 % 3;
    printf("-10 %% 3 = %d\n", res2); // -10 % 3 = -1

    int res3 = 10 % -3;
    printf("10 %% -3 = %d\n", res3); // 10 % -3 = 1

    int res4 = -10 % -3;
    printf("-10 %% -3 = %d\n", res4); // -10 % -3 = -1

    return 0;
}
  • 示例:自增和自减
#include <stdio.h>

int main() {

    int i1 = 10, i2 = 20;
    int i  = i1++;
    printf("i = %d\n", i); // i = 10
    printf("i1 = %d\n", i1); // i1 = 11

    i = ++i1;
    printf("i = %d\n", i); // i = 12
    printf("i1 = %d\n", i1); // i1 = 12

    i = i2--;
    printf("i = %d\n", i); // i = 20
    printf("i2 = %d\n", i2); // i2 = 19

    i = --i2;
    printf("i = %d\n", i); // i = 18
    printf("i2 = %d\n", i2); // i2 = 18

    return 0;

  • 示例:
#include <stdio.h>

/*
  随意给出一个整数,打印显示它的个位数,十位数,百位数的值。
  格式如下:
    数字xxx的情况如下
    个位数:
    十位数:
    百位数:
  例如:
    数字153的情况如下
    个位数3
    十位数5
    百位数1
 */
int main() {

    int num = 153;

    int bai = num / 100;
    int shi = num % 100 / 10;
    int ge  = num % 10;
    printf("百位为:%d \n", bai);
    printf("十位为:%d \n", shi);
    printf("个位为:%d \n", ge);

    return 0;
}

第三章:关系运算符(

3.1 概述

  • 常见的关系运算符(比较运算符),如下所示:
运算符 描述 操作数个数 组成的表达式的值 副作用
== 相等 2 0 或 1
!= 不相等 2 0 或 1
< 小于 2 0 或 1
> 大于 2 0 或 1
<= 小于等于 2 0 或 1
>= 大于等于 2 0 或 1

Note

  • ① C 语言中,没有严格意义上的布尔类型,可以使用 0 或 1表示布尔类型的值。
  • ② 不要将 == 写成 === 是比较运算符,而 = 是赋值运算符。
  • >=<=含义是只需要满足 大于或等于小于或等于其中一个条件,结果就返回真。
  • 示例:
#include <stdio.h>

int main() {

    int a = 8;
    int b = 7;

    printf("a > b 的结果是:%d \n", a > b); // a > b 的结果是1
    printf("a >= b 的结果是:%d \n", a >= b); // a >= b 的结果是1
    printf("a < b 的结果是:%d \n", a < b); // a < b 的结果是0
    printf("a <= b 的结果是:%d \n", a <= b); // a <= b 的结果是0
    printf("a == b 的结果是:%d \n", a == b); // a == b 的结果是0
    printf("a != b 的结果是:%d \n", a != b); // a != b 的结果是1

    return 0;
}

第四章:逻辑运算符(

4.1 概述

  • 常见的逻辑运算符,如下所示:
运算符 描述 操作数个数 组成的表达式的值 副作用
&& 逻辑与 2 0 或 1
|| 逻辑或 2 0 或 1
! 逻辑非 2 0 或 1
  • 逻辑运算符提供逻辑判断功能,用于构建更复杂的表达式,如下所示:
a b a && b a || b !a
1 1 1 1 0
1 0 0 1 0
0 1 0 1 1
0 0 0 0 1

Note

  • ① 对于逻辑运算符来说,任何非零值都表示零值表示,如:5 || 0 返回 1 5 && 0 返回 0
  • ② 逻辑运算符的理解:
    • && 的理解就是:两边条件,同时满足
    • ||的理解就是:两边条件,二选一
    • ! 的理解就是:条件取反
  • ③ 短路现象:
    • 对于 a && b 操作来说,当 a 为假(或 0 )时,因为 a && b 结果必定为 0所以不再执行表达式 b。
    • 对于 a || b 操作来说,当 a 为真(或非 0 )时,因为 a || b 结果必定为 1所以不再执行表达式 b。

4.2 应用示例

  • 示例:
#include <stdio.h>

int main() {

    int a = 0;
    int b = 0;

    printf("请输入整数a的值");
    scanf("%d", &a);
    printf("请输入整数b的值");
    scanf("%d", &b);

    if (a > b) {
        printf("%d > %d", a, b);
    } else if (a < b) {
        printf("%d < %d", a, b);
    } else {
        printf("%d = %d", a, b);
    }

    return 0;
}
  • 示例:
#include <stdio.h>

// 短路现象
int main() {

    int i = 0;
    int j = 10;
    if (i && j++ > 0) {
        printf("床前明月光\n"); // 这行代码不会执行
    } else {
        printf("我叫郭德纲\n");
    }
    printf("%d \n", j); //10

    return 0;
}
  • 示例:
#include <stdio.h>

// 短路现象

int main() {

    int i = 1;
    int j = 10;
    if (i || j++ > 0) {
        printf("床前明月光 \n");
    } else {
        printf("我叫郭德纲 \n"); // 这行代码不会被执行
    }
    printf("%d\n", j); //10

    return 0;
}

第五章:赋值运算符(

5.1 概述

  • 常见的赋值运算符,如下所示:
运算符 描述 操作数个数 组成的表达式的值 副作用
== 赋值 2 左边操作数的值
+= 相加赋值 2 左边操作数的值
-= 相减赋值 2 左边操作数的值
*= 相乘赋值 2 左边操作数的值
/= 相除赋值 2 左边操作数的值
%= 取余赋值 2 左边操作数的值
<<= 左移赋值 2 左边操作数的值
>>= 右移赋值 2 左边操作数的值
&= 按位与赋值 2 左边操作数的值
^= 按位异或赋值 2 左边操作数的值
|= 按位或赋值 2 左边操作数的值

Note

  • ① 赋值运算符的第一个操作数(左值)必须是变量的形式,第二个操作数可以是任何形式的表达式。
  • ② 赋值运算符的副作用针对第一个操作数。

5.2 应用示例

  • 示例:
#include <stdio.h>

int main() {

    int a = 3;
    a += 3; // a = a + 3
    printf("a = %d\n", a); // a = 6

    int b = 3;
    b -= 3; // b = b - 3
    printf("b = %d\n", b); // b = 0

    int c = 3;
    c *= 3; // c = c * 3
    printf("c = %d\n", c); // c = 9

    int d = 3;
    d /= 3; // d = d / 3
    printf("d = %d\n", d); // d = 1

    int e = 3;
    e %= 3; // e = e % 3
    printf("e = %d\n", e); // e = 0

    return 0;
}

第六章:位运算符(了解)

6.1 概述

  • C 语言提供了一些位运算符能够让我们操作二进制位bit
  • 常见的位运算符,如下所示。
运算符 描述 操作数个数 运算规则 副作用
& 按位与 2 两个二进制位都为 1 ,结果为 1 ,否则为 0 。
| 按位或 2 两个二进制位只要有一个为 1包含两个都为 1 的情况),结果为 1 ,否则为 0 。
^ 按位异或 2 两个二进制位一个为 0 ,一个为 1 ,结果为 1否则为 0 。
~ 按位取反 2 将每一个二进制位变成相反值,即 0 变成 1 1 变 成 0 。
<< 二进制左移 2 将一个数的各二进制位全部左移指定的位数,左 边的二进制位丢弃,右边补 0。
>> 二进制右移 2 将一个数的各二进制位全部右移指定的位数,正数左补 0负数左补 1右边丢弃。

Note

操作数在进行位运算的时候,以它的补码形式计算!!!

6.2 输出二进制位

  • 在 C 语言中,printf 是没有提供输出二进制位的格式占位符的;但是,我们可以手动实现,以方便后期操作。

  • 示例:

#include <stdio.h>

/**
 * 获取指定整数的二进制表示
 * @param num 整数
 * @return 二进制表示的字符串,不包括前导的 '0b' 字符
 */
char* getBinary(int num) {
    static char binaryString[33];
    int         i, j;

    for (i = sizeof(num) * 8 - 1, j = 0; i >= 0; i--, j++) {
        const int bit   = (num >> i) & 1;
        binaryString[j] = bit + '0';
    }

    binaryString[j] = '\0';
    return binaryString;
}

int main() {

    int a = 17;
    int b = -12;

    printf("整数 %d 的二进制表示:%s \n", a, getBinary(a));
    printf("整数 %d 的二进制表示:%s \n", b, getBinary(b));

    return 0;
}

6.3 按位与

  • 按位与 & 的运算规则是:如果二进制对应的位上都是 1 才是 1 ,否则为 0 ,即:

    • 1 & 1 的结果是 1
    • 1 & 0 的结果是 0
    • 0 & 1 的结果是 0
    • 0 & 0 的结果是 0
  • 示例:9 & 7 = 1

  • 示例:-9 & 7 = 7

6.4 按位或

  • 按位与 | 的运算规则是:如果二进制对应的位上只要有 1 就是 1 ,否则为 0 ,即:

    • 1 | 1 的结果是 1
    • 1 | 0 的结果是 1
    • 0 | 1 的结果是 1
    • 0 | 0 的结果是 0
  • 示例:9 | 7 = 15

  • 示例:-9 | 7 = -9

6.5 按位异或

  • 按位与 ^ 的运算规则是:如果二进制对应的位上一个为 1 一个为 0 就为 1 ,否则为 0 ,即:
    • 1 ^ 1 的结果是 0
    • 1 ^ 0 的结果是 1
    • 0 ^ 1 的结果是 1
    • 0 ^ 0 的结果是 0

Note

按位异或的场景有:

  • ① 交换两个数值:异或操作可以在不使用临时变量的情况下交换两个变量的值。
  • ② 加密或解密:异或操作用于简单的加密和解密算法。
  • ③ 错误检测和校正异或操作可以用于奇偶校验位的计算和检测错误RAID-3 以及以上)。
  • ……
  • 示例:9 ^ 7 = 14

  • 示例:-9 ^ 7 = -16

6.6 按位取反

  • 运算规则:如果二进制对应的位上是 1则结果为 0如果是 0 ,则结果为 1 。

    • ~0 的结果是 1
    • ~1 的结果是 0
  • 示例:~9 = -10

  • 示例:~-9 = 8

6.7 二进制左移

  • 在一定范围内,数据每向左移动一位,相当于原数据 × 2。正数、负数都适用

  • 示例:3 << 4 = 48 3 × 2^4

  • 示例:-3 << 4 = -48 -3 × 2 ^4

6.8 二进制右移

  • 在一定范围内,数据每向右移动一位,相当于原数据 ÷ 2。正数、负数都适用

Note

  • ① 如果不能整除,则向下取整。
  • ② 右移运算符最好只用于无符号整数,不要用于负数。因为不同系统对于右移后如何处理负数的符号位,有不同的做法,可能会得到不一样的结果。
  • 示例:69 >> 4 = 4 69 ÷ 2^4

  • 示例:-69 >> 4 = -5 -69 ÷ 2^4

第七章:三元运算符(

7.1 概述

  • 语法:
条件表达式 ? 表达式1 : 表达式2 ;

Note

  • 如果条件表达式为非 0 (真),则整个表达式的值是表达式 1 。
  • 如果条件表达式为 0 (假),则整个表达式的值是表达式 2 。

7.2 应用示例

  • 示例:
#include <stdio.h>

int main() {

    int m      = 110;
    int n      = 20;
    int result = m > n ? m : n;
    printf("result = %d\n", result); // result = 110

    return 0;
}

第八章:运算符的优先级和结合性(

7.1 概述

  • 在数学中,如果一个表达式是 a + b * c ,我们知道其运算规则就是:先算乘除再算加减。其实,在 C 语言中也是一样的先算乘法再算加减C 语言中乘除的运算符比加减的运算符的优先级要高。

7.2 优先级和结合性

  • 优先级结合性的定义,如下所示:
    • ① 所谓的优先级:就是当多个运算符出现在同一个表达式中时,先执行哪个运算符。
    • ② 所谓的结合性:就是当多个相同优先级的运算符出现在同一个表达式中的时候,是从左到右运算,还是从右到左运算。
      • 左结合性:具有相同优先级的运算符将从左到右➡️)进行计算。
      • 右结合性:具有相同优先级的运算符将从右到左⬅️)进行计算。

Note

技巧:先看优先级;如果优先级相同,再看结合性

  • C 语言中运算符的优先级有几十个,有的运算符优先级不同,有的运算符优先级相同,如下所示:
优先级 运算符 名称或含义 结合方向
0 () 小括号,最高优先级 ➡️(从左到右)
1 ++-- 后缀自增和自减,如:i++i-- ➡️(从左到右)
() 小括号,函数调用,如:sum(1,2)
[] 数组下标,如:arr[0]arr[1]
. 结构体或共用体成员访问
-> 结构体或共用体成员通过指针访问
2 ++-- 前缀自增和自减,如:++i--i ⬅️(从右到左)
+ 一元加运算符,表示操作数的正,如:+2
- 一元减运算符,表示操作数的负,如:-3
! 逻辑非运算符(逻辑运算符)
~ 按位取反运算符(位运算符)
typename 强制类型转换
* 解引用运算符
& 取地址运算符
sizeof 取大小运算符
3 / 除法运算符(算术运算符) ➡️(从左到右)
* 乘法运算符(算术运算符)
% 取模(取余)运算符(算术运算符)
4 + 二元加运算符(算术运算符),如:2 + 3 ➡️(从左到右)
- 二元减运算符(算术运算符),如:3 - 2
5 << 左移位运算符(位运算符) ➡️(从左到右)
>> 右移位运算符(位运算符)
6 > 大于运算符(比较运算符) ➡️(从左到右)
>= 大于等于运算符(比较运算符)
< 小于运算符(比较运算符)
<= 小于等于运算符(比较运算符)
7 == 等于运算符(比较运算符) ➡️(从左到右)
!= 不等于运算符(比较运算符)
8 & 按位与运算符(位运算符) ➡️(从左到右)
9 ^ 按位异或运算符(位运算符) ➡️(从左到右)
10 | 按位或运算符(位运算符) ➡️(从左到右)
11 && 逻辑与运算符(逻辑运算符) ➡️(从左到右)
12 || 逻辑或运算符(逻辑运算符) ➡️(从左到右)
13 ?: 三目(三元)运算符 ⬅️(从右到左)
14 = 简单赋值运算符(赋值运算符) ⬅️(从右到左)
/= 除后赋值运算符(赋值运算符)
*= 乘后赋值运算符(赋值运算符)
%= 取模后赋值运算符(赋值运算符)
+= 加后赋值运算符(赋值运算符)
-= 减后赋值运算符(赋值运算符)
<<= 左移后赋值运算符(赋值运算符)
>>= 右移后赋值运算符(赋值运算符)
&= 按位与后赋值运算符(赋值运算符)
^= 按位异或后赋值运算符(赋值运算符)
|= 按位或后赋值运算符(赋值运算符)
15 , 逗号运算符 ➡️(从左到右)

Warning

  • ① 不要过多的依赖运算符的优先级来控制表达式的执行顺序,这样可读性太差,尽量使用小括号来控制表达式的执行顺序。
  • ② 不要把一个表达式写得过于复杂,如果一个表达式过于复杂,则把它分成几步来完成。
  • ③ 运算符优先级不用刻意地去记忆,总体上:一元运算符 > 算术运算符 > 关系运算符 > 逻辑运算符 > 三元运算符 > 赋值运算符。