C语言课程笔记:进制转换、编码原理与数组内存分配全解

第一部分:进制转换、编码原理与位运算

1. 进制基础与互转方法

1.1 四种常用进制对照

进制前缀/标识基数数码范围C语言表示
二进制0b (C++14/C23)20, 10b1010
八进制080-7012
十进制100-910
十六进制0x / 0X160-9, A-F0xA

1.2 核心互转规则

⭐ 二 ↔ 八:三位一组
  • 二进制 → 八进制:从右向左每3位二进制对应1位八进制。
  • 八进制 → 二进制:每1位八进制展开为3位二进制。
二进制:  011  101  010
八进制:   3    5    2   → 0352

验证: 0352(八) = 3×64 + 5×8 + 2 = 234(十)
⭐ 二 ↔ 十六:四位一组
  • 二进制 → 十六进制:从右向左每4位二进制对应1位十六进制。
  • 十六进制 → 二进制:每1位十六进制展开为4位二进制。
二进制:  1111  1010  0011
十六进制:  F     A     3   → 0xFA3

验证: 0xFA3(十六) = 15×256 + 10×16 + 3 = 4003(十)
⭐ 十 → 二/八/十六:除基取余法
  • 反复除以目标基数,记录余数,逆序排列即为结果。
例: 234 → 二进制
234 ÷ 2 = 117 ... 0
117 ÷ 2 = 58  ... 1
58  ÷ 2 = 29  ... 0
29  ÷ 2 = 14  ... 1
14  ÷ 2 = 7   ... 0
7   ÷ 2 = 3   ... 1
3   ÷ 2 = 1   ... 1
1   ÷ 2 = 0   ... 1
逆序读取: 11101010 → 0xEA

⭐ 二/八/十六→十:按位数乘以进制的次幂
  • 核心规则:从右向左数位(最右为第0位),每位数码 × 基数^位序,结果累加。

    快速示例
    进制示例展开计算十进制结果
    二进制(R=2)110101101×2⁷+1×2⁶+1×2⁴+1×2²+1×2¹ = 128+64+16+4+2214
    八进制(R=8)3523×8²+5×8¹+2×8⁰ = 192+40+2234
    十六进制(R=16)0xFA315×16²+10×16¹+3×16⁰ = 3840+160+34003

⭐ 老师强调

  • 计算机内部只存储二进制,八进制和十六进制只是人类可读的“缩写”。
  • 十六进制最常用:1位十六进制 = 4位二进制,与字节(8bit)天然对齐,两个十六进制位恰好表示一个字节。
  • 调试内存、查看寄存器、网络协议分析时,几乎全部使用十六进制。

2. 原码、反码、补码

2.1 三种编码定义(以8位有符号整数为例)

编码正数规则负数规则示例(+5 / -5)
原码最高位符号位(0),其余为数值最高位符号位(1),其余为数值00000101 / 10000101
反码与原码相同符号位不变,数值位按位取反00000101 / 11111010
补码与原码相同反码 + 100000101 / 11111011

2.2 ⭐ 为什么计算机用补码?

  1. 统一加减法A - B 等价于 A + (-B的补码),硬件只需一个加法器。
  2. 消除±0歧义:原码中 +0=00000000-0=10000000;补码中0唯一表示为 00000000
  3. 多表示一个数:8位补码范围 -128 ~ +127,比原码多一个 -12810000000)。

2.3 补码运算示例

计算: 5 + (-3) = ?

  5 的补码:  00000101
-3 的补码:  11111101  (-3原码10000011 → 反码11111100 → +1 = 11111101)
─────────────────────
相加结果:   00000010  (进位1自然丢弃)

00000010 = +2 ✅ 结果正确

⭐ 老师强调

  • C语言中所有有符号整数都以补码形式存储和运算
  • 右移 >> 对有符号数是算术右移(高位补符号位),对无符号数是逻辑右移(高位补0)。
  • -1 的补码是全1(0xFFFFFFFF),这在位运算中经常作为掩码使用。
  • 溢出时补码运算仍然正确(模运算性质),但C标准中有符号溢出是未定义行为,实际依赖硬件补码实现。

第二部分:动态内存分配与指针进阶

4. 动态内存分配基础

4.1 静态数组 vs 动态数组

// 栈上静态分配:大小编译期确定
int arr[5];

// 堆上动态分配:大小运行时决定
int *arr2 = (int *)malloc(10 * sizeof(int));

4.2 完整代码示例:一维动态数组

#include <stdio.h>
#include <stdlib.h>

int main() {
    int arr[5]; // 三段式普通数组(栈)

    int *arr2; // 单纯定义int指针
    arr2 = (int *)malloc(10 * sizeof(int)); // 手动开辟堆内存

    if (arr2 == NULL) {
        printf("内存分配失败!\n");
        return 1;
    }

    // ⭐ 两种等价访问方式
    arr2[0] = 11;       // 下标法
    *(arr2 + 2) = 33;   // 完全等价于 arr2[2] = 33;
    arr2[8] = 36;

    printf("%d\n", arr2[0]);      // 11
    printf("%d\n", *(arr2 + 2));  // 33
    printf("%d\n", arr2[8]);      // 36

    free(arr2);   // ⭐ 用完必释放
    arr2 = NULL;  // ⭐ 防野指针

    return 0;
}

⭐ 老师强调

  • arr2[i]*(arr2 + i) 完全等价,编译器内部自动转换。
  • malloc 返回 void*,C语言可隐式转换,但显式强转更安全、更清晰
  • 必须检查返回值是否为 NULL
  • free 后立即置 NULL,防止悬空指针误用。

5. 二级指针与动态二维数组

5.1 内存模型

int **arr
    │
    ▼
┌──────────┐
│ arr[0] ──────▶ [10个int]  ← malloc(10*sizeof(int))
│ arr[1] ──────▶ [25个int]  ← malloc(25*sizeof(int))
│ arr[2] ──────▶ [7个int]   ← malloc(7*sizeof(int))
│ arr[3] │
│ arr[4] │
└──────────┘
↑ malloc(5*sizeof(int*))

⭐ 关键点:行指针数组连续,但各行数据块在堆上独立分配、不一定相邻,支持不规则(锯齿)数组。

5.2 完整代码示例

#include <stdio.h>
#include <stdlib.h>

int main() {
    // 第一步:分配行指针数组
    int **arr = (int **)malloc(5 * sizeof(int *));
    if (!arr) return 1;

    // 第二步:逐行独立分配(长度可不同)
    arr[0] = (int *)malloc(10 * sizeof(int));
    arr[1] = (int *)malloc(25 * sizeof(int));
    arr[2] = (int *)malloc(7 * sizeof(int));

    // 验证
    arr[0][0] = 100;
    arr[1][24] = 200;
    arr[2][6] = 300;
    printf("%d %d %d\n", arr[0][0], arr[1][24], arr[2][6]);

    // ⭐ 第三步:从内向外释放
    free(arr[0]);
    free(arr[1]);
    free(arr[2]);
    free(arr);

    return 0;
}

⭐ 老师强调

  • 释放顺序不可逆:先释放每一行,最后释放指针数组。反过来会导致行内存永久泄漏。
  • 两次解引用 arr[i][j],中间任一层为 NULL 即崩溃,调试时分层检查。

6. sizeof 对动态数组的陷阱

6.1 错误示范

int *arr2 = (int *)malloc(10 * sizeof(int));
int length = sizeof(arr2) / sizeof(int); // ❌ 结果是指针大小/int大小,不是10

6.2 原因与正确做法

场景sizeof 返回值能否获取长度
静态数组 int a[10]整个数组字节数
动态指针 int *p指针自身字节数(4/8)
函数参数 int a[]退化为指针

✅ 唯一可靠方式:用变量显式记录长度。

int len = 10;
int *arr2 = (int *)malloc(len * sizeof(int));
// 后续所有操作使用 len

附录:本节课核心易错点速查表

类别易错点正确做法
进制二↔八/十六分组方向搞反始终从右向左分组
编码混淆原码/反码/补码转换负数补码 = 反码+1,记住即可
内存malloc不检查NULL分配后必判空
内存free后置悬空指针free后立即赋NULL
内存二维数组释放顺序错误从内向外,先行后指针数组
内存sizeof算动态数组长度用变量手动记录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CarIise

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值