目录
[3.1. 加载并连接sqlite3数据库](#3.1. 加载并连接sqlite3数据库)
[3.2. 通过select查询人脸表格的数据](#3.2. 通过select查询人脸表格的数据)
[3.3. 读取查询的结果并循环插入map容器](#3.3. 读取查询的结果并循环插入map容器)
[4.1 连接数据库](#4.1 连接数据库)
[4.2 定义 task_id](#4.2 定义 task_id)
[4.3 创建 S_THREAD_MAP 对象](#4.3 创建 S_THREAD_MAP 对象)
[thread_map 对象创建后的内存状态](#thread_map 对象创建后的内存状态)
[4.4 查询数据库](#4.4 查询数据库)
[1. sqlite3_prepare()](#1. sqlite3_prepare())
[2. sqlite3_step()](#2. sqlite3_step())
[3. sqlite3_column_text()](#3. sqlite3_column_text())
[4. sqlite3_column_int()](#4. sqlite3_column_int())
[5. sqlite3_column_blob()](#5. sqlite3_column_blob())
[6. memcpy()](#6. memcpy())
[4.5 赋值给 thread_map 的成员](#4.5 赋值给 thread_map 的成员)
[4.6 注册到全局](#4.6 注册到全局)
[4.7 完整执行流程图](#4.7 完整执行流程图)
[1.1 查找相关函数](#1.1 查找相关函数)
[1.2 插入相关函数](#1.2 插入相关函数)
[1.3 删除相关函数](#1.3 删除相关函数)
[1.4 容量相关函数](#1.4 容量相关函数)
[1.5 迭代器相关函数](#1.5 迭代器相关函数)
[1.6 修改相关函数](#1.6 修改相关函数)
[1.7 查找操作详解](#1.7 查找操作详解)
[2.1、stmt 的本质](#2.1、stmt 的本质)
[2.2.1 准备SQL语句](#2.2.1 准备SQL语句)
[2.2.2 第一次调用 sqlite3_step()](#2.2.2 第一次调用 sqlite3_step())
[2.2.3 提取数据](#2.2.3 提取数据)
[2.2.4 继续下一行](#2.2.4 继续下一行)
[3.string str(name)](#3.string str(name))
1.本章节介绍
本章节主要介绍如何通过查询人脸数据库数据库表格把数据存储到map容器,map容器底层是内存处理,用map去处理数据能够达到高效,快速查询的效果。在这个项目里面,map起到了快速查询的功能,key存储的是人脸的结构体(People),value存储的是人脸具体的数据(rockx_face_feature_t)。
2.读取数据库保存到map流程框图

上图是读取数据库并加载到Map的过程,分别有三步:第一步,加载并连接sqlite3数据库、第二步:通过select查询人脸表格的数据、第三步:把表格的数据循环插入到map容器。
3.读取数据库保存到map的代码截图

init_face_data 是读取数据库数据并插入到map容器的自定义函数,这里有几个重要的步骤分别是加载数据库(Connection_sqlite3DataBase)、查询数据库信息并存放到Map容器(QueryPeopleData )、把Map存放到全局变量里面(set_thread_map)
3.1. 加载并连接 sqlite3 数据库

这里封装了一个了SQLITE3连接数据库的函数Connection_sqlite3Database,这个函数的实现如下图

这个函数直接调用了sqlite3的api sqlite3_open来初始化人脸数据库,若返回值不等于SQLITE_OK则初始化数据库失败,否则就初始化成功。
3.2. 通过 select 查询人脸表格的数据

上图是通过sql语句查询人脸表格,"select name, feature_size, face_feature, image_size, image_data from face_data_table ", 其中name : 名字、feature_size : 特征值长度、face_feature: 人脸特征数据、image_size : 图片长度、image_data : 图片数据。face_data_table则是存储人脸的数据库表格。并使用sqlite3_prepare 进行sql的预处理**。**
3.3. 读取查询的结果并循环插入 map 容器

上图是读取sqlite3数据库的数据,并循环插入到map容器。这里的关键是使用了sqlite3提供的函数sqlite3_step来获取select的结果,若结果等于SQLITE_ROW( 表示当前返回结果有数据) 则表示有数据,就获取每一行的数据库数据。
sqlite3_column_text(stmt, 0) : 表示的是查找select语句的第一个元素name,索引号是0,类型是字符串,并进行绑定。
sqlite3_column_int(stmt, 1) : 表示的是查找select语句的第二个元素feature_size,索引号是1,类型是int, 并进行绑定。
sqlite3_column_blob(stmt, 2) : 表示的是查找select语句的第三个元素face_feature,索引号是2,类型是blob, 并进行绑定。
sqlite3_column_int(stmt, 3) : 表示的是查找select语句的第四个元素image_size,索引号是3,类型是int, 并进行绑定。
sqlite3_column_blob(stmt, 4) : 表示的是查找select语句的第五个元素image_data,索引号是4,类型是blob, 并进行绑定。
读取完上述的数据后,就把上述的数据插到map。
4.代码详解

4.1 连接数据库
int Connection_sqlite3DataBase()
|---------|---------------------|
| 功能 | 打开SQLite3数据库连接 |
| 参数 | 无 |
| 返回值 | 0-成功,失败则exit(1)退出程序 |
4.2 定义 task_id
int task_id = 0;
定义 task_id,标识当前任务或线程的ID编号
4.3 创建 S_THREAD_MAP 对象
S_THREAD_MAP thread_map;
class S_THREAD_MAP {public:
int map_id;
map<string, rockx_face_feature_t> thread_map;
map<People, rockx_face_feature_t> thread_people_map;};
|----------|----------------|
| | map_id |
| 类型 | int |
| 访问权限 | public(公开) |
| 用途 | 存储这个map对象的ID编号 |
| 当前值 | 未初始化(垃圾值) |
|----------|-------------------------------------|
| | thread_map |
| 类型 | map<string, rockx_face_feature_t> |
| 访问权限 | public |
| 用途 | 存储 姓名→人脸特征 的映射表 |
| 当前状态 | 空map |
typedef struct {
float feature[512]; // 512维特征向量
int len; // 特征长度,固定512
} rockx_face_feature_t;
map 容器说明:
- 键(key):string类型,存储姓名
- 值(value):rockx_face_feature_t类型,存储512维特征向量
- 特点:自动按姓名排序
map容器详细使用看扩1
|----------|-------------------------------------|
| | thread_people_map |
| 类型 | map<People, rockx_face_feature_t> |
| 访问权限 | public |
| 用途 | 存储 People对象→人脸特征 的映射表 |
| 当前状态 | 空map |
struct People {
string people_name; // 姓名
vector<char> images; // 图片二进制数据
};
thread_map 对象创建后的内存状态
thread_map (对象)
│
├── map_id = ? (垃圾值,未初始化)
├── thread_map = {} (空map)
└── thread_people_map = {} (空map)
4.4 查询数据库
map<People, rockx_face_feature_t> maps = QueryPeopleData();
|---------|-----------------------------------------------------|
| 功能 | 从数据库查询所有人脸完整数据 |
| 参数 | 无 |
| 返回值 | map<People, rockx_face_feature_t> - People到特征的映射表 |
cs
map<People, rockx_face_feature_t> QueryPeopleData()
{
rockx_face_feature_t rockx_face_feature = {0, 0};
map<People, rockx_face_feature_t> people_map;
sqlite3_stmt *stmt;
char *sql = "select name, feature_size, face_feature, image_size, image_data from face_data_table";
int id = 0, len = 0;
char *name;
int feature_size;
int image_size;
vector<char> images;
People first_people;
int ret = sqlite3_prepare(db, sql, strlen(sql), &stmt, 0);
if (ret == SQLITE_OK)
{
while (sqlite3_step(stmt) == SQLITE_ROW)
{
name = (char *)sqlite3_column_text(stmt, 0);
printf("name = %s\n", name);
feature_size = sqlite3_column_int(stmt, 1);
printf("feature_size = %d\n", feature_size);
const void *feature = sqlite3_column_blob(stmt, 2);
memset(rockx_face_feature.feature, 0, feature_size);
memcpy(rockx_face_feature.feature, feature, feature_size);
rockx_face_feature.len = feature_size;
image_size = sqlite3_column_int(stmt, 3);
printf("image_size = %d\n", image_size);
const char *image_data = (const char *)sqlite3_column_blob(stmt, 4);
for (int i = 0; i < image_size; i++)
{
images.push_back(image_data[i]);
}
first_people.people_name = string(name);
first_people.images = images;
string str(name);
// people_map.insert(pair<const People, rockx_face_feature_t>(first_people, rockx_face_feature));
people_map.insert(make_pair(first_people, rockx_face_feature));
// people_map.insert(first_people, rockx_face_feature);
}
}
数据提详细流程看扩2
1. sqlite3_prepare()
cs
sqlite3_stmt *stmt;
char *sql = "select name, feature_size, face_feature, image_size, image_data from face_data_table";
int sqlite3_prepare(sqlite3 *db, const char *zSql, int nByte, sqlite3_stmt **ppStmt, const char **pzTail)
|----------------|------------------|
| 功能 | 将SQL语句编译成字节码 |
| 参数1 db | 数据库句柄 |
| 参数2 zSql | SQL语句字符串 |
| 参数3 nByte | SQL长度,-1表示自动计算 |
| 参数4 ppStmt | 输出参数,返回预处理语句对象 |
| 参数5 pzTail | 未使用部分指针 |
| 返回值 | SQLITE_OK(0)表示成功 |
2. sqlite3_step()
int sqlite3_step(sqlite3_stmt *pStmt)
| 项目 | 说明 |
|---|---|
| 功能 | 执行预处理语句,移动到下一行结果 |
| 参数 | 预处理语句句柄 |
| 返回值 | SQLITE_ROW - 还有一行数据 |
| SQLITE_DONE - 没有更多数据 |
3. sqlite3_column_text()
const unsigned char *sqlite3_column_text(sqlite3_stmt *pStmt, int iCol)
|--------------|------------------------------------|
| 功能 | 获取当前行的文本列数据 |
| 参数1 | 预处理语句句柄 |
| 参数2 iCol | 列索引,0表示第1列(name字段) |
| 返回值 | const unsigned char* - 指向文本数据的指针 |
4. sqlite3_column_int()
int sqlite3_column_int(sqlite3_stmt *pStmt, int iCol)
|--------------|-------------|
| 功能 | 获取当前行的整数列数据 |
| 参数1 | 预处理语句句柄 |
| 参数2 iCol | 列索引 |
| 返回值 | 整数值 |
5. sqlite3_column_blob()
const void *sqlite3_column_blob(sqlite3_stmt *pStmt, int iCol)
|--------------|----------------------------|
| 功能 | 获取当前行的二进制数据 |
| 参数1 | 预处理语句句柄 |
| 参数2 iCol | 列索引 |
| 返回值 | const void* - 指向二进制数据的指针 |
6. memcpy()
void *memcpy(void *dest, const void *src, size_t n)
|--------------|--------|
| 功能 | 拷贝内存区域 |
| 参数1 dest | 目标地址 |
| 参数2 src | 源地址 |
| 参数3 n | 拷贝的字节数 |
| 返回值 | 目标地址 |
7.vector::push_back()
cs
const char *image_data = (const char *)sqlite3_column_blob(stmt, 4);
for (int i = 0; i < image_size; i++)
{
images.push_back(image_data[i]);
}
从数据库读取二进制的图片数据,逐字节复制到 vector<char> 容器中。
void push_back(const T& value)
| 项目 | 说明 |
|---|---|
| 功能 | 在vector末尾添加一个元素 |
| 参数 | 要添加的元素值 |
| 返回值 | 无 |
| vector的长度自动增加1 |
图示

执行前

执行中(逐字节复制)

执行后

8.make_pair()
pair<People, rockx_face_feature_t> make_pair(People first, rockx_face_feature_t second)
| 项目 | 说明 |
|---|---|
| 功能 | 创建一对键值对 |
| 参数1 | 键(People对象) |
| 参数2 | 值(特征向量) |
| 返回值 | pair对象 |
9.map::insert()
pair<iterator, bool> insert(const value_type& val)
| 项目 | 说明 |
|---|---|
| 功能 | 向map中插入键值对 |
| 参数 | pair对象 |
| 返回值 | pair<迭代器, 是否成功> |
QueryPeopleData() 开始
│
▼
┌─────────────────────────────┐
│ sqlite3_prepare() │
│ 编译SQL语句 │
└─────────────────────────────┘
│
▼
┌─────────────────────────────┐
│ while(sqlite3_step()) │
│ 遍历每一行数据 │
└─────────────────────────────┘
│
▼
┌─────────────────────────────┐
│ sqlite3_column_text(stmt,0) │
│ 提取数据库信息 │
└─────────────────────────────┘
│
▼
┌─────────────────────────────┐
│ sqlite3_column_blob(stmt,4) │
│ 提取图片数据 │
│ for循环 push_back() │
│ 拷贝到 vector<char> │
└─────────────────────────────┘
│
▼
┌─────────────────────────────┐
│ 构建 People 对象 │
│ people_name = name │
│ images = 图片数据 │
└─────────────────────────────┘
│
▼
┌─────────────────────────────┐
│ make_pair() + insert() │
│ 存入 people_map │
└─────────────────────────────┘
│
▼
┌─────────────────────────────┐
│ 返回 people_map │
└─────────────────────────────┘
返回值 maps 的内容
maps (map<People, rockx_face_feature_t>)
│
├── 键: People1 (姓名="张三", 图片数据)
│ 值: rockx_face_feature (512维特征)
│
├── 键: People2 (姓名="李四", 图片数据)
│ 值: rockx_face_feature (512维特征)
│
└── 键: People3 (姓名="王五", 图片数据)
值: rockx_face_feature (512维特征)
4.5 赋值给 thread_map 的成员
thread_map.thread_people_map = maps;
| 项目 | 说明 |
|---|---|
| 左操作数 | thread_map.thread_people_map - S_THREAD_MAP对象的成员 |
| 右操作数 | maps - QueryPeopleData()返回的map |
| 操作 | 将maps赋值给thread_people_map |
赋值后的状态
thread_map (对象)
│
├── map_id = ? (仍是垃圾值)
├── thread_map = {} (仍然是空map)
└── thread_people_map = maps (包含数据库所有人脸数据)
4.6 注册到全局
S_THREAD_MAP g_thread_maps[MAX_MAP_NUM]; //类对象数组,全局存储
set_thread_map(task_id, &thread_map);
int set_thread_map(unsigned int map_id, S_THREAD_MAP *map)
{
pthread_mutex_lock(&g_thread_maps_mutex);
g_thread_maps[map_id] = *map;
pthread_mutex_unlock(&g_thread_maps_mutex);
return 0;
}
| 项目 | 说明 |
|---|---|
| 功能 | 将线程数据注册到全局管理器中 |
| 参数1 task_id | 任务ID,这里是0 |
| 参数2 thread_map | 指向S_THREAD_MAP对象的指针 |
4.7 完整执行流程图

扩展
1.map容器
1.1 查找相关函数
| 函数 | 功能 | 参数 | 返回值 |
|---|---|---|---|
| find(key) | 根据键查找元素位置 | key:要查找的键 | 找到返回指向该元素的迭代器,没找到返回 end() |
| count(key) | 统计键出现的次数 | key:要统计的键 | map中返回0或1(因为键不能重复) |
| lower_bound(key) | 查找第一个不小于key的位置 | key:比较的键 | 返回指向第一个 >= key 的迭代器 |
| upper_bound(key) | 查找第一个大于key的位置 | key:比较的键 | 返回指向第一个 > key 的迭代器 |
| equal_range(key) | 获取等于key的范围 | key:要查找的键 | 返回一个pair,包含lower_bound和upper_bound |
1.2 插入相关函数
| 函数 | 功能 | 参数 | 返回值 |
|---|---|---|---|
| insert(pair) | 插入一个键值对 | pair<Key,Value> 对象 | 返回pair<迭代器,bool>,bool表示是否插入成功 |
| insert(init_list) | 插入多个元素 | 初始化列表 | 无 |
| emplace(args) | 原地构造并插入 | 构造键值对的参数 | 返回pair<迭代器,bool> |
| emplace_hint(pos, args) | 带提示位置的插入 | pos:插入位置提示,args:构造参数 | 返回迭代器 |
m["key"] = 100; // 方式1:下标(最常用)
m.insert({"key", 100}); // 方式2:初始化列表
m.insert(make_pair("key", 100)); // 方式3:make_pair
m.insert(pair<string,int>("key",100)); // 方式4:pair
1.3 删除相关函数
| 函数 | 功能 | 参数 | 返回值 |
|---|---|---|---|
| erase(key) | 按键删除元素 | key:要删除的键 | 返回删除的元素个数(0或1) |
| erase(iterator) | 按迭代器位置删除 | iterator:指向要删除元素的迭代器 | 返回被删除元素的下一个迭代器 |
| erase(first, last) | 删除范围内的元素 | first:起始位置,last:结束位置 | 无 |
| clear() | 清空所有元素 | 无 | 无 |
1.4 容量相关函数
| 函数 | 功能 | 参数 | 返回值 |
|---|---|---|---|
| size() | 获取元素个数 | 无 | map中键值对的数量 |
| empty() | 判断是否为空 | 无 | true表示空,false表示非空 |
| max_size() | 获取最大容量 | 无 | map能容纳的最大元素个数 |
1.5 迭代器相关函数
| 函数 | 功能 | 参数 | 返回值 |
|---|---|---|---|
| begin() | 获取第一个元素的迭代器 | 无 | 指向第一个元素的迭代器 |
| end() | 获取尾后迭代器 | 无 | 指向最后一个元素之后位置的迭代器 |
| rbegin() | 获取反向第一个元素的迭代器 | 无 | 指向最后一个元素的反向迭代器 |
| rend() | 获取反向尾后迭代器 | 无 | 指向第一个元素之前的反向迭代器 |
| cbegin() | 获取常量正向迭代器 | 无 | 不能修改元素的begin() |
| cend() | 获取常量尾后迭代器 | 无 | 不能修改元素的end() |
1.6 修改相关函数
| 函数 | 功能 | 参数 | 返回值 |
|---|---|---|---|
| at(key) | 访问指定键的值(带边界检查) | key:要访问的键 | 返回值的引用,不存在抛异常 |
| operator[] | 下标访问运算符 | key:要访问的键 | 返回值的引用,不存在则自动创建 |
| swap(map) | 交换两个map的内容 | map:要交换的另一个map对象 | 无 |
1.7 查找操作详解
find(key) 是最常用的
| 项目 | 说明 |
|---|---|
| 功能 | 在map中查找指定的键 |
| 参数 | key - 要查找的键值 |
| 返回值 | 找到 → 返回指向该元素的迭代器(可以访问键和值) |
| 没找到 → 返回 end()(尾后迭代器) | |
| 时间复杂度 | O(log n) |
count(key) 用于快速判断存在
| 项目 | 说明 |
|---|---|
| 功能 | 统计指定键出现的次数 |
| 参数 | key - 要统计的键 |
| 返回值 | 0 - 键不存在 |
| 1 - 键存在 | |
| 时间复杂度 | O(log n) |
at(key) 安全的访问方式
| 项目 | 说明 |
|---|---|
| 功能 | 访问指定键对应的值 |
| 参数 | key - 要访问的键 |
| 返回值 | 键存在 → 返回值的引用 |
| 键不存在 → 抛出 out_of_range 异常 | |
| 时间复杂度 | O(log n) |
operator[] 最方便但有风险
| 项目 | 说明 |
|---|---|
| 功能 | 访问或创建指定键对应的值 |
| 参数 | key - 要访问/创建的键 |
| 返回值 | 键存在 → 返回已有值的引用 |
| 键不存在 → 自动创建并返回默认值的引用 | |
| 时间复杂度 | O(log n) |
| 注意 | 即使只读取,如果键不存在也会自动创建! |
2.stmt和从数据库提取数据
2.1、stmt 的本质
sqlite3_stmt *stmt;
| 项目 | 说明 |
|---|---|
| stmt 是什么 | 预处理语句对象(Prepared Statement) |
| 它保存什么 | SQL语句的编译结果 (字节码) |
| 它不保存什么 | 不保存查询结果数据 |
| 工作原理 | 像是一个"迭代器",逐行移动获取数据 |
2.2、实际执行过程详解
2.2.1 准备SQL语句
sqlite3_prepare(db, sql, strlen(sql), &stmt, 0); // 编译SQL语句
SELECT name, feature_size, face_feature, image_size, image_data
FROM face_data_table
假设数据库中有3条记录:
| 行号 | name | feature_size | face_feature | image_size | image_data |
|---|---|---|---|---|---|
| 1 | 张三 | 512 | [0.1,0.2,...] | 102400 | [0xFF,0xD8,...] |
| 2 | 李四 | 512 | [0.3,0.4,...] | 98000 | [0xFF,0xD8,...] |
| 3 | 王五 | 512 | [0.5,0.6,...] | 105000 | [0xFF,0xD8,...] |
此时 stmt 的状态:

2.2.2 第一次调用 sqlite3_step()
while (sqlite3_step(stmt) == SQLITE_ROW)
sqlite3_step() 函数详解
| 项目 | 说明 |
|---|---|
| 功能 | 执行预处理语句,并移动到下一行结果 |
| 参数 | stmt - 预处理语句句柄 |
| 返回值 | SQLITE_ROW - 成功移动到一行数据 |
SQLITE_DONE - 没有更多数据 |
|
SQLITE_ERROR - 执行出错 |
执行过程:
第1次调用 sqlite3_step(stmt)
↓
返回 SQLITE_ROW,指向第1行数据
↓
执行循环体内的代码(处理第1行)
↓
第2次调用 sqlite3_step(stmt)
↓
返回 SQLITE_ROW,指向第2行数据
↓
执行循环体内的代码(处理第2行)
↓
第3次调用 sqlite3_step(stmt)
↓
返回 SQLITE_ROW,指向第3行数据
↓
执行循环体内的代码(处理第3行)
↓
第4次调用 sqlite3_step(stmt)
↓
返回 SQLITE_DONE(没有第4行了)
↓
退出 while 循环
此时:
stmt 的游标向下移动一行
↓
┌─────────────────────────────────────────┐
│ 第1行: 张三,512,[特征],102400,[图片] ← 现在指向这行 │
│ 第2行: 李四,512,[特征],98000,[图片] │
│ 第3行: 王五,512,[特征],105000,[图片] │
└─────────────────────────────────────────┘
stmt 内部保存了当前行的"位置"
但数据还在数据库里,没有复制到 stmt 中!
2.2.3 提取数据
1.提取姓名(第0列)
name = (char *)sqlite3_column_text(stmt, 0);
printf("name = %s\n", name);
|---------|------------------------------------|
| 功能 | 获取当前行指定列的文本类型数据 |
| 参数1 | stmt - 预处理语句句柄 |
| 参数2 | 0 - 列索引,0表示第1列(name字段) |
| 返回值 | const unsigned char* - 指向文本数据的指针 |
name 的类型是 char*,需要强制类型转换
此时:
SQLite 根据 stmt 保存的位置,去数据库找到第1行
取出第1行的第0列数据
把数据复制到内存中
返回指向这个内存的指针
stmt 本身仍然不保存数据,只是知道去哪里取!
当前指向第1行:name列的值是 "张三"sqlite3_column_text(stmt, 0) 返回 → 指向 "张三" 的指针
↓
name = 那个指针
↓
printf("%s", name) 输出 → 张三
2.提取特征大小(第1列)
feature_size = sqlite3_column_int(stmt, 1);
printf("feature_size = %d\n", feature_size);
当前指向第1行:feature_size列的值是 512sqlite3_column_int(stmt, 1) 返回 → 512
↓
feature_size = 512
↓
printf 输出 → feature_size = 512
3.提取特征数据(第2列)
const void *feature = sqlite3_column_blob(stmt, 2); //获取数据指针
memset(rockx_face_feature.feature, 0, feature_size); //清空目标缓冲区
memcpy(rockx_face_feature.feature, feature, feature_size); //拷贝数据
rockx_face_feature.len = feature_size; //设置长度
sqlite3_column_blob() 函数详解
| 项目 | 说明 |
|---|---|
| 功能 | 获取当前行指定列的二进制数据 |
| 参数1 | stmt - 预处理语句句柄 |
| 参数2 | 2 - 列索引,2表示第3列(face_feature字段) |
| 返回值 | const void* - 指向二进制数据的指针 |
人脸特征是一个 512维的float数组,占用 512 × 4 = 2048 字节,是二进制数据,不能用文本存储。
这里有问题
memset(rockx_face_feature.feature, 0, feature_size);
memcpy(rockx_face_feature.feature, feature, feature_size);
feature_size的值是 512 (特征维度数),但实际字节数应该是512 × sizeof(float) = 2048!正确写法:
int feature_bytes = feature_size * sizeof(float); // 512 * 4 = 2048
memset(rockx_face_feature.feature, 0, feature_bytes);
memcpy(rockx_face_feature.feature, feature, feature_bytes);
4.提取图片大小(第3列)
image_size = sqlite3_column_int(stmt, 3);
printf("image_size = %d\n", image_size);
|----------|-----------------------|
| 列索引 | 3 - 第4列(image_size字段) |
| 数据类型 | INTEGER |
| 存储内容 | 图片文件的字节数(如 102400) |
5.提取图片数据(第4列)
const char *image_data = (const char *)sqlite3_column_blob(stmt, 4);
数据库中的 image_data 字段存储的是原始图片文件的完整内容:

2.2.4 继续下一行
sqlite3_step(stmt); // 移动到第2行
此时:
stmt 的游标移动到第2行
┌─────────────────────────────────────────┐
│ 第1行: 张三,512,[特征],102400,[图片] │
│ 第2行: 李四,512,[特征],98000,[图片] ← 现在指向这行 │
│ 第3行: 王五,512,[特征],105000,[图片] │
└─────────────────────────────────────────┘
之前第1行的数据?已经被释放或覆盖了!
2.3完整数据提取流程图

3.string str(name)
| 项目 | 说明 |
|---|---|
| string | C++标准库中的字符串类 |
| str | 定义了一个string类型的变量,变量名是str |
| name | 一个char*类型的C风格字符串指针 |
| 整体作用 | 将C风格字符串(char*)转换为C++ string对象 |
3.1.内存对比
转换前 (name):
name (char*指针)
│
▼
┌────┬────┬────┬────┬────┬────┬────┐
│ '张' │ '三' │ '\0' │ │ │ │ │
└────┴────┴────┴────┴────┴────┴────┘
C风格:以\0结尾的字符数组
3.转换后 (str):
str (string对象)
┌─────────────────────────────┐
│ 内部包含: │
│ - 指向堆内存的指针 │
│ - 字符串长度 (2) │
│ - 容量大小 │
└─────────────────────────────┘
│
▼
┌────┬────┐
│ '张' │ '三' │
└────┴────┘
C++风格:知道长度,不依赖\0
3.2.原因
场景1:存入map(最常见原因)
cs
map<string, rockx_face_feature_t> rockx_face_feature_map;
// ❌ 不能直接用 char* 作为map的键
// map 要求键是 string 类型
// ✅ 需要转换为 string
string str(name);
rockx_face_feature_map[str] = rockx_face_feature;
// 或者
rockx_face_feature_map.insert(pair<string, rockx_face_feature_t>(str, rockx_face_feature));
场景2:赋值给其他string变量
cs
People first_people;
first_people.people_name = string(name); // 直接转换并赋值
// 等价于:
string str(name);
first_people.people_name = str;