day24 目录遍历、双向链表、栈
显示指定目录下的所有 .h 文件
功能描述
遍历指定目录(递归进入子目录),查找所有以 .h 为后缀的头文件,将其完整路径(路径 + 文件名)存储到双向链表中,并正向或反向打印输出。
核心技术点
- 使用 
opendir/readdir/closedir进行目录遍历 - 使用 
DT_DIR判断是否为目录,实现递归 - 使用 
sprintf拼接路径字符串 - 使用 
strcmp判断文件后缀是否为.h - 使用双向链表动态存储匹配的文件路径
 
双向链表结构定义(DouLink.h)
        
            
            
              c
              
              
            
          
          #ifndef _DOULINK_H_
#define _DOULINK_H_
// 定义通用数据类型,用于存储文件路径
typedef struct person {
    char path[512];           // 存储文件的完整路径(含目录)
} DATATYPE;
// 双向链表节点
typedef struct dou_node {
    DATATYPE data;            // 数据域
    struct dou_node *prev;    // 指向前一个节点
    struct dou_node *next;    // 指向后一个节点
} DouLinkNode;
// 双向链表管理结构
typedef struct list {
    DouLinkNode *head;        // 指向链表头节点
    int clen;                 // 当前链表长度
} DouLinkList;
// 遍历方向枚举
typedef enum {
    DIR_FORWARD,              // 正向遍历
    DIR_BACKWARD              // 反向遍历
} DIRECT;
// 函数指针类型,用于查找、删除等操作
typedef int (*PFUN)(DATATYPE* data, void* arg);
// 函数声明
DouLinkList *CreateDouLinkList();
int InsertHeadDouLinkList(DouLinkList *dl, DATATYPE *data);
int InsertTailDouLinkList(DouLinkList *dl, DATATYPE *data);
int InsertPosDouLinkList(DouLinkList *dl, DATATYPE *data, int pos);
DouLinkNode *FindDouLinkList(DouLinkList *dl, PFUN fun, void* arg);
int ModifyDouLinkList(DouLinkList *dl, char *name, DATATYPE *newdata);
int DeleteDouLinkList(DouLinkList *dl, char *name);
int GetSizeDouLinkList(DouLinkList *dl);
int IsEmptyDouLinkList(DouLinkList *dl);
int DestroyDouLinkList(DouLinkList *dl);
int ShowDouLinkList(DouLinkList *dl, DIRECT direct);
#endif // _DOULINK_H_
        ✅ 说明 :该头文件定义了通用的双向链表结构,适用于多种数据类型。当前用于存储
.h文件路径。
双向链表实现(DouLink.c)
        
            
            
              c
              
              
            
          
          #include "DouLink.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 创建空双向链表
DouLinkList *CreateDouLinkList() {
    DouLinkList *dl = malloc(sizeof(DouLinkList));
    if (NULL == dl) {
        perror("CreateDouLinkList malloc");
        return NULL;
    }
    dl->head = NULL;
    dl->clen = 0;
    return dl;
}
// 头插法插入节点
int InsertHeadDouLinkList(DouLinkList *dl, DATATYPE *data) {
    DouLinkNode *newnode = malloc(sizeof(DouLinkNode));
    if (NULL == newnode) {
        perror("InsertHeadDouLinkList malloc");
        return 1;
    }
    memcpy(&newnode->data, data, sizeof(DATATYPE));
    newnode->next = NULL;
    newnode->prev = NULL;
    if (IsEmptyDouLinkList(dl)) {
        dl->head = newnode;
    } else {
        newnode->next = dl->head;
        dl->head->prev = newnode;
        dl->head = newnode;
    }
    dl->clen++;
    return 0;
}
// 正向或反向打印链表
int ShowDouLinkList(DouLinkList *dl, DIRECT direct) {
    DouLinkNode *tmp = dl->head;
    if (DIR_FORWARD == direct) {
        while (tmp) {
            printf("%s\n", tmp->data.path);  // 输出文件路径
            tmp = tmp->next;
        }
    } else {
        // 找到最后一个节点
        while (tmp && tmp->next) {
            tmp = tmp->next;
        }
        // 从尾向前遍历
        while (tmp) {
            printf("%s\n", tmp->data.path);
            tmp = tmp->prev;
        }
    }
    return 0;
}
// 判断链表是否为空
int IsEmptyDouLinkList(DouLinkList *dl) {
    return 0 == dl->clen;
}
// 尾插法插入节点
int InsertTailDouLinkList(DouLinkList *dl, DATATYPE *data) {
    if (IsEmptyDouLinkList(dl)) {
        return InsertHeadDouLinkList(dl, data);
    } else {
        DouLinkNode *newnode = malloc(sizeof(DouLinkNode));
        if (NULL == newnode) {
            perror("InsertTailDouLinkList malloc");
            return 1;
        }
        memcpy(&newnode->data, data, sizeof(DATATYPE));
        newnode->next = NULL;
        newnode->prev = NULL;
        DouLinkNode *tmp = dl->head;
        while (tmp->next) {
            tmp = tmp->next;
        }
        newnode->prev = tmp;
        tmp->next = newnode;
    }
    dl->clen++;
    return 0;
}
// 在指定位置插入节点
int InsertPosDouLinkList(DouLinkList *dl, DATATYPE *data, int pos) {
    int size = GetSizeDouLinkList(dl);
    if (pos < 0 || pos > size) {
        printf("InsertPosDouLinkList pos error\n");
        return 1;
    }
    if (0 == pos) {
        return InsertHeadDouLinkList(dl, data);
    } else if (size == pos) {
        return InsertTailDouLinkList(dl, data);
    } else {
        DouLinkNode *newnode = malloc(sizeof(DouLinkNode));
        if (NULL == newnode) {
            perror("InsertPosDouLinkList malloc");
            return 1;
        }
        memcpy(&newnode->data, data, sizeof(DATATYPE));
        newnode->next = NULL;
        newnode->prev = NULL;
        DouLinkNode *tmp = dl->head;
        while (pos--) {
            tmp = tmp->next;
        }
        newnode->next = tmp;
        newnode->prev = tmp->prev;
        tmp->prev = newnode;
        newnode->prev->next = newnode;
        dl->clen++;
    }
    return 0;
}
// 通用查找函数:通过函数指针匹配数据
DouLinkNode *FindDouLinkList(DouLinkList *dl, PFUN fun, void* arg) {
    DouLinkNode* tmp = dl->head;
    while (tmp) {
        if (fun(&tmp->data, arg)) {
            return tmp;
        }
        tmp = tmp->next;
    }
    return NULL;
}
// 获取链表长度
int GetSizeDouLinkList(DouLinkList *dl) {
    return dl->clen;
}
// 销毁链表(释放所有节点和链表结构)
int DestroyDouLinkList(DouLinkList *dl) {
    DouLinkNode *tmp = NULL;
    while (dl->head) {
        tmp = dl->head;
        dl->head = dl->head->next;
        free(tmp);
    }
    free(dl);
    return 0;
}
        ✅ 理想运行结果:
CreateDouLinkList()返回一个空链表指针,head=NULL,clen=0- 插入操作后链表长度递增,
 ShowDouLinkList可按正/反顺序输出路径DestroyDouLinkList释放所有内存,无泄漏
目录遍历与头文件查找(main.c)
        
            
            
              c
              
              
            
          
          #include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "DouLink.h"
// 指定起始路径(可修改)
char path[] = "/home/linux/20250818cd";
// 判断文件是否为 .h 后缀
int is_headfile(char* path) {
    if (strlen(path) < 3) return 0;
    char* tmp = &path[strlen(path) - 2];  // 取最后两个字符前的位置
    if (0 == strcmp(tmp, ".h")) {
        return 1;
    }
    return 0;
}
// 递归遍历目录,将 .h 文件路径插入链表
int do_ls(char* path, DouLinkList* dl) {
    DIR* dir = opendir(path);
    if (NULL == dir) {
        perror("opendir");
        return 1;
    }
    struct dirent* info;
    while ((info = readdir(dir)) != NULL) {
        char newpath[512] = {0};
        sprintf(newpath, "%s/%s", path, info->d_name);  // 拼接完整路径
        if (info->d_type == DT_DIR) {
            // 忽略 . 和 ..
            if (strcmp(info->d_name, ".") == 0 || strcmp(info->d_name, "..") == 0) {
                continue;
            }
            // 递归进入子目录
            do_ls(newpath, dl);
        } else {
            // 非目录,检查是否为 .h 文件
            DATATYPE data = {0};
            if (is_headfile(newpath)) {
                strcpy(data.path, newpath);
                InsertHeadDouLinkList(dl, &data);  // 头插存储
            }
        }
    }
    closedir(dir);
    return 0;
}
// 主函数:创建链表、遍历目录、打印结果
int main(int argc, char** argv) {
    DouLinkList* dl = CreateDouLinkList();
    do_ls("/home/linux", dl);  // 从 /home/linux 开始递归查找
    printf("=== 所有 .h 文件路径(正向输出)===\n");
    ShowDouLinkList(dl, DIR_FORWARD);
    DestroyDouLinkList(dl);
    return 0;
}
        ✅ 理想运行结果示例:
=== 所有 .h 文件路径(正向输出)=== /home/linux/20250818cd/test.h /home/linux/20250818cd/include/utils.h /home/linux/20250818cd/module/config.h
- 成功递归进入所有子目录
 - 正确识别
 .h文件并存储完整路径- 输出顺序取决于插入方式(头插 → 逆序)
 
双向链表(通用版)
功能说明
该双向链表支持插入、删除、查找、修改、遍历等操作,适用于存储结构化数据(如学生信息)。
数据结构定义(DouLink.h)
        
            
            
              c
              
              
            
          
          #ifndef _DOULINK_H_
#define _DOULINK_H_
typedef struct person {
    char name[32];
    char sex;
    int age;
    int score;
} DATATYPE;
typedef struct dou_node {
    DATATYPE data;
    struct dou_node *prev;
    struct dou_node *next;
} DouLinkNode;
typedef struct list {
    DouLinkNode *head;
    int clen;
} DouLinkList;
typedef enum {
    DIR_FORWARD,
    DIR_BACKWARD
} DIRECT;
typedef int (*PFUN)(DATATYPE* data, void* arg);
// 函数声明(略去重复部分)
DouLinkList *CreateDouLinkList();
int InsertHeadDouLinkList(DouLinkList *dl, DATATYPE *data);
int InsertTailDouLinkList(DouLinkList *dl, DATATYPE *data);
int InsertPosDouLinkList(DouLinkList *dl, DATATYPE *data, int pos);
DouLinkNode *FindDouLinkList(DouLinkList *dl, PFUN fun, void* arg);
int ModifyDouLinkList(DouLinkList *dl, PFUN fun, void* arg, DATATYPE *newdata);
int DeleteDouLinkList(DouLinkList *dl, PFUN fun, void* arg);
int GetSizeDouLinkList(DouLinkList *dl);
int IsEmptyDouLinkList(DouLinkList *dl);
int DestroyDouLinkList(DouLinkList *dl);
int ShowDouLinkList(DouLinkList *dl, DIRECT direct);
#endif
        ✅ 说明 :此版本
DATATYPE包含姓名、性别、年龄、成绩,适用于人员管理。
链表操作实现(DouLink.c)
CreateDouLinkList、InsertHead/InsertTail/InsertPos、ShowDouLinkList、IsEmpty、GetSize实现同上(略)- 新增
 ModifyDouLinkList、DeleteDouLinkList、DestroyDouLinkList
            
            
              c
              
              
            
          
          // 根据条件修改节点数据
int ModifyDouLinkList(DouLinkList *dl, PFUN fun, void *arg, DATATYPE *newdata) {
    DouLinkNode *tmp = FindDouLinkList(dl, fun, arg);
    if (NULL == tmp) {
        printf("ModifyDouLinkList failure...\n");
        return 1;
    }
    memcpy(&tmp->data, newdata, sizeof(DATATYPE));
    return 0;
}
// 根据条件删除节点
int DeleteDouLinkList(DouLinkList *dl, PFUN fun, void *arg) {
    DouLinkNode *tmp = FindDouLinkList(dl, fun, arg);
    if (NULL == tmp) {
        printf("del failure...\n");
        return 1;
    }
    if (tmp == dl->head) {
        dl->head = dl->head->next;
        if (dl->head) dl->head->prev = NULL;
    } else {
        if (tmp->next) tmp->next->prev = tmp->prev;
        tmp->prev->next = tmp->next;
    }
    free(tmp);
    dl->clen--;
    return 0;
}
// 销毁整个链表
int DestroyDouLinkList(DouLinkList *dl) {
    DouLinkNode *tmp;
    while (dl->head) {
        tmp = dl->head;
        dl->head = dl->head->next;
        free(tmp);
    }
    free(dl);
    return 0;
}
        测试程序(test.c)
        
            
            
              c
              
              
            
          
          #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "DouLink.h"
// 按姓名查找
int findperbyname(DATATYPE* data, void* arg) {
    return 0 == strcmp(data->name, (char*)arg);
}
// 按年龄查找
int findperbyage(DATATYPE* data, void* arg) {
    return data->age == *(int*)arg;
}
int main(int argc, char** argv) {
    DouLinkList* dl = CreateDouLinkList();
    DATATYPE data[] = {
        {"zhangsan", 'm', 20, 80},
        {"lisi", 'f', 23, 84},
        {"wangmaizi", 'f', 32, 90},
        {"guanerge", 'm', 50, 91},
        {"liubei", 'm', 51, 92},
        {"zhangfei", 'm', 50, 80},
    };
    InsertHeadDouLinkList(dl, &data[0]);
    InsertHeadDouLinkList(dl, &data[1]);
    InsertHeadDouLinkList(dl, &data[2]);
    printf("------------正向输出--------------------\n");
    ShowDouLinkList(dl, DIR_FORWARD);
    printf("------------反向输出--------------------\n");
    ShowDouLinkList(dl, DIR_BACKWARD);
    InsertTailDouLinkList(dl, &data[3]);
    printf("------------尾插后--------------------\n");
    ShowDouLinkList(dl, DIR_FORWARD);
    InsertPosDouLinkList(dl, &data[4], 2);
    printf("------------位置插入后--------------------\n");
    ShowDouLinkList(dl, DIR_FORWARD);
    int age = 50;
    DouLinkNode* tmp = FindDouLinkList(dl, findperbyage, &age);
    if (tmp) {
        printf("找到年龄为50的人: %s, 成绩: %d\n", tmp->data.name, tmp->data.score);
    }
    ModifyDouLinkList(dl, findperbyname, "lisi", &data[5]);
    printf("------------修改后--------------------\n");
    ShowDouLinkList(dl, DIR_BACKWARD);
    DeleteDouLinkList(dl, findperbyname, "liubei");
    printf("------------删除后--------------------\n");
    ShowDouLinkList(dl, DIR_BACKWARD);
    DestroyDouLinkList(dl);
    return 0;
}
        ✅ 理想运行结果:
- 正确插入、查找、修改、删除
 - 输出格式:
 name:xxx sex:x age:xx score:xx- 修改后
 lisi变为zhangfei,liubei被删除
顺序表与链表对比
| 对比项 | 顺序表 | 链表 | 
|---|---|---|
| 存储方式 | 连续内存空间 | 物理上不连续,逻辑上连续 | 
| 查找性能 | O(1) ------ 支持随机访问 | O(n) ------ 需遍历 | 
| 插入/删除 | O(n) ------ 需移动元素 | O(1) ------ 只需修改指针(已知位置) | 
| 空间分配 | 静态/固定大小,易浪费或溢出 | 动态分配,灵活,无空间浪费 | 
循环链表
- 将单链表最后一个节点的 
next指针指向头节点 或第一个有效节点,形成环状结构。 - 空表判断:
head == NULL - 非空表遍历条件:
p->next != head(代替p->next != NULL) - 常用于约瑟夫环、循环任务调度等场景。
 
双向链表(结构简写版)
            
            
              c
              
              
            
          
          typedef struct DulNode {
    ElemType data;               // 数据域
    struct DulNode *pri;         // 指向前驱
    struct DulNode *next;        // 指向后继
} DulNode, *DuLinkList;
        ✅ 注意:原始代码中
sturct拼写错误,已修正为struct
栈(Stack)
定义
限定仅在表尾 进行插入和删除操作的线性表,遵循 "先进后出"(LIFO) 原则。
核心概念
- 栈顶:允许操作的一端(插入/删除)
 - 栈底:不允许操作的一端
 - 基本操作 :
- 入栈(Push)
 - 出栈(Pop)
 - 获取栈顶元素(GetTop)
 
 
分类
- 顺序栈(数组实现)
 - 链式栈(链表实现)

 
链式栈结构定义(LinkStack.h)
        
            
            
              c
              
              
            
          
          #ifndef _LINKSTACK_H_
#define _LINKSTACK_H_
typedef struct {
    char name[50];
    char sex;
    int age;
    int score;
} DATATYPE;
typedef struct _linkstacknode {
    DATATYPE data;
    struct _linkstacknode *next;
} LinkStackNode;
typedef struct {
    LinkStackNode* top;   // 栈顶指针
    int clen;             // 元素个数
} LinkStack;
// 函数声明
LinkStack* CreateLinkStack();
int PushLinkStack(LinkStack* ls, DATATYPE* data);
int PopLinkStack(LinkStack* ls);
DATATYPE* GetTopLinkStack(LinkStack* ls);
int IsEmptyLinkStack(LinkStack* ls);
int GetSizeLinkStack(LinkStack* ls);
int DestroyLinkStack(LinkStack* ls);
#endif
        链式栈实现(LinkStack.c)
        
            
            
              c
              
              
            
          
          #include "LinkStack.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
LinkStack* CreateLinkStack() {
    LinkStack* ls = malloc(sizeof(LinkStack));
    if (!ls) {
        perror("CreateLinkStack malloc error");
        return NULL;
    }
    ls->top = NULL;
    ls->clen = 0;
    return ls;
}
int PushLinkStack(LinkStack* ls, DATATYPE* data) {
    LinkStackNode* newnode = malloc(sizeof(LinkStackNode));
    if (!newnode) {
        perror("PushLinkStack malloc error");
        return 1;
    }
    memcpy(&newnode->data, data, sizeof(DATATYPE));
    newnode->next = ls->top;
    ls->top = newnode;
    ls->clen++;
    return 0;
}
int PopLinkStack(LinkStack* ls) {
    if (IsEmptyLinkStack(ls)) {
        printf("linkstack is empty\n");
        return 1;
    }
    LinkStackNode* tmp = ls->top;
    ls->top = ls->top->next;
    free(tmp);
    ls->clen--;
    return 0;
}
DATATYPE* GetTopLinkStack(LinkStack* ls) {
    if (IsEmptyLinkStack(ls)) return NULL;
    return &ls->top->data;
}
int IsEmptyLinkStack(LinkStack* ls) {
    return 0 == ls->clen;
}
int GetSizeLinkStack(LinkStack* ls) {
    return ls->clen;
}
int DestroyLinkStack(LinkStack* ls) {
    while (!IsEmptyLinkStack(ls)) {
        PopLinkStack(ls);
    }
    free(ls);
    return 0;
}
        测试程序(test_stack.c)
        
            
            
              c
              
              
            
          
          #include <stdio.h>
#include "LinkStack.h"
int main() {
    LinkStack* ls = CreateLinkStack();
    DATATYPE data[] = {
        {"zhangsan", 'm', 20, 80},
        {"lisi", 'f', 23, 84},
        {"wangmaizi", 'f', 32, 90},
        {"guanerge", 'm', 50, 91},
        {"liubei", 'm', 51, 92}
    };
    for (int i = 0; i < 5; i++) {
        PushLinkStack(ls, &data[i]);
    }
    printf("出栈顺序(LIFO):\n");
    while (!IsEmptyLinkStack(ls)) {
        DATATYPE* tmp = GetTopLinkStack(ls);
        printf("name:%s score:%d\n", tmp->name, tmp->score);
        PopLinkStack(ls);
    }
    DestroyLinkStack(ls);
    return 0;
}
        ✅ 理想运行结果:
出栈顺序(LIFO): name:liubei score:92 name:guanerge score:91 name:wangmaizi score:90 name:lisi score:84 name:zhangsan score:80
使用栈实现 C 源文件符号匹配
原理
- 遇到 
(、[、{入栈 - 遇到 
)、]、}检查栈顶是否匹配,匹配则出栈,否则报错 - 文件结束时栈应为空,否则有未闭合符号
 
实现代码(check_brackets.c)
        
            
            
              c
              
              
            
          
          #include <stdio.h>
#include <string.h>
#include "LinkStack.h"
// 记录符号位置的结构
typedef struct {
    char c;
    int row;
    int col;
} SymData;
int do_check(char* linebuf, LinkStack* ls, int num) {
    char* tmp = linebuf;
    SymData data = {0};
    SymData* top = NULL;
    int col = 1;
    while (*tmp) {
        switch (*tmp) {
            case '(':
            case '[':
            case '{':
                data.c = *tmp;
                data.row = num;
                data.col = col;
                PushLinkStack(ls, (DATATYPE*)&data);
                break;
            case ')':
                top = (SymData*)GetTopLinkStack(ls);
                if (!top || top->c != '(') {
                    printf("括号不匹配: ) at row %d col %d\n", num, col);
                    return 1;
                }
                PopLinkStack(ls);
                break;
            case ']':
                top = (SymData*)GetTopLinkStack(ls);
                if (!top || top->c != '[') {
                    printf("括号不匹配: ] at row %d col %d\n", num, col);
                    return 1;
                }
                PopLinkStack(ls);
                break;
            case '}':
                top = (SymData*)GetTopLinkStack(ls);
                if (!top || top->c != '{') {
                    printf("括号不匹配: } at row %d col %d\n", num, col);
                    return 1;
                }
                PopLinkStack(ls);
                break;
        }
        tmp++;
        col++;
    }
    return 0;
}
int main() {
    LinkStack* ls = CreateLinkStack();
    FILE* fp = fopen("/home/linux/2.c", "r");
    if (!fp) {
        printf("fopen error\n");
        return 1;
    }
    int num = 1;
    char buf[1024];
    while (fgets(buf, sizeof(buf), fp)) {
        if (do_check(buf, ls, num)) {
            fclose(fp);
            DestroyLinkStack(ls);
            return 1;
        }
        num++;
    }
    if (IsEmptyLinkStack(ls)) {
        printf("✅ 所有括号匹配成功!\n");
    } else {
        SymData* top = (SymData*)GetTopLinkStack(ls);
        printf("❌ 存在未闭合符号: %c at row %d col %d\n", top->c, top->row, top->col);
    }
    fclose(fp);
    DestroyLinkStack(ls);
    return 0;
}
        ✅ 理想运行结果:
- 若括号全部匹配:输出
 ✅ 所有括号匹配成功!- 若有不匹配:输出具体错误位置
 - 支持跨行匹配
 
栈的核心要点总结
- 本质:受限制的线性表,仅允许在一端进行操作
 - 操作特性:先进后出(LIFO)
 - 两端定义 :
- 栈顶:可进行插入/删除
 - 栈底:固定,不可操作
 
 - 主要作用 :
- 递归调用管理
 - 表达式求值
 - 回溯算法
 - 符号匹配检查
 
 - 基本操作 :
- 入栈(Push)
 - 出栈(Pop)
 - 获取栈顶(GetTop)