数据结构---Makefile 文件(格式、文件变量、调用、伪目标)、gcc编译的四个步骤、双向链表(概念、作用、应用)

目录

一、Makefile 文件

1、Makefile 语法

2、Makefile 中的变量

   1)自定义变量(无数据类型)

   2)系统变量

3、gcc 编译的四个步骤

4、调用执行 Makefile

5、通配符(%)

6、时间戳

二、双向链表

1、概念

2、作用

   1)双向遍历

   2)高效插入/删除

3、应用

   1)函数封装

   2)头文件(声明)

   3)在主函数中书写格式


一、Makefile 文件

        Makefile 用来管理代码的编译和链接。

1、Makefile 语法

        用法规则(格式)

        例如:

a.out:main.c link.c
    gcc main.c link.c -o a.out
对于  gcc  main.c  link.c  -o  a.out

目标文件:a.out

依赖文件(源文件):main.c  link.c

2、Makefile 中的变量

   1)自定义变量(无数据类型)

        统一当字符串处理,

        定义时:变量名=值

        引用时:$(变量名)

        头文件引用 ( 指定头文件位置 ) :-I$((变量名)  

        库文件引用 ( 指定使用的库文件位置 ) :-L$(变量名)  

例如:    

   2)系统变量

        $^:   :所有依赖文件

        $<:   :第一个依赖文件

        $@:   :生成的目标

例如:   

3、gcc 编译的四个步骤

        在正式使用 Makefile 之前,我们需要了解 gcc 的四个编译步骤,以便更好地理解、应用 Makefile 文件。gcc 编译的四个步骤为:(格式中皆以 main.c 文件为例)

        1)预处理:处理和 “ # ” 相关的指令(头文件展开与条件声明等);

                格式:gcc -E main.c -o main.i

        2)编译:将源程序转换成汇编指令;

                格式:gcc -S main.i -o main.s

        3)汇编:将汇编指令生成二进制指令;

                格式:gcc -c main.c -o main.o

        4)链接:实现多文件以及函数的链接关系;

                格式:gcc main.o(其他 .o 文件) -o app

4、调用执行 Makefile

        在终端输入 make ,可以执行当前文件路径下的 Makefile(编译),然后运行。

        在终端输入 make clen 执行伪目标。当重复输入 make 会重复执行 Makefile 导致出现 “ make: 'a.out' is up to date. ” 字样,为防止这种现象出现,需再在 Makefile 中加入伪目标指令,其一般格式为:

例如:删除目标文件

clean:
      rm $(OBJ)

5、通配符(%)

        在文件夹中,存在多个 .c 、.o 文件,可以通过在 Makefile 中使用通用符来简化该过程,使得修改方便

例如将注释掉的 main.o 与 link.o 几行,可整体写为含 % 号的两行:

可以简写为:

最终书写格式为(注释部分可以删掉):

6、时间戳

        在编译代码时,只编译修改后的源文件,其他没有修改的,只进行链接即可,提高程序编译效率,节省时间

二、双向链表

1、概念

双向链表时一种线形数据结构,由多个结点组成,每个结点包含三部分:

        (1) 数据域(data):存储结点的具体信息(如数值、字符等)。

        (2) 前驱指针域(ppre):存储前一个结点的地址(或引用),用于指向当前结点的上一个结点。

        (3) 后继指针域(pnext):存储后一个结点的地址(或引用),用于指向当前结点的后一个结点。

        链表的第一个结点(头结点)的前驱指针为 NULL,最后一个结点(尾结点)的后继指针为 NULL,结点之间通过前驱和后继指针形成 “ 双向链接 ”。 

        链接效果为:

2、作用

        支持双向遍历和高效的结点前后操作。

   1)双向遍历

        既能从头到尾遍历(通过后继指针),也能从尾到头遍历(通过前驱指针),灵活性更高。

   2)高效插入/删除

        若已知某节点,无需从头遍历即可直接找到其前驱节点,在该结点进行操作,时间复杂度为 O(1)。

3、应用

   1)函数封装

        (1) 双向链表的创建函数的封装

DLink_t *create_doulink()
{
    DLink_t *pdlink = malloc(sizeof(DLink_t));

    if(NULL == pdlink)
    {
        printf("malloc error!");
        return NULL;
    }
    pdlink -> phead = NULL;
    pdlink -> clen = 0;

    return pdlink;
}

        (2) 双向链表---判断是否为空链表函数的封装

/*是否为空的判断*/
int is_empty_doulink(DLink_t *pdlink)
{
    return NULL == pdlink -> phead;
}

        (3) 双向链表---遍历函数的封装(正反)

/*遍历*/
void dlink_for_each(DLink_t *pdlink, int dir)
{
    if(is_empty_doulink(pdlink))
    {
        return ;
    }
    DNode_t *ptmp = pdlink -> phead;
    if(dir) //正向遍历pnext
    {
        while(ptmp != NULL)
        {
            printf("%d %s %d\n", ptmp->data.id, ptmp->data.name, ptmp->data.score);
            ptmp = ptmp -> pnext;
        }
    }
    else  //反向遍历ppre
    {
        while(ptmp -> pnext != NULL)
        {
            ptmp = ptmp -> pnext;
        }
        while(ptmp != NULL)
        {
            printf(" %d %s %d\n", ptmp->data.id, ptmp->data.name, ptmp->data.score);
            ptmp = ptmp -> ppre;
        }
    }
    printf("\n");
}

        (4) 双向链表---头部插入函数的封装

/*头插*/
int insert_doulink_head(DLink_t *pdlink, Data_type_t data)
{
    DNode_t *pnode = malloc(sizeof(DNode_t));
    if(NULL == pnode)
    {
        printf("malloc error");
        return -1;
    }
    pnode -> data = data;
    pnode -> ppre = NULL;
    pnode -> pnext = NULL;

    if(is_empty_doulink(pdlink))
    {
        pdlink -> phead = pnode;
    }
    else
    {
        pnode -> pnext = pdlink -> phead;
        pdlink -> phead -> ppre = pnode;
        pdlink -> phead = pnode;
    }
    pdlink -> clen++;
    return 0;
}

        (5) 双向链表---头删函数的封装

/*头删*/
int delete_doulink_head(DLink_t *pdlink)
{
    if(is_empty_doulink(pdlink))
    {
        return -1;
    }
    DNode_t *ptmp = pdlink -> phead;
    pdlink -> phead = ptmp -> pnext;
    if(pdlink -> phead != NULL)
    {
        pdlink -> phead -> ppre = NULL;
    }
    free(ptmp);

    pdlink -> clen--;
    return 0;
}

        (6) 双向链表---尾部插入数据函数的封装

/*尾插*/
int insert_doulink_tail(DLink_t *pdlink, Data_type_t data)
{
    DNode_t *pnode = malloc(sizeof(DNode_t));
    if(NULL == pnode)
    {
        printf("malloc error!");
        return -1;
    }
    pnode -> data = data;
    pnode -> pnext = NULL;
    pnode -> ppre = NULL;

    if(is_empty_doulink(pdlink))
    {
        pdlink -> phead = pnode;
    }
    else
    {
        DNode_t *ptmp = pdlink -> phead;
        while(ptmp -> pnext != NULL)
        {
            ptmp = ptmp -> pnext;
        }
        ptmp -> pnext = pnode;
        pnode -> ppre = ptmp;
    }

    pdlink -> clen++;
    return 0;
}

        (7) 双向链表---尾删函数的封装


/*尾删*/
int delete_doulink_tail(DLink_t *pdlink)
{
    if(is_empty_doulink(pdlink))
    {
        return -1;
    }

    DNode_t *ptmp = pdlink -> phead;
    if(1 == pdlink -> clen)
    {
        free(ptmp);
        pdlink -> phead = NULL;
    }
    else
    {
        while(ptmp -> pnext != NULL)
        {
            ptmp = ptmp -> pnext;
        }
        ptmp -> ppre -> pnext = NULL;//前结点为NULL 
        free(ptmp);  
    }
    pdlink -> clen--;
    return 0;
}

        (8) 双向链表---查找函数的封装

/*查找*/
DNode_t *find_doulink(DLink_t *pdlink, Data_type_t data)
{
    DNode_t *ptmp = pdlink -> phead;
    while(ptmp != NULL)
    {
        if(data.id == ptmp -> data.id)
        {
            return ptmp;
        }
        ptmp = ptmp -> pnext;
    }
    return NULL;

}

        (9) 双向链表---修改成绩函数的封装

/*修改成绩*/
int change_doulink(DLink_t *pdlink, char *olddata, int newdata)
{
    DNode_t *ptmp = pdlink -> phead;
    if(ptmp != NULL && strcmp(ptmp -> data.name, olddata ))
    {
        ptmp -> data.score = newdata;
        return 0;
    }
    return -1;
}

        (10) 双向链表---删除指定结点函数的封装

/*删除指定结点*/
int delete_doulink_num(DLink_t *pdlink, Data_type_t data)
{
    DNode_t *ptmp = find_doulink(pdlink, data);
    if(ptmp != NULL)
    {
        ptmp -> ppre -> pnext = ptmp -> pnext;
        ptmp -> pnext -> ppre = ptmp -> ppre;
        free(ptmp);
        return 0;
    }
    return -1;

}

上述内容需包含以下头文件

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

   2)头文件(声明)

        (1) 封装结点(以将 data 定义为结构体为例)

typedef struct stu
{
    int id;
    char name[32];
    int score;
}Data_type_t;

typedef struct dounode
{
    Data_type_t data;
    struct dounode *ppre;
    struct dounode *pnext;
}DNode_t;

        (2) 封装对象

typedef struct doulink
{
    DNode_t *phead;
    int clen;
}DLink_t;

        (3) 函数声明

#ifndef _DOULINK_H_
#define _DOULINK_H_

extern DLink_t *create_doulink();
extern int insert_doulink_head(DLink_t *pdlink, Data_type_t data);
extern int is_empty_doulink(DLink_t *pdlink);
extern void dlink_for_each(DLink_t *pdlink, int dir);
extern int insert_doulink_tail(DLink_t *pdlink, Data_type_t data);
extern int delete_doulink_head(DLink_t *pdlink);
extern int delete_doulink_tail(DLink_t *pdlink);
extern DNode_t *find_doulink(DLink_t *pdlink, Data_type_t data);
extern int change_doulink(DLink_t *pdlink, char *olddata, int newdata);
extern int delete_doulink_num(DLink_t *pdlink, Data_type_t data);

#endif

   3)在主函数中书写格式

#include<stdio.h>
#include"doulink.h"

int main(void)
{
    Data_type_t stus[5] = {{1, "zhangsan", 99},
    {2, "lisi", 100},
    {3, "wangwu", 90},
    {4, "maliu", 56},
    {5, "tianqi", 66}};

    DLink_t *pdlink = create_doulink();
    if(NULL == pdlink)
    {
        return -1;
    }
//头插
    insert_doulink_head(pdlink, stus[0]);
    insert_doulink_head(pdlink, stus[1]);
    insert_doulink_head(pdlink, stus[2]);
    insert_doulink_head(pdlink, stus[3]);
    insert_doulink_head(pdlink, stus[4]);
    dlink_for_each(pdlink, 1);//正向遍历pnext
    dlink_for_each(pdlink, 0);//反向遍历ppre
  
//尾插 
    insert_doulink_tail(pdlink, stus[2]);
    insert_doulink_tail(pdlink, stus[3]);
    insert_doulink_tail(pdlink, stus[4]);
    dlink_for_each(pdlink, 1);//遍历

    delete_doulink_head(pdlink);//头删
    dlink_for_each(pdlink, 1);

    delete_doulink_tail(pdlink);//尾删
  
    DNode_t *ret = NULL;
    ret = find_doulink(pdlink, stus[3]);//查找
    printf("%d %s %d\n", ret->data.id, ret->data.name, ret->data.score);
    printf("\n");

    change_doulink(pdlink, i, 83);//修改成绩
    dlink_for_each(pdlink, 1);
       
    delete_doulink_num(pdlink, stus[3]);//删除指定结点
    dlink_for_each(pdlink, 1);
       
       return 0;
}

【END】

<think>我们之前已经给出了家庭菜谱管理系统的完整代码,现在需要详细解释每个文件的功能。下面是对每个文件的详细说明: ### 1. `data_structures.h` **功能**:定义系统中使用的数据结构和全局变量- 定义了常量(如最大长度、最大数量等)。 - 定义了枚举类型:`MealType`(餐型)、`Taste`(口味)、`State`(状态)、`Cuisine`(菜系)。 - 定义了菜谱结构体`Recipe`,包含菜谱的所有信息和一个指向下一个菜谱的指针(用于链表)。 - 声明了全局变量:`recipe_list`(菜谱链表头指针)、`recipe_count`(菜谱数量)、`lovers_list`(喜好者列表)、`lover_count`(喜好者数量)。 ### 2. `recipe_manager.h` **功能**:声明菜谱管理相关的函数。 - 声明了核心功能函数:`add_recipe`(添加)、`delete_recipe`(删除)、`modify_recipe`(修改)、`search_recipes`(查找)、`random_recommendation`(随机推荐)。 - 声明了辅助函数:`print_recipe`(打印菜谱详情)、`find_recipe_by_id`(按ID查找)、`find_recipe_by_name`(按名称查找)、`get_multi_select_options`(获取多选选项)、`get_meal_type`(获取餐型)、`get_cuisine`(获取菜系)、`get_lovers`(获取喜好者)。 ### 3. `file_io.h` **功能**:声明文件操作相关的函数。 - `save_data`:将菜谱链表保存到二进制文件- `load_data`:从二进制文件加载菜谱链表- `save_lovers`:将喜好者列表保存到文本文件- `load_lovers`:从文本文件加载喜好者列表。 ### 4. `main.c` **功能**:程序入口,主循环和主菜单。 - 包含`main`函数:程序入口点。 - 全局变量的定义(在头文件中声明,在此处定义)。 - `display_main_menu`:显示主菜单。 - `lover_management`:处理喜好者管理(添加、删除、查看)。 - 主循环:根据用户选择调用相应功能或退出。 ### 5. `recipe_manager.c` **功能**:实现菜谱管理的核心功能。 - `get_multi_select_options`:实现多选选项的输入(使用位掩码)。 - `get_meal_type`:获取餐型(通过菜单选择)。 - `get_cuisine`:获取菜系(通过菜单选择)。 - `get_lovers`:从喜好者列表中选择喜好者。 - `print_recipe`:打印菜谱的详细信息(将枚举和位掩码转换为可读字符串)。 - `find_recipe_by_id`和`find_recipe_by_name`:分别在链表中按ID或名称查找菜谱。 - `add_recipe`:添加新菜谱,引导用户输入菜谱的各个字段,并加入链表- `delete_recipe`:按ID或名称查找菜谱,确认后删除。 - `modify_recipe`:按ID或名称查找菜谱,选择要修改的字段进行修改。 - `search_recipes`:提供多种查找方式(ID、名称、餐型、口味、状态、菜系、显示全部)。 - `random_recommendation`:随机推荐菜谱,支持一餐或多天推荐(多天推荐时生成每天的餐食安排)。 ### 6. `file_io.c` **功能**:实现数据的保存和加载。 - `save_data`:将菜谱链表以二进制形式写入文件(注意:直接写入结构体,但链表指针不保存,保存和加载时链表关系重建)。 - `load_data`:从二进制文件读取菜谱数据,重建链表(注意:读取时每个菜谱动态分配内存,并连接成链表)。 - `save_lovers`:将喜好者列表(字符串数组)按行写入文本文件- `load_lovers`:从文本文件读取喜好者列表。 ### 7. `Makefile` **功能**:构建系统的编译脚本。 - 定义编译器(g++,虽然代码是C,但这里使用了g++,也可以改为gcc)和编译选项。 - 定义目标程序名`recipe_system`。 - 定义源文件和头文件- 提供`all`(默认目标)、`clean`(清理)、`run`(编译并运行)等规则。 ### 补充说明 - **链表操作**:菜谱使用单向链表管理,添加时采用头插法。 - **位掩码**:口味和状态是多选的,使用整数的不同位来表示,例如,`SOUR`对应第0位(1),`SWEET`对应第1位(2),以此类推。多选时使用按位或操作组合,检查时使用按位与操作。 - **文件存储**:菜谱数据用二进制格式保存,但注意链表中的指针并不保存,保存的是每个节点的数据,加载时重新构建链表。喜好者用文本格式保存。 - **随机推荐**:一餐推荐时,先收集符合条件的菜谱,然后随机选择。多天推荐时,按天和餐型分别收集候选菜谱,然后随机选择,避免重复。 ### 注意事项 - 该代码在保存链表时,每个节点被依次写入文件。但是,当结构体中有指针时(如链表节点的`next`指针),直接保存指针的值是无效的(因为下次运行地址会变)。因此,我们只保存数据,在加载时重新构建链表- 多天推荐中的食材采购清单功能尚未实现,作为扩展点。 ### 编译运行 - 在终端进入项目目录,执行`make`进行编译,然后执行`make run`运行程序。 - 程序运行后,数据文件(`recipes.dat`和`lovers.dat`)将保存在当前目录。 §§相关问题§§: 1. 在C语言中,链表与数组相比有哪些优缺点? 2. 如何改进文件存储方式以支持更复杂的数据结构(如多级链表)? 3. 随机推荐功能中,如何避免重复推荐同一道菜? 4. 在多天推荐中,如何实现食材采购清单的生成?</think>下面是对家庭菜谱管理系统每个文件功能的详细解释,包括其核心职责和关键实现细节: ### 1. `data_structures.h` - 数据结构定义 **功能**:定义系统使用的核心数据结构和全局变量 ```c #ifndef DATA_STRUCTURES_H #define DATA_STRUCTURES_H #include <stdbool.h> // 系统常量定义(最大长度限制) #define MAX_NAME_LEN 50 // 名称最大长度 #define MAX_INGREDIENTS 20 // 食材最大数量 #define MAX_STEPS 20 // 步骤最大数量 #define MAX_LOVERS 10 // 喜好者最大数量 #define MAX_RECIPES 100 // 菜谱最大数量 // 枚举定义(餐型、口味、状态、菜系) typedef enum { BREAKFAST, MAIN_COURSE, DESSERT, SNACK, DRINK } MealType; // 餐型枚举 typedef enum { SOUR = 1, SWEET = 2, BITTER = 4, SPICY = 8, SALTY = 16, COLD = 32, HOT = 64 } Taste; // 口味枚举(位掩码设计) // ...(其他枚举定义) // 菜谱核心结构体 typedef struct Recipe { int id; // 唯一标识符 char name[MAX_NAME_LEN]; MealType meal_type; int taste_flags; // 位掩码存储多选口味 // ...(其他属性) struct Recipe* next; // 链表指针 } Recipe; // 全局数据声明 extern Recipe* recipe_list; // 菜谱链表头指针 extern int recipe_count; // 当前菜谱数量 extern char lovers_list[MAX_LOVERS][MAX_NAME_LEN]; // 喜好者列表 extern int lover_count; // 当前喜好者数量 #endif ``` **关键点**: - 使用位掩码技术处理多选属性(口味、状态) - 链表结构管理菜谱数据 - 全局变量声明供其他文件使用 ### 2. `recipe_manager.h` - 功能接口声明 **功能**:声明所有菜谱管理功能的接口 ```c #ifndef RECIPE_MANAGER_H #define RECIPE_MANAGER_H #include "data_structures.h" // 核心功能声明 void add_recipe(); // 添加菜谱 void delete_recipe(); // 删除菜谱 void modify_recipe(); // 修改菜谱 void search_recipes(); // 查找菜谱 void random_recommendation();// 随机推荐 // 辅助函数声明 void print_recipe(Recipe* recipe); // 打印菜谱详情 Recipe* find_recipe_by_id(int id); // 按ID查找 // ...(其他辅助函数) #endif ``` **关键点**: - 声明CRUD(增删改查)核心功能 - 定义辅助函数接口 - 提供数据访问抽象层 ### 3. `file_io.h` - 文件操作接口 **功能**:声明数据持久化相关函数 ```c #ifndef FILE_IO_H #define FILE_IO_H #include "data_structures.h" // 文件操作接口 void save_data(); // 保存菜谱数据 void load_data(); // 加载菜谱数据 void save_lovers(); // 保存喜好者数据 void load_lovers(); // 加载喜好者数据 #endif ``` **关键点**: - 数据持久化接口 - 分离数据存储逻辑 - 支持二进制和文本格式存储 ### 4. `main.c` - 程序入口和主控 **功能**:系统入口、主循环和菜单控制 ```c #include "data_structures.h" #include "recipe_manager.h" #include "file_io.h" // 全局变量定义(实际存储) Recipe* recipe_list = NULL; int recipe_count = 0; // ... // 主菜单显示 void display_main_menu() { printf("\n家庭菜谱管理系统\n"); printf("1. 添加菜谱\n"); // ...(其他菜单项) } // 喜好者管理子菜单 void lover_management() { printf("\n喜好者管理\n"); printf("1. 添加喜好者\n"); // ... } // 主函数 int main() { load_data(); // 启动时加载数据 load_lovers(); while (running) { display_main_menu(); // 处理用户选择 switch(choice) { case 1: add_recipe(); break; // ...(其他功能调用) } } save_data(); // 退出时保存数据 return 0; } ``` **关键点**: - 程序入口和主循环 - 菜单导航控制 - 数据初始化和清理 - 功能调度中心 ### 5. `recipe_manager.c` - 核心业务逻辑 **功能**:实现所有菜谱管理功能 ```c #include "data_structures.h" #include "recipe_manager.h" // 多选选项处理(位掩码应用) void get_multi_select_options(const char* prompt, int* flags, const char* options[], int count) { printf("%s (输入空行结束):\n", prompt); for (int i = 0; i < count; i++) { printf("%d. %s\n", i+1, options[i]); } *flags = 0; // 初始化位掩码 while (1) { fgets(input, sizeof(input), stdin); if (input[0] == '\n') break; int choice = atoi(input); *flags |= (1 << (choice-1)); // 设置对应位 } } // 添加菜谱实现 void add_recipe() { Recipe* new_recipe = malloc(sizeof(Recipe)); new_recipe->id = recipe_count + 1; // 自动生成ID // 获取用户输入 printf("输入菜名: "); fgets(new_recipe->name, MAX_NAME_LEN, stdin); // ...(其他字段输入) // 链表操作(头插法) new_recipe->next = recipe_list; recipe_list = new_recipe; recipe_count++; } // 随机推荐算法 void random_recommendation() { srand(time(NULL)); // 初始化随机种子 if (choice == 1) { // 单餐推荐 // 收集符合条件的候选菜谱 Recipe* candidates[MAX_RECIPES]; int candidate_count = 0; Recipe* current = recipe_list; while (current) { if (current->meal_type == meal_type) { candidates[candidate_count++] = current; } current = current->next; } // 随机选择 for (int i = 0; i < count; i++) { int idx = rand() % candidate_count; print_recipe(candidates[idx]); // 避免重复:替换为最后一个元素 candidates[idx] = candidates[--candidate_count]; } } // ...(多天推荐实现) } ``` **关键点**: - 位掩码技术的实际应用 - 链表操作(添加、删除、遍历) - 随机算法实现(Fisher-Yates洗牌变体) - 用户输入处理和多选交互 - 复杂查询逻辑实现 ### 6. `file_io.c` - 数据持久化实现 **功能**:实现数据存储和加载的具体逻辑 ```c #include "data_structures.h" // 保存菜谱数据(链表到二进制文件) void save_data() { FILE* file = fopen("recipes.dat", "wb"); Recipe* current = recipe_list; while (current) { // 注意:不保存next指针(只保存数据) fwrite(current, sizeof(Recipe) - sizeof(Recipe*), 1, file); current = current->next; } fclose(file); } // 加载菜谱数据(二进制文件链表) void load_data() { FILE* file = fopen("recipes.dat", "rb"); Recipe* last = NULL; while (!feof(file)) { Recipe* new_recipe = malloc(sizeof(Recipe)); if (fread(new_recipe, sizeof(Recipe) - sizeof(Recipe*), 1, file) != 1) { free(new_recipe); break; } new_recipe->next = NULL; // 重建链表 if (!recipe_list) recipe_list = new_recipe; else last->next = new_recipe; last = new_recipe; recipe_count++; } fclose(file); } // 文本格式保存喜好者 void save_lovers() { FILE* file = fopen("lovers.dat", "w"); for (int i = 0; i < lover_count; i++) { fprintf(file, "%s\n", lovers_list[i]); } fclose(file); } ``` **关键点**: - 链表数据的序列化和反序列化 - 二进制和文本格式混合存储 - 指针处理(不保存指针值) - 错误处理和内存管理 ### 7. `Makefile` - 构建系统 **功能**:自动化编译和项目管理 ```makefile CC = gcc # C编译器 CFLAGS = -Wall -Wextra -std=c99 -g # 编译选项 TARGET = recipe_system # 目标程序名 SOURCES = main.c recipe_manager.c file_io.c # 源文件 HEADERS = data_structures.h recipe_manager.h file_io.h # 头文件 # 默认构建目标 all: $(TARGET) # 链接规则 $(TARGET): $(SOURCES) $(HEADERS) $(CC) $(CFLAGS) -o $(TARGET) $(SOURCES) # 清理构建产物 clean: rm -f $(TARGET) *.dat # 运行程序 run: $(TARGET) ./$(TARGET) .PHONY: all clean run ``` **关键点**: - 自动化编译流程 - 依赖关系管理 - 常用命令封装(clean, run) - 跨平台支持(Linux/macOS) ### 系统架构总结 1. **分层架构**: - 数据层(data_structures) - 业务层(recipe_manager) - 持久层(file_io) - 表示层(main) 2. **模块化设计**: - 每个文件职责单一 - 通过头文件定义接口 - 低耦合高内聚 3. **关键技术**: - 链表数据结构 - 位掩码多选处理 - 文件序列化 - 随机算法 - 用户交互设计 4. **扩展性**: - 通过链表支持动态增长 - 模块化设计易于功能扩展 - 数据格式支持新属性添加
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值