C语言手搓迷你DBMS:建表改表与文件持久化实战

发布时间:2026/10/17 14:45:32

C语言手搓迷你DBMS:建表改表与文件持久化实战 简介这份资源是山东科技大学信息科学与工程学院《数据库系统概论》课程设计的实验报告面向计算机科学与技术专业学生及需要完成类似数据库课程设计的学习者。报告围绕实现一个简易DBMS展开核心功能包括CREATE TABLE建表与ALTER TABLE修改表涉及表结构定义、列完整性约束、物理存储结构设计、命令行与图形化界面交互以及程序流程图绘制并参考了王珊、萨师煊《数据库系统概论》等教材。压缩包内共1个doc文档约408KB完整记录了需求分析、设计思想、模块划分与实现过程包含新建表、修改表、显示已建表信息、保存并退出四个基本模块的说明。目前已有933人学习下载适合作为数据库课程设计参考模板帮助读者理解表结构持久化存储、SQL语句解析与动态修改表结构等关键实现思路。1. 从一份课程设计说起用 C 语言手搓一个能建表改表的迷你 DBMS很多人第一次接触数据库都是从写 SQL 开始的但真让你自己动手实现一个能解析CREATE TABLE、ALTER TABLE的小型数据库管理系统多数人还是会卡在“怎么把表结构存下来、怎么再读回来”这一步。这份课程设计做的就是这件事用 C 语言实现一个简单的 DBMS支持创建表、修改表、读取已建表、保存并退出四个核心功能命令行和图形界面两种交互方式都要有。它适合正在学数据库系统概论、需要交课程设计的学生也适合想搞明白“SQL 语句到底是怎么被程序吃进去、拆开、再存成文件”的开发者。整个项目不依赖任何数据库引擎纯靠数组和文件流把表结构持久化到本地文本文件里麻雀虽小但建表、改表、存表、读表这条链路是完整的。2. 整体架构与存储设计四个模块怎么串起来2.1 功能模块划分与主流程程序围绕四个功能展开新建表、修改表、显示已建表信息、保存并退出。主流程是一个循环菜单用户选择操作后进入对应的 SQL 解析函数解析成功后更新内存中的表结构数组退出时统一写回文件。// 主菜单循环伪代码结构 int main() { int choice; while (1) { printf(1.新建表 2.修改表 3.显示表信息 4.保存并退出\n); scanf(%d, choice); switch (choice) { case 1: printf(请输入 CREATE TABLE 语句\n); gets(SQL); // 读取整条 SQL SQL_CREATE(); // 解析并存入数组 break; case 2: printf(请输入 ALTER TABLE 语句\n); gets(SQL); SQL_ALTER(); // 解析并修改数组 break; case 3: ShowTables(); // 遍历数组打印 break; case 4: Save(); // 写回 table.txt return 0; } } }这里的关键点是所有表结构先存在内存数组里只有退出时才落盘。这样做的好处是修改表结构时不用反复读写文件坏处是程序崩溃会丢数据。课程设计场景下够用但如果你要改成实用工具建议每次操作后都同步写文件。2.2 表结构的物理存储数组怎么定义表名、字段名、字段类型、约束条件全部用字符数组存储维度设计直接决定了能建多少表、每张表能有多少字段。#define LEN 20 #define MAX 50 char tableName[LEN][MAX]; // 表名最多 LEN 张表 char fieldName[LEN][LEN][MAX]; // 字段名每表最多 LEN 个字段 char fieldType[LEN][LEN][MAX]; // 字段类型 char fieldCondition[LEN][LEN][MAX]; // 列级约束条件 char f_t_len[LEN][LEN][MAX]; // 字段长度如 (20) int Pro_Num[LEN]; // 每张表的字段个数 int number 0; // 当前已建表数量tableName[LEN][MAX]表示最多 LEN 张表每张表名最长 MAX 个字符。fieldName[LEN][LEN][MAX]是三维数组第一维是表索引第二维是字段索引第三维是字段名本身。这种设计在 C 语言课程设计里很常见优点是直观、不用动态内存缺点是容量固定、浪费空间。如果你要扩展可以把LEN和MAX调大或者改成链表加动态分配。2.3 持久化策略为什么每次保存都全量重写保存表信息时程序先把table.txt清空再把内存中所有表结构重新写进去。这个策略看起来笨但避免了“在文件中间修改某张表”的复杂定位逻辑。void Save() { ofstream outFile(table.txt, ios::trunc); // 清空重写 outFile number endl; // 先写表数量 for (int i 0; i number; i) { outFile tableName[i] endl; // 表名 outFile Pro_Num[i] endl; // 字段个数 for (int j 0; j Pro_Num[i]; j) { outFile fieldName[i][j] fieldType[i][j] f_t_len[i][j] fieldCondition[i][j] endl; } } outFile.close(); }写入顺序是表数量 → 每张表的表名 → 字段个数 → 每个字段的四个属性。读取时按同样顺序反向解析即可。这种“顺序写、顺序读”的格式比在文件里做随机访问简单得多也是课程设计里最稳妥的做法。3. 核心模块拆解CREATE TABLE 的解析链路3.1 词法扫描怎么从字符串里抠出关键字SQL_CREATE()的第一步是跳过前导空格然后逐字符读取用tolower()统一转小写后比对关键字。int SQL_CREATE() { int i 0, j; char temp[LEN]; // 跳过开头空格 for (; SQL[i] ; i); // 读取第一个单词 for (j 0; SQL[i] ! SQL[i] ! ;; i, j) { temp[j] tolower(SQL[i]); } temp[j] \0; if (strcmp(temp, create) ! 0) { printf(你输入的 create 有误请重新创建\n); return 0; } // 继续判断 table... }这里用tolower()是为了让CREATE、Create、create都能通过。SQL是全局字符数组存的是用户输入的整条语句。i是当前扫描位置j是临时缓冲区索引。判断完create后同样的逻辑再判断table然后开始提取表名。3.2 提取表名与字段定义表名提取完后进入do-while循环逐个解析字段名、类型、长度和约束。// 提取表名 for (; SQL[i] ; i); for (j 0; SQL[i] ! SQL[i] ! ;; i, j) { t_Name[j] SQL[i]; } t_Name[j] \0; strcpy(tableName[number], t_Name); // 循环提取字段 do { // 跳过空格和逗号 for (; SQL[i] || SQL[i] ,; i); if (SQL[i] ;) break; // 字段名 for (j 0; SQL[i] ! ; i, j) { f_Name[j] SQL[i]; } f_Name[j] \0; strcpy(fieldName[number][num], f_Name); // 字段类型 for (; SQL[i] ; i); for (j 0; SQL[i] ! SQL[i] ! ,; i, j) { temp[j] tolower(SQL[i]); } temp[j] \0; // 匹配 char/int/float/double/date // ... num; } while (SQL[i] ! ;);字段类型只支持五种char、int、float、double、date。匹配时用strcmp逐个比对匹配不上就报错。字段长度只对char、float、double提取且必须是纯数字否则报错。约束条件支持primary key、unique、not null其他情况存为NULL。3.3 约束条件的识别与存储约束条件的解析是嵌套在字段循环里的先读一个单词再判断是不是primary、unique、not。// 读取约束条件第一个词 for (; SQL[i] ; i); for (j 0; SQL[i] ! SQL[i] ! , SQL[i] ! ;; i, j) { condition[j] tolower(SQL[i]); } condition[j] \0; if (strcmp(condition, primary) 0) { // 继续读 key for (; SQL[i] ; i); for (j 0; SQL[i] ! SQL[i] ! ,; i, j) { temp[j] tolower(SQL[i]); } temp[j] \0; if (strcmp(temp, key) 0) { strcpy(fieldCondition[number][num], primary key); } } else if (strcmp(condition, unique) 0) { strcpy(fieldCondition[number][num], unique); } else if (strcmp(condition, not) 0) { // 继续读 null // ... strcpy(fieldCondition[number][num], not null); } else { strcpy(fieldCondition[number][num], NULL); }primary key和not null都是两个单词所以需要二次读取。unique是单个单词直接存。如果用户没写约束就存NULL。这里有个细节fieldCondition是二维数组[LEN][MAX]但代码里有时写成fieldCondition[number][num] primary key这是原代码的笔误正确写法应该是strcpy。4. ALTER TABLE 的实现改表比建表更容易翻车4.1 表存在性检查与分支判断ALTER TABLE的第一步是提取表名然后遍历tableName数组确认表是否存在。int SQL_ALTER() { char alterName[MAX]; int al_num; // ... 解析 alter 和 table 关键字 ... // 提取表名 for (; SQL[i] ; i); for (j 0; SQL[i] ! ; i, j) { t_name[j] SQL[i]; } t_name[j] \0; strcpy(alterName, t_name); // 查找表 for (al_num 0; al_num number; al_num) { if (strcmp(tableName[al_num], alterName) 0) break; } if (al_num number) { printf(你输入的表 %s 不存在\n, t_name); return 0; } // 继续判断 ADD / DROP / MODIFY }al_num记录找到的表索引后续所有修改都作用在这个索引上。如果遍历完都没找到说明表不存在直接报错返回。4.2 ADD 子句追加新列ADD 分支负责在现有表末尾追加一个字段需要解析字段名、类型、长度和约束然后写入数组的Pro_Num[al_num]位置。if (tolower(SQL[i]) a) { // 判断 add // ... // 提取新字段名 for (; SQL[i] ; i); for (j 0; SQL[i] ! ; i, j) { field_name[j] SQL[i]; } field_name[j] \0; int pos Pro_Num[al_num]; // 新字段插入位置 strcpy(fieldName[al_num][pos], field_name); // 提取类型、长度、约束与 CREATE 类似 // ... Pro_Num[al_num]; // 字段数加一 }关键点是pos Pro_Num[al_num]新字段直接放在已有字段后面不需要移动其他数据。这也是用数组存储的好处追加操作是 O(1)。4.3 DROP 与 MODIFY 子句DROP 按约束名删除MODIFY 修改字段类型。这两个分支的解析逻辑与 ADD 类似但操作的是已有字段。// DROP 分支删除指定约束 if (tolower(SQL[i]) d) { // 解析 drop 和约束名 // 遍历该表所有字段找到匹配的约束并清空 for (int k 0; k Pro_Num[al_num]; k) { if (strcmp(fieldCondition[al_num][k], dropName) 0) { strcpy(fieldCondition[al_num][k], NULL); } } } // MODIFY 分支修改字段类型 if (tolower(SQL[i]) m) { // 解析 modify、字段名、新类型 // 找到字段后更新 fieldType 和 f_t_len for (int k 0; k Pro_Num[al_num]; k) { if (strcmp(fieldName[al_num][k], modifyName) 0) { strcpy(fieldType[al_num][k], newType); // 更新长度... } } }DROP 在课程设计里实现的是“删除约束”而不是“删除列”这和标准 SQL 的DROP COLUMN有区别。如果你要改成删列需要把后续字段整体前移并减少Pro_Num。5. 避坑与排查课程设计里最容易翻车的几个点5.1 文件路径写死导致换机就崩现象在自己电脑上跑得好好的换一台机器或者换个目录程序一启动就报错或者读不到表。原因原代码里文件路径写成了C:\\Users\\Administrator\\Desktop\\DBMS.txt这是绝对路径换环境必然失效。解决改成相对路径table.txt或者用argv[0]动态获取程序所在目录。最稳妥的做法是把文件放在程序同级目录用fopen(table.txt, r)直接访问。5.2 字段长度只存了一位数字现象定义char(20)的字段保存后再读取长度变成了(2)或者乱码。原因原代码里f_t_len[number][num][1] f_length 48;只把长度转成一个字符如果长度是 20204868对应字符D完全错了。解决长度应该用sprintf或itoa转成字符串再存。比如sprintf(f_t_len[number][num], (%d), f_length);。5.3 约束条件用字符赋值而不是字符串拷贝现象设置了primary key保存后读出来变成乱码或者只剩一个字符。原因代码里写的是fieldCondition[number][num] primary key;这是把字符串赋给单个字符变量编译能过但运行结果完全错误。解决统一用strcpy(fieldCondition[number][num], primary key);。所有字符串赋值都要用strcpy或strncpy不能用。5.4 读取时没有处理文件结束标志现象程序启动后显示表信息最后多出一张空表或者乱码表。原因Read()函数里用while(!ioFile.eof())判断文件结束但eof()只有在读操作失败后才返回 true导致最后一次循环多读一次。解决改成while(ioFile tableName[number])用流读取的返回值判断是否成功。或者读完一条记录后立即检查ioFile.eof()并break。5.5 数组越界没有检查现象建了太多表或者字段太多程序崩溃或者数据覆盖。原因tableName[LEN][MAX]的LEN是 20建第 21 张表时越界写入覆盖了相邻内存。解决每次number之前检查if (number LEN) { printf(表数量已达上限\n); return 0; }。字段数同理在num前检查num LEN。6. 二次开发与扩展从课程设计到能用的工具如果你想让这个项目从“交作业”变成“真能用”有几个方向可以改。第一把固定数组换成动态链表或std::vector这样表数量和字段数量不再受限。第二把文件存储从纯文本改成二进制或 JSON 格式读取时不用再逐字符解析直接反序列化就行。第三加一个简单的 SQL 解析器支持INSERT、SELECT、DELETE让它真正能存数据而不只是存表结构。// 用链表替代固定数组的示例结构 typedef struct Field { char name[MAX]; char type[MAX]; char length[MAX]; char condition[MAX]; struct Field *next; } Field; typedef struct Table { char name[MAX]; int fieldCount; Field *fields; struct Table *next; } Table; Table *tableList NULL; // 表链表头指针改成链表后建表就是往链表尾部追加节点改表就是遍历链表找到目标节点再修改。删除表就是链表节点摘除。这样容量问题彻底解决代价是代码复杂度上升需要自己管理内存释放。另一个实用的改造是加一个简单的图形界面。课程设计要求里提到了“命令行和图形化界面两种形式”但原代码只实现了命令行。你可以用 Windows API 或者 GTK 做一个窗口把输入框、按钮、列表控件绑到现有的解析函数上。解析逻辑不用动只是把scanf换成从输入框读字符串把printf换成往列表控件写数据。验证改造是否成功我一般会走一遍完整流程建三张表每张表带不同约束改其中一张表的字段类型删一个约束保存退出再重新打开看数据是否完整。这个流程能覆盖 90% 的 bug。从那以后我每次改完存储相关的代码都强制走一遍“建表→改表→保存→重启→读表”的闭环不然很容易出现“写进去读不出来”的玄学问题。希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/17 14:45:32

人工智能搜索技术实战:从状态空间到A*算法落地

简介:这份PDF资料面向人工智能初学者与算法进阶学习者,系统梳理搜索技术这一AI问题求解的核心分支。内容从搜索技术概述切入,说明问题求解可抽象为状态空间中的搜索过程,并依次讲解状态空间表示、盲目搜索、启发式搜索、A算法与A*…

2026/10/17 14:45:32

2024年CSP-J初赛备考指南:信息学奥赛入门组知识点与真题解析

简介:2024年信息学奥赛初中组(CSP-J)初赛真题详细分析文档,面向备战该项赛事的初中生与信息学竞赛教练,提供完整初赛试题和逐题解析。内容按题型编排,系统梳理单项选择题与阅读程序题等核心板块&#xff0c…

2026/10/17 14:45:32

数据中心智能化运维落地指南:设备管理、资源调度与告警收敛

简介:围绕数据中心智能化运维与虚拟化数据中心管理,这份技术文档面向运维工程师、IT管理者及数据中心规划人员,旨在解决传统运维方式难以支撑现代数据中心规模与复杂度持续增长的问题。文档依次阐述智能化设备管理、资源调度、任务分配、监控…

2026/10/17 15:55:41

AI辅助MBA论文写作:9个靠谱工具与避坑指南

先给一句实话:AI论文网站不是“一键生成MBA论文”的神器,谁要是这么跟你保证,趁早拉黑。真正靠谱的用法,是把这些工具当成贴身研究助理——帮你找文献、理逻辑、调格式、改病句,但论文里的核心判断、行业分析和决策建议…

2026/10/17 15:55:41

软件系统实施方案怎么写?从环境基线到验收交付的落地指南

简介:这份资源是一份面向软件项目实施人员、项目经理及IT管理者的《软件系统实施方案》文档,聚焦于软件项目从启动到交接的全流程落地方法,适合需要编写实施方案或规范实施流程的从业者参考。压缩包内共1个doc文件,约72KB&#xf…

2026/10/17 15:55:41

MATLAB实现Koch曲线:分形迭代与复数坐标可视化

1. 项目概述:一条“自相似”的线,如何用几十行代码画出无限细节?你有没有见过雪花的边缘?放大再放大,它总在重复着相似的锯齿结构;或者海岸线的轮廓,无论卫星图还是显微镜下的岩石断面&#xff…

2026/10/17 15:55:41

HTTP协议分析实验:Wireshark抓包拆解请求/响应报文与GET/POST对比

HTTP协议分析实验,是计算机网络课程里最能让人“开窍”的一个实验。我当初做这个实验的时候,第一次在抓包工具里亲眼看到自己访问网页时发出的那些字节,才真正理解课本上说的“请求报文”“响应报文”不是抽象框架,而是实实在在写…

2026/10/17 15:55:41

Git 心智模型与事故恢复:新人版本控制实战指南

新同事第一天入职,组长让我抽个空把开发环境给他过一遍。我原本的计划特别朴素:花二十分钟把 Git 的日常链路演示一遍,能 clone、add、commit、push、pull,先跑起来别出岔子就行。结果这位新同学第一句“Git 和 SVN 到底差在哪”就…

2026/10/17 15:50:40

CC-Switch 完整安装与使用指南:从下载到 API 配置全流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 5:33:43

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/16 4:03:48

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/17 14:20:58

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/17 0:04:32

PlanarSLAM:面向曼哈顿世界的点线面联合SLAM系统

简介:本资源是面向机器人视觉与SLAM研究者的C开源RGB-D SLAM系统实现,专为结构化室内场景(如办公室、走廊)设计,通过融合点、线、平面三类几何特征并引入“曼哈顿世界”假设,显著提升位姿跟踪鲁棒性与稠密地…

2026/10/17 0:04:32

TDH Workflow调度实战:从DAG依赖驱动到任务编排与故障排查

1. 从一个调度脚本说起:TDH Workflow到底在解决什么问题第一次接触TDH的Workflow,是在一个数据清洗项目里。当时团队每天要跑几十个ETL任务,任务之间有严格的先后依赖——A表清洗完才能跑B表的关联,B表跑完才能触发C表的聚合。最开…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑