一文带你彻底理清C 语言核心知识 与 面试高频考点:从栈溢出到指针 全面解析 附带笔者手写2.4k行代码加注释

news2025/5/30 13:44:08

引言:C 语言的魅力与挑战

从操作系统内核到嵌入式系统,从高性能计算到网络编程,C 语言高效、灵活和贴近硬件的特性,始终占据着不可替代的地位。然而,C 语言的强大也伴随着较高的学习曲线,尤其是指针、内存管理和复杂数据结构的操作  经常搞得人晕.....

哥们这次特地基于 700 余行实战代码,系统梳理 C 语言的核心知识点,从基础语法到高级应用,从内存模型到算法实现,帮助读者建立完整的 C 语言知识体系。

第一部分:笔者搜集的 csdn牛客里扣github博客园开源中国等头部技术论坛的大厂考点实战:

一、基础概念与内存模型

1. 指针与数组的本质区别

题目:以下关于指针与数组的说法错误的是( )
A. 数组名在多数情况下会衰退为指针
B. sizeof(arr)返回数组总字节数,sizeof(ptr)返回指针字节数
C. 指针可以进行算术运算,数组名不能
D. 数组元素存储在堆上,指针变量存储在栈上

解析
答案 D。数组元素存储位置取决于定义方式:局部数组在栈上,全局 / 静态数组在数据段,动态分配的数组在堆上。指针变量本身是变量,存储位置由定义位置决定(局部指针在栈,全局指针在数据段)。

关键点

  • 数组名衰退规则:除sizeof(arr)&arr外,数组名会衰退为指向首元素的指针
  • 指针算术运算本质是地址偏移,步长由指向类型决定

2. 指针大小与平台相关性

题目:在 64 位 Linux 系统中,以下指针类型的大小分别是( )

int *p1; 
void **p2; 
char (*p3)[10]; 
int (*p4)(int, int);

A. 8,8,8,8 B. 4,8,8,4 C. 8,16,8,8 D. 4,4,4,4

解析
答案 A。在 64 位系统中,所有指针类型(包括函数指针、多级指针、数组指针)的大小均为 8 字节,与指向类型无关。

关键点

  • 指针大小仅由操作系统位数决定:32 位 4 字节,64 位 8 字节
  • 函数指针、数组指针本质仍是指针,遵循相同大小规则

3. 野指针成因与危害

题目:以下代码会产生野指针的是( )

A. int *p; *p = 10;
B. int *p = (int*)malloc(sizeof(int)); free(p); p = NULL;
C. int arr[5], *p = arr; p += 5;
D. int *p = &(int){10};

解析
答案 A、C、D。

  • A:未初始化的指针直接解引用,是典型野指针
  • C:指针超出数组边界,指向无效内存
  • D:临时变量地址在表达式结束后失效,形成野指针

关键点
野指针常见成因:

  1. 未初始化的指针
  2. 释放后未置 NULL 的指针
  3. 越界访问的指针
  4. 指向临时变量的指针

二、指针与数组高级操作

4. 二维数组与指针运算

题目:对于二维数组int arr[3][4],以下表达式值为arr[1][2]的是( )
A. *(arr + 1 + 2)
B. *(arr [1] + 2)
C. ((arr + 2) + 1)
D. arr[1] + 2

解析
答案 B。

  • arr是指向int[4]的指针,arr[1]等价于*(arr+1),类型为int*
  • arr[1]+2是指向arr[1][2]的指针,解引用后得到值

关键点
二维数组在内存中按行存储,arr[i][j]等价于*(*(arr+i)+j)

5. 指针数组与数组指针辨析

题目:定义int (*p)[5]int *p[5],以下说法正确的是( )
A. 两者都是指针数组,存储 5 个 int * 指针
B. 前者是数组指针,后者是指针数组
C. 前者指针指向 5 个 int,后者数组存储 5 个指针
D. 两者没有区别

解析
答案 B。

  • int (*p)[5]:数组指针,指向包含 5 个 int 的数组
  • int *p[5]:指针数组,包含 5 个 int * 指针

关键点

  • 括号优先级:*p[5][]优先级高于*,先成数组
  • (int (*)[5])中括号改变优先级,先成指针

6. 字符串指针与数组的陷阱

题目:分析以下代码的输出:

void string_trap() {
    char str[] = "hello";
    char *ptr = "world";
    str[0] = 'H';
    ptr[0] = 'W';
}

A. 编译错误
B. 运行时错误(段错误)
C. 正常运行,str 变为 "Hello",ptr 变为 "World"
D. str 变为 "Hello",ptr 指向的字符串不变

解析
答案 B。

  • str是数组,存储在栈上,可以修改
  • ptr指向字符串常量,存储在代码段,修改会导致段错误

关键点

  • 字符串字面量存储在只读区,不能修改
  • 数组名作为左值时可修改元素

三、内存管理与指针操作

7. 动态内存分配与指针

题目:以下代码存在的问题是( )

c

运行

void memory_bug() {
    int *p = (int*)malloc(10 * sizeof(int));
    for (int i=0; i<10; i++) p[i] = i;
    int *q = (int*)realloc(p, 20 * sizeof(int));
    free(p);
}

A. 没有问题
B. realloc 后未检查返回值
C. free (p) 释放了已重新分配的内存
D. 内存泄漏

解析
答案 B、C。

  • realloc 可能失败,需检查返回值
  • realloc 成功时,p 的地址可能改变,原 p 被 q 覆盖后释放 q 才正确

关键点
realloc 使用规范:

void *new_ptr = realloc(old_ptr, new_size);
if (new_ptr) {
    old_ptr = new_ptr;
}

8. 指针与结构体对齐

题目:已知结构体:

struct Data {
    char c;
    int i;
    double d;
};

在 64 位系统中,sizeof(struct Data)的结果是( )
A. 13 B. 16 C. 24 D. 32

解析
答案 B。

  • 64 位系统默认对齐为 8 字节
  • char c占 1,补 3 到 4 字节
  • int i占 4,累计 8 字节
  • double d占 8,累计 16 字节

关键点
结构体对齐规则:

  1. 每个成员按自身大小和对齐参数取最小对齐
  2. 整体大小为最大对齐参数的整数倍

9. 多级指针操作

题目:执行以下代码后,**pp的值是( )

int a = 10, b = 20;
int *p = &a, **pp = &p;
p = &b;

A. 10 B. 20 C. 编译错误 D. 运行时错误

解析
答案 B。

  • pp是指向p的指针,p先指向a,后指向b
  • **pp等价于*p,即b的值

关键点
多级指针本质是指针的指针,解引用次数等于级别数

四、函数指针与回调

10. 函数指针数组实现计算器

题目:用函数指针数组实现四则运算计算器,要求支持+-*/,并处理除零错误。

解析

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

// 函数指针类型
typedef int (*OpFunc)(int, int);

// 四则运算函数
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }
int divide(int a, int b) {
    if (b == 0) {
        printf("错误:除数不能为零\n");
        exit(1);
    }
    return a / b;
}

int main() {
    // 函数指针数组
    OpFunc ops[4] = {add, subtract, multiply, divide};
    char op;
    int a, b;
    
    printf("输入运算(+ - * /): ");
    scanf(" %c", &op);
    printf("输入两个操作数: ");
    scanf("%d %d", &a, &b);
    
    // 根据操作符选择函数
    int idx = -1;
    switch (op) {
        case '+': idx = 0; break;
        case '-': idx = 1; break;
        case '*': idx = 2; break;
        case '/': idx = 3; break;
        default: printf("不支持的操作符\n"); return 1;
    }
    
    printf("结果: %d\n", ops[idx](a, b));
    return 0;
}

关键点

  • 函数指针数组实现多态操作
  • 类型安全检查与错误处理

五、算法与数据结构中的指针应用

11. 链表逆序(指针操作)

题目:用指针操作实现单链表的逆序,要求时间复杂度 O (n),空间复杂度 O (1)。

解析

struct Node {
    int data;
    struct Node *next;
};

struct Node* reverseList(struct Node* head) {
    struct Node *prev = NULL;
    struct Node *current = head;
    struct Node *next = NULL;
    
    while (current != NULL) {
        next = current->next;    // 保存下一个节点
        current->next = prev;   // 反转指针
        prev = current;          // 移动prev
        current = next;          // 移动current
    }
    return prev;  // 新的头节点
}

关键点


三指针法:prevcurrentnext配合实现指针反转

12. 快速排序中的指针应用

题目:用指针操作实现快速排序,要求使用void*指针实现通用排序。

解析

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

// 交换函数
void swap(void *a, void *b, size_t size) {
    char temp[size];
    memcpy(temp, a, size);
    memcpy(a, b, size);
    memcpy(b, temp, size);
}

// 分区函数
int partition(void *base, int low, int high, size_t size, int (*cmp)(const void*, const void*)) {
    void *pivot = (char*)base + high * size;
    int i = low - 1;
    
    for (int j = low; j < high; j++) {
        void *elem = (char*)base + j * size;
        if (cmp(elem, pivot) <= 0) {
            i++;
            swap((char*)base + i * size, elem, size);
        }
    }
    swap((char*)base + (i+1) * size, pivot, size);
    return i + 1;
}

// 快速排序
void quickSort(void *base, int n, size_t size, int (*cmp)(const void*, const void*)) {
    if (n > 1) {
        int pi = partition(base, 0, n-1, size, cmp);
        quickSort(base, pi, size, cmp);
        quickSort((char*)base + (pi+1)*size, n - pi - 1, size, cmp);
    }
}

// 测试
int intCmp(const void *a, const void *b) {
    return *(int*)a - *(int*)b;
}

int main() {
    int arr[] = {3, 1, 4, 1, 5, 9, 2};
    int n = sizeof(arr)/sizeof(arr[0]);
    
    quickSort(arr, n, sizeof(int), intCmp);
    
    for (int i=0; i<n; i++) {
        printf("%d ", arr[i]);
    }
    return 0;
}

关键点

  • void*指针实现通用排序
  • 内存操作函数memcpy实现任意类型交换

六、综合应用与陷阱

13. 指针与数组传参陷阱

题目:以下函数中sizeof(arr)的结果是( )

void func(int arr[10]) {
    printf("%zu\n", sizeof(arr));
}

int main() {
    int arr[5];
    func(arr);
    return 0;
}

A. 20 B. 8 C. 40 D. 编译错误

解析
答案 B。数组作为函数参数时衰退为指针,sizeof(arr)返回指针大小(64 位系统 8 字节)

关键点
数组传参本质是传递指针,无法在函数内获取原始数组大小

14. 指针运算与类型转换

题目:计算以下表达式的值(64 位系统):

char *p = "hello";
int *q = (int*)p;
q += 1;

q - p的值是( )
A. 1 B. 4 C. 8 D. 编译错误

解析
答案 B。int*指针 + 1 偏移 4 字节(int 大小),char*指针偏移 1 字节,差值为 4

关键点
指针算术运算的步长由指向类型决定

15. 内存泄漏检测

题目:找出以下代码中的内存泄漏:

void leak_demo() {
    int *p1 = (int*)malloc(10*sizeof(int));
    int *p2 = (int*)realloc(p1, 20*sizeof(int));
    if (p2 == NULL) return;
    free(p1);
}

解析
realloc成功时,p1指向的内存已被重新分配,原p1地址可能改变,直接free(p1)会释放新分配的内存,导致原内存泄漏。应改为:

int *p2 = realloc(p1, 20*sizeof(int));
if (p2) {
    p1 = p2; // 更新指针
}

七、编程题(大厂算法题)

16. 指针实现字符串拷贝(模拟 strcpy)

题目:用指针操作实现strcpy函数,要求处理边界情况。

解析

char* my_strcpy(char* dest, const char* src) {
    char* res = dest;
    if (dest == NULL || src == NULL) return NULL;
    
    while (*src) {
        *dest++ = *src++;
    }
    *dest = '\0';
    return res;
}

17. 指针实现链表环检测(Floyd 判圈算法)

题目:用指针操作实现链表环检测,要求时间复杂度 O (n),空间复杂度 O (1)。

解析

int hasCycle(struct Node *head) {
    if (head == NULL || head->next == NULL) return 0;
    
    struct Node *slow = head;
    struct Node *fast = head->next;
    
    while (slow != fast) {
        if (fast == NULL || fast->next == NULL) return 0;
        slow = slow->next;
        fast = fast->next->next;
    }
    return 1;
}

18. 指针与内存池设计

题目:设计一个简单内存池,要求:

  1. 预分配一块大内存
  2. 实现内存分配与释放
  3. 避免碎片

解析

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

#define POOL_SIZE 1024*1024  // 1MB内存池

typedef struct {
    char* memory;          // 内存池起始地址
    char* current;         // 当前分配位置
    int size;              // 内存池大小
} MemoryPool;

// 初始化内存池
MemoryPool* initMemoryPool(int size) {
    MemoryPool* pool = (MemoryPool*)malloc(sizeof(MemoryPool));
    if (pool == NULL) return NULL;
    
    pool->memory = (char*)malloc(size);
    if (pool->memory == NULL) {
        free(pool);
        return NULL;
    }
    
    pool->current = pool->memory;
    pool->size = size;
    return pool;
}

// 从内存池分配内存
void* allocFromPool(MemoryPool* pool, int size) {
    if (pool == NULL || size <= 0) return NULL;
    
    if (pool->current + size > pool->memory + pool->size) {
        printf("内存池不足\n");
        return NULL;
    }
    
    void* res = pool->current;
    pool->current += size;
    return res;
}

// 释放内存池
void freeMemoryPool(MemoryPool* pool) {
    if (pool == NULL) return;
    free(pool->memory);
    free(pool);
}

八、系统级指针操作

19. 指针与类型转换(内存复用)

题目:用指针操作实现 int 与 float 的内存互转,要求不使用联合。

解析

20. 指针与位操作(内存映射)

题目:用指针操作实现将整数的第 3 位和第 7 位取反。

解析

int flipBits(int num) {
    // 第3位和第7位掩码
    int mask = (1 << 3) | (1 << 7);
    return num ^ mask;
}

// 指针版本
void flipBitsPtr(int *num) {
    int mask = (1 << 3) | (1 << 7);
    *num ^= mask;
}

面试题解析方法论

大厂指针题核心考点归纳:

  1. 指针本质:地址操作、类型系统、衰退规则
  2. 内存模型:栈堆数据段、对齐规则、生命周期
  3. 算法应用:链表 / 数组操作、排序 / 搜索中的指针技巧
  4. 系统编程:内存管理、函数指针、类型转换
  5. 安全问题:野指针、内存泄漏、越界访问

解题思路:

  1. 画图分析:指针操作时画出内存布局
  2. 类型推导:从定义推导指针类型(如int (*)[5]是数组指针)
  3. 边界测试:空指针、越界、类型转换等边界情况
  4. 内存跟踪:动态分配时跟踪指针变化

这些题目覆盖了腾讯、阿里等大厂面试中指针相关的核心考点,从基础概念到系统级编程,结合算法与数据结构,适合进阶学习和面试准备。建议在理解原理的基础上,动手实现并调试代码,加深对指针本质的理解。

第二部分:相关知识点详解

一、C 语言基础数据结构与内存模型

1.1 结构体:数据组织的基石

结构体是 C 语言中组织复杂数据的核心机制,它允许我们将不同类型的数据组合成一个有机整体。在实际项目中,结构体的设计直接影响程序的效率和可维护性。

// 多用途节点结构体,适用于链表和树结构
struct Node {
    int value;              // 节点存储的值
    struct Node *next;      // 链表中的下一个节点
    struct Node **children; // 树结构中的子节点数组
    int child_count;        // 子节点数量
};

// 人员信息结构体
struct Person {
    char name[10];          // 姓名
    int age;                // 年龄
};

// 栈数据结构实现
struct stack {
    int data[100];          // 栈存储数组
    int top;                // 栈顶指针
};

上述代码定义了三种常用结构体:多用途节点结构体Node、人员信息结构体Person和栈结构体stack。其中Node结构体的设计体现了 C 语言的灵活性 —— 通过next指针可以构建链表,通过children指针数组可以构建树结构,这种 "一结构多用途" 的设计在实际开发中非常常见。

结构体使用实战:栈的实现与应用

栈是计算机科学中最基础的数据结构之一,下面我们通过stack结构体实现一个完整的栈,并演示其基本操作:

#define maxL 99 // 栈的最大容量

// 初始化栈
void initStack(struct stack *s) {
    s->top = -1;
}

// 判断栈是否为空
int isEmpty(struct stack *s) {
    return s->top == -1 ? 1 : -100;
}

// 判断栈是否已满
int isFull(struct stack *s) {
    return s->top == maxL ? 1 : 0;
}

// 入栈操作
void pushStack(struct stack *s, int value) {
    if (!isFull(s)) {
        s->data[++(s->top)] = value;
    }
}

// 出栈操作
int popStack(struct stack *s) {
    if (!isEmpty(s)) {
        return s->data[(s->top)--];
    }
    return -1;
}

// 获取栈顶元素
int peekTop(struct stack *s) {
    return s->data[s->top];
}

// 栈操作测试
void stackTest() {
    struct stack s;
    initStack(&s);
    
    printf("初始化后,栈是否为空: %d\n", isEmpty(&s));
    pushStack(&s, 1);
    printf("压入 1 后,栈是否为空: %d\n", isEmpty(&s));
    pushStack(&s, 2);
    pushStack(&s, 3);
    printf("栈顶元素: %d\n", peekTop(&s));
    pushStack(&s, 4);
    printf("栈顶元素: %d\n", peekTop(&s));
    popStack(&s);
    printf("弹出元素后,栈顶元素: %d\n", peekTop(&s));
}

这段代码完整实现了栈的基本操作:初始化、判空、判满、入栈、出栈和获取栈顶元素。在stackTest函数中,我们演示了栈的基本使用流程。需要注意的是,这里使用数组实现栈,属于顺序栈,其优点是访问效率高,缺点是容量固定。在实际项目中,当需要动态调整栈大小时,可以考虑使用链表实现栈结构。

1.2 指针:C 语言的灵魂

指针是 C 语言的核心特性,也是让许多初学者望而生畏的难点。理解指针,本质上是理解计算机内存的工作原理。

// 野指针成因演示
void wild_pointer_cause() {
    int a = 10;
    int *new_ptr = &a;  // 合法指针,指向变量a
    
    int *heap_ptr = (int *)malloc(sizeof(int)); // 在堆上分配内存
    free(heap_ptr);  // 释放内存,但指针未置为NULL
    // heap_ptr现在成为野指针
    
    int arr[5] = {1, 2, 3, 4, 5};
    int *arr_ptr = arr;
    arr_ptr = arr_ptr + 10;  // 指针越界,成为野指针
}

// 指针关系运算演示
void pointer_relation() {
    int arr[5] = {1, 2, 3, 4, 5};
    int *p1 = arr + 1;  // 指向arr[1]
    int *p2 = arr + 3;  // 指向arr[3]
    
    printf("p1 < p2: %s\n", (p1 < p2) ? "true" : "false");  // 指针地址比较
    printf("p1 == arr+1: %s\n", (p1 == arr + 1) ? "true" : "false");  // 指针相等比较
}

// 指针算术运算演示
void pointer_arithmetic_application() {
    int arr[5] = {1, 2, 3, 4, 5};
    int *p = arr;
    
    for (int i = 0; i < 5; i++) {
        printf("arr[%d] = %d\n", i, *(p + i));  // 通过指针算术访问数组元素
    }
}
指针与数组的爱恨情仇

在 C 语言中,指针与数组的关系极为密切,但又有着本质区别。许多初学者容易混淆两者,导致程序出现难以试的错误。

// 数组与指针区别演示
void array_pointer_question() {
    int arr[5] = {1, 2, 3, 4, 5};
    
    printf("sizeof(arr) = %zu\n", sizeof(arr));       // 输出数组总字节数:5*4=20
    printf("sizeof(arr+0) = %zu\n", sizeof(arr + 0)); // 输出指针字节数:8(64位系统)
}

// 数组指针应用:二维数组操作
void array_ptr_application() {
    int arr[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
    int (*p)[3] = arr;  // 定义指向包含3个int的数组的指针
    
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            // 两种等价的访问方式
            printf("1 the num: %d-%d:%d\n", i, j, *(p + i)[j]);
            printf("2 the num: %d-%d:%d\n", i, j, p[i][j]);
        }
    }
}

1.3 C 语言内存模型:从栈到堆的全景图

理解 C 语言的内存分布是写出高效、稳定程序的关键。C 程序的内存通常分为以下几个区域:

// 内存分布演示
void memory_distribution_demo() {
    // 栈区:存储局部变量
    int localVar = 10;
    char str[20] = "hello";
    
    // 堆区:动态分配的内存
    int *heapPtr = (int *)malloc(sizeof(int));
    if (heapPtr == NULL) {
        printf("内存分配失败\n");
        return;
    }
    *heapPtr = 20;
    
    // 数据段:存储全局变量和静态变量
    static int staticVar = 30;
    
    // 代码段:存储可执行代码和字符串常量
    const char *strConst = "world";
    
    // 释放堆内存
    free(heapPtr);
}

二、字符串处理:从基础函数到自定义实现

字符串是 C 语言中最常用的数据类型之一,熟练掌握字符串处理是 C 程序员的基本素养。标准库提供了丰富的字符串函数,但了解其实现原理能帮助我们更好地使用它们。

2.1 字符串处理函数的自定义实现

c

// 自定义strstr函数:查找子字符串
char *my_strstr(const char *haystack, const char *needle) {
    assert(haystack != NULL && needle != NULL);
    
    // 空字符串特殊处理
    if (*needle == '\0') {
        return NULL;
    }
    
    while (*haystack) {
        const char *h = haystack;
        const char *n = needle;
        
        // 逐个字符比较
        while (*h && *n && *h == *n) {
            h++;
            n++;
        }
        
        // 找到匹配的子字符串
        if (*n == '\0') {
            return (char *)haystack;
        }
        
        haystack++;
    }
    
    return NULL;
}

// 自定义strncpy函数:复制指定长度的字符串
char *my_strncpy(char *dest, const char *src, size_t n) {
    assert(dest != NULL && src != NULL);
    
    size_t i = 0;
    char *tempPtr = dest;
    
    // 复制src中的字符,直到n个或遇到'\0'
    while (i < n && src[i]) {
        dest[i] = src[i];
        i++;
    }
    
    // 填充剩余位置为'\0'
    while (i < n) {
        dest[i] = '\0';
        i++;
    }
    
    return tempPtr;
}

// 自定义strncat函数:连接指定长度的字符串
char *my_strncat(char *dest, const char *src, size_t n) {
    assert(dest != NULL && src != NULL);
    
    size_t len = strlen(dest);
    char *res = dest;
    size_t i;
    
    // 连接src中的字符,直到n个或遇到'\0'
    for (i = 0; i < n && src[i] != '\0'; i++) {
        dest[len + i] = src[i];
    }
    
    dest[i + len] = '\0';
    return res;
}

// 自定义strncmp函数:比较指定长度的字符串
int my_strncmp(const char *s1, const char *s2, size_t n) {
    assert(s1 != NULL && s2 != NULL);
    
    for (size_t i = 0; i < n; i++) {
        char c1 = s1[i] ? s1[i] : '\0';
        char c2 = s2[i] ? s2[i] : '\0';
        
        if (c1 != c2) {
            return c1 - c2;
        }
    }
    
    return 0;
}

// 自定义strchr函数:查找字符
char *my_strchr(const char *str, int c) {
    assert(str != NULL);
    
    const char *p = str;
    while (*p) {
        if (*p == (char)c) {
            return (char *)p;
        }
        p++;
    }
    
    // 处理查找'\0'的情况
    return (char)c == '\0' ? (char *)p : NULL;
}

2.2 字符串处理实战:从复制到拼接

c

// 字符串复制函数
char *str_copy(const char *src) {
    // 分配内存,+1用于存储'\0'
    char *dest = (char *)malloc(strlen(src) + 1);
    assert(dest != NULL);
    
    char *p = dest;
    while (*src) {
        *p++ = *src++;
    }
    *p = '\0';
    
    return dest;
}

// 字符串拼接演示
void char_ptr_application() {
    char str1[20] = "Hello";
    char *str2 = ", World!";
    char *p1 = str1 + strlen(str1);
    char *p2 = str2;
    
    // 手动拼接字符串
    while (*p2) {
        *p1++ = *p2++;
    }
    *p1 = '\0';
    
    printf("拼接后的字符串: %s\n", str1);
}

// 字符串处理函数测试
void string_functions_test() {
    printf("字符串复制测试:\n");
    char *copy = str_copy("test");
    printf("复制后的字符串: %s\n", copy);
    free(copy);
    
    printf("\nstrncpy测试:\n");
    char dest[10] = {0};
    my_strncpy(dest, "hello", 3);
    printf("复制结果: %s\n", dest);
    
    printf("\nstrncat测试:\n");
    char dest_cat[10] = {'1', '2', '\0'};
    char src_cat[] = {'3', '4', 'a', '\0'};
    my_strncat(dest_cat, src_cat, 3);
    printf("拼接结果: %s\n", dest_cat);
    
    printf("\nstrncmp测试:\n");
    char s1[] = {'1', '2', '3', '5'};
    char s2[] = {'1', '2', '3'};
    printf("比较结果: %d\n", my_strncmp(s1, s2, 4));
    
    printf("\nstrchr测试:\n");
    char test[] = {'1', '2', 'a', 'b', 'c', 'f', 'l', '6', '\0'};
    char *res = my_strchr(test, 'a');
    if (res) {
        printf("找到字符 'a',位置: %ld\n", res - test);
    } else {
        printf("未找到字符\n");
    }
    
    printf("\nstrstr测试:\n");
    char haystack[] = {'1', 'a', '2', 'b', 'c', '\0'};
    char needle[] = {'b', 'c', '\0'};
    char *strstr_res = my_strstr(haystack, needle);
    if (strstr_res) {
        printf("找到子字符串,位置: %ld\n", strstr_res - haystack);
    } else {
        printf("未找到子字符串\n");
    }
}

三、算法与数据结构:从排序到递归

3.1 排序算法:快速排序的实现与优化

快速排序是实践中常用的高效排序算法,其平均时间复杂度为 O (n log n)。

c

// 交换函数
void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 快速排序的分区函数
int partition(int arr[], int low, int high) {
    int pi = arr[high];  // 选择最后一个元素作为基准
    int i = low - 1;     // 小于基准的元素的边界
    
    for (int j = low; j <= high - 1; j++) {
        // 如果当前元素小于等于基准
        if (arr[j] <= pi) {
            i++;
            swap(&arr[i], &arr[j]);
        }
    }
    swap(&arr[i + 1], &arr[high]);
    return i + 1;
}

// 快速排序主函数
void quick_Sort(int arr[], int low, int high) {
    if (low < high) {
        // 分区操作,返回基准的正确位置
        int pi = partition(arr, low, high);
        
        // 递归排序基准左侧和右侧
        quick_Sort(arr, low, pi - 1);
        quick_Sort(arr, pi + 1, high);
    }
}

// 快速排序测试
void quick_sort_test() {
    int arr[] = {4, 5, 6, 7, 15, 234, 46, 698, 238, 258, 45, 2, 36, 26, 123, 77, 5, 48, 45, 2, 5, 325, 32, 1, 6};
    int len = sizeof(arr) / sizeof(arr[0]);
    
    printf("排序前数组:\n");
    for (int i = 0; i < len; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    
    quick_Sort(arr, 0, len - 1);
    
    printf("排序后数组:\n");
    for (int i = 0; i < len; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

3.2 递归算法:斐波那契数列的实现

递归是一种强大的算法思想,常用于解决可以分解为相似子问题的场景。

c

// 斐波那契数列递归实现
int fibonacci(int n) {
    if (n <= 1) {
        return n;
    }
    return fibonacci(n - 1) + fibonacci(n - 2);
}

// 斐波那契数列测试
void fibonacci_test() {
    printf("fib(5) = %d\n", fibonacci(5));   // 输出5
    printf("fib(10) = %d\n", fibonacci(10)); // 输出55
}

// 递归优化思路:记忆化搜索
int fibonacci_memo(int n, int *memo) {
    if (n <= 1) {
        return n;
    }
    
    // 如果已经计算过,直接返回结果
    if (memo[n] != -1) {
        return memo[n];
    }
    
    // 计算并存储结果
    memo[n] = fibonacci_memo(n - 1, memo) + fibonacci_memo(n - 2, memo);
    return memo[n];
}

// 记忆化搜索测试
void fibonacci_memo_test() {
    int n = 20;
    int *memo = (int *)calloc(n + 1, sizeof(int));
    for (int i = 0; i <= n; i++) {
        memo[i] = -1;
    }
    
    printf("fib_memo(20) = %d\n", fibonacci_memo(20, memo));
    free(memo);
}

四、高级主题:从函数指针到内存管理

4.1 函数指针:C 语言的回调机制

函数指针是 C 语言中实现回调机制的基础,也是许多高级特性的基石。

c

// 加法函数
int add(int a, int b) {
    return a + b;
}

// 减法函数
int subtract(int a, int b) {
    return a - b;
}

// 函数指针数组演示
void func_ptr_array_demo() {
    // 定义函数指针类型
    typedef int (*OpFunc)(int, int);
    
    // 创建函数指针数组
    OpFunc ops[] = {add, subtract};
    
    printf("5+3=%d\n", ops[0](5, 3));
    printf("5-3=%d\n", ops[1](5, 3));
}

// 回调函数示例:排序比较函数
int compare_asc(const void *a, const void *b) {
    return *(int *)a - *(int *)b;
}

// qsort函数使用演示
void qsort_demo() {
    int arr[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};
    int n = sizeof(arr) / sizeof(arr[0]);
    
    printf("排序前:\n");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    
    // 使用qsort排序,传入比较函数
    qsort(arr, n, sizeof(int), compare_asc);
    
    printf("排序后:\n");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

4.2 内存管理:从 malloc 到野指针防护

c

// 动态内存分配演示
int *dy_alloc(int n) {
    int *res = (int *)malloc(n * sizeof(int));
    if (res == NULL) {
        printf("内存分配失败\n");
        exit(1);
    }
    
    // 初始化分配的内存
    for (int i = 0; i < n; i++) {
        res[i] = i;
    }
    
    return res;
}

// 内存管理最佳实践
void memory_management_best_practices() {
    // 分配内存
    int *ptr1 = (int *)malloc(10 * sizeof(int));
    if (ptr1 == NULL) {
        printf("内存分配失败\n");
        return;
    }
    
    // 使用内存
    for (int i = 0; i < 10; i++) {
        ptr1[i] = i;
    }
    
    // 重新分配内存
    int *ptr2 = (int *)realloc(ptr1, 20 * sizeof(int));
    if (ptr2 == NULL) {
        free(ptr1);
        printf("内存重新分配失败\n");
        return;
    }
    ptr1 = ptr2; // 更新指针
    
    // 继续使用内存
    for (int i = 10; i < 20; i++) {
        ptr1[i] = i;
    }
    
    // 释放内存
    free(ptr1);
    ptr1 = NULL; // 置为NULL,避免野指针
}

// 野指针防护策略
void wild_pointer_prevention() {
    int *ptr = NULL; // 初始化为NULL
    
    // 分配内存
    ptr = (int *)malloc(sizeof(int));
    if (ptr == NULL) {
        printf("内存分配失败\n");
        return;
    }
    
    // 使用内存
    *ptr = 10;
    
    // 释放内存
    free(ptr);
    ptr = NULL; // 释放后立即置为NULL
    
    // 安全检查
    if (ptr != NULL) {
        // 不会执行到这里
    }
}

五、综合实战:从链表到矩阵操作

5.1 链表操作:从创建到遍历

c

// 在链表头部插入节点
struct Node *insert_atFirst(struct Node *head, int data) {
    struct Node *n_node = (struct Node *)malloc(sizeof(struct Node));
    if (n_node == NULL) {
        printf("内存分配失败\n");
        return head;
    }
    
    n_node->value = data;
    n_node->next = head;
    n_node->children = NULL;
    n_node->child_count = 0;
    
    return n_node;
}

// 打印链表
void print_List(struct Node *node) {
    struct Node *l = node;
    while (l != NULL) {
        printf("链表节点值: %d\n", l->value);
        l = l->next;
    }
}

// 链表操作测试
void linked_list_test() {
    struct Node *head_node = NULL;
    
    head_node = insert_atFirst(head_node, 123);
    printf("第一个节点值: %d\n", head_node->value);
    
    head_node = insert_atFirst(head_node, 4);
    printf("第二个节点值: %d\n", head_node->value);
    
    print_List(head_node);
}

5.2 矩阵操作:转置与拼接

c

// 矩阵转置
void zhuanzhi(int arr[3][3]) {
    for (int i = 0; i < 3; i++) {
        for (int j = i + 1; j < 3; j++) {
            int temp = arr[i][j];
            arr[i][j] = arr[j][i];
            arr[j][i] = temp;
        }
    }
}

// 矩阵转置测试
void matrix_transpose_test() {
    int arr[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
    
    printf("转置前矩阵:\n");
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
    
    zhuanzhi(arr);
    
    printf("转置后矩阵:\n");
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}

// 字符串拼接
void connect_char(char *a, char *b) {
    char *p1 = a + strlen(a);
    while (*b) {
        *p1 = *b;
        p1++;
        b++;
    }
    *p1 = '\0';
}

// 字符串拼接测试
void string_concatenation_test() {
    char char1[] = {'1', '2', '3', '4', '5', 'a', '\0'};
    char char2[] = {'b', '9', '\0'};
    
    printf("拼接前char1: %s, char2: %s\n", char1, char2);
    
    connect_char(char1, char2);
    
    printf("拼接后char1: %s\n", char1);
}

六、C 语言编程最佳实践与常见陷阱

6.1 编程规范与最佳实践

c

// 命名规范演示
#define MAX_STACK_SIZE 100 // 宏定义使用全大写
typedef struct {
    int data[MAX_STACK_SIZE];
    int top;
} Stack; // 结构体类型使用大写开头

// 函数命名使用小写加下划线
Stack* stack_create() {
    Stack *s = (Stack *)malloc(sizeof(Stack));
    if (s == NULL) {
        return NULL;
    }
    s->top = -1;
    return s;
}

// 注释规范
int calculate_sum(int a, int b) {
    // 计算两个整数的和
    return a + b;
}

// 代码缩进与格式
if (n > 0) {
    for (int i = 0; i < n; i++) {
        if (arr[i] > 0) {
            process(arr[i]);
        }
    }
} else {
    printf("n 必须为正数\n");
}

6.2 常见陷阱与解决方案

c

// 数组越界陷阱
void array_overflow_trap() {
    int arr[5] = {1, 2, 3, 4, 5};
    int *ptr = arr;
    
    // 危险操作:越界访问
    for (int i = 0; i < 10; i++) {
        printf("%d ", ptr[i]); // 访问越界,行为未定义
    }
}

// 内存泄漏陷阱
void memory_leak_trap() {
    while (1) {
        int *ptr = (int *)malloc(sizeof(int));
        // 忘记调用free(ptr)
    }
}

// 空指针解引用陷阱
void null_pointer_dereference() {
    int *ptr = NULL;
    *ptr = 10; // 空指针解引用,程序崩溃
}

// 解决方案:防御性编程
void defensive_programming() {
    int *ptr = (int *)malloc(sizeof(int));
    if (ptr == NULL) {
        printf("内存分配失败\n");
        return;
    }
    
    *ptr = 10;
    
    // 使用指针前检查是否为NULL
    if (ptr != NULL) {
        printf("ptr的值: %d\n", *ptr);
    }
    
    free(ptr);
    ptr = NULL; // 释放后置为NULL
}

七、总结+本人vscode本地编辑的源码:


 

1 附录1 指针知识代码源码:

2  附录代码2 :指针知识点总结
 

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

// 结构体定义
// 定义 Node 结构体,用于多级指针操作和链表
struct Node
{
    int value;              // 节点值
    struct Node *next;      // 指向下一个节点的指针(用于链表)
    struct Node **children; // 子节点数组(用于多级指针)
    int child_count;        // 子节点数量
};

// 定义 Person 结构体,包含姓名和年龄
struct Person
{
    char name[10];
    int age;
};

// 定义栈结构体
struct stack
{
    int data[100]; // 假设栈最大容量为 100
    int top;
};

#define maxL 99 // 栈的最大容量

// 函数声明
// 栈操作函数
void initStack(struct stack *s);
int isEmpty(struct stack *s);
int isFull(struct stack *s);
void pushStack(struct stack *s, int value);
int popStack(struct stack *s);
int peekTop(struct stack *s);

// 链表操作函数
struct Node *insert_atFirst(struct Node *head, int data);
void print_List(struct Node *node);

// 排序函数
void quick_Sort(int arr[], int low, int high);
int partition(int arr[], int low, int high);
void swap(int *a, int *b);

// 字符串处理函数
char *my_strstr(const char *haystack, const char *needle);
char *my_strncpy(char *dest, const char *src, size_t n);
char *my_strncat(char *dest, const char *src, size_t n);
int my_strncmp(const char *s1, const char *s2, size_t n);
char *my_strchr(const char *str, int c);
char *str_copy(const char *src);

// 其他函数
void wild_pointer_cause();
void pointer_relation();
void pointer_arithmetic_application();
void array_pointer_question();
void array_ptr_application();
void char_ptr_application();
void const_pointer();
void value_pass_application();
void pass_row_ptr(int arr[][3], int rows);
void qsort_demo();
void func_ptr_array();
void testFuncPtr();
int fibonacci(int n);
void test_fibonacci();
void typedef_usage();
void dachangmianshi3();
void test_strncpy();
void test_str_cat();
void test_strncmp();
void test_strchar();
void test_strstr();
void arrSort(char *arr[], int n);
int *dy_alloc(int n);
void add_child(struct Node *parent, struct Node *child);
void *arrtoPtrInt(void *x);
int getStrLen(char *a);
char *strCpyFn(char *dest, char *src);
void zhuanzhi(int arr[3][3]);
void connect_char(char *a, char *b);

// 加法函数
int add(int a, int b);
// 减法函数
int subtract(int a, int b);
// 测试字符串复制函数
void test_str_copy();

// 栈操作函数实现
// 初始化栈
void initStack(struct stack *s)
{
    s->top = -1;
}

// 判断栈是否为空
int isEmpty(struct stack *s)
{
    return s->top == -1 ? 1 : -100;
}

// 判断栈是否已满
int isFull(struct stack *s)
{
    return s->top == maxL ? 1 : 0;
}

// 入栈操作
void pushStack(struct stack *s, int value)
{
    if (!isFull(s))
    {
        s->data[++(s->top)] = value;
    }
}

// 出栈操作
int popStack(struct stack *s)
{
    if (!isEmpty(s))
    {
        return s->data[(s->top)--];
    }
    return -1;
}

// 获取栈顶元素
int peekTop(struct stack *s)
{
    return s->data[s->top];
}

// 链表操作函数实现
// 在链表头部插入节点
struct Node *insert_atFirst(struct Node *head, int data)
{
    struct Node *n_node = (struct Node *)malloc(sizeof(struct Node));
    n_node->value = data;    // 修改为正确的成员名 value
    n_node->next = head;     // 使用正确的成员名 next
    n_node->children = NULL; // 初始化子节点数组
    n_node->child_count = 0; // 初始化子节点数量
    return n_node;
}

// 打印链表节点数据
void print_List(struct Node *node)
{
    struct Node *l = node;
    while (l != NULL)
    {
        printf("当前打印的节点:数据为 %d \n", l->value); // 修改为正确的成员名 value
        l = l->next;                                      // 使用正确的成员名 next
    }
}

// 排序函数实现
// 快速排序
void quick_Sort(int arr[], int low, int high)
{
    if (low < high)
    {
        int pi = partition(arr, low, high);
        quick_Sort(arr, low, pi - 1);
        quick_Sort(arr, pi + 1, high);
    }
}

// 分区函数
int partition(int arr[], int low, int high)
{
    int pi = arr[high];
    int i = low - 1;
    for (int j = low; j <= high - 1; j++)
    {
        if (arr[j] <= pi)
        {
            i++;
            swap(&arr[i], &arr[j]);
        }
    }
    swap(&arr[i + 1], &arr[high]);
    return i + 1;
}

// 交换两个整数的值
void swap(int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 字符串处理函数实现
// 自定义 strstr 函数,查找子字符串
char *my_strstr(const char *haystack, const char *needle)
{
    assert(haystack != NULL && needle != NULL);
    if (*needle == '\0')
    {
        return NULL;
    }
    while (*haystack)
    {
        const char *h = haystack;
        const char *n = needle;
        while (*h && *n && *h == *n)
        {
            h++;
            n++;
        }
        if (*n == '\0')
        {
            return (char *)haystack;
        }
        haystack++;
    }
    return NULL;
}

// 自定义 strncpy 函数,复制指定长度的字符串
char *my_strncpy(char *dest, const char *src, size_t n)
{
    assert(dest != NULL && src != NULL);
    size_t i = 0;
    char *tempPtr = dest;
    while (i < n && src[i])
    {
        dest[i] = src[i];
        i++;
    }
    while (i < n)
    {
        dest[i] = '\0';
        i++;
    }
    return tempPtr;
}

// 自定义 strncat 函数,连接指定长度的字符串
char *my_strncat(char *dest, const char *src, size_t n)
{
    assert(dest != NULL && src != NULL);
    size_t len = strlen(dest);
    char *res = dest;
    size_t i;
    for (i = 0; i < n && src[i] != '\0'; i++)
    {
        dest[len + i] = src[i];
    }
    dest[i + len] = '\0';
    return res;
}

// 自定义 strncmp 函数,比较指定长度的字符串
int my_strncmp(const char *s1, const char *s2, size_t n)
{
    assert(s1 != NULL && s2 != NULL);
    for (size_t i = 0; i < n; i++)
    {
        char c1 = s1[i] ? s1[i] : '\0';
        char c2 = s2[i] ? s2[i] : '\0';
        if (c1 != c2)
        {
            return c1 - c2;
        }
    }
    return 0;
}

// 自定义 strchr 函数,查找字符
char *my_strchr(const char *str, int c)
{
    printf("\n___>>>>> in the strchar func!!\n");
    assert(str != NULL);
    const char *p = str;
    while (*p)
    {
        if (*p == (char)c)
        {
            return (char *)p;
        }
        p++;
    }
    return (char)c == '\0' ? (char *)p : NULL;
}

// 自定义字符串复制函数
char *str_copy(const char *src)
{
    char *dest = (char *)malloc(strlen(src) + 1);
    assert(dest != NULL);
    char *p = dest;
    while (*src)
    {
        *p++ = *src++;
    }
    *p = '\0';
    return dest;
}

// 其他函数实现
// 演示野指针的成因
void wild_pointer_cause()
{
    int a = 10;
    int *new_ptr = &a;

    int *heap_ptr = (int *)malloc(sizeof(int));
    free(heap_ptr);

    int arr[5] = {1, 2, 3, 4, 5};
}

// 演示指针的关系运算
void pointer_relation()
{
    int arr[5] = {1, 2, 3, 4, 5};
    int *p1 = arr + 1;
    int *p2 = arr + 3;
    printf("p1 < p2: %s\n", (p1 < p2) ? "true" : "false");
    printf("p1 == arr+1: %s\n", (p1 == arr + 1) ? "true" : "false");
}

// 演示指针的算术运算
void pointer_arithmetic_application()
{
    int arr[5] = {1, 2, 3, 4, 5};
    int *p = arr;
    for (int i = 0; i < 5; i++)
    {
        printf("arr[%d] = %d\n", i, *(p + i));
    }
}

// 演示数组指针相关问题
void array_pointer_question()
{
    int arr[5] = {1, 2, 3, 4, 5};
    printf("sizeof(arr) = %zu\n", sizeof(arr));
    printf("sizeof(arr+0) = %zu\n", sizeof(arr + 0));
}

// 演示数组指针的应用
void array_ptr_application()
{
    int arr[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
    int (*p)[3] = arr;
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            printf("1 the numn :%d-%d:%d\n", i, j, *(p + i)[j]);
            printf("2 the num: %d -%d : %d\n", i, j, p[i][j]);
        }
    }
    printf("\n");
}

// 演示字符指针的应用
void char_ptr_application()
{
    char str1[20] = "Hello";
    char *str2 = ", World!";
    char *p1 = str1 + strlen(str1);
    char *p2 = str2;
    while (*p2)
    {
        *p1++ = *p2++;
    }
    *p1 = '\0';
    printf("拼接后的字符串: %s\n", str1);
}

// 演示 const 指针的使用
void const_pointer()
{
    int val = 10;
    const int *cp1 = &val;
    int *const cp2 = &val;
}

// 演示值传递的应用
void value_pass_application()
{
    printf("-->>\nin hte 21 value-pass-func:\n---->>\n");
    int x = 5;
    void value_pass(int *);
    value_pass(&x);
    printf("x的值不变: %d\n", x);
}

// 传递二维数组的行指针
void pass_row_ptr(int arr[][3], int rows)
{
    for (int i = 0; i < rows; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}

// 比较函数,用于 qsort
int compare_asc(const void *a, const void *b)
{
    return *(int *)a - *(int *)b;
}

// 演示 qsort 函数的使用
void qsort_demo()
{
    int arr[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};
    int n = sizeof(arr) / sizeof(arr[0]);
    qsort(arr, n, sizeof(int), compare_asc);
    for (int i = 0; i < n; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

// 演示函数指针数组的使用
int add(int a, int b);
int subtract(int a, int b); // 修正函数声明

void func_ptr_array()
{
    typedef int (*OpFunc)(int, int);
    OpFunc ops[] = {add, subtract};
    printf("5+3=%d\n", ops[0](5, 3));
    printf("5-3=%d\n", ops[1](5, 3));
}

// 测试函数指针
void testFuncPtr()
{
    // 示例代码可根据实际需求添加
}

// 斐波那契数列递归实现
int fibonacci(int n)
{
    if (n <= 1)
        return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

// 测试斐波那契数列
void test_fibonacci()
{
    printf("fib(5) = %d\n", fibonacci(5));
}

// 演示 typedef 的使用
void typedef_usage()
{
    typedef int IntArray[5];
    IntArray arr;
    arr[0] = 10;
    printf("typedef 使用示例: %d\n", arr[0]);
}

// 大厂面试相关代码
void dachangmianshi3()
{
#define PTR_INT int *
    typedef int *ptr_int;
    PTR_INT a, b;
    ptr_int x, y;
    printf("--->>> %s %s \n", "int*", "int");
    printf("--->>> %s %s \n", "int*", "int*");
}

// 测试 strncpy 函数
void test_strncpy()
{
    char dest[10] = {0};
    my_strncpy(dest, "hello", 3);
    printf("strncpy---复制结果: %s\n", dest);
}

// 测试 strncat 函数
void test_str_cat()
{
    printf("\n---->>>>>\n strcat 函数测试\n");
    char dest[10] = {'1', '2', '\0'};
    char src[] = {'3', '4', 'a', '\0'};
    char *res = my_strncat(dest, src, 3);
    printf("--->>> \n str cat func --->>> \n%s", res);
}

// 测试 strncmp 函数
void test_strncmp()
{
    char s1[] = {'1', '2', '3', '5'};
    char s2[] = {'1', '2', '3'};
    printf("\n--->>>\nstrnCmp 函数测试结果:\n%d", my_strncmp(s1, s2, 4));
}

// 测试 strchr 函数
void test_strchar()
{
    char test[] = {'1', '2', 'a', 'b', 'c', 'f', 'l', '6', '\0'};
    char *res = my_strchr(test, '7');
    if (!res)
    {
        printf("not found!!\n");
    }
    else
    {
        printf("founded !!!! res is %c  \n\n", *res);
    }
}

// 测试 strstr 函数
void test_strstr()
{
    char haystack[] = {'1', 'a', '2', 'b', 'c', '\0'};
    char needle[] = {'b', 'c', '\0'};
    char *res = my_strstr(haystack, needle); // 使用自定义的 my_strstr 函数
    if (res)
    {
        printf("-->>>>>>\n now in strstr func, res is :%c \n", *res);
    }
    else
    {
        printf("-->>>>>>\n now in strstr func, not found \n");
    }
}

// 对字符串数组进行排序
void arrSort(char *arr[], int n)
{
    for (int i = 0; i < n - 1; i++)
        for (int j = 0; j < n - 1 - i; j++)
            if (strcmp(arr[j], arr[j + 1]) > 0)
            {
                char *temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
}

// 动态分配数组
int *dy_alloc(int n)
{
    int *res = (int *)malloc(n * sizeof(int));
    for (int i = 0; i < n; i++)
        res[i] = i;
    return res;
}

// 添加子节点
void add_child(struct Node *parent, struct Node *child)
{
    parent->children = realloc(parent->children, (parent->child_count + 1) * sizeof(struct Node *));
    parent->children[parent->child_count++] = child;
}

// void 指针通用类型转换
void *arrtoPtrInt(void *x)
{
    return (char *)(x) + 2;
}

// 获取字符串长度
int getStrLen(char *a)
{
    char *p = a;
    while (*p)
        p++;
    return p - a;
}

// 字符串拷贝函数
char *strCpyFn(char *dest, char *src)
{
    char *result = dest;
    while (*src)
        *dest++ = *src++;
    *dest = '\0';
    return result;
}

// 矩阵转置
void zhuanzhi(int arr[3][3])
{
    for (int i = 0; i < 3; i++)
    {
        for (int j = i + 1; j < 3; j++)
        {
            int temp = arr[i][j];
            arr[i][j] = arr[j][i];
            arr[j][i] = temp;
        }
    }
}

// 连接两个字符串
void connect_char(char *a, char *b)
{
    char *p1 = a + strlen(a);
    while (*b)
    {
        *p1 = *b;
        p1++;
        b++;
    }
    *p1 = '\0';
}

// 加法函数
int add(int a, int b)
{
    return a + b;
}

// 减法函数
int subtract(int a, int b)
{
    return a - b;
}

// 测试字符串复制函数
void test_str_copy()
{
    char *copy = str_copy("test");
    printf("复制后的字符串: %s\n", copy);
    free(copy);
}
void value_pass(int *p)
{
    printf("在 value_pass 函数中:原值 = %d\n", *p);
    *p = *p + 10;
    printf("在 value_pass 函数中:新值 = %d\n", *p);
}

// 主函数
int main()
{
    printf("=== C 语言知识点综合测试 ===\n\n");

    // 野指针相关测试
    printf("野指针相关测试:\n");
    wild_pointer_cause();
    printf("\n");

    // 指针关系和算术运算测试
    printf("指针关系和算术运算测试:\n");
    pointer_relation();
    pointer_arithmetic_application();
    printf("\n");

    // 数组指针测试
    printf("数组指针测试:\n");
    array_pointer_question();
    array_ptr_application();
    printf("\n");

    // 字符指针测试
    printf("字符指针测试:\n");
    char_ptr_application();
    printf("\n");

    // const 指针测试
    printf("const 指针测试:\n");
    const_pointer();
    printf("\n");

    // 值传递测试
    printf("值传递测试:\n");
    value_pass_application();
    printf("\n");

    // 二维数组行指针传递测试
    printf("二维数组行指针传递测试:\n");
    int arr2d[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
    pass_row_ptr(arr2d, 3);
    printf("\n");

    // 字符串复制测试
    printf("字符串复制测试:\n");
    test_str_copy(); // 确保函数已声明和定义
    printf("\n");

    // qsort 函数测试
    printf("qsort 函数测试:\n");
    qsort_demo();
    func_ptr_array();
    testFuncPtr();
    printf("\n");

    // 斐波那契数列测试
    printf("斐波那契数列测试:\n");
    test_fibonacci();
    printf("\n");

    // typedef 使用测试
    printf("typedef 使用测试:\n");
    typedef_usage();
    printf("\n");

    // 大厂面试相关代码测试
    printf("大厂面试相关代码测试:\n");
    dachangmianshi3();
    printf("\n");

    // 字符串处理函数测试
    printf("字符串处理函数测试:\n");
    test_strncpy();
    test_str_cat();
    test_strncmp();
    test_strchar();
    test_strstr();
    printf("\n");

    // 栈操作测试
    printf("栈操作测试:\n");
    struct stack s;
    initStack(&s);
    printf("初始化后,栈是否为空: %d\n", isEmpty(&s));
    pushStack(&s, 1);
    printf("压入 1 后,栈是否为空: %d\n", isEmpty(&s));
    pushStack(&s, 2);
    pushStack(&s, 3);
    printf("栈顶元素: %d\n", peekTop(&s));
    pushStack(&s, 4);
    printf("栈顶元素: %d\n", peekTop(&s));
    popStack(&s);
    printf("弹出元素后,栈顶元素: %d\n", peekTop(&s));
    printf("\n");

    // 链表操作测试
    printf("链表操作测试:\n");
    struct Node *head_node = NULL;
    head_node = insert_atFirst(head_node, 123);
    printf("第一个节点: %d\n", head_node->value); // 修改为正确的成员名 value
    head_node = insert_atFirst(head_node, 4);
    printf("第二个节点: %d\n", head_node->value); // 修改为正确的成员名 value
    print_List(head_node);
    printf("\n");

    // 快速排序测试
    printf("快速排序测试:\n");
    int arr11[] = {4, 5, 6, 7, 15, 234, 46, 698, 238, 258, 45, 2, 36, 26, 123, 77, 5, 48, 45, 2, 5, 325, 32, 1, 6};
    int len = sizeof(arr11) / sizeof(arr11[0]);
    quick_Sort(arr11, 0, len - 1);
    for (int i = 0; i < len; i++)
    {
        printf("排序后输出的结果: %d\n", arr11[i]);
    }
    printf("\n");

    printf("=== 测试结束 ===\n");
    return 0;
}

第三部分:本人结合AI所总结的相关c语言知识点总结

 

知识点总结表格:

考点分类具体知识点常见题型解题关键点易错点
基础概念指针与数组的本质区别选择题(如数组名衰退规则)数组名在sizeof&操作外衰退为指针,数组存储位置由定义方式决定混淆数组与指针的本质,认为数组名始终是指针
指针大小与平台相关性选择题(64 位系统指针大小)指针大小仅由操作系统位数决定(32 位 4 字节,64 位 8 字节)误以为指针大小与指向类型有关
野指针成因与危害代码找错题未初始化、释放未置 NULL、越界访问、指向临时变量是野指针主因忽视临时变量地址失效问题
数组与指针二维数组与指针运算表达式求值题(如arr[1][2]的指针表示)二维数组按行存储,arr[i][j]等价于*(*(arr+i)+j)错误计算指针偏移量
指针数组与数组指针辨析定义辨析题int (*p)[5]是数组指针,int *p[5]是指针数组混淆括号优先级导致类型判断错误
字符串指针与数组陷阱代码运行结果题字符串字面量存储在只读区,数组可修改,指针指向不可修改修改字符串常量导致段错误
内存管理动态内存分配与 realloc 陷阱代码找错题realloc 需检查返回值,成功时原指针可能改变直接释放原指针导致内存泄漏
指针与结构体对齐sizeof 计算题结构体对齐规则:成员按自身大小对齐,整体为最大对齐参数整数倍忽视对齐导致结构体大小计算错误
多级指针操作指针解引用题多级指针解引用次数等于级别数,注意指针指向的指针层级解引用次数不足或过多导致访问错误
函数指针函数指针数组实现计算器编程题定义函数指针类型,通过数组索引调用对应函数函数指针类型定义错误
通用排序中的 void * 指针算法实现题使用void*memcpy实现任意类型排序,需传入比较函数类型转换时未考虑内存对齐问题
算法应用链表逆序(指针操作)数据结构题三指针法:prevcurrentnext配合反转指针指针更新顺序错误导致链表断裂
快速排序中的指针应用算法题用指针偏移实现任意类型分区,void*配合memcpy交换元素分区时偏移量计算错误
系统级操作指针与数组传参陷阱sizeof 计算题数组作为参数衰退为指针,无法获取原始大小在函数内用sizeof(arr)获取数组大小
指针运算与类型转换表达式求值题指针算术步长由指向类型决定,不同类型指针相减得到元素个数忽视指针类型不同导致偏移量错误
内存泄漏检测与预防代码分析题动态内存分配后需正确释放,realloc后更新指针遗漏realloc后的指针更新导致内存泄漏
指针实现字符串拷贝编程题指针遍历源字符串,逐个字符复制,处理NULL输入未处理源字符串或目标字符串为NULL的情况
链表环检测(Floyd 判圈算法)算法题快慢指针法:慢指针每次走 1 步,快指针每次走 2 步,相遇则有环边界条件处理不当(空链表或单节点链表)
指针与内存池设计系统编程题预分配大内存,用指针跟踪分配位置,避免碎片内存池边界检查缺失导致越界
指针与类型转换(内存复用)底层编程题通过强制类型转换实现不同类型内存复用,如intfloat互转违反类型安全原则导致未定义行为
指针与位操作(内存映射)位运算题用指针定位内存

逻辑关系图:

指针知识体系
│
├── 基础概念
│   ├── 指针本质(地址操作、类型系统)
│   ├── 指针与数组的关系
│   │   ├── 数组名衰退规则
│   │   ├── 二维数组内存布局
│   │   └── 指针数组 vs 数组指针
│   ├── 指针大小(平台相关性)
│   └── 野指针
│       ├── 成因(未初始化、释放未置NULL、越界、临时变量)
│       └── 危害(段错误、数据破坏)
│
├── 内存管理
│   ├── 动态分配(malloc/realloc/free)
│   │   ├── realloc陷阱(返回值检查、指针更新)
│   │   └── 内存泄漏检测
│   ├── 结构体对齐
│   │   ├── 对齐规则(成员对齐、整体对齐)
│   │   └── sizeof计算
│   └── 多级指针操作(解引用层级)
│
├── 函数指针
│   ├── 函数指针定义与调用
│   ├── 函数指针数组(回调机制)
│   └── 通用编程(void*指针+比较函数)
│
├── 算法与数据结构
│   ├── 链表操作
│   │   ├── 链表逆序(三指针法)
│   │   └── 环检测(Floyd算法)
│   ├── 排序算法
│   │   ├── 快速排序(指针分区)
│   │   └── 通用排序实现
│   └── 字符串处理(指针实现strcpy等)
│
└── 系统级编程
    ├── 指针传参与数组衰退
    ├── 指针运算(类型转换、偏移量)
    ├── 内存池设计(预分配与指针跟踪)
    └── 底层操作(位运算、内存复用)

最后:   
                    觉得我写得不错的,还请各位给一个点赞收藏关注!


 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2386904.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构第5章 树与二叉树(竟成)

第 5 章 树与二叉树 【考纲内容】 1.树的基本概念 2.二叉树 &#xff08;1&#xff09;二叉树的定义及其主要特征 &#xff08;2&#xff09;二叉树的顺序存储结构和链式存储结构 &#xff08;3&#xff09;二叉树的遍历 &#xff08;4&#xff09;线索二叉树的基本概念和构造 …

# 深入解析BERT自然语言处理框架:原理、结构与应用

深入解析BERT自然语言处理框架&#xff1a;原理、结构与应用 在自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;BERT&#xff08;Bidirectional Encoder Representations from Transformers&#xff09;框架的出现无疑是一个重要的里程碑。它凭借其强大的语言表示能…

Cadence学习笔记之---PCB过孔替换、封装更新,DRC检查和状态查看

目录 01 | 引 言 02 | 环境描述 03 | 过孔替换 04 | 封装更新 05 | PCB状态查看 06 | DRC检查 07 | 总 结 01 | 引 言 终于终于来到了Cadence学习笔记的尾声&#xff01; 在上一篇文章中&#xff0c;讲述了如何布线、如何铺铜&#xff0c;以及布线、铺铜过程中比较重要…

系统开发和运行知识

软件生存周期 软件生存周期包括可行性分析与项目开发计划、需求分析、概要设计、详细设计、编码和单元测试、综合测试及维护阶段。 1、可行性分析与项目开发计划 主要任务是确定软件的开发目标及可行性。该阶段应该给出问题定义、可行性分析和项目开发计划。 2、需求分析 需求…

【C++高级主题】异常处理(四):auto_ptr类

目录 一、auto_ptr 的诞生&#xff1a;为异常安全的内存分配而设计 1.1 传统内存管理的痛点 1.2 auto_ptr 的核心思想&#xff1a;RAII 与内存绑定 1.3 auto_ptr 的基本定义&#xff08;简化版&#xff09; 二、auto_ptr 的基本用法&#xff1a;将指针绑定到智能对象 2.1…

STM32CubeMX配置使用通用定时器产生PWM

一、定时器PWM功能简介 定时器&#xff0c;顾名思义&#xff0c;就是定时的功能&#xff0c;定时器在单片机中算是除GPIO外最基本的外设。在ST中&#xff0c;定时器分为几种&#xff0c;基础定时器&#xff0c;通用定时器&#xff0c;高级定时器和低功耗定时器。其中定时器除了…

WebSphere Application Server(WAS)8.5.5教程第十四讲:JPA

一、JPA 以下是对 JPA&#xff08;Java Persistence API&#xff09; 的深入详解&#xff0c;适用于具备一定 Java EE / Jakarta EE 背景的开发者&#xff0c;尤其是对数据持久化机制感兴趣的人员。 1、什么是 JPA&#xff1f; Java Persistence API&#xff08;JPA&#xf…

Linux系统调用深度剖析

Linux系统调用深度剖析与实践案例 目录 Linux系统调用深度剖析与实践案例 一、Linux系统调用概述 二、进程管理相关系统调用 1. fork():进程克隆与多任务处理 2. exec系列:程序加载与替换 3. wait/waitpid:进程状态同步 三、文件操作相关系统调用 1. 文件描述符操作…

动态规划-918.环形子数组的最大和-力扣(LeetCode)

一、题目解析 听着有点复杂&#xff0c;这里一图流。 将环形问题转化为线性问题。 二、算法原理 1.状态表示 2.状态转移方程 详细可以移步另一篇博客&#xff0c;53. 最大子数组和 - 力扣&#xff08;LeetCode&#xff09; 3.初始化 由于计算中需要用到f[i-1]和g[i-1]的值&…

STM32:Modbus通信协议核心解析:关键通信技术

知识点1【 Modbus通信】 1、Modbus的概述 Modbus是OSI模型第七层的应用层报文传输协议 协议&#xff1a;说明有组包和解包的过程 2、通信机制 Modelbus是一个请求/应答协议 通信机制&#xff1a;主机轮询&#xff0c;从机应答的机制。每个从设备有唯一的地址&#xff0c;主…

线程封装与互斥

目录 线程互斥 进程线程间的互斥相关背景概念 互斥量mutex 互斥量的接口 初始化互斥量有两种方法&#xff1a; 销毁互斥量 互斥量加锁和解锁 改进售票系统 互斥量实现原理探究 互斥量的封装 线程互斥 进程线程间的互斥相关背景概念 临界资源&#xff1a;多线程执行流共…

Spring AI 系列之一个很棒的 Spring AI 功能——Advisors

1. 概述 由AI驱动的应用程序已成为我们的现实。我们正在广泛地实现各种RAG应用程序、提示API&#xff0c;并利用大型语言模型&#xff08;LLM&#xff09;创建项目。借助 Spring AI&#xff0c;我们可以更快速地完成这些任务。 在本文中&#xff0c;我们将介绍一个非常有价值…

Vue3 + TypeScript + el-input 实现人民币金额的输入和显示

输入人民币金额的参数要求&#xff1a; 输入要求&#xff1a; 通过键盘&#xff0c;只允许输入负号、小数点、数字、退格键、删除键、方向左键、方向右键、Home键、End键、Tab键&#xff1b;负号只能在开头&#xff1b;只保留第一个小数点&#xff1b;替换全角输入的小数点&a…

2.1 C++之条件语句

学习目标&#xff1a; 理解程序的分支逻辑&#xff08;根据不同条件执行不同代码&#xff09;。掌握 if-else 和 switch 语句的用法。能编写简单的条件判断程序&#xff08;如成绩评级、游戏选项等&#xff09;。 1 条件语句的基本概念 什么是条件语句&#xff1f; 程序在执…

Linux `ls` 命令深度解析与高阶应用指南

Linux `ls` 命令深度解析与高阶应用指南 一、核心功能解析1. 基本作用2. 与类似命令对比二、选项系统详解1. 常用基础选项2. 进阶筛选选项三、高阶应用技巧1. 组合过滤查询2. 格式化输出控制3. 元数据深度分析四、企业级应用场景1. 存储空间监控2. 安全审计3. 自动化运维五、特…

【MPC控制 - 从ACC到自动驾驶】5. 融会贯通:MPC在ACC中的优势总结与知识体系构建

【MPC控制 - 从ACC到自动驾驶】融会贯通&#xff1a;MPC在ACC中的优势总结与知识体系构建 在过去的四天里&#xff0c;我们一起经历了一段奇妙的旅程&#xff1a; Day 1: 我们认识了自适应巡航ACC这位“智能领航员”&#xff0c;并初见了模型预测控制MPC这位“深谋远虑的棋手…

初等数论--Garner‘s 算法

0. 介绍 主要通过混合积的表示来逐步求得同余方程的解。 对于同余方程 { x ≡ v 0 ( m o d m 0 ) x ≡ v 1 ( m o d m 1 ) ⋯ x ≡ v k − 1 ( m o d m k − 1 ) \begin{equation*} \begin{cases} x \equiv v_0 \quad (\ \bmod \ m_0)\\ x \equiv v_1 \quad (\ \bmod \ m_1)…

NV211NV212美光科技颗粒NV219NV220

NV211NV212美光科技颗粒NV219NV220 技术架构解析&#xff1a;从颗粒到存储系统 近期美光科技发布的NV211、NV212、NV219、NV220系列固态颗粒&#xff0c;凭借其技术突破引发行业关注。这些颗粒基于176层QLC堆叠工艺&#xff0c;单Die容量预计在2026年可达1Tb&#xff0c;相当…

SQL解析工具JSQLParser

目录 一、引言二、JSQLParser常见类2.1 Class Diagram2.2 Statement2.3 Expression2.4 Select2.5 Update2.6 Delete2.7 Insert2.8 PlainSelect2.9 SetOperationList2.10 ParenthesedSelect2.11 FromItem2.12 Table2.13 ParenthesedFromItem2.14 SelectItem2.15 BinaryExpressio…

Wave Terminal + Cpolar:SSH远程访问的跨平台实战+内网穿透配置全解析

文章目录 前言1. Wave Terminal安装2. 简单使用演示3. 连接本地Linux服务器3.1 Ubuntu系统安装ssh服务3.2 远程ssh连接Ubuntu 4. 安装内网穿透工具4.1 创建公网地址4.2 使用公网地址远程ssh连接 5. 配置固定公网地址 前言 各位开发者朋友&#xff0c;今天为您介绍一款颠覆性操…