在写这篇文章之前大家先了解我之前写的关于用c语言操作sqlite3的博客,链接地址如下:
https://blog.csdn.net/makunIT/article/details/105192076
关于sqlite3_exec的回调函数的知识,我也是在做一个项目中学习到的,看了一些博客吧,很多博客,都表达的不是很清楚,所以我想写这篇博客,记录自己的学习过程。大家先了解一下sqlite3_exec()函数吧。
1、sqlite3_exec()
函数原型
#include <sqlite3.h>
int sqlite_exec(sqlite *db,
const char *sql,
int (*callback)(void *,int,char **,char **),
void *,
char **errmsg);
①函数说明:用来执行sql语句,查询的结果返回给回调函数callback。
②参数说明:
第一个参数:db是用于保存打开的数据库文件dbname的信息;
第二个参数:sql你要执行命令的语句;
第三个参数:callback,回调函数,用来处理查询结果,如果不需要回调(比如做insert 或者delete 操作时),可以输入NULL;
第四个参数:void *是你所提供的指针,你可以传递任何一个指针参数到这里,这个参数最终会传到回调函数里面,如果不需要传递指针给回调函数,可以填NULL;
第五个参数:errmsg,返回错误信息,注意是指针的指针。
③返回值:执行成功返回SQLITE_OK,否则返回其他值
说明:通常,sqlite3_callback和它后面的void*这两个位置都可以填NULL。填NULL表示你不需要回调。比如你做insert 操作,做delete操作,就没有必要使用回调。而当你做select 时,就要使用回调,因为sqlite3 把数据查出来,得通过回调告诉你查出了什么数据。虽然回调显得代码整齐,但有时候你还是想要非回调的select查询。这可以通过sqlite3_get_table 函数做到。
那么了解了以上sqlite3_exec()函数,那么我下面将讲解一下sqlite_exec()的参数回调函数callback
2、sqlite3_exec()的回调函数callback
在了解callback之前,我们先了解一下什么是回调函数:回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数;
那么在sqlite3_exec中,我们应该这样理解先执行*sql对应的功能命令,然后将结果传递给回调函数,回调函数根据结果再进一步执行。这代表着,这个 “回调函数”才是最有意义的,我们要讲我们需要的功能,通过回调函数来实现,不管是获取数据库表中有效信息,还是其他动作。
函数原型:
typedef int(*sqlite_callback)(void* para,
int columenCount,
char** columnValue,
char** columnName);
①函数说明:由用户处理查询的结果;
②参数说明:
第一个参数:para: 由sqlite3_exec传入的参数指针,或者说是指针参数;
第二个参数:columnCount: 查询到的这一条记录由多少个字段(多少列);
第三个参数:columnValue : 查询出来的数据都保存在这里,它实际上是个1 维数组(不要以为是2 维数组),每一个元素都是一个char * 值,是一个字段内容(用字符串来表示,以‘\0’结尾);
第四个参数:columnName : 该参数是双指针,语columnValue是对应的,表示这个字段的字段名称;
③返回值:执行成功返回SQLITE_OK,否则返回其他值
说明:回调函数多数时候不是执行1次,而是会循环执行n次,当我们使用select进行sql功能时,往往输出的结果会是 多行,那么 有n行,就会执行n次的 回调函数
下面我将会写两段代码,来实现回调函数,一种是直接用insert直接插入到db表中,一种是用snprintf()函数来实现向表中插入数据的。大家先来看第一种吧
第一种的代码:
/*********************************************************************************
* Copyright: (C) 2020 makun<1394987689@qq.com>
* All rights reserved.
*
* Filename: sqlite_delete.c
* Description: This file
*
* Version: 1.0.0(2020年03月27日)
* Author: makun <1394987689@qq.com>
* ChangeLog: 1, Release initial version on "2020年03月27日 00时47分05秒"
*
********************************************************************************/
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sqlite3.h>
#include <stdlib.h>
static int callback(void *data, int argc, char **argv, char **azColName);
int main (int argc, char **argv)
{
sqlite3 *db=NULL;
int rc ;
char *zerrmsg=0;
char *sql;
char *data= "callback function called";
//1、创建数据库,如果存在则打开
/***********************************************************************************************************************/
rc = sqlite3_open("sqlite.db",&db);
if(rc)
{
printf("create sqlite failure:%s\n",sqlite3_errmsg(db));
exit(1);
}
printf("create sqlite successfuly\n");
//2、创建数据库表
/***********************************************************************************************************************/
sql = "CREATE TABLE COMPANY(" \
"ID INT PRIMARY KEY NOT NULL," \
"NAME TEXT NOT NULL," \
"AGE INT NOT NULL," \
"ADDRESS CHAR(50)," \
"SALARY REAL );";
rc = sqlite3_exec(db,sql,callback, 0, &zerrmsg);
if( rc !=SQLITE_OK)
{
printf("SQL error!:%s\n",zerrmsg);
sqlite3_free(zerrmsg);
}
printf("create table COMPANY successfuly\n");
//3、向表中插入数据
/***********************************************************************************************************************/
sql="INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) " \
"VALUES (1, 'Paul', 32, 'California', 20000.00 ); " \
"INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) " \
"VALUES (2, 'Allen', 25, 'Texas', 15000.00 ); " \
"INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY)" \
"VALUES (3, 'Teddy', 23, 'Norway', 20000.00 );";
rc=sqlite3_exec(db,sql,callback,(void *)data,&zerrmsg);
if(rc !=SQLITE_OK)
{
printf("SQL error!:%s\n",zerrmsg);//打印错误信息
sqlite3_free(zerrmsg);//释放掉zerrmsg的内存空间
}
else
{
printf("create company successfuly\n");
}
//4、查询表中的数据
/**********************************************************************************************************************/
sql = "SELECT * from COMPANY";
rc = sqlite3_exec(db, sql, callback, (void*)data, &zerrmsg);
if( rc != SQLITE_OK )
{
printf("SQL error: %s\n", zerrmsg);
sqlite3_free(zerrmsg);
}
else
{
printf( "Select from COMPANY successfully\n");
}
sqlite3_close(db);//关闭数据库
return 0;
}
//5、查询表中的数据时用到的回调函数
/*********************************************************************************************************************/
static int callback(void *data, int argc, char **argv, char **azColName)
{
int i;
printf( "%s ", (const char*)data);
for(i=0; i<argc; i++)
{
printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
}
printf("\n");
return 0;
}
/**********************************************************************************************************************/
程序结果如下:
可以看出来,由于sql命令行为 select* from COMPANY,该命令会将表中所有信息都输出,总共5个字段(列),包含3条信息(行),所以这个回调函数会被执行3次,如上式结果理解这个逻辑,非常重要。
第二种代码:
/*********************************************************************************
* Copyright: (C) 2020 makun<1394987689@qq.com>
* All rights reserved.
*
* Filename: sqlite_insert.c
* Description: This file
*
* Version: 1.0.0(2020年03月27日)
* Author: makun <1394987689@qq.com>
* ChangeLog: 1, Release initial version on "2020年03月27日 00时47分05秒"
*
********************************************************************************/
#include <stdio.h>
#include <string.h>
#include <sqlite3.h>
#include <stdlib.h>
#pragma pack(1)
typedef struct tlv_data
{
unsigned char sn[12];
unsigned char temp[7];
unsigned char datatime[11];
int id;
}tlv_send_data;
#pragma pack()
static int callback(void *ptr, int argc, char **argv, char **azCoName);
void dump_buf(char *data, int len);
//int delete_db(sqlite3 *db);
int main (int argc, char **argv)
{
sqlite3 *db=NULL;
int rc = -1;
char *zerrmsg=0;
char *sql;
char *sql2;
char *sql3;
int rd;
char sn[12]={0xfd,0x01,12,52,50,49,30,30,30,31,61,0};
char temp[7]={0xfd,0x02,7,17,0,52,0xf9};
char datatime[11]={0xfd,0x03,11,78,5,13,15,1,28,0x4e,0x2a};
tlv_send_data read_db_t;
dump_buf(sn, 12);//以十六进制输出
dump_buf(temp,7);
dump_buf(datatime,11);
//1创建数据库
/*******************************************************************************************************************/
rc = sqlite3_open("data.db",&db);
if(rc < 0)
{
printf("create data failure:%s\n",sqlite3_errmsg(db));
return -1;
}
else
{
printf("create data successfuly\n");
}
//2创建数据库的表格
/********************************************************************************************************************/
if(sqlite3_exec(db,"create table if not exists temperature(id INTEGER PRIMARY KEY AUTOINCREMENT,sn varchar,temp varchar,datatime varchar);",NULL,NULL,&zerrmsg)!=SQLITE_OK)
{
printf("Create failed:%s\n",zerrmsg);
return -1;
}
printf("Open table temperature successfully!\n");
//3向表格中插入数据
/************************************************************************************************************************/
char insert[1024];
memset(insert,0, sizeof(insert));
//将三个数组中的字节存放到数据库中,以字符串格式存储
snprintf(insert,sizeof(insert),"insert into temperature values(null,'%s','%s','%s');",sn,temp,datatime);
if((sqlite3_exec(db, insert, NULL, NULL,&zerrmsg) != SQLITE_OK ))
{
printf("sql error:%s\n",zerrmsg);
sqlite3_free(zerrmsg);
}
printf("Insert successfuly\n");
//4读取数据库中表中的数据 ,只读取数据表中一行数据
/************************************************************************************************************************/
memset(&read_db_t,0,sizeof(read_db_t));
read_db(db, &read_db_t);
// delete_db(db);
return 0;
}
//5读取数据库中表中的数据,只读取表中一行数据
/***********************************************************************************************************/
int read_db(sqlite3 *db,tlv_send_data *pp)
{
char *zErrmsg=NULL;
int rc;
char *sql2="select *from temperature limit 1 ";
rc=sqlite3_exec(db, sql2,callback, (void *)pp,&zErrmsg);
if(rc !=SQLITE_OK)
{
printf("sql error:%s\n", zErrmsg);
sqlite3_free(zErrmsg);
}
else
{
printf("select from temperature successfuly\n");
}
return 0;
}
//6读取数据时用到的回调函数
/********************************************************************************************************************/
static int callback(void *ptr, int argc, char **argv, char **azCoName)
{
int i;
tlv_send_data *data =(tlv_send_data *)ptr;//将ptr强制转换为tlv_send_data,然后存放获取第一条数据的信息,然后返回给参数pp
for(i=0;i <argc; i++)
{
printf("%s = %s\n",azCoName[i],argv[i]?argv[i]:"NULL" );
if(!strcmp("id",azCoName[i]))
{
data->id=atoi(argv[i]);
}
else if(!strcmp("sn",azCoName[i]))
{
strcpy(data->sn,argv[i]);
dump_buf(data->sn,12);
}
else if(!strcmp("temp",azCoName[i]))
{
strcpy(data->temp,argv[i]);
dump_buf(data->temp,7);
}
else if(!strcmp("datatime",azCoName[i]))
{
strcpy(data->datatime,argv[i]);
dump_buf(data->datatime,11);
}
}
printf("\n");
return 0;
}
//以十六进制输出数组中的数据
/************************************************************************************************************************/
void dump_buf( char *data, int len)//data指针,指向buf的首地址,len是buf的长度
{
int i;
for(i=0; i<len; i++)
{
printf("0x%02x ",(unsigned char)data[i]);
if( 0 == (i+1)%16 )
printf("\n");
}
printf("\n");
}
/**********************************************************************************************************************/
/*int delete_db(sqlite3 *db)
{
char *zErrmsg =NULL;
char *sql3="DELETE FROM temperature where id =(SELECT id from temperature limit 1)";
if((sqlite3_exec(db, sql3, NULL, NULL,&zErrmsg) != SQLITE_OK ))
{
printf("sql error:%s\n",zErrmsg);
sqlite3_free(zErrmsg);
}
printf("delect from temperature successfuly\n");
return 0;
}*/
分析:在上式中我将三个数组中的字节按照%s的格式存放在数据库中,当我用回调函数读取数据库中的数据时,我们会发现,图片中打印的是乱码,printf函数以参数"%s"输出字符串时过程为:
(1)从首地址开始逐字节寻址,把存储单元(一个字节)内的数据转换为ASCII字符格式输出。
(2)直到某一个字节内存的元素为字符’\0’时,输出此字符并且寻址结束。
字符数组里没有’\0’,因此使用printf %s 输出时,可能会因为没有结束的’\0’而多输出一些乱码或是字符串。如果要正确的输出字符数组,最好一位一位输出。
另外根据上面的情况我们知道,callback函数,第三个参数:columnValue : 查询出来的数据都保存在这里,它实际上是个1 维数组(不要以为是2 维数组),每一个元素都是一个char * 值,是一个字段内容(用字符串来表示,以‘\0’结尾);
而我们在定义temp时temp[4]=0,temp[5]=52,temp[6]=f9但是当我们用回调函数打印时我们会惊奇的发现的temp[5]=0;temp[6]=0,这个数据出现的问题就是callback的第三个参数引起的,它将查询的数据用char *保存的,(用字符串来表示,以’\0‘结尾),我在做项目的时候就发现了这一点,刚开始看的时候关于这个细节没有把握住,以至于花了自己一点时间,但是对于犯错,我们并不可怕,可怕的是遇到错误我们就放弃了。