C语言内存管理


1. 静态开辟

  • 定义:编译时分配固定大小的内存,生命周期与程序一致。

  • 特点: 在栈或全局区分配(如全局变量、局部数组)。 大小不可变,自动释放(栈变量在函数结束时释放)。

  • 示例

    int arr[100];          // 全局区(静态内存)
    void func() {
        char str[50];      // 栈区(函数结束时自动释放)
    }
    

2. 动态开辟

  • 定义:运行时通过函数手动分配堆内存,需显式释放。

  • 核心函数malloc(size):分配未初始化的内存。 calloc(num, size):分配并初始化为0。 free(ptr):释放内存。

  • 示例

    int *p = (int*)malloc(10 * sizeof(int)); // 分配10个int的空间
    if (p == NULL) { /* 处理失败 */ }
    free(p); // 释放
    

3. 动态开辟的使用场景

  • 适用情况未知数据大小:如运行时输入的数组长度。 大内存需求:栈空间不足时(如大型矩阵)。 灵活生命周期:需跨函数长期存在的数据。 数据结构:链表、树等动态结构。
  • 反面案例: 小规模、生命周期短的变量应优先用栈内存。

4. 动态开辟之realloc

  • 作用:调整已分配内存块的大小(扩大或缩小)。

  • 原型

    void *realloc(void *ptr, size_t new_size);
    
  • 行为成功:返回新指针(可能原地扩展或重新分配)。 失败:返回NULL,原指针仍有效。

  • 示例

    int *p = (int*)malloc(5 * sizeof(int));
    p = (int*)realloc(p, 10 * sizeof(int)); // 扩容到10个int
    if (p == NULL) { /* 处理失败 */ }
    free(p);
    
  • 注意事项: 若realloc失败,需保留原指针避免内存泄漏。 缩小时可能原地调整,无需移动数据。


小结

特性 静态开辟 动态开辟
分配时机 编译时 运行时
内存区域 栈/全局区
大小 固定 可调整(realloc
释放方式 自动(栈)/程序结束(全局) 手动(free
适用场景 已知大小、短生命周期 未知大小、长生命周期

C语言字符串

以下是C语言中关于字符串操作的知识点总结:


2. 字符串的两种形式

  1. 字符数组(可修改):

    char str1[] = "Hello"; // 栈区,可修改
    str1[0] = 'h'; // 合法
    
  2. 字符指针(常量字符串,不可修改):

    char *str2 = "World"; // 常量区,不可修改
    // str2[0] = 'w'; // 非法操作,会导致段错误
    

3. 指针挪动获取字符串信息(手写API)

通过指针遍历字符串:

// 示例:手写strlen函数(求字符串长度)
int my_strlen(const char *str) {
    int len = 0;
    while (*str != '\0') { // 指针遍历到'\0'结束
        len++;
        str++; // 指针后移
    }
    return len;
}

// 调用
char *s = "Hello";
printf("Length: %d\n", my_strlen(s)); // 输出5

4. 字符串的比较

  • 标准库函数strcmp(str1, str2)
    返回值:

    • 0:字符串相等。
    • >0str1 大于 str2(按ASCII码)。
    • <0str1 小于 str2
  • 手写实现

    int my_strcmp(const char *s1, const char *s2) {
        while (*s1 == *s2) {
            if (*s1 == '\0') return 0; // 同时结束
            s1++;
            s2++;
        }
        return *s1 - *s2; // 返回差值
    }
    

5. 字符串查找、包含与拼接

  1. 查找字符strchr(str, ch)
    返回首次出现字符 ch 的指针,未找到返回 NULL
  2. 查找子串strstr(str, substr)
    返回首次出现子串的位置。
  3. 字符串拼接strcat(dest, src)
    src 拼接到 dest 末尾(需确保 dest 空间足够)。
  • 手写拼接函数

    void my_strcat(char *dest, const char *src) {
        while (*dest != '\0') dest++; // 找到dest结尾
        while (*src != '\0') {
            *dest = *src; // 逐个复制
            dest++;
            src++;
        }
        *dest = '\0'; // 添加结束符
    }
    

6. 大小写转换(手写API)

  • 库函数toupper(ch)tolower(ch)(需包含 <ctype.h>)。

  • 手写实现

    // 转大写
    void to_upper(char *str) {
        while (*str != '\0') {
            if (*str >= 'a' && *str <= 'z') {
                *str -= 32; // ASCII码差值
            }
            str++;
        }
    }
    
    // 转小写
    void to_lower(char *str) {
        while (*str != '\0') {
            if (*str >= 'A' && *str <= 'Z') {
                *str += 32;
            }
            str++;
        }
    }
    
    // 调用
    char s[] = "Hello";
    to_upper(s); // "HELLO"
    

小结

  1. 字符串形式:字符数组(可修改) vs 字符指针(常量)。
  2. 指针操作:通过指针挪动遍历字符串(如手写 strlen)。
  3. 比较strcmp 或手写实现,注意返回值含义。
  4. 查找与拼接strchrstrstrstrcat 的用法及手写逻辑。
  5. 大小写转换:基于ASCII码的差值计算(A=65a=97)。

C语言中关于结构体、枚举等


2. 结构体定义与使用

  • 定义:结构体是用户自定义的复合数据类型,用于将不同类型的数据组合在一起。

    struct Student {
        int id;
        char name[20];
        float score;
    };
    
  • 声明变量

    struct Student stu1; // 直接声明
    struct Student stu2 = {1001, "Alice", 90.5}; // 初始化
    
  • 访问成员:使用 . 运算符。

    stu1.id = 1002;
    printf("Name: %s\n", stu2.name);
    

3. 结构体指针与动态内存开辟

  • 结构体指针:指向结构体变量的指针。

    struct Student *pStu = &stu1;
    pStu->id = 1003; // 通过指针访问成员(等价于 (*pStu).id)
    
  • 动态内存开辟:使用 malloccalloc 动态分配内存。

    struct Student *p = (struct Student*)malloc(sizeof(struct Student));
    p->score = 85.0;
    free(p); // 释放内存
    

4. 结构体数组

  • 定义和初始化:

    struct Student class[3] = {
        {1001, "Bob", 78.5},
        {1002, "Tom", 92.0},
        {1003, "Lily", 88.5}
    };
    
  • 访问元素:

    printf("%s's score: %.1f\n", class[1].name, class[1].score);
    

5. 结构体与结构体指针取别名

  • 使用 typedef 简化类型名:

    typedef struct Student {
        int id;
        char name[20];
    } Student; // 结构体别名
    
    Student stu; // 直接使用别名声明变量
    
  • 结构体指针别名:

    typedef struct Student* PStudent; // 指针别名
    PStudent p = &stu;
    

6. 枚举(enum)

  • 定义:枚举是一种用于定义命名常量的数据类型。

    enum Weekday {Mon=1, Tue, Wed, Thu, Fri, Sat, Sun};
    
  • 使用

    enum Weekday today = Wed;
    if (today == Wed) printf("Today is Wednesday!\n");
    
  • 特点: 枚举常量默认从 0 开始递增,可显式赋值(如 Mon=1)。 常用于提高代码可读性(如状态码、选项等)。


小结

  1. 结构体:组合异构数据,通过 .-> 访问成员。
  2. 动态内存:需手动管理(malloc/free),指针操作高效但需防内存泄漏。
  3. 结构体数组:连续存储,适合批量处理数据。
  4. 别名typedef 简化复杂类型声明。
  5. 枚举:增强可读性,限制变量取值范围。

C语言文件操作及加密解密


1. 文件操作基础

  • 文件指针FILE *类型,用于操作文件。
  • 打开模式"r":只读(文件必须存在)。 "w":只写(覆盖创建)。 "a":追加(文件不存在则创建)。 "rb"/"wb":二进制模式。

2. 文件的读

  • 核心函数fopen():打开文件。 fgetc():逐字符读取。 fgets():逐行读取。 fread():二进制读取。 fclose():关闭文件。

  • 示例

    FILE *fp = fopen("test.txt", "r");
    if (fp == NULL) { perror("Error"); return; }
    
    char buffer[100];
    while (fgets(buffer, 100, fp) != NULL) { // 逐行读取
        printf("%s", buffer);
    }
    fclose(fp);
    

3. 文件的写

  • 核心函数fputc():逐字符写入。 fputs():写入字符串。 fwrite():二进制写入。

  • 示例

    FILE *fp = fopen("output.txt", "w");
    if (fp == NULL) { perror("Error"); return; }
    
    fputs("Hello, World!\n", fp); // 写入字符串
    fclose(fp);
    

4. 文件复制

  • 逐字节复制

    FILE *src = fopen("source.txt", "rb");
    FILE *dest = fopen("dest.txt", "wb");
    if (!src || !dest) { perror("Error"); return; }
    
    char ch;
    while ((ch = fgetc(src)) != EOF) { // 逐字节复制
        fputc(ch, dest);
    }
    fclose(src);
    fclose(dest);
    

5. 获取文件大小

  • 方法1:fseek + ftell

    FILE *fp = fopen("test.txt", "rb");
    if (fp == NULL) { perror("Error"); return; }
    
    fseek(fp, 0, SEEK_END); // 移动到文件末尾
    long size = ftell(fp);  // 获取当前位置(即大小)
    fclose(fp);
    printf("Size: %ld bytes\n", size);
    

6. 文件加密与解密

  • 简单异或加密

    void encrypt_file(const char *filename, const char *key) {
        FILE *fp = fopen(filename, "rb+");
        if (!fp) { perror("Error"); return; }
        
        int key_len = strlen(key);
        int i = 0;
        char ch;
        while ((ch = fgetc(fp)) != EOF) {
            ch ^= key[i % key_len]; // 异或加密
            fseek(fp, -1, SEEK_CUR);
            fputc(ch, fp);
            i++;
        }
        fclose(fp);
    }
    // 调用:encrypt_file("data.txt", "secret");
    
  • 解密:相同操作(异或的自反性)。


7. 字符串密码加密与解密

  • 基于ASCII的加密

    void encrypt_str(char *str, const char *key) {
        int key_len = strlen(key);
        for (int i = 0; str[i] != '\0'; i++) {
            str[i] += key[i % key_len]; // 简单位移加密
        }
    }
    
    void decrypt_str(char *str, const char *key) {
        int key_len = strlen(key);
        for (int i = 0; str[i] != '\0'; i++) {
            str[i] -= key[i % key_len]; // 反向解密
        }
    }
    
    // 调用示例
    char password[] = "mypassword123";
    encrypt_str(password, "key");
    printf("Encrypted: %s\n", password);
    decrypt_str(password, "key");
    printf("Decrypted: %s\n", password);
    

小结

操作 关键函数/方法 注意事项
文件读 fgets, fread 检查文件是否打开成功
文件写 fputs, fwrite 模式"w"会覆盖原内容
文件复制 逐字节或fread/fwrite批量操作 二进制模式避免文本换行符问题
文件大小 fseek(fp, 0, SEEK_END) + ftell 适用于二进制文件
文件加密 异或、位移等算法 密钥管理是关键
字符串加密 ASCII码操作或复杂算法(如AES) 避免简单加密(易被破解)