感受一下C++操作SQLite有多麻煩
學(xué)程序設(shè)計的估計都要學(xué) C/C++砾莱。當(dāng)年學(xué)的時候無非是覺得指針用起來有點暈,用的時候要考慮清楚腊瑟,要十分小心,并沒有覺得有多難魔策,也沒覺得C/C++用起來會很麻煩河胎。
在后來的應(yīng)用的場景中游岳,慢慢遠離了C++其徙,很快上手了C#。直到最近唾那,為了追求效率,想拿C++試一把期犬。結(jié)果是效率提升到?jīng)]看出太多避诽,其麻煩程度讓我徹底放棄了。
就不拿python這種極端的做對比了鲤妥,用C#做對比足夠了。以查詢返回多條記錄的操作為例棉安。
C#的方法
C#查詢SQLite并返回多條記錄的操作核心代碼一般是這樣的:
//第一,聲名數(shù)據(jù)庫連接字符串衷模,關(guān)鍵參數(shù)是數(shù)據(jù)庫文件的路徑和UTF8的編碼
SQLiteConnection conn = new SQLiteConnection("Data Source=database.db;Pooling=true;UTF8Encoding=True;Version=3");
//聲名一個數(shù)據(jù)讀取對象
SQLiteDataReader dr;
//聲名一個命令操作對象菇爪,關(guān)鍵參數(shù)是SQL語句字符串
SQLiteCommand cmd = new SQLiteCommand("SELECT * FROM TABLE", conn);
//打開連接
conn.Open();
//獲取返回數(shù)據(jù)指針
dr = cmd.ExecuteReader();
//通過whi循環(huán)分條讀取記錄,針對不同類型調(diào)用不同的讀取方法熙揍。
if (dr.HasRows){
while (dr.Read()){
dr.GetString(0);
dr.GetInt32(1);
dr.GetDouble(2);
}
}
//關(guān)閉連接
dr.Close();
conn.Close();
是不是看上去C#的操作也不是特別簡單呢氏涩?但好在邏輯上非常清晰,每個方法的參數(shù)都實實在在意系,沒有什么其他感覺上是多余的東西饺汹。而且,如果對比了C++的兜辞,就會知道C#是多么的輕而易舉。
C++的方法
C++操作SQLite就復(fù)雜就不提他在前期配置和生成Linker的一堆麻煩了凶硅,只看代碼扫皱。
首先,數(shù)據(jù)庫連接要這樣打開:
sqlite3* db = NULL;//數(shù)據(jù)庫指針
sqlite3_stmt* stmt = NULL;//用于一些查詢的返回值
char *zErrMsg = 0;//保存返回的錯誤信息
sqlite3_open("dag.db", &db);//打開數(shù)據(jù)庫氢妈,把打開的指針傳遞給db
C++到是有很多方法來實現(xiàn)多條返回記錄的獲取扰才。
回調(diào)函數(shù)就是一種。很幸運蕾总,我是不知道啥叫回調(diào)函數(shù)的粥航,只知道可以向下面這樣用递雀。
//sqlite每查到一條記錄蚀浆,就調(diào)用一次這個回調(diào)函數(shù)。
int callback(void*para , int nCount, char** pValue, char** pName) {
string s;
for(int i=0;i<nCount;i++){
s+=pName[i];
s+=":";
s+=pValue[i];
s+="\n";
}
cout<<s<<endl;
return 0;
}
// para 是你在 sqlite3_exec 里傳入的 void* 參數(shù), 通過 para 參數(shù)可以傳入一些特殊的指針(比如類指 針杨凑、結(jié)構(gòu)指針)摆昧,然后在這里面強制轉(zhuǎn)換成對應(yīng)的類型。
// nCount 是這一條記錄有多少個字段伺帘。
// char** pValue 是個關(guān)鍵值,查出來的數(shù)據(jù)都保存在這里伪嫁,它是個1維數(shù)組(不要以為是2維數(shù)組)偶垮,每一個元素都是一個 char* 值,是一個字段內(nèi)容晶伦。
// char** pName 跟 pValue 是對應(yīng)的啄枕,表示這個字段的字段名稱, 也是個1維數(shù)組频祝。
有了這個回調(diào)函數(shù)的聲名,就可以在主程序段用下面語句查詢了:
rc = sqlite3_exec(db, sql, callback, (void*)data, &zErrMsg);
//第 1 個參數(shù)是前面 open 函數(shù)得到的指針常空。
//第 2 個參數(shù)是一條 sql 語句盖溺。
//第 3 個參數(shù)上面聲名的回調(diào)函數(shù),當(dāng)這條語句執(zhí)行之后昆禽,sqlite3 會去調(diào)用。
//第 4 個參數(shù)是你所提供的指針醉鳖,你可以傳遞任何一個指針參數(shù)到這里,這個參數(shù)最終會傳到回調(diào)函數(shù)里面壮韭,如果不需要傳遞指針給回調(diào)函數(shù)纹因,可以填 NULL。
//第 5 個參數(shù)是錯誤信息瞭恰。注意是指針的指針。sqlite3 里面有很多固定的錯誤信息是牢,執(zhí)行失敗時可以查閱這個指針陕截。
//這條sql語句是否正常執(zhí)行還可以通過函數(shù)的返回值判斷,并打印錯誤信息
if (rc != SQLITE_OK){
fprintf(stderr, "SQL error: %s\n", zErrMsg);
sqlite3_free(zErr.Msg);
}
好了社搅,是不是感覺和C#很不一樣乳规。
當(dāng)然,C++也有和C#相似一點的操作方法:
//先使用 sqlite3_prepare_v2 執(zhí)行 sql 語句
sqlite3_prepare_v2(db, sql, 512, &stmt, NULL);
//再使用 sqlite3_step 遍歷查詢返回的結(jié)果
//這里可以用循環(huán)進行遍歷
if (sqlite3_step(stmt) != SQLITE_ROW)
cout << "qurey error" << endl;
//每次獲取的結(jié)果笙以,使用 sqlite3_column 系列函數(shù)獲取內(nèi)容,不同類型要使用不同的函數(shù)猖腕,第二個參數(shù)是查詢的字段索引
sqlite3_column_text(stmt, 0);
//這一系列函數(shù)類似于C#中的GetString, GetInt32恨闪,如下面這些:
double sqlite3_column_double(sqlite3_stmt*, int iCol);
int sqlite3_column_int(sqlite3_stmt*, int iCol);
//一共有十多個
好了,如果使用后面這種方法到也還算是不錯了老玛。
然而,如果你的數(shù)據(jù)庫里有中文麸粮。不好意思,很有可能會出現(xiàn)亂碼余素。因為C++是ASCII,數(shù)據(jù)庫一般是UTF-8威根。這個問題在C#中只是一個連接字符的參數(shù)設(shè)置视乐。本以為C++差不多的×裘溃可現(xiàn)實并不是這樣伸刃。C++要自行轉(zhuǎn)碼∨趼可令人不解的是,這個轉(zhuǎn)碼竟然要這么長的代碼挚币,唯一可以慶幸的是扣典,直接copy下面的函數(shù)就行了。
//UTF-8轉(zhuǎn)Unicode
std::wstring Utf82Unicode(const std::string& utf8string){
int widesize = ::MultiByteToWideChar(CP_UTF8, 0, utf8string.c_str(), -1, NULL, 0);
if (widesize == ERROR_NO_UNICODE_TRANSLATION){
throw std::exception("Invalid UTF-8 sequence.");
}
if (widesize == 0){
throw std::exception("Error in conversion.");
}
std::vector<wchar_t> resultstring(widesize);
int convresult = ::MultiByteToWideChar(CP_UTF8, 0, utf8string.c_str(), -1, &resultstring[0], widesize);
if (convresult != widesize) {
throw std::exception("La falla!");
}
return std::wstring(&resultstring[0]);
}
//unicode 轉(zhuǎn)為 ascii
string WideByte2Acsi(wstring& wstrcode){
int asciisize = ::WideCharToMultiByte(CP_OEMCP, 0, wstrcode.c_str(), -1, NULL, 0, NULL, NULL);
if (asciisize == ERROR_NO_UNICODE_TRANSLATION){
throw std::exception("Invalid UTF-8 sequence.");
}
if (asciisize == 0){
throw std::exception("Error in conversion.");
}
std::vector<char> resultstring(asciisize);
int convresult = ::WideCharToMultiByte(CP_OEMCP, 0, wstrcode.c_str(), -1, &resultstring[0], asciisize, NULL, NULL);
if (convresult != asciisize){
throw std::exception("La falla!");
}
return std::string(&resultstring[0]);
}
//utf-8 轉(zhuǎn) ascii
string UTF_82ASCII(string& strUtf8Code){
string strRet("");
//先把 utf8 轉(zhuǎn)為 unicode
wstring wstr = Utf82Unicode(strUtf8Code);
//最后把 unicode 轉(zhuǎn)為 ascii
strRet = WideByte2Acsi(wstr);
return strRet;
}
//ascii 轉(zhuǎn) Unicode
wstring Acsi2WideByte(string& strascii){
int widesize = MultiByteToWideChar(CP_ACP, 0, (char*)strascii.c_str(), -1, NULL, 0);
if (widesize == ERROR_NO_UNICODE_TRANSLATION){
throw std::exception("Invalid UTF-8 sequence.");
}
if (widesize == 0){
throw std::exception("Error in conversion.");
}
std::vector<wchar_t> resultstring(widesize);
int convresult = MultiByteToWideChar(CP_ACP, 0, (char*)strascii.c_str(), -1, &resultstring[0], widesize);
if (convresult != widesize){
throw std::exception("La falla!");
}
return std::wstring(&resultstring[0]);
}
//Unicode 轉(zhuǎn) Utf8
std::string Unicode2Utf8(const std::wstring& widestring){
int utf8size = ::WideCharToMultiByte(CP_UTF8, 0, widestring.c_str(), -1, NULL, 0, NULL, NULL);
if (utf8size == 0){
throw std::exception("Error in conversion.");
}
std::vector<char> resultstring(utf8size);
int convresult = ::WideCharToMultiByte(CP_UTF8, 0, widestring.c_str(), -1, &resultstring[0], utf8size, NULL, NULL);
if (convresult != utf8size){
throw std::exception("La falla!");
}
return std::string(&resultstring[0]);
}
//ascii 轉(zhuǎn) Utf8
string ASCII2UTF8(string& strAsciiCode){
string strRet("");
//先把 ascii 轉(zhuǎn)為 unicode
wstring wstr = Acsi2WideByte(strAsciiCode);
//最后把 unicode 轉(zhuǎn)為 utf8
strRet = Unicode2Utf8(wstr);
return strRet;
}
驚不驚喜,意不意外湿硝。是的,你沒看錯,他就是這么長任连。不僅如此,還要告訴你的是ASCII2UTF8函數(shù)的參數(shù)類型是string&,而且返回值類型是string繁涂。而 sqlite3column_text 的返回值類型是unsigned char*二驰。所以,轉(zhuǎn)碼需要先把 unsigned char* 用轉(zhuǎn)為 string矿酵,而轉(zhuǎn) string 要先轉(zhuǎn)char*,即 string((char*)sqlite3_column_text(stmt, 0))全肮。如果是用 cout 輸出的話棘捣,還得使用string 的 c_str() 方法,把 string 再轉(zhuǎn)為char*乍恐。
好了,有了這些資料百匆,用 C++ 操作 SQLite 差不多是足夠了瞧毙。但我是不會再用了,留給專業(yè)人士吧宙彪。我還是繼續(xù)我的C#和Python。