恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
C语言实现迷你DBMS:SQL解析、表管理与文件持久化
首页
资讯中心
/
C语言实现迷你DBMS:SQL解析、表管理与文件持久化
C语言实现迷你DBMS:SQL解析、表管理与文件持久化
发布时间:2026/10/18 0:41:02
简介这份资源是山东科技大学信息科学与工程学院《数据库系统概论》课程设计的实验报告面向计算机科学与技术专业学生及需要完成数据库课程设计的学习者核心任务是实现一个简易DBMS支持CREATE TABLE与ALTER TABLE语句完成建表、改表、读取与保存表结构等操作。报告完整记录了需求分析、设计思想、物理存储结构设计、程序流程图及命令行与图形化界面两种交互方式的实现思路并给出用数组存储表名、属性名、类型与约束条件以及通过table.txt持久化表信息的具体方案。压缩包内为1个doc文档约408KB即课程设计说明书全文包含任务书、参考资料、设计步骤与代码结构说明可直接作为同类课程设计的参考模板与写作范例。目前已有933人学习下载适合需要借鉴建表改表实现逻辑、梳理设计文档结构或准备数据库课程设计答辩的读者参考使用。1. 从一行 SQL 到一套迷你 DBMS这门课设到底要交什么很多人第一次看到“实现一个简单的 DBMS”这个题目第一反应是去翻《数据库系统概论》的目录然后把 ER 图、范式、SQL 语法挨个抄一遍交上去。但真正带过这门课设的人都知道老师要看的不是你对 SQL 有多熟而是你能不能把CREATE TABLE Students (ID INT PRIMARY KEY, Name CHAR(20), Age INT);这行字符串变成内存里一张结构化的表再把它持久化到磁盘上下次打开程序还能原样读回来。这个项目适合计算机科学与技术专业大三学生做数据库课程设计也适合想理解 DBMS 内部表管理机制的开发者拿来练手。核心就三件事解析 SQL 语句、用数组或结构体存表结构、用文件做持久化。把这三件事串起来一份能跑、能演示、能写进实验报告的课设就立住了。2. 需求拆解与技术选型为什么用 C 而不是数据库2.1 功能边界四个模块撑起一个闭环这个课设的功能需求其实很收敛原文任务书里写得很清楚一共四个模块新建表、修改表、显示已建表的信息、保存并退出。看起来简单但每个模块背后都有必须交代的设计决策。新建表要支持CREATE TABLE 表名 (列名数据类型[列完整性约束条件][,...][,表完整性约束条件])这个完整语法。数据类型至少覆盖char、int、float、double、date五种约束条件要能识别primary key、unique、not null。修改表要支持ALTER TABLE 表名 [ADD 新列名数据类型[列完整性约束]] [DROP 列完整性约束名] [MODIFY 列名数据类型]三种操作。显示表信息就是把当前内存里所有表的结构打印出来。保存并退出则是把表结构写回文件保证下次启动能恢复。这四个模块不是孤立的。新建和修改操作的是同一份内存数据结构显示是读这份结构保存是把它序列化到磁盘。所以设计的第一步不是写代码而是确定这份内存数据结构长什么样。2.2 技术选型C 语言 文件存储的合理性为什么用 C 而不是直接调 SQLite因为课设的目的是让你理解 DBMS 内部怎么管表不是让你学 API 调用。用 C 语言手写解析和存储虽然代码量大但每一步逻辑都是透明的。原文里选了 VC6 和 CodeBlocks 作为开发环境这在当年是标准配置现在用 Dev-C 或者 VS Code 配 MinGW 也完全能跑。存储方案用的是纯文本文件table.txt。这个选择很务实不需要引入任何数据库驱动不需要处理二进制格式的字节对齐用fstream的和就能完成读写。代价是存储效率低、不支持并发但课设场景下这些都不是问题。我一般会建议学生在报告里主动提一句“本设计采用文本文件存储牺牲了存储密度和并发能力换取实现简单和可调试性”这样老师反而觉得你想清楚了 trade-off。2.3 内存数据结构四个数组的职责划分原文里用了四个字符数组来存表信息这个设计直接决定了后续所有代码的写法#define LEN 50 #define MAX 100 char tableName[LEN][MAX]; // 表名tableName[i] 是第 i 张表的名字 char fieldName[LEN][LEN][MAX]; // 属性名fieldName[i][j] 是第 i 张表第 j 个字段的名字 char fieldType[LEN][LEN][MAX]; // 属性类型存 int、char 等字符串 char fieldCondition[LEN][LEN][MAX]; // 列级约束条件存 primary key、not null 等 int Pro_Num[LEN]; // 每张表的字段个数 int number 0; // 当前已创建的表数量这里tableName是二维的第一维是表序号第二维是表名字符串。fieldName、fieldType、fieldCondition是三维的第一维表序号第二维字段序号第三维字符串内容。Pro_Num记录每张表有多少个字段这样遍历字段时不用靠\0判断边界。注意原文里fieldCondition的赋值用了单引号比如fieldCondition[number][num] primary key;这在 C 里是错的单引号只能包一个字符。正确写法是strcpy(fieldCondition[number][num], primary key);。这个坑我在后面避坑章节会展开说。3. 核心模块实现SQL 解析、建表与改表3.1 CREATE TABLE 的词法解析流程解析CREATE TABLE语句的核心思路是跳过空格逐个提取关键字验证拼写然后把表名、字段名、类型、长度、约束依次存进数组。原文的SQL_CREATE()函数就是这个流程我把它拆成几个关键步骤来说明。第一步是验证CREATE关键字。从SQL数组当前位置开始跳过前导空格然后逐字符读取直到遇到空格或分号转小写后和create比较for(i 0; 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; }这里用tolower是为了让CREATE、Create、create都能通过。temp是临时缓冲区存当前提取的关键字。验证完create后同样的逻辑验证table。第二步是提取表名。跳过空格后逐字符读取直到空格或分号存进t_Name再拷贝到tableName[number]。注意这里number是全局的表计数器每成功创建一张表就加一。第三步是循环提取每个字段。用do-while循环每次迭代处理一个字段定义直到遇到分号结束。每个字段的处理分四小步提取字段名、提取字段类型、提取字段长度如果是 char/float/double、提取约束条件。字段类型的匹配用了一个数组做映射char type[5][LEN] {char, int, float, double, date}; char Type1[5] {C, I, F, B, D};遍历type数组如果匹配到某个类型就用对应的Type1字符做标记。这个标记后续用来判断是否需要读取长度参数——C、F、B需要读长度I、D不需要。字段长度的提取有个细节原文用f_length f_length * 10 (SQL[i] - 48)把数字字符转成整数然后存进f_t_len数组时又用f_length 48转回字符。这个来回转换其实没必要直接存整数字符串更清晰。但原文的逻辑能跑通因为长度通常是个位数48不会溢出 char 范围。约束条件的识别是逐个关键字比对读到primary就继续读下一个词验证是不是key读到unique就直接标记读到not就继续读验证是不是null。如果都不匹配就报错。3.2 ALTER TABLE 的表查找与三种操作ALTER TABLE的解析比CREATE多了一步先找到要修改的表。原文的SQL_ALTER()函数里提取表名后用一个循环在tableName数组里查找for(al_num 0; al_num number; al_num) { if(strcmp(tableName[al_num], alterName) 0) break; // 找到了al_num 就是表索引 } if(al_num number) { printf(你输入的表%s不存在请重新输入!\n, t_name); return 0; }找到表之后根据ALTER后面的关键字分三种情况处理。ADD是添加新列逻辑和CREATE里提取字段几乎一样只是把新字段追加到fieldName[al_num][Pro_Num[al_num]]位置然后Pro_Num[al_num]。DROP是删除列需要先找到列名对应的索引然后把后面的字段全部前移一位最后Pro_Num[al_num]--。MODIFY是修改列类型直接找到对应字段覆盖fieldType和fieldCondition即可。原文的代码里ADD分支写得比较完整DROP和MODIFY没有展开。补全DROP的关键代码如下// 在 ALTER TABLE ... DROP 列名 分支中 for(j 0; j Pro_Num[al_num]; j) { if(strcmp(fieldName[al_num][j], drop_col_name) 0) break; // 找到要删除的列索引 j } if(j Pro_Num[al_num]) { printf(列%s不存在!\n, drop_col_name); } else { for(int k j; k Pro_Num[al_num] - 1; k) { strcpy(fieldName[al_num][k], fieldName[al_num][k1]); strcpy(fieldType[al_num][k], fieldType[al_num][k1]); strcpy(fieldCondition[al_num][k], fieldCondition[al_num][k1]); } Pro_Num[al_num]--; // 字段数减一 }这段逻辑的核心是“找到索引、前移覆盖、计数减一”。前移的时候三个数组要同步移动漏掉任何一个都会导致字段名和类型对不上。3.3 文件持久化先清空再重写的策略保存表信息到table.txt的策略原文说得很直白每次保存前先清空文件然后把内存里所有表重新写一遍。这个策略的好处是不用处理“修改后原位置覆盖”的麻烦坏处是表多了以后写入量大。课设场景下表数量通常不超过十张这个代价可以接受。写入格式是这样的先写表名再写字段个数然后逐个字段写字段名、类型、长度、约束。原文的读取函数Read()就是按这个格式反向解析的ioFile tableName[number]; // 读表名 ioFile Pro_Num[number]; // 读字段个数 for(i 0; i Pro_Num[number]; i) { ioFile fieldName[number][i]; // 读字段名 ioFile fieldType[number][i]; // 读字段类型 // 如果是 char/float/double还要读长度标记 if(strcmp(fieldType[number][i], char) 0 || strcmp(fieldType[number][i], float) 0 || strcmp(fieldType[number][i], double) 0) { ioFile mark[i]; // 读长度如 (20) } ioFile _condition1[i]; // 读约束条件 // 根据约束条件关键字做转换 if(strcmp(_condition1[i], primary) 0) { strcpy(fieldCondition[number][i], primary key); } else if(strcmp(_condition1[i], unique) 0) { strcpy(fieldCondition[number][i], unique); } else if(strcmp(_condition1[i], not) 0) { ioFile _condition2[i]; // 继续读 null strcpy(fieldCondition[number][i], not null); } else { strcpy(fieldCondition[number][i], _condition1[i]); } } number; // 表计数加一这里有个容易翻车的点约束条件primary key和not null在文件里是分开两个词存的读取时要先读第一个词判断后再决定要不要读第二个词。如果写入时没有按这个格式存读取时就会错位导致后面的字段全部解析错误。提示文件路径建议用相对路径table.txt而不是原文里的绝对路径C:\\Users\\Administrator\\Desktop\\DBMS.txt。绝对路径换台机器就跑不了答辩演示时容易出洋相。4. 避坑与常见问题那些年课设翻过的车4.1 单引号赋值字符串导致编译报错现象编译时报warning: multi-character character constant或者直接报错运行时约束条件显示乱码。原因C 语言里单引号只能包一个字符fieldCondition[number][num] primary key;这种写法是把一个多字符常量赋给 char 变量行为未定义。解决所有字符串赋值统一用strcpy。比如strcpy(fieldCondition[number][num], primary key);。检查全文把所有单引号包多字符的地方都改掉。4.2 文件读取时字段错位现象程序启动后读取table.txt表结构显示错乱字段名和类型对不上或者直接崩溃。原因写入和读取的格式不一致。比如写入时约束条件存的是primary key两个词读取时只读了一个词就开始读下一个字段导致后续全部错位。解决写入和读取用同一套格式约定。建议在报告里画一张存储格式表明确每个字段占几个 token。读取时严格按 token 数解析遇到primary和not要记得多读一个词。4.3 表名或字段名含空格导致解析中断现象输入CREATE TABLE my table (...)时表名被截断成my后面的table被当成关键字。原因解析器用空格作为分隔符表名和字段名不允许含空格。这是 SQL 标准的要求但输入时容易忽略。解决在解析前先检查表名和字段名是否含空格如果含就报错提示用户。或者在报告里明确写“本设计不支持含空格的标识符”。4.4 修改表后忘记更新字段计数现象ALTER TABLE ... ADD之后显示表信息时新字段没出来或者DROP之后显示多出一个空字段。原因ADD操作忘了Pro_Num[al_num]DROP操作忘了Pro_Num[al_num]--。解决把Pro_Num的更新和数组操作绑在一起写加字段就加计数删字段就减计数。可以在代码里加注释提醒自己。4.5 保存时文件打开失败没有提示现象程序显示“保存成功”但下次打开数据没了。原因fstream打开文件失败时不会抛异常ioFile处于失败状态后续写入全部无效。解决打开文件后立即检查if(!ioFile) { printf(文件打开失败!\n); return; }。保存和读取都要加这个检查。5. 验收与答辩怎么证明你的 DBMS 真的能跑验收这个课设老师通常不会只看代码而是让你当场演示。演示流程我建议按这个顺序走先展示一个空的table.txt启动程序输入CREATE TABLE Students (ID INT PRIMARY KEY, Name CHAR(20), Age INT);然后选“显示表信息”让老师看到表结构正确。接着输入ALTER TABLE Students ADD Address CHAR(50);再显示一次证明 ADD 生效。然后输入ALTER TABLE Students DROP Age;显示确认 Age 没了。最后选“保存并退出”重新启动程序再显示一次证明持久化成功。这个流程覆盖了创建、修改、显示、保存四个模块而且每一步都有可见的输出。比干讲代码有说服力得多。答辩时老师常问的问题有几个你的表结构存在哪里怎么保证下次能读回来如果两张表重名怎么办ALTER 修改表的时候怎么找到目标表这些问题在报告里都要有答案。表结构存在table.txt用文本格式序列化重名可以在CREATE时检查tableName数组里是否已存在同名表ALTER查找目标表用strcmp遍历比较。代码量方面完整的实现大概在 500 到 800 行 C 代码分三个文件比较清晰main.c放主循环和菜单parser.c放SQL_CREATE和SQL_ALTERstorage.c放Read和Save。报告里把流程图和存储格式表放进去再附上关键代码段和运行截图基本就齐了。注意答辩演示前一定要在答辩用的机器上跑一遍确认编译环境没问题。我见过太多在宿舍跑得好好的到教室一编译就报错的案例。从那以后我每次带课设都要求学生先在目标机器上完整走一遍演示流程把table.txt删掉从零开始跑。这个习惯帮我省了很多尴尬。希望帮到你。本文还有配套的精品资源点击获取