基於sqlite資料庫的C語言編程

來源:互聯網
上載者:User

一 SQLITE 操作入門
sqlite 提供的是一些 C 函數介面,你可以用這些函數操作資料庫。通過使用這些介面,傳遞一些標準 sql 語句(以 char * 類型)給 sqlite 函數, sqlite 就會為你操作資料庫。
sqlite 跟 MS 的 access 一樣是檔案型資料庫,就是說,一個資料庫就是一個檔案,此資料庫裡可以建立很多的表,可以建立索引、觸發器等等,但是,它實際上得到的就是一個檔案。備份這個檔案就備份了整個資料庫。
sqlite 不需要任何資料庫引擎,這意味著如果你需要 sqlite 來儲存一些使用者資料,甚至都不需要安裝資料庫 ( 如果你做個小軟體還要求人家必須裝了 sqlserver 才能運行,那也太黑心了 ) 。
下面開始介紹資料庫基本操作。
1 基本流程
( 1 )關鍵資料結構
sqlite 裡最常用到的是 sqlite3 * 類型。從資料庫開啟開始, sqlite 就要為這個類型準備好記憶體,直到資料庫關閉,整個過程都需要用到這個類型。當資料庫開啟時開始,這個類型的變數就代表了你要操作的資料庫。下面再詳細介紹。
( 2 )開啟資料庫
int sqlite3_open( 檔案名稱 , sqlite3 ** );
用這個函數開始資料庫操作。
需要傳入兩個參數,一是資料庫檔案名,比如: c:\\DongChunGuang_Database.db 。
檔案名稱不需要一定存在,如果此檔案不存在, sqlite 會自動建立它。如果它存在,就嘗試把它當資料庫檔案來開啟。
sqlite3 ** 參數即前面提到的關鍵資料結構。這個結構底層細節如何,你不要關它。
函數傳回值表示操作是否正確,如果是 SQLITE_OK 則表示操作正常。相關的傳回值 sqlite 定義了一些宏。具體這些宏的含義可以參考 sqlite3.h 檔案。裡面有詳細定義(順便說一下, sqlite3 的代碼注釋率自稱是非常高的,實際上也的確很高。只要你會看英文, sqlite 可以讓你學到不少東西)。
下面介紹關閉資料庫後,再給一段參考代碼。
( 3 ) 關閉資料庫
int sqlite3_close(sqlite3 *);
前面如果用 sqlite3_open 開啟了一個資料庫,結尾時不要忘了用這個函數關閉資料庫。
下面給段簡單的代碼:
extern "C"
#include "./sqlite3.h"
};
int main( int , char** )
sqlite3 * db = NULL; // 聲明sqlite 關鍵結構指標
int result;
// 開啟資料庫
// 需要傳入 db 這個指標的指標,因為 sqlite3_open 函數要為這個指標分配記憶體,還要讓db 指標指向這個記憶體區
result = sqlite3_open( “ c:\\Dcg_database.db ” , &db );
if( result != SQLITE_OK )
{
// 資料庫開啟失敗
return -1;
// 資料庫作業碼
// …
// 資料庫開啟成功
// 關閉資料庫
sqlite3_close( db );
return 0;
這就是一次資料庫操作過程。
二 SQL 陳述式操作
在sqlite下有一些命令和在mysql下是不一樣的,例如很多命令式以點好開頭的,用 .help 可以查看。
但是標準的SQL文法sqlite是支援的。
( 1 )執行 sql 語句
int sqlite3_exec(sqlite3*, const char *sql, sqlite3_callback, void *, char **errmsg );
這就是執行一條 sql 語句的函數。
第 1 個參數不再說了,是前面 open 函數得到的指標。說了是關鍵資料結構。
第 2 個參數 const char *sql 是一條 sql 語句,以 \0 結尾。
第 3 個參數 sqlite3_callback 是回調,當這條語句執行之後, sqlite3 會去調用你提供的這個函數。(什麼是回呼函數,自己找別的資料學習)
第 4 個參數 void * 是你所提供的指標,你可以傳遞任何一個指標參數到這裡,這個參數最終會傳到回呼函數裡面,如果不需要傳遞指標給回呼函數,可以填 NULL 。等下我們再看回呼函數的寫法,以及這個參數的使用。
第 5 個參數 char ** errmsg 是錯誤資訊。注意是指標的指標。 sqlite3 裡面有很多固定的錯誤資訊。執行 sqlite3_exec 之後,執行失敗時可以查閱這個指標(直接 printf( “ %s\n ” ,errmsg) )得到一串字串資訊,這串資訊告訴你錯在什麼地方。 sqlite3_exec 函數通過修改你傳入的指標的指標,把你提供的指標指向錯誤提示資訊,這樣 sqlite3_exec 函數外面就可以通過這個 char* 得到具體錯誤提示。
說明:通常, sqlite3_callback 和它後面的 void * 這兩個位置都可以填 NULL 。填 NULL 表示你不需要回調。比如你做 insert 操作,做 delete 操作,就沒有必要使用回調。而當你做 select 時,就要使用回調,因為 sqlite3 把資料查出來,得通過回調告訴你查出了什麼資料。
( 2 ) exec 的回調
typedef int (*sqlite3_callback)( void *, int , char **, char **);
你的回呼函數必須定義成上面這個函數的類型。下面給個簡單的例子:
//sqlite3 的回呼函數
// sqlite 每查到一條記錄,就調用一次這個回調
int LoadMyInfo( void * para, int n_column, char ** column_value, char ** column_name )
//para 是你在 sqlite3_exec 裡傳入的 void * 參數
// 通過para 參數,你可以傳入一些特殊的指標(比如類指標、結構指標),然後在這裡面強制轉換成對應的類型(這裡面是void* 類型,必須強制轉換成你的類型才可用)。然後操作這些資料
//n_column 是這一條記錄有多少個欄位 ( 即這條記錄有多少列)
// char ** column_value 是個關索引值,查出來的資料都儲存在這裡,它實際上是個1 維數組(不要以為是2 維數組),每一個元素都是一個 char * 值,是一個欄位內容(用字串來表示,以\0 結尾)
//char ** column_name 跟 column_value 是對應的,表示這個欄位的欄位名稱
// 這裡,我不使用 para 參數。忽略它的存在.
int i;
printf( “ 記錄包含 %d 個欄位\n ” , n_column );
for( i = 0 ; i < n_column; i ++ )
printf( “ 欄位名:%s ß > 欄位值:%s\n ” , column_name[i], column_value[i] );
printf( “ ------------------\n “ );
return 0;
int main( int , char ** )
sqlite3 * db;
int result;
char * errmsg = NULL;
result = sqlite3_open( “ c:\\Dcg_database.db ” , &db );
if( result != SQLITE_OK )
{
// 資料庫開啟失敗
return -1;
// 資料庫作業碼
// 建立一個測試表,表名叫 MyTable_1 ,有2 個欄位: ID 和 name 。其中ID 是一個自動增加的類型,以後insert 時可以不去指定這個欄位,它會自己從0 開始增加
result = sqlite3_exec( db, “ create table MyTable_1( ID integer primary key autoincrement, name nvarchar(32) ) ” , NULL, NULL, errmsg );
if(result != SQLITE_OK )
printf( “ 建立表失敗,錯誤碼:%d ,錯誤原因:%s\n ” , result, errmsg );
// 插入一些記錄
result = sqlite3_exec( db, “ insert into MyTable_1( name ) values ( ‘ 走路 ’ ) ” , 0, 0, errmsg );
if(result != SQLITE_OK )
printf( “ 插入記錄失敗,錯誤碼:%d ,錯誤原因:%s\n ” , result, errmsg );
result = sqlite3_exec( db, “ insert into MyTable_1( name ) values ( ‘ 騎單車 ’ ) ” , 0, 0, errmsg );
if(result != SQLITE_OK )
printf( “ 插入記錄失敗,錯誤碼:%d ,錯誤原因:%s\n ” , result, errmsg );
result = sqlite3_exec( db, “ insert into MyTable_1( name ) values ( ‘ 坐汽車 ’ ) ” , 0, 0, errmsg );
if(result != SQLITE_OK )
printf( “ 插入記錄失敗,錯誤碼:%d ,錯誤原因:%s\n ” , result, errmsg );
// 開始查詢資料庫
result = sqlite3_exec( db, “ select * from MyTable_1 ” , LoadMyInfo, NULL, errmsg );
// 關閉資料庫
sqlite3_close( db );
return 0;
通 過上面的例子,應該可以知道如何開啟一個資料庫,如何做資料庫基本操作。
有這些 知識,基本上可以應付很多資料庫操作了。
( 3 )不使用回調查詢資料庫
上面介紹的 sqlite3_exec 是使用回調來執行 select 操作。還有一個方法可以直接查詢而不需要回調。但是,我個人感覺還是回調好,因為代碼可以更加整齊,只不過用回調很麻煩,你得聲明一個函數,如果這個函數是類成員函數,你還不得不把它聲明成 static 的(要問為什嗎?這又是 C++ 基礎了。 C++ 成員函數實際上隱藏了一個參數: this , C++ 調用類的成員函數的時候,隱含把類指標當成函數的第一個參數傳遞進去。結果,這造成跟前面說的 sqlite 回呼函數的參數不相符。只有當把成員函式宣告成 static 時,它才沒有多餘的隱含的 this 參數)。
雖然回調顯得代碼整齊,但有時候你還是想要非回調的 select 查詢。這可以通過 sqlite3_get_table 函數做到。
int sqlite3_get_table(sqlite3*, const char *sql, char ***resultp, int *nrow, int *ncolumn, char **errmsg );
第 1 個參數不再多說,看前面的例子。
第 2 個參數是 sql 語句,跟 sqlite3_exec 裡的 sql 是一樣的。是一個很普通的以 \0 結尾的 char * 字串。
第 3 個參數是查詢結果,它依然一維數組(不要以為是二維數組,更不要以為是三維數組)。它記憶體布局是:第一行是欄位名稱,後面是緊接著是每個欄位的值。下面用例子來說事。
第 4 個參數是查詢出多少條記錄(即查出多少行)。
第 5 個參數是多少個欄位(多少列)。
第 6 個參數是錯誤資訊,跟前面一樣,這裡不多說了。
下面給個簡單例子 :
int main( int , char ** )
sqlite3 * db;
int result;
char * errmsg = NULL;
char **dbResult; // 是 char ** 類型,兩個* 號
int nRow, nColumn;
int i , j;
int index;
result = sqlite3_open ( “ c:\\Dcg_database.db ” , &db );
if( result != SQLITE_OK )
{
// 資料庫開啟失敗
return -1;
}
// 資料庫作業碼
// 假設前面已經建立了 MyTable_1 表
// 開始查詢,傳入的 dbResult 已經是 char ** ,這裡又加了一個 & 取地址符,傳遞進去的就成了 char ***
result = sqlite3_get_table ( db, “ select * from MyTable_1 ” , &dbResult, &nRow, &nColumn, &errmsg );
if( SQLITE_OK == result )
{
// 查詢成功
index = nColumn; // 前面說過 dbResult 前面第一行資料是欄位名稱,從 nColumn 索引開始才是真正的資料
printf( “ 查到%d 條記錄\n ” , nRow );
for( i = 0; i < nRow ; i++ )
{
printf( “ 第 %d 條記錄\n ” , i+1 );
for( j = 0 ; j < nColumn; j++ )
{
printf( “ 欄位名:%s ß > 欄位值:%s\n ” , dbResult[j], dbResult [index] );
++index; // dbResult 的欄位值是連續的,從第0 索引到第 nColumn - 1 索引都是欄位名稱,從第 nColumn 索引開始,後面都是欄位值,它把一個二維的表(傳統的行列標記法)用一個扁平的形式來表示
}
printf( “ -------\n ” );
}
}
// 到這裡,不論資料庫查詢是否成功,都釋放 char** 查詢結果,使用 sqlite 提供的功能來釋放
sqlite3_free_table ( dbResult );
// 關閉資料庫
sqlite3_close ( db );
return 0;
到這個例子為止, sqlite3 的常用用法都介紹完了。
用以上的方法,再配上 sql 語句,完全可以應付絕大多數資料庫需求。
但有一種情況,用上面方法是無法實現的:需要 insert 、 select 二進位。當需要處理位元據時,上面的方法就沒辦法做到。
三 操作二進位
我自己編了一個fruit的程式,將水果圖片插入資料庫,然後讀出,當然還是一個水果。(這裡沒有給出)
sqlite 操作位元據需要用一個輔助的資料類型: sqlite3_stmt * 。
這個資料類型記錄了一個“ sql 語句”。為什麼我把 “ sql 語句” 用雙引號引起來?因為你可以把 sqlite3_stmt * 所表示的內容看成是 sql 語句,但是實際上它不是我們所熟知的 sql 語句。它是一個已經把 sql 語句解析了的、用 sqlite 自已標幟記錄的內部資料結構。
正因為這個結構已經被解析了,所以你可以往這個語句裡插入位元據。當然,把位元據插到 sqlite3_stmt 結構裡可不能直接 memcpy ,也不能像 std::string 那樣用 + 號。必須用 sqlite 提供的函數來插入。
( 1 )寫入二進位
下面說寫二進位的步驟。
要插入二進位,前提是這個表的欄位的類型是 blob 類型。我假設有這麼一張表:
create table Tbl_2( ID integer, file_content blob )
首先聲明
sqlite3_stmt * stat;
然後 ,把一個 sql 語句解析到 stat 結構裡去:
sqlite3_prepare ( db, “ insert into Tbl_2( ID, file_content) values( 10, ? ) ” , -1, &stat, 0 );
上 面的函數完成 sql 語句的解析。第一個參數跟前面一樣,是個 sqlite3 * 類型變數,第二個參數是一個 sql 語句。
這個 sql 語句特別之處在於 values 裡面有個 ? 號。在 sqlite3_prepare 函數裡, ? 號表示一個未定的值,它的值等下才插入。
第三個參數我寫的是 -1 ,這個參數含義是前面 sql 語句的長度。如果小於 0 , sqlite 會自動計算它的長度(把 sql 語句當成以 \0 結尾的字串)。
第四個參數是 sqlite3_stmt 的指標的指標。解析以後的 sql 語句就放在這個結構裡。
第五個參數我也不知道是幹什麼的。為 0 就可以了。
如果這個函數執行成功(傳回值是 SQLITE_OK 且 stat 不為 NULL ),那麼下面就可以開始插入位元據 。
sqlite3_bind_blob ( stat, 1, pdata, ( int )(length_of_data_in_bytes), NULL ); // pdata 為資料緩衝區,length_of_data_in_bytes 為資料大小,以位元組為單位
這個函數一共有 5 個參數。
第 1 個參數:是前面 prepare 得到的 sqlite3_stmt * 類型變數。
第 2 個參數: ? 號的索引。前面 prepare 的 sql 語句裡有一個 ? 號,假如有多個 ? 號怎麼插入?方法就是改變 bind_blob 函數第 2 個參數。這個參數我寫 1 ,表示這裡插入的值要替換 stat 的第一個 ? 號(這裡的索引從 1 開始計數,而非從 0 開始)。如果你有多個 ? 號,就寫多個 bind_blob 語句,並改變它們的第 2 個參數就替換到不同的 ? 號。如果有 ? 號沒有替換, sqlite 為它取值 null 。
第 3 個參數:位元據起始指標。
第 4 個參數:位元據的長度,以位元組為單位。
第 5 個參數:是個析夠回呼函數,告訴 sqlite 當把資料處理完後調用此函數來析夠你的資料。這個參數我還沒有使用過,因此理解也不深刻。但是一般都填 NULL ,需要釋放的記憶體自己用代碼來釋放。
bind 完了之後,位元據就進入了你的“ sql 語句”裡了。你現在可以把它儲存到資料庫裡:
int result = sqlite3_step ( stat );
通過這個語 句, stat 表示的 sql 語句就被寫到了資料庫裡。
最後,要把 sqlite3_stmt 結 構給釋放:
sqlite3_finalize ( stat ); // 把剛才分配的內容析構掉
( 2 )讀出二進位
下面說讀二進位的步驟 。
跟前面一樣 ,先聲明 sqlite3_stmt * 類型變數:
sqlite3_stmt * stat;
然後, 把一個 sql 語句解析到 stat 結構裡去:
sqlite3_prepare ( db, “ select * from Tbl_2 ” , -1, &stat, 0 );
當 prepare 成功之後(傳回值是 SQLITE_OK ),開始查詢資料。
int result = sqlite3_step ( stat );
這一句的傳回值是 SQLITE_ROW 時表示成功(不是 SQLITE_OK )。
你可以迴圈執行 sqlite3_step 函數,一次step 查詢出一條記錄。直到傳回值不為 SQLITE_ROW 時表示查詢結束。
然後開始擷取第一個欄位 :ID 的值。ID 是個整數,用下面這個語句擷取它的值:
int id = sqlite3_column_int( stat, 0 ); // 第2 個參數表示擷取第幾個欄位內容,從0 開始計算,因為我的表的ID 欄位是第一個欄位,因此這裡我填0
下面開始擷取 file_content 的值,因為 file_content 是二進位,因此我需要得到它的指標,還有它的長度:
const void * pFileContent = sqlite3_column_blob ( stat, 1 );
int len = sqlite3_column_bytes ( stat, 1 );
這樣就得到了二進位的值 。
把 pFileContent 的內容儲存出來之後,不要忘了釋放 sqlite3_stmt 結構:
sqlite3_finalize ( stat ); // 把剛才分配的內容析構掉
( 3 )重複使用 sqlite3_stmt 結構
如果你需要重複使用 sqlite3_prepare 解析好的 sqlite3_stmt 結構,需要用函數: sqlite3_reset 。
result = sqlite3_reset (stat);
這樣, stat 結構又成為 sqlite3_prepare 完成時的狀態,你可以重新為它 bind 內容。
4 交易處理
sqlite 是支援交易處理的。如果你知道你要同步刪除很多資料,不仿把它們做成一個統一的事務。
通常一 次 sqlite3_exec 就是一次事務,如果你要刪除 1 萬條資料, sqlite 就做了 1 萬次:開始新事務 -> 刪除一條資料 -> 提交事務 -> 開始新事務 -> … 的過程。這個操作是很慢的。因為時間都花在了開始事務、提交事務上。
你可以把這些同類操作做成一個事務,這樣如果操作錯誤,還能夠復原事務。
事務的操作沒有特別的介面函數,它就是一個普通的 sql 語句而已:
分別如下 :
int result;
result = sqlite3_exec ( db, "begin transaction", 0, 0, &zErrorMsg ); // 開始一個事務
result = sqlite3_exec ( db, "commit transaction", 0, 0, &zErrorMsg ); // 提交事務
result = sqlite3_exec ( db, "rollback transaction", 0, 0, &zErrorMsg ); // 復原事務
關於代碼,可以參照下面一片文章,我已經在這個基礎上編寫了一個程式,有一個問題是,我們用fseek和ftell求檔案的長度之後,要記得用SEEK_SET將位置指標設定為檔案的開始處,粗心的同學不要忘了這個。代碼轉自於這裡
1.首先包含相關標頭檔:
#include "sqlite3.h"
#pragma comment(lib,"sqlite3.lib")
2.建立資料庫:
sqlite3 * db;
sqlite3_stmt *stat;
char *zErrMsg=0;
sqlite3_open("F:\\C++\\sqlite\\Bin.db",&db);
if(db==NULL)
{
return;
}
sqlite3_exec(db,"create table image (filename varchar(128) unique,img blob);",0,0,&zErrMsg);
sqlite3_close(db);
3.將圖片插入資料庫:
sqlite3 * db;
sqlite3_stmt *stat;
char *zErrMsg=0;
FILE *fp=NULL;
long filesize=0;
char* ffile=NULL;
char* buf=NULL;
sqlite3_open("F:\\C++\\sqlite\\Bin.db",&db);
if(db==NULL)
{
return;
}
fp=fopen("F:\\C++\\sqlite\\131.jpg","rb");
if(fp!=NULL)
{
fseek(fp,0,SEEK_END);
filesize=ftell(fp);
fseek(fp,0,SEEK_SET);
ffile=new char[filesize];
size_t sz=fread(ffile,sizeof(char),filesize,fp);
fclose(fp);
}
//sqlite3_exec(db,"create table image (filename varchar(128) unique,img blob);",0,0,&zErrMsg);
sqlite3_prepare(db,"insert into image values ('girl.jpg',?)",-1,&stat,0);
sqlite3_bind_blob(stat,1,ffile,filesize,NULL);
sqlite3_step(stat);
delete[] ffile;
sqlite3_finalize(stat);
sqlite3_close(db);
4.匯出圖片:
sqlite3 * db;
sqlite3_stmt *stat2;
char *zErrMsg=0;
long filesize=0;
int rc;
char* buf=NULL;
sqlite3_open("F:\\C++\\sqlite\\Bin.db",&db);
if(db==NULL)
{
return;
}
sqlite3_prepare(db,"select * from image",-1,&stat2,0);
sqlite3_step(stat2);
//while (rc == SQLITE_ROW)
FILE *fp2;
fp2=fopen("F:\\C++\\sqlite\\Show.jpg","wb");
const void *ImgData=sqlite3_column_blob(stat2,1);
int size=sqlite3_column_bytes(stat2,1);
size_t ret=fwrite(ImgData,sizeof(char),size,fp2);
fclose(fp2);
sqlite3_finalize(stat2);
sqlite3_close(db);
5.更新圖片:
sqlite3 * db;
sqlite3_stmt *stat;
char *zErrMsg=0;
FILE *fp=NULL;
long filesize=0;
char* ffile=NULL;
char* buf=NULL;
sqlite3_open("F:\\C++\\sqlite\\Bin.db",&db);
if(db==NULL)
{
return;
}
fp=fopen("F:\\C++\\sqlite\\124.jpg","rb");
if(fp!=NULL)
{
fseek(fp,0,SEEK_END);
filesize=ftell(fp);
fseek(fp,0,SEEK_SET);
ffile=new char[filesize];
size_t sz=fread(ffile,sizeof(char),filesize,fp);
fclose(fp);
}
sqlite3_prepare(db,"update image set img=? where filename='girl.jpg'",-1,&stat,0);
sqlite3_bind_blob(stat,1,ffile,filesize,NULL);
sqlite3_step(stat);
delete[] ffile;
sqlite3_finalize(stat);
sqlite3_close(db);

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.