艾莉丝努力练剑头像
关注
【AI大模型接入SDK】SQLite C API编程实战:学生表CRUD案例封面图

【AI大模型接入SDK】SQLite C API编程实战:学生表CRUD案例


头像


🎬 个人主页:艾莉丝努力练剑

❄专栏传送门:《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录》
《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 整体业务案例需求

  1. 创建 / 打开 SQLite 数据库文件

  2. 创建学生表Student

    1. 字段类型约束
      stuidINTEGER主键,自增 AUTOINCREMENT
      nameTEXT学生姓名
      genderTEXT学生性别
      ageINTEGER学生年龄
      gapREAL学生学分
  3. CRUD 操作:插入学生、按姓名单条查询、查询全部学生、按姓名更新、按姓名删除

  4. 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 注入

  1. 构造 SQL 语句:使用?占位符,不要拼接用户输入字符串。
  2. 预处理 SQL:调用sqlite3_prepare_v2(),生成sqlite3_stmt*预处理对象。
    1. 该函数将 SQL 编译成虚拟机字节码,后续可以重复执行。
  3. 绑定参数:sqlite3_bind_xxx(stmt, index, value,...)
    1. 占位符下标从 1 开始;
    2. SQLITE_TRANSIENT:告知 SQLite 内部复制字符串副本,函数返回后外部字符串销毁也安全。
  4. 执行语句:调用sqlite3_step(stmt)
    1. 增删改:期待返回SQLITE_DONE;
    2. 查询单条:期待返回SQLITE_ROW;
    3. 查询多条:循环调用sqlite3_step()直到返回SQLITE_DONE。
  5. 读取结果集(仅查询):sqlite3_column_xxx(stmt, colIndex)
    1. 列索引从 0 开始;
    2. sqlite3_column_text返回const unsigned char*,需要reinterpret_cast<const char*>转为 C++ 字符串。
  6. 释放预处理对象:sqlite3_finalize(stmt)
    1. 强制必须调用,无论成功失败,否则内存泄漏。

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 封装要点与坑点

  1. 数据库句柄sqlite3*不能拷贝,类必须禁用拷贝构造、拷贝赋值。
  2. 析构函数中调用sqlite3_close,保证对象销毁自动关闭数据库。
  3. 任何分支路径(正常 return、异常 return)**sqlite3_stmt**必须 finalize,否则内存泄漏。
  4. 字符串绑定优先使用SQLITE_TRANSIENT,避免野指针。
  5. 禁止直接拼接 SQL 字符串,必须使用?占位符绑定参数防御 SQL 注入。

1.8 SQLite 补充面试拓展知识点

  • SQLite 默认线程模式:默认单线程模式,多线程使用必须配置打开线程安全编译选项。
  • 事务:默认每条 DML 语句自动开启事务,批量插入大量数据手动开启事务可以极大提升性能。
  • 数据类型:SQLite 是动态类型数据库,列声明类型只是建议,实际存储可以存放任意类型值。
  • 自增主键:AUTOINCREMENT只用于 INTEGER 主键,不写 AUTOINCREMENT 也会自动递增,但是会复用删除过的 ID;写 AUTOINCREMENT 保证 ID 单调永不复用。

1.9 简述使用 SQLite C API 执行带参数 SELECT 查询

  1. 使用 C++ std::string 构造 SQL 语句,使用?占位符代替用户输入。
  2. 调用sqlite3_prepare_v2预编译 SQL,得到sqlite3_stmt*预处理对象,校验返回码SQLITE_OK。
  3. 使用sqlite3_bind_xxx系列函数,按占位符序号(从 1 开始)绑定外部传入参数。
  4. 循环调用sqlite3_step,返回SQLITE_ROW代表读到一行记录;返回SQLITE_DONE代表结果集读取完毕。
  5. 在每一次得到SQLITE_ROW后调用sqlite3_column_xxx读取每一列数据,列下标从 0 开始。
  6. 无论执行成功还是失败,必须调用sqlite3_finalize释放 stmt 预处理对象,防止内存泄漏。
  7. 通过sqlite3_errmsg捕获错误信息用于日志输出。
  8. 禁止拼接用户输入字符串到 SQL 语句,防止 SQL 注入攻击。

**注意:**增删改操作和查询流程高度相似,区别在于 step 期待返回码为SQLITE_DONE,不需要读取 column 列数据。所有 SQLite C API 程序都遵循 prepare -> bind -> step -> finalize 范式。


结尾

uu们,本文的内容到这里就全部结束了,艾莉丝在这里再次感谢您的阅读!

艾莉丝努力练剑

C/C++ & Linux 底层探索者 | 一个正在努力练剑的技术博主


👀 【关注】 跟随我一起深耕技术领域,见证每一次成长。
❤️ 【点赞】 让优质内容被更多人看见,让知识传递更有力量。
⭐ 【收藏】 把核心知识点存好,在需要时随时查、随时用。
💬 【评论】 分享你的经验或疑问,评论区一起交流避坑!

不要忘记给博主“一键四连”哦!

“今日练剑达成!”
文章配图

“技术之路难免有困惑,但同行的人会让前进更有方向。”

结语:希望对学习Linux相关内容的uu有所帮助,不要忘记给博主“一键四连”哦!

往期回顾:

【AI大模型接入SDK】SQLite基础概念与C的API开发

🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡
૮₍ ˶ ˊ ᴥ ˋ˶₎ა

在这里插入图片描述

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/2401_89899187/article/details/166137231

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--