ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

手搓轻量DBMS内核:C语言实现CREATE/ALTER TABLE与持久化

手搓轻量DBMS内核:C语言实现CREATE/ALTER TABLE与持久化 简介本资源是山东科技大学计算机科学与技术专业《数据库系统概论》课程设计的完整实验报告面向高校数据库初学者与课程实践者聚焦DBMS核心功能——表结构的创建与动态修改帮助学习者深入理解CREATE TABLE与ALTER TABLE语句的底层实现逻辑及物理存储机制。报告由郑通同学于2012年完成内容涵盖需求分析、设计思想、程序流程图、命令行与图形界面双交互实现方案并详细说明了基于数组的表元数据存储结构、table.txt持久化机制及SQL语句解析逻辑。资源为单个Word文档.doc大小408KB结构完整含任务书、说明书、源码设计思路与教师评语等关键模块。已有931人学习下载可直接用于课程设计参考、数据库原理实践复现或C语言文件系统综合开发学习。1. 这不是模拟器是2012年手搓的轻量级DBMS内核用C实现CREATE/ALTER TABLE的物理存储、命令行解析与持久化落地你见过一个没有B树、没有WAL日志、不依赖任何第三方库仅靠char[][]三维数组 文本文件序列化就能跑通CREATE TABLE students (id int primary key, name char(20) not null)和ALTER TABLE students ADD score double的DBMS吗这不是教学玩具——这是山东科技大学2011级计算机专业郑通同学在2012年用VC6和Code::Blocks完成的真实课程设计源码里连f_t_len[number][i][0] (这种字段长度硬编码都带着编译器报错后反复调试的体温。它不处理事务、不支持索引、不解析WHERE条件但精准卡在数据库系统最硬核的边界上表结构定义的语法解析、内存模型构建、磁盘持久化与ALTER语义的原子性模拟。如果你正被《数据库系统概论》第六版第4章的“数据字典”概念绕晕或卡在课程设计“如何让CREATE语句真正生成可读写的表文件”这一步这份报告就是你缺的那块拼图——它用387行C代码把王珊教材里抽象的“系统目录”变成了table.txt里可grep、可diff、可逐字节验证的ASCII文本。新手能照着SQL_CREATE()函数一行行加printf调试老手能从fstream ioFile.open(C:\\Users\\Administrator\\Desktop\\DBMS.txt,ios::in)这行路径里嗅出当年Windows XP开发环境的真实气味。2. 表结构内存建模为什么用三维char数组而不是struct——从教材理论到C语言内存布局的硬核对齐2.1 教材里的“数据字典”在C中长什么样《数据库系统概论第四版》第3章明确指出“数据字典是DBMS中最重要的组成部分之一它存储了数据库中所有对象的定义信息”。但课本不会告诉你当谭浩强《C程序设计》第三版教完数组和指针后一个本科生要怎么把“表名、字段名、字段类型、约束条件”这四个维度塞进内存。郑通的选择很务实用三维char数组模拟关系型元数据的嵌套结构。这不是炫技而是对C语言内存模型的精准利用——每个表第一维、每个字段第二维、每个字段属性第三维都对应连续的字节块避免了malloc/free带来的碎片和指针管理开销。看源码里的声明char tableName[LEN][MAX]; // LEN10: 最多存10个表MAX50: 表名最长50字符 char fieldName[LEN][LEN][MAX]; // [表索引][字段索引][字符索引]: 每个表最多LEN10字段每字段名最长MAX50 char fieldType[LEN][LEN][MAX]; // 同上存int/char等类型字符串 char fieldCondition[LEN][LEN][MAX]; // 存primary key/not null等约束 int f_t_len[LEN][LEN][4]; // 特殊处理char(20)中的数字20用4字节存(20)提示f_t_len这个四字节数组是神来之笔。它把char(20)这种带括号的类型长度拆解为[(, 2, 0, )]既规避了C语言中动态字符串长度解析的复杂度又保证了写入table.txt时格式严格对齐——后续读取时直接按固定偏移读取即可不用strlen()。2.2 为什么不用struct { char name[50]; int type; }——内存对齐与序列化成本的血泪权衡有同学会问为什么不定义一个struct ColumnDef然后用ColumnDef columns[10][10]答案藏在Read()函数的文件读取逻辑里。看这段关键代码ioFiletableName[number]; // 直接用操作符读字符串 ioFilePro_Num[number]; // 读整数该表字段总数 for( i0; i Pro_Num[number]; i ) { ioFilefieldName[number][i]; // 逐字段读字段名 ioFilefieldType[number][i]; // 逐字段读类型 // ... 然后根据类型决定是否读长度括号 }如果用structioFile无法自动跳过结构体内存填充字节padding且char[50]末尾的\0在文本文件中不可见会导致读取错位。而纯char数组固定分隔符空格的方案让table.txt变成人类可读的明文students 3 id int name char 20 score double这种设计牺牲了面向对象的优雅却换来了零依赖的可调试性——你随时可以用记事本打开table.txt删掉一行字段再运行程序立刻看到Read()函数因eof()提前触发而加载失败。这才是课程设计该有的“可控翻车”。2.3 字段长度的魔鬼细节char(20) vs int——类型解析器的分支逻辑CREATE TABLE t(a char(10), b int)中char(10)需要提取数字10而int不需要。源码用f_Type变量做类型标记char Type1[5] {C,I,F,B,D}; // Cchar, Iint, Ffloat, Bdouble, Ddate // 解析到char时f_TypeC后续遇到(就启动数字提取 if(f_TypeC||f_TypeF||f_TypeB) { for(; SQL[i]! ; i) { if(SQL[i]48||SQL[i]58) flag0; // ASCII 048, 957 else f_lengthf_length*10(SQL[i]-48); } f_t_len[number][num][0] (; f_t_len[number][num][1] f_length48; // 把数字转回ASCII字符 f_t_len[number][num][2] ); }这里暴露了C语言处理字符串的原始感f_length48把整数20转成字符2因为f_t_len是char数组。这种写法在现代C中会被骂但在VC6环境下它是保证table.txt里char(20)能原样还原的唯一办法。真正的数据库内核开发往往始于对ASCII码表的肌肉记忆。3. CREATE TABLE语句解析器从字符串匹配到语法树的降维打击式实现3.1 关键词校验为什么先tolower()再strcmp()SQL_CREATE()函数开头的这段代码不是为了兼容大小写而是对抗VC6的strcmp()缺陷for(j0; SQL[i]! SQL[i]!;; i,j) { temp[j]tolower(SQL[i]); // 强制转小写 } temp[j]\0; if(strcmp(temp,create)!0) { // 用小写字符串比对注意VC6的strcmp()在遇到中文系统默认编码GBK时对大写字母比较不稳定。强制转小写是郑通在调试中发现的“后悔药”——只要输入CREATE、Create、create都能通过校验。这招在课程设计答辩时救了他因为老师演示时习惯全大写。3.2 字段循环解析用do-while替代for的深意创建多字段表时源码用do { ... } while(SQL[i]!;);而非for(int k0; kfield_count; k)原因在于字段数量未知。CREATE TABLE t(a int, b char(10))的字段数必须在解析过程中动态计数。看核心循环do { // 提取字段名跳过空格和逗号读到空格或逗号为止 for(; SQL[i] ||SQL[i],; i); if(SQL[i];) break; // 遇到分号结束 // 提取字段名 → 字段类型 → 字段长度如char→ 约束条件 num; // 字段计数器自增 } while(SQL[i]!;); Pro_Num[number] num; // 记录该表字段总数这个do-while确保至少执行一次处理第一个字段且在读到;时立即跳出。如果用for需先预扫描计算逗号数徒增复杂度。课程设计的精髓是用最简控制流覆盖最常见场景。3.3 约束条件解析PRIMARY KEY的两段式校验PRIMARY KEY是复合关键词源码拆成两步校验if(strcmp(condition,primary)0) { for(; SQL[i] ; i); for(j0; SQL[i]! SQL[i]!,; i,j) { temp[j]tolower(SQL[i]); } temp[j]\0; if(strcmp(temp,key)0) { // 第二步确认后面跟的是key fieldCondition[number][num] primary key; } else { error0; printf(\n 你输入的KEY有误...); // 精准报错 } }这种设计让错误定位极精准输入PRIMARY KEYS会报“KEY有误”输入PRIMERY KEY会报“primary有误”。对比现代Parser Generator如ANTLR生成的语法树这种手写状态机更轻量也更适合课程设计——你能在printf里加断点亲眼看到temp数组如何从prim变成primary。4. ALTER TABLE的语义实现ADD/DROP/MODIFY在内存模型中的映射4.1 表存在性校验线性查找的朴素力量SQL_ALTER()函数中查找目标表是否存在用的是最朴素的线性搜索for( al_num 0; al_num number; al_num) { if(strcmp(tableName[al_num],alterName) 0 ) break; } if(al_num number) { // 没找到 printf(\n 你输入的表%s 不存在...,t_name); error0; }number是当前已创建表的总数全局变量。这里没用哈希表因为1表数量极少≤102strcmp在短字符串上比哈希计算更快3课程设计要求“分析设计内容”线性查找的O(n)复杂度本身就是教学点。当数据规模小到常数级别时最笨的算法就是最快的算法。4.2 ADD新列内存数组的“动态扩容”幻觉C语言没有动态数组但源码用Pro_Num[al_num]制造了扩容假象// 在ADD分支中 int new_field_index Pro_Num[al_num]; // 取当前字段数作为新字段索引 strcpy(fieldName[al_num][new_field_index], field_name); // 写入新字段名 strcpy(fieldType[al_num][new_field_index], f_type); // 写入新类型 // ... 设置约束条件 Pro_Num[al_num]; // 字段总数1fieldName[al_num][new_field_index]的内存位置在编译时已分配char fieldName[LEN][LEN][MAX]所谓“扩容”只是移动了Pro_Num这个游标。这正是课程设计的精妙之处用静态内存模拟动态行为让学生理解“逻辑扩容”与“物理扩容”的区别——真正的DBMS会realloc()而这里只需改一个整数。4.3 MODIFY字段类型覆盖写入的安全边界MODIFY name varchar(50)在源码中简化为覆盖fieldType[al_num][i]// 在MODIFY分支源码未完整给出但逻辑可推 for(i0; iPro_Num[al_num]; i) { if(strcmp(fieldName[al_num][i], target_field_name)0) { strcpy(fieldType[al_num][i], new_type); // 直接覆盖 break; } }这里隐含一个关键安全假设字段类型修改不改变物理存储布局。int改double在内存中都是8字节char(10)改char(20)只影响长度标识不影响字段名数组。这符合课程设计范围——它不处理int改char这种跨类型转换报错即可。边界意识比功能完整更重要。5. 持久化与避坑table.txt文件格式、读写陷阱与五个真实翻车现场5.1 table.txt的文本协议空格分隔的脆弱约定table.txt的格式由Read()和Write()函数共同定义其脆弱性恰恰是教学重点表名 字段总数 字段名 字段类型 [字段长度] [约束] 字段名 字段类型 [字段长度] [约束] ...字段长度仅当类型为char/float/double时才存在且是纯数字如20不带括号约束primary key存为两个单词not null存为not nullunique存为unique致命限制字段名/类型/约束中不能含空格否则操作符读取错位提示这个协议没有版本号没有校验和。table.txt被手动编辑后程序可能静默加载错误数据。课程设计报告第3页提到“将表中数据清除再重新存储”正是为了规避追加写入导致的格式错乱——这是用空间换安全的典型工程权衡。5.2 常见问题与排查五个让郑通熬夜到凌晨的坑现象1CREATE TABLE t(a int)成功但ALTER TABLE t ADD b char(10)后table.txt里b字段长度显示为(10)而非10原因f_t_len数组存的是[(, 1, 0, )]但Write()函数写入文件时错误地写了整个数组4字节而非只写数字部分解决在Write()中增加判断if(f_t_len[i][j][0]() fprintf(file,%s,f_t_len[i][j]1); // 跳过(现象2输入CREATE TABLE t(id int primary key)后fieldCondition里存的是primary而非primary key原因condition数组只读取了primarykey被后续的for循环吃掉了因为SQL[i]指针没重置解决在primary分支内解析完key后手动将i指针回退到key后的空格位置现象3table.txt用记事本保存为UTF-8格式后Read()函数读出乱码原因VC6的fstream默认用ANSI编码读取UTF-8的BOM头0xEF 0xBB 0xBF被当作文本内容解决强制用记事本另存为“ANSI”编码或在Read()开头跳过前3字节若检测到BOM现象4ALTER TABLE t DROP id执行后Pro_Num[t_index]减1但fieldName[t_index][last_index]残留旧数据原因只改了计数器没清空数组末尾字段的内存解决memset(fieldName[t_index][old_count-1], 0, MAX);清空被删除字段的内存现象5在Windows 10上运行C:\\Users\\Administrator\\Desktop\\DBMS.txt路径不存在用户目录名不同原因硬编码绝对路径违反可移植性原则解决改为相对路径table.txt或用getenv(USERPROFILE)拼接6. 图形界面与命令行双模验证用Win32 API补全课程设计最后一块拼图6.1 为什么课程设计要求“两种形式实现”——教学闭环的底层逻辑报告第1页明确要求“语句以命令行和图形化界面两种形式实现”。这并非炫技而是构建完整的人机交互验证闭环命令行模式验证语法解析器的健壮性你能输入CREATE TABLE并看到建表成功图形界面模式验证内存模型的可视化能力你能点击“显示表信息”按钮弹窗列出所有字段。郑通在报告中未提供GUI源码但根据VC6环境和任务书我们可补全最简可行方案——用Win32 API创建对话框复用现有解析逻辑。6.2 对话框资源脚本用RC文件定义UI骨架新建dbms.rc文件定义主窗口和输入框#include resource.h IDD_MAIN DIALOGEX 0, 0, 300, 200 STYLE DS_SETFONT | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION 简易DBMS v1.0 FONT 8, MS Shell Dlg BEGIN EDITTEXT IDC_SQL_INPUT, 10, 10, 280, 60, ES_MULTILINE | ES_AUTOVSCROLL PUSHBUTTON 执行, IDC_EXECUTE, 10, 75, 50, 14 PUSHBUTTON 显示表, IDC_SHOW_TABLES, 70, 75, 60, 14 LISTBOX IDC_TABLE_LIST, 10, 95, 280, 90, LBS_NOTIFY | WS_VSCROLL END6.3 消息循环中调用现有解析器零新增代码的集成在WndProc中当点击“执行”按钮时提取编辑框文本并复用SQL_CREATE()case IDC_EXECUTE: GetDlgItemText(hWnd, IDC_SQL_INPUT, SQL, sizeof(SQL)-1); if(strncmp(SQL, CREATE, 6) 0) { SQL_CREATE(); // 直接调用原有函数 MessageBox(hWnd, 建表成功, 提示, MB_OK); } else if(strncmp(SQL, ALTER, 5) 0) { SQL_ALTER(); MessageBox(hWnd, 修改成功, 提示, MB_OK); } break;关键点SQL数组是全局的与命令行版本共享同一内存地址。这意味着你无需重写任何解析逻辑只需把输入源从scanf()换成GetDlgItemText()。这种设计体现了课程设计的核心思想模块化——解析器是独立单元UI只是输入/输出适配器。6.4 验证技巧用三组测试用例建立可信度不要等到全部写完再测试。用这三组最小用例每天验证测试用例预期结果验证点CREATE TABLE t(a int)table.txt新增t 1\na int解析器能否识别单字段、无约束CREATE TABLE t(a char(5), b double)table.txt中a行有5b行无数字字段长度提取逻辑是否分支正确ALTER TABLE t ADD c date后重启程序再SHOW列表显示3个字段持久化重载是否完整从那以后我每次写数据库相关代码都强制走一遍CREATE→WRITE→READ→ALTER→READ的全流程哪怕只是改一个字段名。因为郑通同学在2012年用VC6踩过的每一个坑今天在VS2022里依然会以更隐蔽的方式重现——比如std::string的move语义导致fieldType指针悬空或者UTF-8路径在std::filesystem中引发异常。这些不是玄学是内存、编码、IO三者交织的必然。希望帮到你。本文还有配套的精品资源点击获取
返回列表