kowala's home

kowala's home
這裡是我的學習筆記,陸續增加中。
http://kowala21.blogspot.com

2011-06-14

一些 Unicode 及 Ansi 碼的討論

開這個話題目的,當然也是為了多了解一些編碼知識,為了解決如何在VC++中使用中文字碼問題。

先來看看Unicode及Ansi在記憶體中的排列方式(http://www.regexlab.com/zh/encoding.htm)

在 ASCII 阶段,单字节字符串使用一个字节存放一个字符(SBCS)。比如,"Bob123" 在内存中为:
42    6F    62    31    32    33    00
-    -    -    -    -    -    -
B    o    b    1    2    3    \0

在使用 ANSI 编码支持多种语言阶段,每个字符使用一个字节或多个字节来表示(MBCS),因此,这种方式存放的字符也被称作多字节字符。比如,"中文123" 在中文 Windows 95 内存中为7个字节,每个汉字占2个字节,每个英文和数字字符占1个字节:
D6 D0    CE C4    31    32    33    00
--    --    -    -    -    -
中    文    1    2    3    \0

在 UNICODE 被采用之后,计算机存放字符串时,改为存放每个字符在 UNICODE 字符集中的序号。目前计算机一般使用 2 个字节(16 位)来存放一个序号(DBCS),因此,这种方式存放的字符也被称作宽字节字符。比如,字符串 "中文123" 在 Windows 2000 下,内存中实际存放的是 5 个序号:
2D 4E    87 65    31 00    32 00    33 00    00 00          ← 在 x86 CPU 中,低字节在前
--    --    --    --    --    --
中    文    1    2    3    \0

一共占 10 个字节。


UTF-8的編碼形式(http://www.lihuasoft.net/article/show.php?id=2795)

UTF-8标准就是Unicode(ISO10646)标准的一种变形方式,
UTF的全称是:Unicode/UCS Transformation Format,其实有两种UTF,一种是UTF-8,一种是UTF-16,
不过UTF-16使用较少,其对应关系如下:
在Unicode中编码为 0000 - 007F 的 UTF-8 中编码形式为: 0xxxxxxx
在Unicode中编码为 0080 - 07FF 的 UTF-8 中编码形式为: 110xxxxx 10xxxxxx
在Unicode中编码为 0000 - 007F 的 UTF-8 中编码形式为: 1110xxxx 10xxxxxx 10xxxxxx


C++ 中相关实现方法 (http://www.regexlab.com/zh/encoding.htm)

声明一段字符串常量:
// ANSI 字符串,内容长度 7 字节
char     sz[20] = "中文123";

// UNICODE 字符串,内容长度 5 个 wchar_t(10 字节)
wchar_t wsz[20] = L"\x4E2D\x6587\x0031\x0032\x0033";

UNICODE 字符串的 I/O 操作,字符与字节的转换操作:
// 运行时设定当前 ANSI 编码,VC 格式
setlocale(LC_ALL, ".936");

// GCC 中格式
setlocale(LC_ALL, "zh_CN.GBK");

// Visual C++ 中使用小写 %s,按照 setlocale 指定编码输出到文件
// GCC 中使用大写 %S
fwprintf(fp, L"%s\n", wsz);

// 把 UNICODE 字符串按照 setlocale 指定的编码转换成字节
wcstombs(sz, wsz, 20);
// 把字节串按照 setlocale 指定的编码转换成 UNICODE 字符串
mbstowcs(wsz, sz, 20);

在 Visual C++ 中,UNICODE 字符串常量有更简单的表示方法。如果源程序的编码与当前默认 ANSI 编码不符,则需要使用 #pragma setlocale,告诉编译器源程序使用的编码:
// 如果源程序的编码与当前默认 ANSI 编码不一致,
// 则需要此行,编译时用来指明当前源程序使用的编码
#pragma setlocale(".936")

// UNICODE 字符串常量,内容长度 10 字节
wchar_t wsz[20] = L"中文123";

以上需要注意 #pragma setlocale 与 setlocale(LC_ALL, "") 的作用是不同的,#pragma setlocale 在编译时起作用,setlocale() 在运行时起作用。

這裡有一些轉換函式 (http://stdsoft.blogbus.com/logs/56468290.html)

static wstring ANSI2Unicode(const string & strin){
                wstring strout;   
                // 预计算所需空间大小(已包含结束字符),单位wchar_t
                int dwNum = MultiByteToWideChar (CP_ACP, 0,strin.c_str(), -1, 0 , 0);
                wchar_t * pBuffer = new wchar_t[dwNum];
                if (!pBuffer) {
                    return strout;
                }
                memset(pBuffer,0,(dwNum)*sizeof(wchar_t));
                if(MultiByteToWideChar(CP_ACP, 0, strin.c_str(),-1,pBuffer,dwNum) >= 0){
                    strout = pBuffer;
                }
                delete[] pBuffer;
                return strout;
}
static string  Unicode2UTF8(const wstring & strin){
                string strout;
                //测试所需存储空间大小(已包含结束字符),单位char
                int dwNum = WideCharToMultiByte(CP_UTF8,0,strin.c_str(),-1,0,0,0,0);
                char* pBuffer = new char[dwNum];
                if (!pBuffer){
                    return strout;
                }
                memset(pBuffer,0,dwNum);             
                if(WideCharToMultiByte(CP_UTF8,0,strin.c_str(),-1,pBuffer,dwNum,0,0) >= 0){
                    strout = pBuffer;
                }
                delete[] pBuffer;
                return strout;
}
static string  ANSI2UTF8(const string & strin){
                return Unicode2UTF8(ANSI2Unicode(strin));
}
static string  Unicode2ANSI(const wstring & strin){
                string strout;
                //测试所需存储空间大小(已包含结束字符),单位char
                int dwNum = WideCharToMultiByte(CP_ACP,0,strin.c_str(),-1,0,0,0,0);
                char* pBuffer = new char[dwNum];
                if (!pBuffer){
                    return strout;
                }
                memset(pBuffer,0,dwNum);
                BOOL use_def_char = FALSE;               if(WideCharToMultiByte(CP_ACP,0,strin.c_str(),-1,pBuffer,dwNum,"?",&use_def_char) >= 0){
                    strout = pBuffer;
                }
                delete[] pBuffer;
                return strout;
}
static wstring UTF82Unicode(const string & strin) {
                wstring strout;
               // 预计算所需空间大小(已包含结束字符),单位wchar_t
                int dwNum = MultiByteToWideChar (CP_UTF8, 0,strin.c_str(), -1, 0 , 0);
                wchar_t* pBuffer = new wchar_t[dwNum];
                if (!pBuffer) {
                    return strout;
                }
                memset(pBuffer,0,dwNum*sizeof(wchar_t));
               if(MultiByteToWideChar(CP_UTF8,0,strin.c_str(),-1,pBuffer,dwNum) >= 0) {
                    strout = pBuffer;
                }
                delete[] pBuffer;
                return strout;
}
static string  UTF82ANSI(const string & strin) {
                return Unicode2ANSI(UTF82Unicode(strin));
}

這位作者的網頁也不錯
http://shukaiyang.myweb.hinet.net/courses/cpp/unicode2ascii.zhtw.htm
http://shukaiyang.myweb.hinet.net/courses/cpp/unicodestr.zhtw.htm

C++ & MySQL 中文問題III


為了解決 MySQL 使用UTF-8 的中文問題,今天尋找到另一個連接器 mysql++
下載原始碼,編譯,準備測試。。。
這個比較麻煩,要自己編譯,還要引用到 MySQL 的 .h 檔,這意味著您必須下載 MySQL Server 的 .h
來,開始玩吧!

首先,請先下載 mysql++-3.1.0.tar.gz 與 mysql-noinstall-5.1.51-win32.zip
在這 http://tangentsoft.net/mysql++ 下載 mysql++
分別解壓縮兩個檔案
先執行 mysql++ 專案

...\Desktop\mysql++-3.1.0\vc2008\mysql++.sln

後,在 mysql-noinstall-5.1.51-win32\include 中,找到下列檔案

mysql-noinstall-5.1.51-win32\include
mysql_version.h
mysql.h
mysql_com.h
mysql_time.h
my_list.h
typelib.h
my_alloc.h

mysql-noinstall-5.1.51-win32\lib\opt
libmysql.lib (這個需再專案屬性中指定所在路徑)

這些要跟您所用的MySQL版本配合,解壓縮到 mysql++-3.1.0\lib 之中,然後把 *.cpp 的符號

#include <mysql_version.h>

改成 

#include "mysql_version.h"

這樣就會找現行目錄了



編譯前先改成 Release,並指定專案屬性中 libmysql.lib 所在路徑
編譯完成的元件,會在目錄中

mysql++-3.1.0\vc2008\Release



我已經編譯好了,適用 mysql-5.1.51-win32

文件下載地址 (mysql 5.1.51.rar)

其他 MySQL 版本,請自行編譯。

改天找時間測試 UTF-8 看看。

這有一些使用的參考文章,是 mysql++ 內附的範例,剛剛在 google 找使用範例,
找了半天,才找到一兩個,突然想到去 mysql++ 裏頭找,果然有
節錄如下

/***********************************************************************
 simple1.cpp - Example showing the simplest way to get data from a MySQL
    table with MySQL++.

 Copyright (c) 1998 by Kevin Atkinson, (c) 1999-2001 by MySQL AB, and
 (c) 2004-2009 by Educational Technology Resources, Inc.  Others may
 also hold copyrights on code in this file.  See the CREDITS.txt file
 in the top directory of the distribution for details.

 This file is part of MySQL++.

 MySQL++ is free software; you can redistribute it and/or modify it
 under the terms of the GNU Lesser General Public License as published
 by the Free Software Foundation; either version 2.1 of the License, or
 (at your option) any later version.

 MySQL++ is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 License for more details.

 You should have received a copy of the GNU Lesser General Public
 License along with MySQL++; if not, write to the Free Software
 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
 USA
***********************************************************************/

#include "cmdline.h"
#include "printdata.h"

#include <mysql++.h>

#include <iostream>
#include <iomanip>

using namespace std;

int
main(int argc, char *argv[])
{
    // Get database access parameters from command line
    mysqlpp::examples::CommandLine cmdline(argc, argv);
    if (!cmdline) {
        return 1;
    }

    // Connect to the sample database.
    mysqlpp::Connection conn(false);
    if (conn.connect(mysqlpp::examples::db_name, cmdline.server(),
            cmdline.user(), cmdline.pass())) {
        // Retrieve a subset of the sample stock table set up by resetdb
        // and display it.
        mysqlpp::Query query = conn.query("select item from stock");
        if (mysqlpp::StoreQueryResult res = query.store()) {
            cout << "We have:" << endl;
            mysqlpp::StoreQueryResult::const_iterator it;
            for (it = res.begin(); it != res.end(); ++it) {
                mysqlpp::Row row = *it;
                cout << '\t' << row[0] << endl;
            }
        }
        else {
            cerr << "Failed to get item list: " << query.error() << endl;
            return 1;
        }

        return 0;
    }
    else {
        cerr << "DB connection failed: " << conn.error() << endl;
        return 1;
    }
}

/***********************************************************************
 simple2.cpp - Retrieves the entire contents of the sample stock table
    using a "store" query, and prints it out.

 Copyright (c) 1998 by Kevin Atkinson, (c) 1999-2001 by MySQL AB, and
 (c) 2004-2009 by Educational Technology Resources, Inc.  Others may
 also hold copyrights on code in this file.  See the CREDITS.txt file
 in the top directory of the distribution for details.

 This file is part of MySQL++.

 MySQL++ is free software; you can redistribute it and/or modify it
 under the terms of the GNU Lesser General Public License as published
 by the Free Software Foundation; either version 2.1 of the License, or
 (at your option) any later version.

 MySQL++ is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 License for more details.

 You should have received a copy of the GNU Lesser General Public
 License along with MySQL++; if not, write to the Free Software
 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
 USA
***********************************************************************/

#include "cmdline.h"
#include "printdata.h"

#include <mysql++.h>

#include <iostream>
#include <iomanip>

using namespace std;

int
main(int argc, char *argv[])
{
    // Get database access parameters from command line
    mysqlpp::examples::CommandLine cmdline(argc, argv);
    if (!cmdline) {
        return 1;
    }

    // Connect to the sample database.
    mysqlpp::Connection conn(false);
    if (conn.connect(mysqlpp::examples::db_name, cmdline.server(),
            cmdline.user(), cmdline.pass())) {
        // Retrieve the sample stock table set up by resetdb
        mysqlpp::Query query = conn.query("select * from stock");
        mysqlpp::StoreQueryResult res = query.store();

        // Display results
        if (res) {
            // Display header
            cout.setf(ios::left);
            cout << setw(31) << "Item" <<
                    setw(10) << "Num" <<
                    setw(10) << "Weight" <<
                    setw(10) << "Price" <<
                    "Date" << endl << endl;

            // Get each row in result set, and print its contents
            for (size_t i = 0; i < res.num_rows(); ++i) {
                cout << setw(30) << res[i]["item"] << ' ' <<
                        setw(9) << res[i]["num"] << ' ' <<
                        setw(9) << res[i]["weight"] << ' ' <<
                        setw(9) << res[i]["price"] << ' ' <<
                        setw(9) << res[i]["sdate"] <<
                        endl;
            }
        }
        else {
            cerr << "Failed to get stock table: " << query.error() << endl;
            return 1;
        }

        return 0;
    }
    else {
        cerr << "DB connection failed: " << conn.error() << endl;
        return 1;
    }
}


/***********************************************************************
 simple3.cpp - Example showing how to use the 'use' method of retrieving
    a table, as opposed to the more common 'store' method illustrated
    by the simple2 example.

 Copyright (c) 2005-2009 by Educational Technology Resources, Inc.
 Others may also hold copyrights on code in this file.  See the CREDITS
 file in the top directory of the distribution for details.

 This file is part of MySQL++.

 MySQL++ is free software; you can redistribute it and/or modify it
 under the terms of the GNU Lesser General Public License as published
 by the Free Software Foundation; either version 2.1 of the License, or
 (at your option) any later version.

 MySQL++ is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 License for more details.

 You should have received a copy of the GNU Lesser General Public
 License along with MySQL++; if not, write to the Free Software
 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
 USA
***********************************************************************/

#include "cmdline.h"
#include "printdata.h"

#include <mysql++.h>

#include <iostream>
#include <iomanip>

using namespace std;

int
main(int argc, char *argv[])
{
    // Get database access parameters from command line
    mysqlpp::examples::CommandLine cmdline(argc, argv);
    if (!cmdline) {
        return 1;
    }

    // Connect to the sample database.
    mysqlpp::Connection conn(false);
    if (conn.connect(mysqlpp::examples::db_name, cmdline.server(),
            cmdline.user(), cmdline.pass())) {
        // Ask for all rows from the sample stock table and display
        // them.  Unlike simple2 example, we retreive each row one at
        // a time instead of storing the entire result set in memory
        // and then iterating over it.
        mysqlpp::Query query = conn.query("select * from stock");
        if (mysqlpp::UseQueryResult res = query.use()) {
            // Display header
            cout.setf(ios::left);
            cout << setw(31) << "Item" <<
                    setw(10) << "Num" <<
                    setw(10) << "Weight" <<
                    setw(10) << "Price" <<
                    "Date" << endl << endl;

            // Get each row in result set, and print its contents
            while (mysqlpp::Row row = res.fetch_row()) {
                cout << setw(30) << row["item"] << ' ' <<
                        setw(9) << row["num"] << ' ' <<
                        setw(9) << row["weight"] << ' ' <<
                        setw(9) << row["price"] << ' ' <<
                        setw(9) << row["sdate"] <<
                        endl;
            }

            // Check for error: can't distinguish "end of results" and
            // error cases in return from fetch_row() otherwise.
            if (conn.errnum()) {
                cerr << "Error received in fetching a row: " <<
                        conn.error() << endl;
                return 1;
            }
            return 0;
        }
        else {
            cerr << "Failed to get stock item: " << query.error() << endl;
            return 1;
        }
    }
    else {
        cerr << "DB connection failed: " << conn.error() << endl;
        return 1;
    }
}