數據庫sqlite3之 sqlite3_exec()第三個參數回調函數的使用

在寫這篇文章之前大家先了解我之前寫的關於用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<[email protected]>
 *                  All rights reserved.
 *
 *       Filename:  sqlite_delete.c
 *    Description:  This file 
 *                 
 *        Version:  1.0.0(2020年03月27日)
 *         Author:  makun <[email protected]>
 *      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<[email protected]>
 *                  All rights reserved.
 *
 *       Filename:  sqlite_insert.c
 *    Description:  This file 
 *                 
 *        Version:  1.0.0(2020年03月27日)
 *         Author:  makun <[email protected]>
 *      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‘結尾),我在做項目的時候就發現了這一點,剛開始看的時候關於這個細節沒有把握住,以至於花了自己一點時間,但是對於犯錯,我們並不可怕,可怕的是遇到錯誤我們就放棄了。

在這裏插入圖片描述在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章