《Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享》
🎬 艾莉丝的简介:

文章目录

1 ~> SQLite C API 编程实战:学生表 CRUD 案例
本案例使用 C++ 封装 SQLite3 C 原生 API,完成数据库创建、建表、增删改查完整流程,梳理 API 标准使用范式。
1.1 SQLite 基础背景
- SQLite 是C 语言实现的文件型嵌入式数据库,无独立服务进程,数据库就是磁盘上一个
.db文件。 - 核心对象
sqlite3*:数据库连接句柄,代表打开的数据库实例。sqlite3_stmt*:预处理语句句柄,保存预编译后的 SQL,用于参数绑定、执行、读取结果集,必须调用**sqlite3_finalize**释放,否则内存泄漏。
- 核心返回码
SQLITE_OK:操作成功SQLITE_DONE:语句执行完成(无返回行,insert/update/delete)SQLITE_ROW:查询到一行结果,可读取列数据
1.2 整体业务案例需求
-
创建 / 打开 SQLite 数据库文件
-
创建学生表
Student-
字段 类型 约束 stuid INTEGER 主键,自增 AUTOINCREMENT name TEXT 学生姓名 gender TEXT 学生性别 age INTEGER 学生年龄 gap REAL 学生学分
-
-
CRUD 操作:插入学生、按姓名单条查询、查询全部学生、按姓名更新、按姓名删除
-
RAII 封装:C++ 类构造打开数据库,析构自动关闭数据库连接
1.3 C++ 源码:可编译
#include <sqlite3.h>
#include <string>
#include <iostream>
#include <cassert>
// 学生信息结构体
struct StudentInfo
{
std::string name;
std::string gender;
int age;
double gap;
};
// SQLite学生数据库封装类 RAII
class StudentDB
{
public:
// 构造函数:打开数据库 + 初始化数据表
explicit StudentDB(const std::string& dbName)
{
int rc = sqlite3_open(dbName.c_str(), &_db);
if (rc != SQLITE_OK)
{
std::cerr << "打开数据库失败: " << sqlite3_errmsg(_db) << std::endl;
sqlite3_close(_db);
_db = nullptr;
return;
}
// 创建学生表
if (!initDataBase())
{
std::cerr << "初始化数据表失败" << std::endl;
sqlite3_close(_db);
_db = nullptr;
}
}
// 析构函数:自动关闭数据库连接
~StudentDB()
{
if (_db != nullptr)
{
sqlite3_close(_db);
_db = nullptr;
}
}
// 禁止拷贝,数据库句柄不可复制
StudentDB(const StudentDB&) = delete;
StudentDB& operator=(const StudentDB&) = delete;
// 插入学生信息
bool insertStudentInfo(const StudentInfo& studentInfo)
{
if (_db == nullptr) return false;
const std::string insertSQL = R"(
INSERT INTO Student(name, gender, age, gap)
VALUES(?,?,?,?);
)";
sqlite3_stmt* stmt = nullptr;
int rc = sqlite3_prepare_v2(_db, insertSQL.c_str(), -1, &stmt, nullptr);
if (rc != SQLITE_OK)
{
std::cerr << "prepare失败:" << sqlite3_errmsg(_db) << std::endl;
return false;
}
// 参数绑定:占位符下标从1开始
sqlite3_bind_text(stmt, 1, studentInfo.name.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 2, studentInfo.gender.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_int(stmt, 3, studentInfo.age);
sqlite3_bind_double(stmt, 4, studentInfo.gap);
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{
std::cerr << "执行insert失败:" << sqlite3_errmsg(_db) << std::endl;
sqlite3_finalize(stmt);
return false;
}
sqlite3_finalize(stmt);
return true;
}
// 根据姓名查询单个学生
bool queryStudentInfo(const std::string& name)
{
if (_db == nullptr) return false;
const std::string querySQL = R"(
SELECT stuid, name, gender, age, gap FROM Student WHERE name=?;
)";
sqlite3_stmt* stmt = nullptr;
int rc = sqlite3_prepare_v2(_db, querySQL.c_str(), -1, &stmt, nullptr);
if (rc != SQLITE_OK)
{
std::cerr << "prepare失败:" << sqlite3_errmsg(_db) << std::endl;
return false;
}
sqlite3_bind_text(stmt, 1, name.c_str(), -1, SQLITE_TRANSIENT);
rc = sqlite3_step(stmt);
if (rc != SQLITE_ROW)
{
std::cerr << "未查询到该学生或者执行错误:" << sqlite3_errmsg(_db) << std::endl;
sqlite3_finalize(stmt);
return false;
}
// 读取结果集列下标从0开始
int stuid = sqlite3_column_int(stmt, 0);
std::string qName(reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1)));
std::string qGender(reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2)));
int qAge = sqlite3_column_int(stmt, 3);
double qGap = sqlite3_column_double(stmt, 4);
std::cout << "==== 查询单个学生 ====" << std::endl;
std::cout << "stuid:" << stuid << std::endl;
std::cout << "name:" << qName << std::endl;
std::cout << "gender:" << qGender << std::endl;
std::cout << "age:" << qAge << std::endl;
std::cout << "gap:" << qGap << std::endl;
sqlite3_finalize(stmt);
return true;
}
// 查询全部学生
bool queryAllStudentInfo()
{
if (_db == nullptr) return false;
const std::string queryAllSQL = R"(
SELECT stuid, name, gender, age, gap FROM Student;
)";
sqlite3_stmt* stmt = nullptr;
int rc = sqlite3_prepare_v2(_db, queryAllSQL.c_str(), -1, &stmt, nullptr);
if (rc != SQLITE_OK)
{
std::cerr << "prepare失败:" << sqlite3_errmsg(_db) << std::endl;
return false;
}
std::cout << "\n==== 全部学生列表 ====" << std::endl;
rc = sqlite3_step(stmt);
while (rc == SQLITE_ROW)
{
int stuid = sqlite3_column_int(stmt, 0);
std::string qName(reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1)));
std::string qGender(reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2)));
int qAge = sqlite3_column_int(stmt, 3);
double qGap = sqlite3_column_double(stmt, 4);
std::cout << "stuid:" << stuid
<< " | name:" << qName
<< " | gender:" << qGender
<< " | age:" << qAge
<< " | gap:" << qGap
<< std::endl;
rc = sqlite3_step(stmt);
}
if (rc != SQLITE_DONE)
{
std::cerr << "遍历结果集异常:" << sqlite3_errmsg(_db) << std::endl;
sqlite3_finalize(stmt);
return false;
}
sqlite3_finalize(stmt);
return true;
}
// 更新学生:根据姓名更新其他字段
bool updateStudentInfo(const std::string& name, const StudentInfo& newInfo)
{
if (_db == nullptr) return false;
const std::string updateSQL = R"(
UPDATE Student SET gender=?, age=?, gap=? WHERE name=?;
)";
sqlite3_stmt* stmt = nullptr;
int rc = sqlite3_prepare_v2(_db, updateSQL.c_str(), -1, &stmt, nullptr);
if (rc != SQLITE_OK)
{
std::cerr << "prepare失败:" << sqlite3_errmsg(_db) << std::endl;
return false;
}
// 绑定顺序和SQL占位符一一对应
sqlite3_bind_text(stmt,1, newInfo.gender.c_str(),-1,SQLITE_TRANSIENT);
sqlite3_bind_int(stmt,2, newInfo.age);
sqlite3_bind_double(stmt,3, newInfo.gap);
sqlite3_bind_text(stmt,4, name.c_str(),-1,SQLITE_TRANSIENT);
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{
std::cerr << "update执行失败:" << sqlite3_errmsg(_db) << std::endl;
sqlite3_finalize(stmt);
return false;
}
sqlite3_finalize(stmt);
return true;
}
// 根据姓名删除学生
bool deleteStudentInfo(const std::string& name)
{
if (_db == nullptr) return false;
const std::string deleteSQL = R"(
DELETE FROM Student WHERE name=?;
)";
sqlite3_stmt* stmt = nullptr;
int rc = sqlite3_prepare_v2(_db, deleteSQL.c_str(), -1, &stmt, nullptr);
if (rc != SQLITE_OK)
{
std::cerr << "prepare失败:" << sqlite3_errmsg(_db) << std::endl;
return false;
}
sqlite3_bind_text(stmt, 1, name.c_str(), -1, SQLITE_TRANSIENT);
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{
std::cerr << "delete执行失败:" << sqlite3_errmsg(_db) << std::endl;
sqlite3_finalize(stmt);
return false;
}
sqlite3_finalize(stmt);
return true;
}
private:
sqlite3* _db = nullptr;
// 私有:初始化数据表,创建Student表
bool initDataBase()
{
const std::string createTableSQL = R"(
CREATE TABLE IF NOT EXISTS Student(
stuid INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
gender TEXT,
age INTEGER,
gap REAL
);
)";
char* errMsg = nullptr;
int rc = sqlite3_exec(_db, createTableSQL.c_str(), nullptr, nullptr, &errMsg);
if (rc != SQLITE_OK)
{
std::cerr << "建表失败:" << errMsg << std::endl;
sqlite3_free(errMsg);
return false;
}
return true;
}
};
int main()
{
StudentInfo info1{"张三", "男", 18, 3.5};
StudentInfo info2{"李四", "女", 19, 3.8};
StudentInfo info3{"王五", "男", 20, 4.0};
StudentInfo info4{"赵六", "女", 21, 4.2};
// RAII打开数据库,出作用域自动关闭
StudentDB db("studentDB.db");
db.insertStudentInfo(info1);
db.insertStudentInfo(info2);
db.insertStudentInfo(info3);
db.insertStudentInfo(info4);
db.queryAllStudentInfo();
// 修改王五学分
info3.gap = 4.5;
db.updateStudentInfo("王五", info3);
db.queryStudentInfo("王五");
// 删除赵六
db.deleteStudentInfo("赵六");
db.queryAllStudentInfo();
return 0;
}
1.4 SQLite C API 标准执行流程
所有带参数 SQL 统一遵循该 6 步流程,禁止字符串拼接 SQL,防止 SQL 注入
- 构造 SQL 语句:使用
?占位符,不要拼接用户输入字符串。 - 预处理 SQL:调用
sqlite3_prepare_v2(),生成sqlite3_stmt*预处理对象。- 该函数将 SQL 编译成虚拟机字节码,后续可以重复执行。
- 绑定参数:
sqlite3_bind_xxx(stmt, index, value,...)- 占位符下标从 1 开始;
SQLITE_TRANSIENT:告知 SQLite 内部复制字符串副本,函数返回后外部字符串销毁也安全。
- 执行语句:调用
sqlite3_step(stmt)- 增删改:期待返回
SQLITE_DONE; - 查询单条:期待返回
SQLITE_ROW; - 查询多条:循环调用
sqlite3_step()直到返回SQLITE_DONE。
- 增删改:期待返回
- 读取结果集(仅查询):
sqlite3_column_xxx(stmt, colIndex)- 列索引从 0 开始;
sqlite3_column_text返回const unsigned char*,需要reinterpret_cast<const char*>转为 C++ 字符串。
- 释放预处理对象:
sqlite3_finalize(stmt)- 强制必须调用,无论成功失败,否则内存泄漏。
1.5 关键 API 详细释义
1.5.1 数据库打开关闭
int sqlite3_open(const char *filename, sqlite3 **ppDb)- filename:数据库文件名;文件名不存在会自动创建磁盘文件。
- ppDb:输出数据库句柄指针。
- 返回值:
SQLITE_OK成功;失败依然会返回非空句柄,必须调用sqlite3_close释放。
int sqlite3_close(sqlite3* db):关闭数据库连接。
1.5.2 prepare 预处理
int sqlite3_prepare_v2(sqlite3 *db, const char *zSql, int nByte, sqlite3_stmt **ppStmt, const char **pzTail)nByte=-1自动计算 SQL 字符串长度;ppStmt输出预处理语句指针;pzTail=nullptr,忽略多余 SQL 文本。
1.5.3 bind 参数绑定系列
sqlite3_bind_int(stmt, idx, intVal);
sqlite3_bind_double(stmt, idx, doubleVal);
sqlite3_bind_text(stmt, idx, text, len, destructor);
- destructor 参数:
SQLITE_TRANSIENT:SQLite 内部拷贝字符串;SQLITE_STATIC:字符串生命周期由调用方保证,SQLite 不拷贝。
1.5.4 step 执行
int sqlite3_step(sqlite3_stmt* stmt)SQLITE_DONE:语句执行完毕,无更多结果;SQLITE_ROW:获取到一行查询结果,可以读取 column。
1.5.5 column 读取结果集
int sqlite3_column_int(sqlite3_stmt*, int iCol);
double sqlite3_column_double(sqlite3_stmt*, int iCol);
const unsigned char* sqlite3_column_text(sqlite3_stmt*, int iCol);
- iCol:列下标从 0 开始,对应 SELECT 查询字段顺序。
1.5.6 错误信息获取
const char* sqlite3_errmsg(sqlite3* db):获取最近一次错误文本。void sqlite3_free(void* ptr):释放sqlite3_exec返回的 errMsg 内存。
1.5.7 sqlite3_exec 简易执行接口
- 适合无参数 DDL 语句(建表),不适合带用户输入 DML,无参数绑定,拼接字符串会产生注入漏洞。
1.6 Linux 编译运行要点
1.6.1 编译命令
# 需要链接sqlite3库
g++ testSqlite3.cpp -o testSqlite3 -lsqlite3
- 报错
fatal error: sqlite3.h: No such file or directory:需要安装开发库
sudo apt install libsqlite3-dev
- 报错找不到文件:必须 cd 到源码所在目录执行编译命令。
1.6.2 运行
./testSqlite3
运行成功后当前目录生成studentDB.db数据库文件,可以使用 sqlite3 命令行工具打开查看:
sqlite3 studentDB.db
1.7 RAII 封装要点与坑点
- 数据库句柄
sqlite3*不能拷贝,类必须禁用拷贝构造、拷贝赋值。 - 析构函数中调用
sqlite3_close,保证对象销毁自动关闭数据库。 - 任何分支路径(正常 return、异常 return)**
sqlite3_stmt**必须 finalize,否则内存泄漏。 - 字符串绑定优先使用
SQLITE_TRANSIENT,避免野指针。 - 禁止直接拼接 SQL 字符串,必须使用
?占位符绑定参数防御 SQL 注入。
1.8 SQLite 补充面试拓展知识点
- SQLite 默认线程模式:默认单线程模式,多线程使用必须配置打开线程安全编译选项。
- 事务:默认每条 DML 语句自动开启事务,批量插入大量数据手动开启事务可以极大提升性能。
- 数据类型:SQLite 是动态类型数据库,列声明类型只是建议,实际存储可以存放任意类型值。
- 自增主键:
AUTOINCREMENT只用于 INTEGER 主键,不写 AUTOINCREMENT 也会自动递增,但是会复用删除过的 ID;写 AUTOINCREMENT 保证 ID 单调永不复用。
1.9 简述使用 SQLite C API 执行带参数 SELECT 查询
- 使用 C++ std::string 构造 SQL 语句,使用
?占位符代替用户输入。 - 调用
sqlite3_prepare_v2预编译 SQL,得到sqlite3_stmt*预处理对象,校验返回码SQLITE_OK。 - 使用
sqlite3_bind_xxx系列函数,按占位符序号(从 1 开始)绑定外部传入参数。 - 循环调用
sqlite3_step,返回SQLITE_ROW代表读到一行记录;返回SQLITE_DONE代表结果集读取完毕。 - 在每一次得到
SQLITE_ROW后调用sqlite3_column_xxx读取每一列数据,列下标从 0 开始。 - 无论执行成功还是失败,必须调用
sqlite3_finalize释放 stmt 预处理对象,防止内存泄漏。 - 通过
sqlite3_errmsg捕获错误信息用于日志输出。 - 禁止拼接用户输入字符串到 SQL 语句,防止 SQL 注入攻击。
**注意:**增删改操作和查询流程高度相似,区别在于 step 期待返回码为
SQLITE_DONE,不需要读取 column 列数据。所有 SQLite C API 程序都遵循 prepare -> bind -> step -> finalize 范式。
结尾
uu们,本文的内容到这里就全部结束了,艾莉丝在这里再次感谢您的阅读!
|
结语:希望对学习Linux相关内容的uu有所帮助,不要忘记给博主“一键四连”哦!
往期回顾:
【AI大模型接入SDK】SQLite基础概念与C的API开发
🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡 ૮₍ ˶ ˊ ᴥ ˋ˶₎ა
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/2401_89899187/article/details/166137231





