【编译原理】DBMS系统设计与实现

个人博客:https://alive0103.github.io/
代码在GitHub:https://github.com/Alive0103/XDU-CS-lab
能点个Star就更好了,欢迎来逛逛哇~❣

一、实验目的与要求

1. 实验目的

  • 加深编译原理基础知识的理解:通过词法分析、语法分析、语法制导翻译等环节,理解编译器的基本原理。
  • 加深数据库系统相关知识的理解:通过实现数据库的基本操作,理解数据库系统、数据结构与操作系统的相关知识。

2. 实验要求

设计并实现一个DBMS原型系统,能够接受基本的SQL语句,对其进行词法分析、语法分析、语义分析,并解释执行SQL语句,实现对数据库文件的基本操作。
支持的SQL语句及功能如下:

SQL语句功能
CREATE DATABASE创建数据库
USE DATABASE选择数据库
CREATE TABLE创建表
SHOW TABLES显示表名
INSERT插入元组
SELECT查询元组
UPDATE更新元组
DELETE删除元组
DROP TABLE删除表
DROP DATABASE删除数据库
EXIT退出系统

注: 支持数据类型有 INTCHAR(N) 等。


二、实验环境配置

  • 操作系统:Windows 10/11
  • 编译器:GCC(MSYS2环境下的mingw-w64-x86_64-gcc)
  • 工具链:Flex、Bison
  • 开发环境:VSCode、PowerShell

环境安装步骤

  1. 安装MSYS2
    下载并安装 MSYS2

  2. 安装开发工具链
    打开MSYS2 MINGW64终端,执行:

    pacman -Syu
    pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-flex mingw-w64-x86_64-bison
    
  3. 配置环境变量
    C:\msys64\mingw64\bin 添加到系统环境变量 PATH

  4. 验证安装
    在终端输入 gcc --versionflex --versionbison --version,输出版本号。


三、系统设计

1. 总体架构设计

本系统采用模块化设计,主要分为四大模块:

  • 词法分析模块(Flex):负责将输入的SQL语句分解为Token流。
  • 语法分析模块(Bison):根据SQL语法规则对Token流进行语法分析,构建语法树并驱动后续操作。
  • 语义执行模块(C代码):在Bison的动作代码中实现具体的数据库操作逻辑。
  • 数据存储与管理模块(C结构体链表):所有数据库、表、字段、记录等信息均存储于内存链表结构中。
数据流转说明
  1. 用户输入SQL语句
  2. Flex将输入分词,生成Token
  3. Bison根据Token流进行语法分析,匹配规则并调用相应C函数
  4. C函数操作内存中的数据库/表/记录链表,实现SQL语义
  5. 输出操作结果或错误信息

2. 主要数据结构设计

2.1 数据库结构体
struct mydb {
    char name[50];              // 数据库名
    struct table *tbroot;       // 指向该数据库下的表链表
    struct mydb *next;          // 下一个数据库
};
  • 设计思路:采用链表管理多个数据库,每个数据库下挂载自己的表链表。
2.2 表结构体
struct table {
    char name[50];              // 表名
    struct field *ffield;       // 字段数组(每个字段包含所有记录的数据)
    int flen;                   // 字段数
    int ilen;                   // 记录数
    struct table *next;         // 下一个表
};
  • 设计思路:每个表有自己的字段数组,字段数组中每个元素存储所有记录的该字段值。
2.3 字段结构体
struct field {
    char name[50];              // 字段名
    int type;                   // 0: int, 1: string
    struct key {
        int intkey;
        char skey[50];
    } key[100];                 // 最多100条记录
};
  • 设计思路:每个字段有一个key数组,key[i]表示第i条记录在该字段的值。
2.4 其他结构体
  • item_def:用于SELECT/UPDATE等语句的字段链表
  • hyper_items_def:用于CREATE TABLE时的字段定义链表
  • value_def:用于INSERT等语句的值链表
  • conditions_def:用于WHERE条件的二叉树
  • table_def:用于多表操作的表链表
  • upcon_def:用于UPDATE语句的赋值链表

3. 主要功能实现细节

3.1 数据库管理
  • 创建数据库createDB()
    检查数据库名是否已存在,若不存在则在dbroot链表尾部插入新数据库节点。
  • 删除数据库dropDB(char *dbname)
    遍历dbroot链表,找到目标数据库,释放其所有表和自身内存。
  • 切换数据库useDB(char *dbname)
    遍历dbroot链表,找到目标数据库,更新全局变量database
3.2 表管理
  • 创建表createTable(char *tableval, struct hyper_items_def *Hitemroot)
    在当前数据库下新建表节点,初始化字段数组,设置字段名和类型。
  • 删除表dropTable(char *tableval)
    遍历表链表,找到目标表,释放其字段数组和自身内存。
  • 显示表showTable()
    遍历当前数据库下的表链表,输出所有表名。
3.3 数据操作
  • 插入记录multiInsert(char *tableval, struct item_def *itemroot, struct value_def *valroot)
    找到目标表,将值链表中的数据依次插入到字段数组的key数组中,支持指定字段插入和全字段插入。
  • 查询记录selectWhere(struct item_def *itemroot, struct table_def *tableroot, struct conditions_def *conroot)
    支持单表和两表联合查询,遍历记录并根据WHERE条件筛选,输出指定字段。
  • 更新记录updates(struct table *tabletemp, struct upcon_def *uptemp, struct conditions_def *conroot)
    遍历表的记录,若满足WHERE条件则将指定字段更新为新值。
  • 删除记录deletes(char *tableval, struct conditions_def *conroot)
    遍历表的记录,若满足WHERE条件则删除该记录(通过后移覆盖实现)。
3.4 词法与语法分析
  • sql.l:定义所有SQL关键字、标识符、数字、字符串的正则表达式,生成Token。
  • sql.y:定义SQL语句的BNF文法,使用Bison的动作代码调用上述C函数,实现SQL语义。

4. 关键实现举例

4.1 CREATE TABLE 语句处理流程
  1. 用户输入:CREATE TABLE STUDENT(SNAME CHAR(20), SAGE INT, SSEX INT);
  2. Flex识别关键字、标识符、类型等Token
  3. Bison匹配CREATE TABLE语法规则,构建hyper_items_def链表
  4. 动作代码调用createTable,在当前数据库下新建表,初始化字段数组
  5. 输出"Table STUDENT created successfully!"
4.2 SELECT 语句处理流程
  1. 用户输入:SELECT SNAME, SAGE FROM STUDENT WHERE SSEX = 1;
  2. Flex分词,Bison语法分析,构建item_def链表和conditions_def条件树
  3. 动作代码调用selectWhere,遍历STUDENT表所有记录,筛选SSEX=1的记录,输出SNAME和SAGE字段

5. 内存管理与异常处理

  • 所有链表节点和数组均动态分配内存,操作完成后及时释放,防止内存泄漏。
  • 对所有输入参数和操作结果进行合法性检查,发现错误及时输出提示信息。

如需进一步细化某一部分(如具体函数实现、数据结构图示、流程图等),请随时告知!


四、实现细节

1. 词法分析(sql.l)

  • 定义SQL关键字、标识符、数字、字符串等Token。
  • 忽略空白字符和注释。

2. 语法分析(sql.y)

  • 定义SQL语句的BNF文法规则。
  • 每个语法规则对应C代码动作,实现具体操作。
  • 通过%union%type支持多种数据类型的语义值传递。

3. 主要C文件

  • sql.c:实现所有数据库、表、记录的操作函数。
  • sql.h:定义所有结构体和函数声明。
  • main函数:在sql.y中,调用yyparse()启动交互式SQL解析。

4. 编译与运行

flex sql.l
bison -d sql.y
gcc -o mysql sql.tab.c lex.yy.c sql.c
./mysql

五、运行结果展示

在这里插入图片描述

六、带注释版代码

用的时候请把注释去掉,其他相关代码见我仓库

sql.l

%{
/* 定义段开始 */
// 包含标准库头文件
#include <stdio.h>   // 标准输入输出函数
#include <stdlib.h>  // 标准库函数,如 exit()
#include <string.h>  // 字符串处理函数,如 strdup()
#include "sql.h"     // 自定义的 SQL 相关头文件
#include "sql.tab.h" // Bison 生成的语法分析器头文件(包含 token 定义)

// 声明外部变量,用于跟踪行号
extern int yylineno;
%}
/* 定义段结束 */

%%

/* 规则段开始 */

/* 关键字处理(不区分大小写) */
/* 匹配 SELECT 或 select,返回 SELECT token */
select | SELECT { return SELECT; }

/* 匹配 DROP 或 drop,返回 DROP token */
drop | DROP { return DROP; }

/* 匹配 TABLE 或 table,返回 TABLE token */
table | TABLE { return TABLE; }

/* 匹配 TABLES 或 tables,返回 TABLES token */
tables | TABLES { return TABLES; }

/* 匹配 DATABASE 或 database,返回 DATABASE token */
database | DATABASE { return DATABASE; }

/* 匹配 DATABASES 或 databases,返回 DATABASES token */
databases | DATABASES { return DATABASES; }

/* 匹配 CREATE 或 create,返回 CREATE token */
create | CREATE { return CREATE; }

/* 匹配 INSERT 或 insert,返回 INSERT token */
insert | INSERT { return INSERT; }

/* 匹配 UPDATE 或 update,返回 UPDATE token */
update | UPDATE { return UPDATE; }

/* 匹配 SET 或 set,返回 SET token */
set | SET { return SET; }

/* 匹配 DELETE 或 delete,返回 DELETE token */
delete | DELETE { return DELETE; }

/* 匹配 FROM 或 from,返回 FROM token */
from | FROM { return FROM; }

/* 匹配 WHERE 或 where,返回 WHERE token */
where | WHERE { return WHERE; }

/* 匹配 INTO 或 into,返回 INTO token */
into | INTO { return INTO; }

/* 匹配 VALUES 或 values,返回 VALUES token */
values | VALUES { return VALUES; }

/* 匹配 AND 或 and,返回 AND token */
and | AND { return AND; }

/* 匹配 OR 或 or,返回 OR token */
or | OR { return OR; }

/* 匹配 INT 或 int,返回 INT token */
int | INT { return INT; }

/* 匹配 CHAR 或 char,返回 CHAR token */
char | CHAR { return CHAR; }

/* 匹配 SHOW 或 show,返回 SHOW token */
show | SHOW { return SHOW; }

/* 匹配 EXIT 或 exit,返回 EXIT token */
exit | EXIT { return EXIT; }

/* 匹配 USE 或 use,返回 USE token */
use | USE { return USE; }

/* 符号处理 */
/* 匹配分号、括号、星号、逗号等符号,直接返回对应字符的 ASCII 值 */
[;] | [(] | [)] | [*] | [,] { return *yytext; }

/* 比较运算符处理 */
"<=" | ">=" | "!" | "<" | ">" | "=" { return *yytext; } 

/* 字符串字面量处理 */
/* 匹配单引号括起的字符串(至少包含一个字母,后接字母/数字/下划线) */
'[A-Za-z][A-Za-z0-9_]*' {
    yylval.strval = strdup(yytext); // 复制字符串内容到 yylval(用于传递值给语法分析器)
    return STRING; // 返回 STRING token
}

/* 标识符处理 */
/* 匹配以字母开头,后接字母/数字/下划线的标识符 */
[A-Za-z][A-Za-z0-9_]* {
    yylval.strval = strdup(yytext); // 复制标识符名称
    return ID; // 返回 ID token
}

/* 数字处理 */
/* 匹配整数 */
[0-9]+ {
    yylval.intval = atoi(yytext); // 将字符串转换为整数值
    return NUMBER; // 返回 NUMBER token
}

/* 换行符处理(可能用于跟踪行号) */
\n {
    return *yytext; // 返回换行符本身(通常建议在此处增加 yylineno)
}

/* 忽略空白字符 */
[ \t]+ { /* 不做任何操作 */ }

%%
/* 规则段结束 */

sql.y

%{
/* 头文件与全局函数声明 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sql.h"  // 包含自定义数据结构声明

/* 接口声明 */
extern int yylex();       // 词法分析器入口
extern int yyparse();     // 语法分析器入口
extern FILE *yyin;        // 输入文件指针
extern char *yytext;      // 当前词法单元文本
extern int yylineno;      // 当前行号计数器

char database[64] = {0};    // 当前使用的数据库名称存储
extern struct mydb *dbroot; // 全局数据库链表头指针

/* 错误处理函数 */
void yyerror(const char *str) {
    fprintf(stderr, "error: %s\n", str);  // 输出带错误定位的提示信息
}

/* 输入结束处理函数 */
int yywrap() {
    return 1;  // 表示输入结束
}

/* 主程序入口 */
int main() {
    printf("SQL>");       // 显示交互提示符
    return yyparse();     // 启动语法分析器
}
%}

/* 联合体定义 - 用于存储不同类型的语义值 */
%union{  
    int intval;              // 整数值存储
    char *strval;            // 字符串值存储
    struct hyper_items_def *Citemsval;  // 表字段定义链表
    struct value_def *valueval;         // 值列表节点
    struct item_def *itemval;           // 查询字段项
    struct conditions_def *conval;      // 条件表达式节点
    struct table_def *tbval;            // 表名链表节点
    struct upcon_def *updateval;        // 更新条件节点
}

/* 终结符声明(词法单元)*/
%token SELECT FROM WHERE AND OR DROP DELETE TABLE CREATE INTO VALUES INSERT UPDATE SET SHOW DATABASE DATABASES TABLES EXIT USE
%token <intval> NUMBER    // 绑定到intval的数值类型
%token <strval> STRING ID INT CHAR  // 绑定到strval的字符串类型

/* 非终结符类型声明 */
%type <intval> comparator          // 比较运算符类型
%type <Citemsval> hyper_items create_items  // 表字段定义相关
%type <valueval> value_list value           // 值列表相关
%type <itemval> item item_list              // 查询字段列表相关
%type <conval> condition conditions         // 条件表达式相关
%type <tbval> tables                        // 表名列表相关
%type <updateval> up_cond up_conds          // 更新条件相关

/* 运算符优先级定义 */
%left OR     // 最低优先级
%left AND    // 高于OR

%%

/* 语法规则部分 */

/* 语句集合规则 */
statements: statements statement  // 多语句递归定义
    | statement                    // 单语句情况
    ;

/* 单个语句类型 */
statement: createsql | showsql | selectsql | insertsql | deletesql | updatesql | dropsql | exitsql | usesql;

/* USE语句规则 */
usesql: USE ID ';' '\n' {          // 语法:USE 数据库名;
    printf("\n");
    useDB($2);                     // 调用数据库切换函数
    printf("\nSQL>");
}

/* SHOW语句规则 */
showsql: SHOW DATABASES ';' '\n' { // 显示所有数据库
    printf("\n");
    showDB();                      // 调用数据库列表显示函数
    printf("\nSQL>");
}
| SHOW TABLES ';' '\n' {          // 显示当前数据库所有表
    printf("\n");
    showTable();                  // 调用表列表显示函数
    printf("\nSQL>");
}

/* CREATE语句规则 */
createsql: CREATE TABLE ID '(' hyper_items ')' ';' '\n' {  // 创建表语法
    printf("\n");
    createTable($3, $5);          // $3=表名,$5=字段定义链表
    printf("\nSQL>");
}
| CREATE DATABASE ID ';' '\n' {  // 创建数据库语法
    strcpy(database, $3);        // 记录当前数据库名
    printf("\n");
    createDB();                  // 调用数据库创建函数
    printf("\nSQL>");
}

/* SELECT语句规则 */
selectsql: SELECT '*' FROM tables ';' '\n' {  // 全字段查询
    printf("\n");
    selectWhere(NULL, $4, NULL); // $4=表名链表,NULL表示无字段列表
    printf("\nSQL>");
}
| SELECT item_list FROM tables ';' '\n' {  // 带字段列表查询
    printf("\n");
    selectWhere($2, $4, NULL);   // $2=字段列表,$4=表名链表
    printf("\nSQL>");
}        
| SELECT '*' FROM tables WHERE conditions ';' '\n' {  // 带条件全字段查询
    printf("\n");
    selectWhere(NULL, $4, $6);  // $6=条件表达式树
    printf("\nSQL>");
}
| SELECT item_list FROM tables WHERE conditions ';' '\n' {  // 完整SELECT语法
    printf("\n");
    selectWhere($2, $4, $6);    // $2=字段列表,$4=表列表,$6=条件
    printf("\nSQL>");
}

/* DELETE语句规则 */
deletesql: DELETE FROM ID ';' '\n' {  // 无条件删除
    printf("\n");
    deletes($3, NULL);         // $3=表名,NULL表示无删除条件
    printf("\nSQL>");
}
| DELETE FROM ID WHERE conditions ';' '\n' {  // 条件删除
    printf("\n");
    deletes($3, $5);          // $5=条件表达式树
    printf("\nSQL>");
}

/* INSERT语句规则 */
insertsql: INSERT INTO ID VALUES '(' value_list ')' ';' '\n' {  // 全字段插入
    printf("\n");
    multiInsert($3, NULL, $6);  // $3=表名,$6=值列表
    printf("\nSQL>");
}
| INSERT INTO ID '(' item_list ')' VALUES '(' value_list ')' ';' '\n' {  // 指定字段插入
    printf("\n");
    multiInsert($3, $5, $9);   // $5=字段列表,$9=值列表
    printf("\nSQL>");
}

/* UPDATE语句规则 */
updatesql: UPDATE ID SET up_conds ';' '\n' {  // 无条件更新
    // 数据库存在性检查
    struct mydb *dbtemp = dbroot;
    struct table *tabletemp = NULL;
    while(dbtemp != NULL) {
        if(strcmp(dbtemp->name, database) == 0) {
            // 遍历表链表查找目标表
            tabletemp = dbtemp->tbroot;
            while(tabletemp != NULL) {
                if(strcmp(tabletemp->name, $2) == 0) {
                    updates(tabletemp, $4, NULL);  // $4=更新条件链表
                    printf("\nSQL>");
                    return 0;
                }
                tabletemp = tabletemp->next;
            }
            printf("error: Table %s doesn't exist!\n", $2);
            printf("\nSQL>");
            return 0;
        }
        dbtemp = dbtemp->next;
    }
    printf("error: Database %s doesn't exist!\n", database);
    printf("\nSQL>");
    return 0;
}
| UPDATE ID SET up_conds WHERE conditions ';' '\n' {  // 条件更新
    // 类似上述数据库/表存在性检查流程
    struct mydb *dbtemp = dbroot;
    struct table *tabletemp = NULL;
    while(dbtemp != NULL) {
        if(strcmp(dbtemp->name, database) == 0) {
            tabletemp = dbtemp->tbroot;
            while(tabletemp != NULL) {
                if(strcmp(tabletemp->name, $2) == 0) {
                    updates(tabletemp, $4, $6);  // $6=WHERE条件表达式
                    printf("\nSQL>");
                    return 0;
                }
                tabletemp = tabletemp->next;
            }
            printf("error: Table %s doesn't exist!\n", $2);
            printf("\nSQL>");
            return 0;
        }
        dbtemp = dbtemp->next;
    }
    printf("error: Database %s doesn't exist!\n", database);
    printf("\nSQL>");
    return 0;
}

/* DROP语句规则 */
dropsql: DROP TABLE ID ';' '\n' {  // 删除表
    printf("\n");
    dropTable($3);          // $3=表名
    printf("\nSQL>");
}
| DROP DATABASE ID ';' '\n' {  // 删除数据库
    printf("\n");
    dropDB($3);            // $3=数据库名
    printf("\nSQL>");
}

/* 退出命令 */
exitsql: EXIT ';' {
    printf("\n");
    printf("exit with code 0!\n");
    exit(0);
}

/* 字段定义规则 */
create_items: ID INT {  // 整数字段定义
    $$ = (struct hyper_items_def *)malloc(sizeof(struct hyper_items_def));
    strcpy($$->field, $1);  // 存储字段名
    $$
->type = 0;           // 类型标记为整数
    $$->next = NULL;        // 初始化链表指针
}
| ID CHAR '(' NUMBER ')' {  // 定长字符字段定义
    $$ = (struct hyper_items_def *)malloc(sizeof(struct hyper_items_def));
    strcpy($$->field, $1);
    $$
->type = 1;           // 类型标记为字符
    $$->next = NULL;
}

/* 字段定义链表构建 */
hyper_items: create_items {  // 单字段情况
    $$
 = $1;
}
| hyper_items ',' create_items {  // 多字段递归组合
    $$ = $3;         // 新节点成为链表头
    $$
->next = $1;   // 原有链表接续
}

/* 查询字段项处理 */
item: ID {  // 单个查询字段
    $$ = (struct item_def *)malloc(sizeof(struct item_def));
    strcpy($$
->field, $1);  // 存储字段名称
    $$->pos = NULL;
    $$
->next = NULL;
}

/* 字段列表链表构建 */
item_list: item {  // 单字段
    $$ = $1;                
}
| item_list ',' item {  // 多字段递归组合
    $$
 = $3;
    $$->next = $1;  // 头插法构建链表
}

/* 值节点处理 */
value: NUMBER {  // 整数值
    $$
 = ((struct value_def *)malloc(sizeof(struct value_def)));
    $$->value.intkey = $1;  // 存储整数值
    $$
->type = 0;           // 类型标记
    $$->next = NULL;
}
| STRING {  // 字符串值
    $$ = ((struct value_def *)malloc(sizeof(struct value_def)));
    strcpy($$->value.skey, $1);  // 复制字符串
    $$
->type = 1;
    $$->next = NULL;
}

/* 值列表链表构建 */
value_list: value {  // 单值
    $$
 = $1;
}
| value_list ',' value {  // 多值递归组合
    $$ = $3;
    $$->next = $1;  // 头插法构建链表
}

/* 比较运算符处理 */
comparator: '=' {$$ = 1; }   // 运算符编码
    | '>' {$$ = 2; }
    | '<' {$$ = 3; }
    | ">=" {$$ = 4; }
    | "<=" {$$ = 5; }
    | '!' '=' {$$
 = 6; }  // 注意:需确保词法分析器支持"!="作为单一token

/* 条件表达式构建 */
condition: item comparator NUMBER {  // 整数比较条件
    $$ = ((struct conditions_def *)malloc(sizeof(struct conditions_def)));
    $$
->type = 0;           // 标记为整数比较
    $$->litem = $1;         // 左操作数(字段)
    $$
->intv = $3;          // 右操作数值
    $$->cmp_op = $2;        // 比较运算符编码
    $$
->left = NULL;
    $$->right = NULL;
}
| item comparator STRING {  // 字符串比较条件
    $$
 = ((struct conditions_def *)malloc(sizeof(struct conditions_def)));
    $$->type = 1;           // 标记为字符串比较
    $$->litem = $1;
    strcpy($$->strv, $3);   // 存储比较字符串
    $$
->cmp_op = $2;
    $$->left = NULL;
    $$
->right = NULL;
}

/* 复杂条件表达式树构建 */
conditions: condition {  // 基础条件
    $$ = $1;
}
| '(' conditions ')' {  // 括号优先级
    $$
 = $2;
}
| conditions AND conditions {  // 逻辑与组合
    $$ = ((struct conditions_def *)malloc(sizeof(struct conditions_def)));
    $$
->cmp_op = 7;       // 自定义AND操作码
    $$->left = $1;        // 左子树
    $$
->right = $3;       // 右子树
}
| conditions OR conditions {  // 逻辑或组合
    $$ = ((struct conditions_def *)malloc(sizeof(struct conditions_def)));
    $$
->cmp_op = 8;       // 自定义OR操作码
    $$->left = $1;
    $$
->right = $3;
}

/* 表名列表处理 */
tables: ID {  // 单表查询
    $$ = ((struct table_def *)malloc(sizeof(struct table_def)));
    strcpy($$
->table, $1);
    $$->next = NULL;
}
| tables ',' ID {  // 多表连接
    $$ = ((struct table_def *)malloc(sizeof(struct table_def)));
    strcpy($$->table, $3);  // 存储新表名
    $$
->next = $1;          // 头插法构建链表
}

/* 更新条件处理 */
up_cond: ID '=' NUMBER {  // 整数字段更新
    $$ = ((struct upcon_def *)malloc(sizeof(struct upcon_def)));
    strcpy($$
->field, $1);  // 目标字段
    $$->type = 0;           // 整数类型标记
    $$
->value.intkey = $3;  // 新值
    $$->next = NULL;
}
| ID '=' STRING {  // 字符串字段更新
    $$ = ((struct upcon_def *)malloc(sizeof(struct upcon_def)));
    strcpy($$->field, $1);
    $$->type = 1;
    strcpy($$->value.skey, $3);  // 存储新字符串值
    $$
->next = NULL;                
}

/* 更新条件链表构建 */
up_conds: up_cond {  // 单个更新条件
    $$ = $1;
}
| up_conds ',' up_cond {  // 多个更新条件
    $$
 = $3;
    $$->next = $1;  // 头插法构建链表
}

%%
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Alive~o.0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值