在 Linux 系统级开发中,我们经常会遇到需要持久化存储数据的场景。相比于 MySQL 等重量级关系型数据库,SQLite3 凭借其零配置、嵌入式、单文件存储的特性,成为了 C/C++ 开发者在 Linux 环境下的首选。本文将带你从零开始,在 Linux 环境下搭建 SQLite3 开发环境,并使用 C 语言编写一个完整的终端词典查询程序。
一、 环境准备:安装 SQLite3 及开发库
在 Ubuntu 系统中,使用 SQLite3 需要安装两个核心组件:命令行工具和 C 语言开发库。
- 安装 SQLite3 命令行客户端(用于交互式测试):
sudo apt-get install sqlite3 - 安装 C 语言开发依赖库(包含
sqlite3.h头文件和链接库):
sudo apt-get install libsqlite3-dev
安装完成后,可以通过 sqlite3 --version 验证是否安装成功。
二、 核心 API 梳理
在 C 语言中操作 SQLite3,主要依赖以下几个核心函数:
sqlite3_open():打开或创建一个数据库文件。sqlite3_exec():执行 SQL 语句,支持传入回调函数处理查询结果。sqlite3_prepare_v2()/sqlite3_step():预处理语句接口,适合参数化查询,安全性更高。sqlite3_close():关闭数据库连接,释放资源。
三、 实战:构建终端词典查询程序
假设我们有一个包含单词和释义的文本文件 dict.txt,我们需要将其导入 SQLite3 数据库,并实现终端交互查询。
1. 定义数据结构与回调函数
为了保持代码的整洁,我们使用结构体来承载数据,并封装一个回调函数用于处理 SELECT 查询的结果。
typedef struct dict_word {
char word[64];
char meaning[512];
} dict_t;
// 查询结果回调函数
int callbackfun(void *arg, int colnum, char **pcontent, char **ptitle) {
for (int i = 0; i < colnum; i++) {
printf("%s\t\t", pcontent[i]);
}
printf("\n");
return 0; // 返回 0 表示继续处理下一行
}
2. 数据库初始化与文件导入
使用 fopen 逐行读取文本文件,结合 sscanf 进行字符串解析,并通过 sprintf 拼接 SQL 语句将数据写入数据库。
int import_dict_from_file(sqlite3 *pdb) {
FILE *fp = fopen("dict.txt", "r");
if (!fp) { perror("fopen failed"); return -1; }
char line[1024] = {0};
dict_t d;
char cmd[1024] = {0};
char *perrmsg = NULL;
while (fgets(line, sizeof(line), fp)) {
// 解析格式:跳过序号,读取单词和含义
if (sscanf(line, "%*d %63s %511[^\n]", d.word, d.meaning) == 2) {
sprintf(cmd, "INSERT OR IGNORE INTO dict (word, meaning) VALUES ('%s', '%s');",
d.word, d.meaning);
if (sqlite3_exec(pdb, cmd, NULL, NULL, &perrmsg) != SQLITE_OK) {
fprintf(stderr, "Insert failed: %s\n", perrmsg);
sqlite3_free(perrmsg);
}
}
}
fclose(fp);
return 0;
}
3. 主程序与终端交互
在 main 函数中完成数据库的打开、建表、导入以及死循环查询逻辑。
int main(void) {
sqlite3 *pdb = NULL;
char *perrmsg = NULL;
char cmd[1024] = {0};
char input_word[64] = {0};
if (sqlite3_open("dict.db", &pdb) != SQLITE_OK) {
fprintf(stderr, "Open DB failed: %s\n", sqlite3_errmsg(pdb));
return -1;
}
// 建表与导入数据(首次运行后建议注释掉)
sqlite3_exec(pdb, "CREATE TABLE IF NOT EXISTS dict (word TEXT PRIMARY KEY, meaning TEXT);", NULL, NULL, &perrmsg);
import_dict_from_file(pdb);
printf("=== 终端词典 (输入 q 退出) ===\n");
while (1) {
printf(">>> ");
scanf("%63s", input_word);
if (strcmp(input_word, "q") == 0) break;
sprintf(cmd, "SELECT word, meaning FROM dict WHERE word = '%s';", input_word);
if (sqlite3_exec(pdb, cmd, callbackfun, NULL, &perrmsg) != SQLITE_OK) {
fprintf(stderr, "Query failed: %s\n", perrmsg);
sqlite3_free(perrmsg);
}
}
sqlite3_close(pdb);
return 0;
}
四、 编译与运行
由于使用了外部动态链接库,编译时需要加上 -lsqlite3 参数:
gcc dict.c -o dict -lsqlite3
./dict
五、 进阶优化建议
在实际的工程开发中,直接使用 sprintf 拼接 SQL 字符串存在 SQL 注入的风险。对于需要频繁执行且带有动态参数的查询,强烈建议使用 预处理语句(Prepared Statements):
- 使用
sqlite3_prepare_v2()编译带有?占位符的 SQL。 - 使用
sqlite3_bind_text()绑定用户输入的参数。 - 使用
sqlite3_step()执行并获取结果。 - 使用
sqlite3_finalize()释放语句资源。
通过这种方式,不仅能彻底杜绝 SQL 注入漏洞,还能大幅提升循环查询时的执行效率。掌握 SQLite3 的 C 语言接口,是迈向 Linux 高级系统开发的重要一步。