ソースを参照

VSCode 插件 Cline 自动写代码

蘭雅sRGB 5 日 前
コミット
ac1d1f1cc2
9 ファイル変更1131 行追加0 行削除
  1. 164 0
      Cline/c_file_io.c
  2. 208 0
      Cline/cpp_file_io.cpp
  3. 535 0
      Cline/memory_allocation.c
  4. 0 0
      libc/01.mysort.cpp
  5. 28 0
      tools/mp3cut.cpp
  6. 36 0
      tools/mp4Tomp3.cpp
  7. 28 0
      tools/mp4box.cpp
  8. 51 0
      tools/mp4cut.cpp
  9. 81 0
      tools/playlist4cut.cpp

+ 164 - 0
Cline/c_file_io.c

@@ -0,0 +1,164 @@
+/*
+ * ============================================================
+ *  C语言文件读写示例 (c_file_io.c)
+ * ============================================================
+ *
+ * C语言使用标准库 <stdio.h> 中提供的一组函数来操作文件,
+ * 核心是 FILE* 这个"文件指针"(其实是一个不透明的结构体指针,
+ * 内部保存了文件描述符、缓冲区、读写位置等信息)。
+ *
+ * 常用函数:
+ *   FILE* fopen(const char *path, const char *mode);   // 打开文件
+ *   int   fclose(FILE *fp);                            // 关闭文件
+ *   int   fprintf(FILE *fp, const char *fmt, ...);     // 格式化写入(文本)
+ *   int   fscanf(FILE *fp, const char *fmt, ...);      // 格式化读取(文本)
+ *   char* fgets(char *buf, int n, FILE *fp);           // 按行读取文本
+ *   size_t fwrite(const void *ptr, size_t size, size_t n, FILE *fp); // 二进制写
+ *   size_t fread(void *ptr, size_t size, size_t n, FILE *fp);        // 二进制读
+ *   int   feof(FILE *fp);   // 是否到达文件末尾
+ *   int   ferror(FILE *fp); // 是否发生错误
+ *
+ * 打开模式 mode 常见取值:
+ *   "r"  只读,文件必须存在
+ *   "w"  只写,文件不存在则创建,存在则清空
+ *   "a"  追加写,文件不存在则创建
+ *   "r+" 读写,文件必须存在
+ *   "w+" 读写,文件不存在则创建,存在则清空
+ *   "a+" 读写追加
+ *   在 mode 后加 "b" 表示以二进制模式打开,如 "rb"、"wb"
+ *   (在 Windows 上文本模式会自动转换 \n <-> \r\n,二进制模式不转换)
+ *
+ * 特点总结:
+ *   1. 面向过程,需要手动检查返回值判断是否成功(没有异常机制)。
+ *   2. 必须显式调用 fclose 关闭文件,否则可能造成资源泄漏或数据未刷新到磁盘。
+ *   3. 类型不安全:fprintf/fscanf 依赖格式化字符串 %d/%s 等,容易出错。
+ *   4. 性能高、历史悠久,几乎所有C库、嵌入式系统都在用。
+ * ============================================================
+ */
+
+#include <stdio.h>   // 标准输入输出:fopen/fclose/fprintf/fscanf/fread/fwrite等
+#include <stdlib.h>  // exit, EXIT_FAILURE
+#include <string.h>  // strlen
+
+/* 定义一个简单的结构体,用于演示二进制读写 */
+typedef struct {
+    int   id;
+    char  name[32];
+    double score;
+} Student;
+
+int main(void)
+{
+    const char *text_file   = "c_demo.txt";
+    const char *binary_file = "c_demo.bin";
+
+    /* ---------------------------------------------------------
+     * 第一部分:文本文件的写入与读取
+     * --------------------------------------------------------- */
+    printf("===== [C语言] 文本文件读写演示 =====\n");
+
+    /* 1. 打开文件用于写入("w" 模式:不存在则创建,存在则清空) */
+    FILE *fp = fopen(text_file, "w");
+    if (fp == NULL) {
+        /* fopen 失败时返回 NULL,必须手动判断 */
+        perror("fopen 写入失败");
+        return EXIT_FAILURE;
+    }
+
+    /* 2. 使用 fprintf 按格式化文本写入,用法类似 printf,只是多了一个 FILE* 参数 */
+    fprintf(fp, "Hello from C!\n");
+    fprintf(fp, "%d,%s,%.2f\n", 1, "Alice", 95.5);
+    fprintf(fp, "%d,%s,%.2f\n", 2, "Bob",   88.0);
+
+    /* 3. 用完必须关闭,fclose 会把缓冲区数据刷新(flush)到磁盘,并释放FILE*资源 */
+    fclose(fp);
+    printf("已写入文件: %s\n", text_file);
+
+    /* 4. 重新打开文件用于读取 */
+    fp = fopen(text_file, "r");
+    if (fp == NULL) {
+        perror("fopen 读取失败");
+        return EXIT_FAILURE;
+    }
+
+    /* 5. 使用 fgets 按行读取文本,最多读取 sizeof(line)-1 个字符,会保留末尾的'\n' */
+    char line[128];
+    printf("--- 按行读取内容 ---\n");
+    while (fgets(line, sizeof(line), fp) != NULL) {
+        printf("读到一行: %s", line); // line里已经带了换行符
+    }
+
+    /* 判断循环结束的原因:是正常到达文件末尾,还是发生了读取错误 */
+    if (ferror(fp)) {
+        perror("读取过程中发生错误");
+    }
+    fclose(fp);
+
+    /* 6. 也可以用 fscanf 按格式解析文本(适合读取结构化数据,例如 "id,name,score") */
+    fp = fopen(text_file, "r");
+    if (fp != NULL) {
+        char first_line[64];
+        fgets(first_line, sizeof(first_line), fp); // 跳过第一行 "Hello from C!"
+        int id; char name[32]; double score;
+        printf("--- 使用fscanf解析结构化数据 ---\n");
+        while (fscanf(fp, "%d,%31[^,],%lf\n", &id, name, &score) == 3) {
+            printf("解析结果: id=%d, name=%s, score=%.2f\n", id, name, score);
+        }
+        fclose(fp);
+    }
+
+    /* ---------------------------------------------------------
+     * 第二部分:二进制文件的写入与读取(更高效,不做文本转换/格式化)
+     * --------------------------------------------------------- */
+    printf("\n===== [C语言] 二进制文件读写演示 =====\n");
+
+    Student students_out[2] = {
+        {1, "Alice", 95.5},
+        {2, "Bob",   88.0}
+    };
+
+    /* 1. 以二进制写模式打开 "wb" */
+    fp = fopen(binary_file, "wb");
+    if (fp == NULL) {
+        perror("fopen 二进制写入失败");
+        return EXIT_FAILURE;
+    }
+
+    /* 2. fwrite(数据指针, 每个元素大小, 元素个数, 文件指针)
+     *    直接把内存中的字节按原样写入文件,速度快,但不同平台字节序/结构体对齐可能不同,
+     *    不适合跨平台/跨编译器长期保存数据。
+     */
+    size_t written = fwrite(students_out, sizeof(Student), 2, fp);
+    printf("写入了 %zu 条学生记录\n", written);
+    fclose(fp);
+
+    /* 3. 以二进制读模式打开 "rb" 并读回 */
+    fp = fopen(binary_file, "rb");
+    if (fp == NULL) {
+        perror("fopen 二进制读取失败");
+        return EXIT_FAILURE;
+    }
+
+    Student students_in[2];
+    size_t read_count = fread(students_in, sizeof(Student), 2, fp);
+    printf("读取了 %zu 条学生记录:\n", read_count);
+    for (size_t i = 0; i < read_count; i++) {
+        printf("  id=%d, name=%s, score=%.2f\n",
+               students_in[i].id, students_in[i].name, students_in[i].score);
+    }
+    fclose(fp);
+
+    /* ---------------------------------------------------------
+     * 第三部分:追加写入演示 ("a" 模式)
+     * --------------------------------------------------------- */
+    printf("\n===== [C语言] 追加写入演示 =====\n");
+    fp = fopen(text_file, "a"); // 追加模式,写入内容会加到文件末尾,不会清空原内容
+    if (fp != NULL) {
+        fprintf(fp, "%d,%s,%.2f\n", 3, "Charlie", 77.0);
+        fclose(fp);
+        printf("已追加一行到 %s\n", text_file);
+    }
+
+    printf("\nC语言文件读写演示结束。\n");
+    return 0;
+}

+ 208 - 0
Cline/cpp_file_io.cpp

@@ -0,0 +1,208 @@
+/*
+ * ============================================================
+ *  C++语言文件读写示例 (cpp_file_io.cpp)
+ * ============================================================
+ *
+ * C++ 使用 <fstream> 头文件中提供的流(stream)类来操作文件,
+ * 常用的三个类:
+ *   std::ifstream  —— 输入文件流(input file stream),用于"读"
+ *   std::ofstream  —— 输出文件流(output file stream),用于"写"
+ *   std::fstream   —— 输入输出文件流,既能读也能写
+ *
+ * 这些类都继承自 std::istream / std::ostream,因此可以像操作
+ * std::cin / std::cout 一样,使用 << 和 >> 运算符读写文件,
+ * 也可以用 getline() 按行读取。
+ *
+ * 打开模式(第二个参数),用 std::ios 里的枚举值组合(用 | 连接):
+ *   std::ios::in     只读
+ *   std::ios::out    只写
+ *   std::ios::app    追加
+ *   std::ios::trunc  打开时清空文件
+ *   std::ios::binary 二进制模式(不做文本转换)
+ *   std::ios::ate    打开后立即定位到文件末尾
+ *
+ * 与C语言最大的不同点(RAII机制):
+ *   ifstream/ofstream 是"资源获取即初始化"(RAII)的对象,
+ *   当它离开作用域(比如函数结束、{}代码块结束)时,
+ *   析构函数会自动调用 close(),把缓冲区数据刷新到磁盘,
+ *   不需要像C语言那样手动调用 fclose(),从而避免忘记关闭
+ *   文件导致的资源泄漏问题。
+ *
+ * 特点总结:
+ *   1. 面向对象,类型安全:可以直接用 << 写入 int/double/string等,
+ *      不需要像C那样写格式化字符串"%d"。
+ *   2. 自动管理资源(RAII),无需手动close,异常安全性更好。
+ *   3. 可以用异常(exceptions)机制处理错误,也可以用状态位(good/fail/eof)判断。
+ *   4. 相比C的stdio,部分场景下性能略低(多了一层抽象),但差距通常很小,
+ *      且可以通过 sync_with_stdio(false) 等方式优化。
+ * ============================================================
+ */
+
+#include <iostream>   // std::cout, std::cerr
+#include <fstream>    // std::ifstream, std::ofstream, std::fstream
+#include <string>     // std::string, std::getline
+#include <vector>     // std::vector 用于存放读取到的数据
+#include <sstream>    // std::istringstream 用于按逗号分割字符串
+
+/* 定义一个结构体,与C版本对应,用于演示二进制读写 */
+struct Student {
+    int    id;
+    char   name[32]; // 为了让二进制布局和C版本一致,这里用char数组而不是std::string
+    double score;
+};
+
+int main()
+{
+    const std::string text_file   = "cpp_demo.txt";
+    const std::string binary_file = "cpp_demo.bin";
+
+    /* ---------------------------------------------------------
+     * 第一部分:文本文件的写入与读取
+     * --------------------------------------------------------- */
+    std::cout << "===== [C++] 文本文件读写演示 =====\n";
+
+    /* 1. 创建 ofstream 对象即可"打开"文件用于写入
+     *    默认模式是 std::ios::out,若省略第二参数也可以。
+     *    与C的"w"模式一样:文件不存在则创建,存在则清空。
+     */
+    {
+        std::ofstream ofs(text_file); // 等价于 ofstream ofs(text_file, std::ios::out);
+        if (!ofs.is_open()) {
+            // is_open() 判断文件是否打开成功,类似C语言判断 fp == NULL
+            std::cerr << "打开文件写入失败: " << text_file << std::endl;
+            return 1;
+        }
+
+        /* 2. 直接使用 << 运算符写入,像 std::cout 一样自然,无需格式化字符串 */
+        ofs << "Hello from C++!" << "\n";
+        ofs << 1 << "," << "Alice" << "," << 95.5 << "\n";
+        ofs << 2 << "," << "Bob"   << "," << 88.0 << "\n";
+
+        std::cout << "已写入文件: " << text_file << std::endl;
+        /* 3. ofs 在这个花括号 {} 结束时会自动析构,自动调用close()刷新并关闭文件,
+         *    不需要像C语言那样手动 fclose()!这就是RAII的好处。
+         */
+    }
+
+    /* 4. 重新打开文件用于读取,使用 ifstream */
+    {
+        std::ifstream ifs(text_file);
+        if (!ifs.is_open()) {
+            std::cerr << "打开文件读取失败: " << text_file << std::endl;
+            return 1;
+        }
+
+        /* 5. 使用 std::getline 按行读取到 std::string 中,
+         *    比C的fgets更安全(不需要担心缓冲区大小、不会保留换行符)。
+         */
+        std::string line;
+        std::cout << "--- 按行读取内容 ---\n";
+        while (std::getline(ifs, line)) {
+            std::cout << "读到一行: " << line << std::endl;
+        }
+        // ifs 离开作用域自动关闭
+    }
+
+    /* 6. 使用 istringstream 解析逗号分隔的结构化数据(比C的fscanf更安全灵活) */
+    {
+        std::ifstream ifs(text_file);
+        std::string line;
+        std::getline(ifs, line); // 跳过第一行 "Hello from C++!"
+
+        std::cout << "--- 使用istringstream解析结构化数据 ---\n";
+        while (std::getline(ifs, line)) {
+            std::istringstream iss(line);
+            std::string id_str, name, score_str;
+            if (std::getline(iss, id_str, ',') &&
+                std::getline(iss, name,   ',') &&
+                std::getline(iss, score_str)) {
+                int id = std::stoi(id_str);
+                double score = std::stod(score_str);
+                std::cout << "解析结果: id=" << id
+                          << ", name=" << name
+                          << ", score=" << score << std::endl;
+            }
+        }
+    }
+
+    /* ---------------------------------------------------------
+     * 第二部分:二进制文件的写入与读取
+     * --------------------------------------------------------- */
+    std::cout << "\n===== [C++] 二进制文件读写演示 =====\n";
+
+    std::vector<Student> students_out = {
+        {1, "Alice", 95.5},
+        {2, "Bob",   88.0}
+    };
+
+    /* 1. 以二进制写模式打开:std::ios::out | std::ios::binary */
+    {
+        std::ofstream ofs(binary_file, std::ios::out | std::ios::binary);
+        if (!ofs.is_open()) {
+            std::cerr << "打开二进制文件写入失败" << std::endl;
+            return 1;
+        }
+
+        /* 2. write(数据地址(需转成char*), 字节数)
+         *    reinterpret_cast<const char*> 把任意类型指针转换为 char* 字节流,
+         *    这是C++里进行二进制IO的标准写法。
+         */
+        ofs.write(reinterpret_cast<const char*>(students_out.data()),
+                  sizeof(Student) * students_out.size());
+        std::cout << "写入了 " << students_out.size() << " 条学生记录\n";
+    }
+
+    /* 3. 以二进制读模式打开并读回 */
+    {
+        std::ifstream ifs(binary_file, std::ios::in | std::ios::binary);
+        if (!ifs.is_open()) {
+            std::cerr << "打开二进制文件读取失败" << std::endl;
+            return 1;
+        }
+
+        std::vector<Student> students_in(2);
+        ifs.read(reinterpret_cast<char*>(students_in.data()),
+                  sizeof(Student) * students_in.size());
+
+        // gcount() 返回上一次read实际读取到的字节数,可用于校验是否读满
+        std::streamsize actually_read = ifs.gcount();
+        size_t record_count = actually_read / sizeof(Student);
+
+        std::cout << "读取了 " << record_count << " 条学生记录:\n";
+        for (size_t i = 0; i < record_count; i++) {
+            std::cout << "  id=" << students_in[i].id
+                       << ", name=" << students_in[i].name
+                       << ", score=" << students_in[i].score << std::endl;
+        }
+    }
+
+    /* ---------------------------------------------------------
+     * 第三部分:追加写入演示 (std::ios::app)
+     * --------------------------------------------------------- */
+    std::cout << "\n===== [C++] 追加写入演示 =====\n";
+    {
+        std::ofstream ofs(text_file, std::ios::app); // 追加模式,不清空原内容
+        if (ofs.is_open()) {
+            ofs << 3 << "," << "Charlie" << "," << 77.0 << "\n";
+            std::cout << "已追加一行到 " << text_file << std::endl;
+        }
+    }
+
+    /* ---------------------------------------------------------
+     * 第四部分:使用fstream同时读写同一个文件(C++特有的便利类)
+     * --------------------------------------------------------- */
+    std::cout << "\n===== [C++] fstream 读写同一文件演示 =====\n";
+    {
+        // in | out 表示既能读也能写;文件必须已存在(不加trunc就不会清空)
+        std::fstream fs(text_file, std::ios::in | std::ios::out);
+        if (fs.is_open()) {
+            std::string first;
+            std::getline(fs, first);
+            std::cout << "fstream读到第一行: " << first << std::endl;
+            // 还可以继续用 fs << ... 写入,实现同一个流对象读写混合操作
+        }
+    }
+
+    std::cout << "\nC++语言文件读写演示结束。\n";
+    return 0;
+}

+ 535 - 0
Cline/memory_allocation.c

@@ -0,0 +1,535 @@
+/**
+ * ============================================================================
+ *  C 语言动态内存分配 · 学习示例
+ * ============================================================================
+ *
+ *  内容大纲:
+ *    第 1 节   malloc  基本内存分配
+ *    第 2 节   calloc  分配并初始化为 0
+ *    第 3 节   realloc 调整内存大小(扩容 / 缩容)
+ *    第 4 节   free    释放内存,防野指针
+ *    第 5 节   动态数组(自动扩容的 vector)
+ *    第 6 节   二维矩阵(指针数组 / 连续内存块 两种方式)
+ *    第 7 节   字符串数组(char** + strdup)
+ *    第 8 节   结构体数组(先数组、后成员的两层分配)
+ *    第 9 节   柔性数组成员(Flexible Array Member, C99)
+ *    第 10 节  常见错误、内存跟踪与良好习惯
+ *
+ *  编译运行(任选其一):
+ *      gcc memory_allocation.c -o memory_allocation.exe -Wall -Wextra -g
+ *      cl  memory_allocation.c /Fe:memory_allocation.exe
+ *
+ *  说明:本程序按 C99 标准书写,注释使用中文,运行时请保证控制台
+ *        使用 UTF-8 编码(Windows 下可用 `chcp 65001`)。
+ * ============================================================================
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* ---------------------------------------------------------------------------
+ * 工具:带错误检查的分配包装
+ * ---------------------------------------------------------------------------
+ * malloc 失败时会返回 NULL。如果拿到 NULL 还继续使用,就是
+ * “空指针解引用”(NULL pointer dereference),程序直接崩溃。
+ * 下面的 xmalloc / xcalloc / xrealloc 把“分配 + 检查 + 报错退出”
+ * 封装起来,后面所有示例都用它们,保证代码既简洁又安全。
+ * ------------------------------------------------------------------------- */
+
+#define XMALLOC(size)        xmalloc(size, __FILE__, __LINE__)
+#define XCALLOC(count, size) xcalloc(count, size, __FILE__, __LINE__)
+#define XREALLOC(ptr, size)  xrealloc(ptr, size, __FILE__, __LINE__)
+
+static void *xmalloc(size_t size, const char *file, int line)
+{
+    void *p = malloc(size);
+    if (p == NULL) {
+        fprintf(stderr, "[内存分配失败] %s:%d 无法分配 %zu 字节\n",
+                file, line, size);
+        exit(EXIT_FAILURE);
+    }
+    return p;
+}
+
+static void *xcalloc(size_t count, size_t size, const char *file, int line)
+{
+    void *p = calloc(count, size);
+    if (p == NULL) {
+        fprintf(stderr, "[内存分配失败] %s:%d 无法分配 %zu x %zu 字节\n",
+                file, line, count, size);
+        exit(EXIT_FAILURE);
+    }
+    return p;
+}
+
+static void *xrealloc(void *ptr, size_t size, const char *file, int line)
+{
+    void *p = realloc(ptr, size);          /* 注意:realloc 可能返回新地址 */
+    if (p == NULL) {
+        fprintf(stderr, "[内存重分配失败] %s:%d 无法调整到 %zu 字节\n",
+                file, line, size);
+        exit(EXIT_FAILURE);
+    }
+    return p;
+}
+
+/* 打印分隔标题,方便看清每个示例的输出 */
+static void section(const char *title)
+{
+    printf("\n================ %s ================\n", title);
+}
+
+/* ============================================================================
+ * 第 1 节  malloc —— 基本内存分配
+ * ----------------------------------------------------------------------------
+ *   void *malloc(size_t size);
+ *   - 在堆上分配 size 个字节的连续内存;
+ *   - 返回 void*(无类型指针),使用时要强制转换成目标类型;
+ *   - 分配的内存【内容不确定】,必须自己初始化后才能读取;
+ *   - 分配失败返回 NULL。
+ * ==========================================================================*/
+static void demo_malloc(void)
+{
+    section("1. malloc 基本分配");
+
+    int *arr;          /* 指向堆上 int 数组的指针 */
+    int  i;
+
+    /* 分配 5 个 int 需要的空间 */
+    arr = XMALLOC(5 * sizeof(int));
+    printf("成功分配 5 个 int(共 %zu 字节)\n", 5 * sizeof(int));
+
+    /*
+     * malloc 不会清零!真正未初始化时内容是无法预测的“垃圾值”。
+     * 直接读取未初始化的内存属于未定义行为,为了安全演示,
+     * 这里用 memset 把内存填成 0xAA,再把它当成 int 打印,
+     * 就能直观看到“这块内存里原来有未知的数据”。
+     */
+    memset(arr, 0xAA, 5 * sizeof(int));
+    printf("malloc 后未初始化(本处用 0xAA 模拟垃圾值):\n");
+    for (i = 0; i < 5; i++)
+        printf("  arr[%d] = 0x%08X\n", i, (unsigned)arr[i]);
+
+    /* 必须自己赋值后才能正常使用 */
+    for (i = 0; i < 5; i++)
+        arr[i] = i * i;
+    printf("手动初始化后:");
+    for (i = 0; i < 5; i++)
+        printf("%d ", arr[i]);
+    printf("\n");
+
+    free(arr);
+}
+
+/* ============================================================================
+ * 第 2 节  calloc —— 分配并清零
+ * ----------------------------------------------------------------------------
+ *   void *calloc(size_t count, size_t size);
+ *   - 分配 count * size 字节,并把所有字节置为 0;
+ *   - 参数写成“个数 x 单个大小”,比 malloc(count*size) 更不容易写错、
+ *     还能避免整数溢出;
+ *   - 同样要检查返回值是否为 NULL。
+ * ==========================================================================*/
+static void demo_calloc(void)
+{
+    section("2. calloc 分配并清零");
+
+    int *arr;
+    int  i;
+
+    arr = XCALLOC(5, sizeof(int));
+    printf("calloc 分配的 5 个 int 全部为 0:\n");
+    for (i = 0; i < 5; i++)
+        printf("  arr[%d] = %d\n", i, arr[i]);
+
+    free(arr);
+}
+
+/* ============================================================================
+ * 第 3 节  realloc —— 调整内存大小
+ * ----------------------------------------------------------------------------
+ *   void *realloc(void *ptr, size_t new_size);
+ *   - 把 ptr 指向的内存块调整到 new_size 字节;
+ *   - 扩容时原有内容会被完整保留,新增部分【未初始化】;
+ *   - 返回值可能是新的地址(原内存可能被搬走),所以一定要:
+ *         ptr = realloc(ptr, new_size);    // 重新赋给 ptr
+ *     绝不要写成:
+ *         realloc(ptr, new_size);          // 丢弃返回值 → 泄漏或悬垂
+ *   - 传入 NULL 等价于 malloc;new_size 为 0 等价于 free(不推荐依赖)。
+ * ==========================================================================*/
+static void demo_realloc(void)
+{
+    section("3. realloc 扩容与缩容");
+
+    int *arr;
+    int  i;
+
+    arr = XMALLOC(4 * sizeof(int));
+    for (i = 0; i < 4; i++)
+        arr[i] = i + 1;
+    printf("初始 4 个元素:");
+    for (i = 0; i < 4; i++) printf("%d ", arr[i]);
+    printf("\n");
+
+    /* 扩容到 8 个 int:旧数据保留,新空间未初始化 */
+    arr = XREALLOC(arr, 8 * sizeof(int));
+    printf("扩容后前 4 个(保留的原数据):");
+    for (i = 0; i < 4; i++) printf("%d ", arr[i]);
+    printf("\n");
+
+    /* 新空间必须初始化后才能使用 */
+    for (i = 4; i < 8; i++)
+        arr[i] = i * 100;
+    printf("初始化后全部 8 个:");
+    for (i = 0; i < 8; i++) printf("%d ", arr[i]);
+    printf("\n");
+
+    /* 缩容到 3 个 int:多余的元素被丢弃 */
+    arr = XREALLOC(arr, 3 * sizeof(int));
+    printf("缩容后只剩 3 个:");
+    for (i = 0; i < 3; i++) printf("%d ", arr[i]);
+    printf("\n");
+
+    free(arr);
+}
+
+/* ============================================================================
+ * 第 4 节  free —— 释放内存,并防止野指针
+ * ----------------------------------------------------------------------------
+ *   void free(void *ptr);
+ *   - 只能释放 malloc / calloc / realloc 返回的指针;
+ *   - 释放后该指针仍是原来的地址,但内存已归还系统,
+ *     这样的指针叫“野指针 / 悬垂指针”(dangling pointer),必须立刻置 NULL;
+ *   - 对 NULL 调用 free 是安全的(什么都不做);
+ *   - 同一块内存只能释放一次(重复释放 = double free,会崩溃)。
+ * ==========================================================================*/
+static void demo_free(void)
+{
+    section("4. free 与野指针");
+
+    int *p = XMALLOC(sizeof(int));
+    *p = 42;
+    printf("释放前 *p = %d\n", *p);
+
+    free(p);
+    /* 释放后 p 仍“指向”旧地址,继续读写它是未定义行为,必须置空 */
+    p = NULL;
+
+    if (p == NULL) {
+        printf("已释放并置空。对 NULL 调用 free 是安全的:\n");
+        free(p);   /* 什么都不做,不会出错 */
+    }
+}
+
+/* ============================================================================
+ * 第 5 节  动态数组(可自动扩容的 int vector)
+ * ----------------------------------------------------------------------------
+ * 最常见的用法:用一个结构体记录“数据指针 + 元素个数 + 容量”,
+ * 满了就用 realloc 把容量翻倍。翻倍扩容后平均每个元素只被拷贝 O(1) 次,
+ * 所以 push 的均摊时间复杂度是 O(1)。
+ * ==========================================================================*/
+
+typedef struct {
+    int *data;   /* 指向堆上数据 */
+    int  len;    /* 当前元素个数 */
+    int  cap;    /* 当前容量(已分配) */
+} IntVec;
+
+static void vec_init(IntVec *v)
+{
+    v->data = NULL;
+    v->len = 0;
+    v->cap = 0;
+}
+
+static void vec_push(IntVec *v, int value)
+{
+    if (v->len == v->cap) {
+        int new_cap = (v->cap == 0) ? 4 : v->cap * 2;    /* 容量翻倍 */
+        v->data = XREALLOC(v->data, (size_t)new_cap * sizeof(int));
+        v->cap = new_cap;
+    }
+    v->data[v->len++] = value;
+}
+
+static void vec_free(IntVec *v)
+{
+    free(v->data);
+    v->data = NULL;      /* 好习惯:释放后置空 */
+    v->len = 0;
+    v->cap = 0;
+}
+
+static void demo_vector(void)
+{
+    section("5. 动态数组 vector");
+
+    IntVec v;
+    int i;
+
+    vec_init(&v);
+    for (i = 0; i < 10; i++)
+        vec_push(&v, i * 10);
+
+    printf("共 %d 个元素(容量 %d):\n", v.len, v.cap);
+    for (i = 0; i < v.len; i++)
+        printf("  v[%d] = %d\n", i, v.data[i]);
+
+    vec_free(&v);
+}
+
+/* ============================================================================
+ * 第 6 节  二维矩阵的两种分配方式
+ * ----------------------------------------------------------------------------
+ *  方式 A:指针数组 —— 每一行单独 malloc。写法直观、可以每行长度不同,
+ *          但各行之间地址不连续,对缓存不友好。
+ *  方式 B:连续内存块 —— 一次 malloc 出 rows*cols 个元素,再用一个
+ *          指针数组把大块内存“切”成一行一行。内存完全连续,性能更好,
+ *          释放也更简单。
+ * ==========================================================================*/
+static void demo_matrix(void)
+{
+    const int rows = 3, cols = 4;
+    int **m_a, **m_b, *storage;
+    int r, c;
+
+    /* ---- 方式 A:指针数组(每行单独分配) ---- */
+    m_a = XMALLOC((size_t)rows * sizeof(int *));
+    for (r = 0; r < rows; r++)
+        m_a[r] = XMALLOC((size_t)cols * sizeof(int));
+
+    for (r = 0; r < rows; r++)
+        for (c = 0; c < cols; c++)
+            m_a[r][c] = r * 10 + c;
+
+    printf("方式 A(每行单独 malloc):\n");
+    for (r = 0; r < rows; r++) {
+        printf("  ");
+        for (c = 0; c < cols; c++)
+            printf("%4d", m_a[r][c]);
+        printf("\n");
+    }
+
+    /* 释放顺序:先释放每一行,再释放行指针数组(顺序不能反) */
+    for (r = 0; r < rows; r++)
+        free(m_a[r]);
+    free(m_a);
+
+    /* ---- 方式 B:连续内存块 ---- */
+    storage = XMALLOC((size_t)(rows * cols) * sizeof(int));
+    m_b = XMALLOC((size_t)rows * sizeof(int *));
+    for (r = 0; r < rows; r++)
+        m_b[r] = storage + r * cols;      /* 把大块内存划分成行 */
+
+    for (r = 0; r < rows; r++)
+        for (c = 0; c < cols; c++)
+            m_b[r][c] = r * 10 + c;
+
+    printf("方式 B(连续内存块):\n");
+    for (r = 0; r < rows; r++) {
+        printf("  ");
+        for (c = 0; c < cols; c++)
+            printf("%4d", m_b[r][c]);
+        printf("\n");
+    }
+
+    /* 方式 B 只需释放两次:先指针数组,再数据块 */
+    free(m_b);
+    free(storage);
+}
+
+/* ============================================================================
+ * 第 7 节  字符串数组
+ * ----------------------------------------------------------------------------
+ *  用 char** 存放多个字符串。字符串字面量不能随意修改,所以要把它们
+ *  复制到堆上(strdup:malloc + strcpy)。MSVC 里 strdup 叫 _strdup,
+ *  下面自己写一个 safe_strdup 保证跨平台可移植。
+ * ==========================================================================*/
+
+static char *safe_strdup(const char *s)
+{
+    size_t n = strlen(s) + 1;             /* 别忘了末尾的 '\0' */
+    char  *copy = XMALLOC(n);
+    memcpy(copy, s, n);
+    return copy;
+}
+
+static void demo_strings(void)
+{
+    section("7. 字符串数组");
+
+    const char *names[] = { "C", "Java", "Python", "Rust" };
+    int   count = (int)(sizeof(names) / sizeof(names[0]));
+    char **strs = XMALLOC((size_t)count * sizeof(char *));
+    int i;
+
+    /* 每个字符串单独复制到堆上 */
+    for (i = 0; i < count; i++)
+        strs[i] = safe_strdup(names[i]);
+
+    printf("共 %d 个字符串:\n", count);
+    for (i = 0; i < count; i++)
+        printf("  strs[%d] = \"%s\"(长度 %zu)\n", i, strs[i], strlen(strs[i]));
+
+    /* 释放顺序:先每个字符串,再字符串指针数组 */
+    for (i = 0; i < count; i++)
+        free(strs[i]);
+    free(strs);
+}
+
+/* ============================================================================
+ * 第 8 节  结构体数组(两层分配)
+ * ----------------------------------------------------------------------------
+ *  结构体里含指针时,内存要分两层分配:
+ *     1. 给“结构体数组”分配内存;
+ *     2. 给每个结构体内部的指针成员分配内存。
+ *  释放顺序与分配顺序相反:先释放每个成员的指针,再释放数组本身。
+ * ==========================================================================*/
+
+typedef struct {
+    char *name;    /* 指向堆上的字符串 */
+    int   score;
+} Student;
+
+static void demo_struct_array(void)
+{
+    section("8. 结构体数组");
+
+    const char *stu_names[] = { "Alice", "Bob", "Carol" };
+    int   count = (int)(sizeof(stu_names) / sizeof(stu_names[0]));
+    Student *list = XMALLOC((size_t)count * sizeof(Student));
+    int i;
+
+    for (i = 0; i < count; i++) {
+        list[i].name = safe_strdup(stu_names[i]);
+        list[i].score = 60 + i * 10;
+    }
+
+    printf("学生列表:\n");
+    for (i = 0; i < count; i++)
+        printf("  %-6s  得分 %d\n", list[i].name, list[i].score);
+
+    /* 释放:先释放每个成员指针,再释放结构体数组 */
+    for (i = 0; i < count; i++)
+        free(list[i].name);
+    free(list);
+}
+
+/* ============================================================================
+ * 第 9 节  柔性数组成员(Flexible Array Member, C99)
+ * ----------------------------------------------------------------------------
+ *  结构体的最后一个成员可以写成不指定长度的数组 items[],
+ *  它不占结构体本身的空间。这样“结构体头 + 数据”可以用一次 malloc
+ *  分配在同一块连续内存里,访问时 items 紧跟在头后面,释放也只需 free 一次。
+ * ==========================================================================*/
+
+typedef struct {
+    size_t len;     /* 元素个数 */
+    int    items[]; /* 柔性数组成员,sizeof(Pack) 不含它 */
+} Pack;
+
+static void demo_flexible_array(void)
+{
+    section("9. 柔性数组成员");
+
+    size_t n = 6;
+    Pack  *pack = XMALLOC(sizeof(Pack) + n * sizeof(int));
+    size_t i;
+
+    pack->len = n;
+    for (i = 0; i < n; i++)
+        pack->items[i] = (int)i * 2;
+
+    printf("sizeof(Pack) = %zu(只包含 len,不含 items)\n", sizeof(Pack));
+    printf("共 %zu 个元素:", pack->len);
+    for (i = 0; i < pack->len; i++)
+        printf("%d ", pack->items[i]);
+    printf("\n");
+    printf("一次 malloc、一次 free:数据与结构体头在同一个内存块里。\n");
+
+    free(pack);
+}
+
+/* ============================================================================
+ * 第 10 节  常见错误、内存跟踪与良好习惯
+ * ----------------------------------------------------------------------------
+ *  常见错误速查:
+ *    1. 不检查 malloc 的返回值          → 见 xmalloc 包装
+ *    2. 分配了却忘记 free               → 内存泄漏(memory leak)
+ *    3. 越界写(分配 5 个却写第 6 个)  → 堆损坏,难排查
+ *    4. free 之后继续使用(野指针)     → 见第 4 节
+ *    5. 同一指针重复 free(double free)→ 崩溃
+ *    6. 释放与分配方式不匹配            → C 中统一用 free
+ *
+ *  下面的跟踪计数器用于演示“谁分配、谁释放”,借此直观看出泄漏:
+ *  分配次数 - 释放次数 > 0 就说明有内存没还回去。
+ * ==========================================================================*/
+
+static int g_alloc_count = 0;
+static int g_free_count  = 0;
+
+#define TRACK_MALLOC(size)    track_malloc(size, __FILE__, __LINE__)
+#define TRACK_FREE(ptr)       do { track_free(ptr, __FILE__, __LINE__); ptr = NULL; } while (0)
+
+static void *track_malloc(size_t size, const char *file, int line)
+{
+    void *p = xmalloc(size, file, line);
+    g_alloc_count++;
+    printf("  [跟踪] 分配 0x%p(%zu 字节)@ %s:%d\n", p, size, file, line);
+    return p;
+}
+
+static void track_free(void *p, const char *file, int line)
+{
+    if (p != NULL) {
+        g_free_count++;
+        printf("  [跟踪] 释放 0x%p @ %s:%d\n", p, file, line);
+    }
+    free(p);
+}
+
+static void demo_pitfalls(void)
+{
+    section("10. 内存跟踪:谁分配、谁释放");
+
+    /* 故意“分配后不释放”,制造一块泄漏内存(学习观察用) */
+    int *leak = TRACK_MALLOC(100 * sizeof(int));
+    (void)leak;   /* 避免“未使用变量”警告 */
+
+    /* 正确的一对:分配后释放 */
+    int *ok = TRACK_MALLOC(10 * sizeof(int));
+    TRACK_FREE(ok);
+
+    printf("\n  已分配 %d 次,已释放 %d 次,差值 %d(>0 表示有泄漏)\n",
+           g_alloc_count, g_free_count, g_alloc_count - g_free_count);
+    if (g_alloc_count != g_free_count)
+        printf("  >> 发现 %d 块内存未释放(人为制造的泄漏,仅供学习)<<\n",
+               g_alloc_count - g_free_count);
+
+    printf("\n  提示:真实项目中可用 Valgrind(Linux)或 AddressSanitizer\n"
+           "        (gcc -fsanitize=address)自动检测泄漏与越界。\n");
+}
+
+/* ============================================================================
+ * 程序入口:依次运行所有示例
+ * ==========================================================================*/
+int main(void)
+{
+    /* 关闭 stdout 缓冲,保证 Windows 控制台也能即时显示中文输出 */
+    setvbuf(stdout, NULL, _IONBF, 0);
+
+    demo_malloc();
+    demo_calloc();
+    demo_realloc();
+    demo_free();
+    demo_vector();
+    demo_matrix();
+    demo_strings();
+    demo_struct_array();
+    demo_flexible_array();
+    demo_pitfalls();
+
+    printf("\n全部示例运行完毕!\n");
+    return 0;
+}

+ 0 - 0
01.mysort.cpp → libc/01.mysort.cpp


+ 28 - 0
tools/mp3cut.cpp

@@ -0,0 +1,28 @@
+#include <string>
+#include <cstring>
+#include <cstdio>
+#include <cctype>
+
+int main(int argc, char* argv[])
+{
+    if (5 != argc) {
+        puts("Usage: mp3cut.exe  sample.mp3  00:08  01:18  Name.mp3 ");
+        puts("       mp3cut.exe  sample.m4a  00:08  01:18  Name.m4a ");
+        return -1;
+    }
+
+//  FFMPEG  按时间截取mp3音乐
+//  ffmpeg  -i sample.mp3  -acodec copy -ss 00:18:45 -to 00:19:36  -y  name.mp3
+
+    char cmdline[4096];
+    sprintf(cmdline, "ffmpeg -i  \"%s\"  -acodec copy -ss %s  -to %s  -y  \"%s\" ",
+            argv[1], argv[2], argv[3], argv[4]);
+
+    FILE* pFile;
+    pFile = fopen(argv[1], "r");
+    if (pFile != NULL) {
+        //  puts(cmdline);
+        system(cmdline);
+    }
+
+}

+ 36 - 0
tools/mp4Tomp3.cpp

@@ -0,0 +1,36 @@
+#include <string>
+#include <cstring>
+#include <cstdio>
+#include <cctype>
+
+int main(int argc, char* argv[])
+{
+    if (1 == argc) {
+        puts("Usage: mp4Tomp3.exe  sample.mp4  [192k]");
+        return -1;
+    }
+
+    const char* bitrate = "192k";
+    if (3 == argc)
+        bitrate = argv[2];
+
+    char cmdline[4096];
+
+    sprintf(cmdline, "ffmpeg -i \"%s\"  -ar 44100 -ac 2 -ab %s -f mp3  \"", argv[1], bitrate);
+    strcat(cmdline, argv[1]);
+
+    char* pch = strrchr(cmdline, '.');
+
+    if (pch != NULL) {
+
+        strcpy(pch, ".mp3\" ");
+
+        FILE* pFile;
+        pFile = fopen(argv[1], "r");
+        if (pFile != NULL) {
+
+            system(cmdline);
+            puts(cmdline);
+        }
+    }
+}

+ 28 - 0
tools/mp4box.cpp

@@ -0,0 +1,28 @@
+#include <string>
+#include <cstring>
+#include <cstdio>
+#include <cctype>
+
+
+int main(int argc, char* argv[])
+{
+    if (3 > argc) {
+        puts("Usage: mp4box.exe  1.mp4  2.mp4  3.mp4 ");
+        return -1;
+    }
+
+    FILE* filelist = fopen("filelist.txt", "w+");
+    for (int i = 1 ; i != argc ; i++) {
+        fprintf(filelist, "file  '%s'\n", argv[i]);
+    }
+    fclose(filelist);
+
+/// ##  ffmpeg合并多个mp4视频
+/// ffmpeg -f concat -i filelist.txt -c copy output_set.mp4
+
+    char cmdline[] = "ffmpeg -f concat -i filelist.txt -c copy output_set.mp4 -y";
+    system(cmdline);
+
+    printf("%\n%s\n", cmdline);
+
+}

+ 51 - 0
tools/mp4cut.cpp

@@ -0,0 +1,51 @@
+#include <string>
+#include <cstring>
+#include <cstdio>
+#include <cctype>
+
+void replace_colon(char* str)
+{
+    while (*str) {
+        if (*str == ':')
+            *str = '-';
+        str++;
+    }
+}
+
+int main(int argc, char* argv[])
+{
+    if (4 != argc) {
+        puts("Usage: mp4cut.exe  sample.mp4  00:08  01:18 ");
+        return -1;
+    }
+
+    /*******
+    FFMPEG  按时间截取视频
+    ffmpeg  -i ./plutopr.mp4   \
+    -vcodec copy -acodec copy -ss 00:18:45 -to 00:19:36  \
+     ./cutout1.mp4  -y
+    *******/
+
+    char cmdline[4096];
+    sprintf(cmdline, "ffmpeg -i  \"%s\"  -vcodec copy -acodec copy -ss %s  -to %s  -y  \"%s\" ",
+            argv[1], argv[2], argv[3], argv[1]);
+
+    char newfile[512];    // 时间戳文件名后缀
+    sprintf(newfile, ".Cut_%s_%s.mp4\" ", argv[2], argv[3]);
+    replace_colon(newfile);
+
+    char* pch = strrchr(cmdline, '.');
+
+    FILE* pFile;
+    pFile = fopen(argv[1], "r");
+    if (pch != NULL) {
+        strcpy(pch, newfile);
+
+        if (pFile != NULL) {
+            //  puts(cmdline);
+
+            system(cmdline);
+        }
+
+    }
+}

+ 81 - 0
tools/playlist4cut.cpp

@@ -0,0 +1,81 @@
+#include <cstring>
+#include <cstdio>
+#include <cctype>
+#include <vector>
+#include <algorithm>
+
+#define LINE_SIZE 1024
+struct Timestamp {
+    char start[64];
+    char end[64];
+    char name[512];
+};
+
+char * strtrim(char *s);
+
+int main(int argc, char* argv[])
+{
+    if (2 > argc) {
+        puts("Usage: playlist4cut.exe  playlist.txt  [timelist.txt] ");
+        return -1;
+    }
+
+    // 时间戳数据
+    Timestamp one = { "00:08", "01:18", "Audiomachine - Age of Dragons" };
+
+    std::vector<Timestamp>  vec_one;
+    char line[LINE_SIZE]; // 读取一行 字符串
+    char* pch;
+
+    FILE* input = fopen(argv[1], "r");
+    if (input == NULL)
+        return -1;
+
+    while (fgets(line, LINE_SIZE, input)) {
+        if (pch = strtok(line, " \t\n")) {
+            strcpy(one.start, pch);
+            pch = strtok(NULL, "\n\r");
+            pch = strtrim(pch);
+            strcpy(one.name, pch);
+
+            vec_one.push_back(one);   // 把读取的时间戳装载到容器
+        }
+    }
+
+    // 副本偏移,用来修改 Timestamp.end
+    std::vector<Timestamp>  vec_copy = vec_one;
+    vec_copy.push_back(one);
+    vec_copy.erase(vec_copy.begin());
+
+
+    FILE* output;
+    if (3 == argc)  // 如果没有输出文件,输出到屏幕
+        output = fopen(argv[2], "w"); // 输出结果文件
+    else
+        output = stdout;
+
+    for (auto it = vec_one.begin(), itcp = vec_copy.begin(); it != vec_one.end(); ++it, ++itcp) {
+        strcpy(it->end, itcp->start);
+        fprintf(output, "::M4ACUT::  %s  %s  \"%s.m4a\"\n", it->start, it->end, it->name);
+    }
+
+    fprintf(output, "\n## 注意最后一行时间结束时间要手工修改  ##");
+
+    return 0;
+}
+
+// strtrim 去掉字符串前后的空格和制表符
+char* strtrim(char* s)
+{
+    char* p = s;
+    while (isspace(*p))
+        ++p;
+
+    char* end = s + strlen(s) - 1;
+    while (isspace(*end))
+        --end;
+    *(end + 1) = '\0';
+
+    strcpy(s, p);
+    return s;
+}