赞
踩
做项目时遇到一个问题,使用sqlite数据库时,如果输入接口参数包含中文字符,数据插入正常,但是使用sqlite打开查看时是乱码,虽然读取出来显示是正常的中文,但是隐患还是挺大的。所以研究下怎么让输入的中文字符在sqlite数据库中正常显示。
中文乱码图:
在VS中通过sqlite3.dll接口对sqlite数据库进行操作,包括打开数据库,插入,查询数据库等,如果操作接口输入参数包含中文字符,会导致操作异常。例如调用sqlite3_open打开数据库文件,如果文件路径出现中文,就会导致打开失败。sqlite3_exec执行sql语句,如果包含中文对应字符就会变成乱码。
这是由于sqlite数据库使用的是UTF-8编码方式,而传入的字符串是ASCII编码或Unicode编码,导致字符串格式错误。
在调用sqlite接口之前,先将字符串转换成UTF-8编码。
1#: ASCII编码
ASCII (美国信息交换标准代码,American Standard Code for Information Interchange)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是最通用的信息交换标准。
2#: ANSI
ANSI是一种字符代码,为使计算机支持更多语言,通常使用 0x00~0x7f 范围的1 个字节来表示 1 个英文字符。超出此范围的使用0x80~0xFFFF来编码,即扩展的ASCII编码。
不同的国家和地区制定了不同的标准,由此产生了 GB2312、GBK、GB18030、Big5、Shift_JIS 等各自的编码标准。
这些使用多个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码。
在简体中文Windows操作系统中,ANSI 编码代表 GBK 编码;在繁体中文Windows操作系统中,ANSI编码代表Big5;在日文Windows操作系统中,ANSI 编码代表 Shift_JIS 编码。
不同 ANSI 编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。ANSI编码表示英文字符时用一个字节,表示中文用两个或四个字节。
为了统一所有文字的编码,Unicode应运而生。Unicode把所有语言都统一到一套编码里,这样就不会再有乱码问题了。
3#: Unicode
Unicode通常用两个字节表示一个字符(UTF-16),原有的英文编码从单字节变成双字节,只需要把高字节全部填为0就可以。
Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。1990年开始研发,1994年正式公布。
因为Unicode有多种编码格式,编码方式不同的文本在硬件看来都是一堆二进制码。所以需要BOM标记来区分。
BOM(Byte Order Mark),字节顺序标记。出现在文本文件头部,Unicode编码标准中用于标识文件是采用哪种格式的编码。
4#: UTF-8
UTF-8是针对Unicode的一种可变长度字符编码。它可以用来表示Unicode标准中的任何字符,而且其编码中的第一个字节仍与ASCII相容,使得原来处理ASCII字符的软件无须或只进行少部份修改后,便可继续使用。因此,它逐渐成为电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。
UTF-8编码规则:
如果只有一个字节则其最高二进制位为0;
如果是多字节,其第一个字节从最高位开始,连续的二进制位值为1的个数决定了其编码的字节数,其余各字节均以10开头。
UTF-8转换表:
举例说明,现在有一个ANSI编码格式的文档
现在将文本以ASCII码的方式输出,代码如下:
#include <stdio.h> int main() { FILE* fp = NULL; char filename[] = "rdxb-ANSI.txt"; char ch; if (fopen_s(&fp, filename, "r") != 0) return -1; while (feof(fp) == 0) { ch = fgetc(fp); //putchar(ch); printf("%hhX ", ch);//以一个字节的16进制整型输出ch if (ch == '\n') putchar('\n');//为控制台输出整齐,当ch为'\n'时控制台换行 } fclose(fp); return 0; }
得到文本结果:
总共4行文本在控制台输出4行
最后一行最后的 FF 换算为二进制为 1111 1111 是文本结束符(EOF)。
可以对照ASCII码表看出,
0000 1010 (0x0A)为换行符’\n’。
0011 0000 (0x30)开始是数字1~9。
0100 0001 (0x41)开始是大写字母A~Z。
0110 0001 (0x61)开始是小写字母a~z。
前三行是纯粹的ASCII码。
最后一行除最后一个换行符外,四个汉字加上全角的逗号(,)句号(。)共6个字符,占12字节。
实际上这就是GBK编码。与ASCII码兼容,每个汉字占两字节。
GBK:
你:C4 E3
好:BA C3
世:CA C0
界:BD E7
继续,UTF-8编码格式文件:
将代码中的文件名"rdxb-ANSI.txt"替换成"rdxb-UTF8.txt",得到结果:
我们发现,UTF-8格式编码的txt文件除汉字外都是ASCII码
汉字部分,每三个字节有一个E开头的字节。
例如 E4 BD A0 转换成二进制
十六进制:E4 BD A0
二进制:1110 0100 1011 1101 1010 0000
符合UTF8规范,第一个字节开头1110,代表这是一个三字节字符。剩下两个是10开头,表示这不是第一个字节。
UTF:
你:E4 BD A0
好:E5 A5 BD
世:E4 B8 96
界:E7 95 8C
最后,UTF-8和Unicode的关系
以中文举例:
“你”
Unicode: 4F 60
0100 1111 0110 0000
UTF-8: E4 BD A0
1110 0100 1011 1101 1010 0000
把Unicode和UTF-8固定格式以外部分对齐可以轻易发现规律。
还记得我们前面介绍的UTF-8规则吗,
3字节以1110开头,其余字节10开头。
所以Unicode转换为UTF-8只需要在每个字节中加入对应的开头的1110和10即可。
当然,不用我们自己去写转换啦~ windows给我们提供了对应函数进行转换。
需要包含头文件:#include<window.h>
1#: MultiByteToWideChar函数
函数功能:
该函数映射一个字符串到一个宽字符(unicode)的字符串。由该函数映射的字符串没必要是多字节字符组。
函数原型:
int MultiByteToWideChar(
UINT CodePage,//指定执行转换的字符集
DWORD dwFlags,//一组位标记
LPCCH lpMultiByteStr,//指向将被转换字符串的字符。
int cchMultiByte,//上个参数:字符串的长度。
LPWSTR lpWideCharStr,//指向接收被转换字符串的缓冲区。
int cchWideChar//缓冲区的宽字符个数。
);
CodePage
参数类型:UINT (无符号整型,unsigned int)
指定执行转换的多字节字符所使用的字符集
这个参数可以为系统已安装或有效的任何字符集所给定的值。你也可以指定其为下面的任意一值:
dwFlags
参数类型:DWORD (双字,4字节,无符号长整形,unsigned long)
一组位标记用以指出是否未转换成预作或宽字符(若组合形式存在),是否使用象形文字替代控制字符,以及如何处理无效字符。你可以指定下面是标记常量的组合,含义如下:
注意:
对于下列代码页,dwFlags必须为0,否则函数返回错误码ERROR_INVALID_FLAGS。
lpWideCharStr
参数类型:LPCCH (指针类型,const char*)
指向将被转换字符串的字符。
cchMultiByte
参数类型:int
指定由参数lpMultiByteStr指向的字符串中字节的个数。
如果lpMultiByteStr指定的字符串以空字符终止。可以设置为-1
如果字符串不是以空字符中止,设置为-1可能失败,可能成功。
此参数设置为0函数将失败。
lpWideCharStr
参数类型:LPWSTR (指针类型,wchar_t*)
指向接收被转换字符串的坐标。
cchWideChar
参数类型:int
指定由参数lpWideCharStr指向的缓冲区的宽字符个数。
若此值为零,函数返回缓冲区所必需的宽字符数,在这种情况下,lpWideCharStr中的缓冲区不被使用。
返回值
如果函数运行成功,并且cchWideChar不为零,返回值是由lpWideCharStr指向的缓冲区中写入的宽字符数。
如果函数运行成功,并且cchMultiByte为零,返回值是接收到待转换字符串的缓冲区所需求的宽字符数大小。
如果函数运行失败,返回值为零。
若想获得更多错误信息,请调用GetLastError函数。它可以返回下面所列错误代码:
ERROR_INSUFFICIENT_BUFFER
ERROR_INVALID_FLAGS
ERROR_INVALID_PARAMETER
ERROR_NO_UNICODE_TRANSLATION。
注意
指针lpMultiByteStr和lpWideCharStr必须不一样。如果一样,函数将失败,GetLastError将返回ERROR_INVALID_PARAMETER的值。
如果MB_ERR_INVALID_CHARS被设置并且在资源字符串中遇到无效的字符时,函数将失败。
如果MB_ERR_INVALID_CHARS不被设置,或是DBCS串中发现了头字节而没有有效的尾字节,无效字符将转换为缺省字符,但不是资源字符串中的缺省字符。
当无效字符被发现,且MB_ERR_INVALID_CHARS值被设置,函数返回零,GetLastErro显示ERROR_NO_UNICODE_TRANSLATION的出错
Windows CE:不支持参数CodePage中的CP_UTF7和CP_UTF8的值,以及参数dwFlags中的WC_NO_BEST_FIT_CHARS值。
2#: WideCharToMultiByte函数
函数功能:
该函数可以映射一个unicode字符串到一个多字节字符串,执行转换的代码页,接收转换字符串,允许额外的控制等操作。
函数原型:
int WideCharToMultiByte(
UINT CodePage,//指定执行转换的字符集
DWORD dwFlags,//一组位标记
LPCWCH lpWideCharStr,//指向将被转换宽字符串的字符。
INT cchWideChar,//上个参数:宽字符串的长度。
LPSTR lpMultiByteStr,//指向接收被转换字符串的缓冲区。
INT cchMultiByte,//缓冲区的字符个数。
LPCCH lpDefaultChar,// 遇到一个不能转换的宽字符,函数便会使用此参数指向的字符
LPBOOL pfUsedDefaultChar //至少有一个字符不能转换为其多字节形式,函数就会把这个变量设为TRUE
);
WideCharToMultiByte函数的参数比MultiByteToWideChar函数多两个,两个函数的前五个参数几乎相同。
lpWideCharStr
参数类型:LPCWCH (指针类型,const wchar_t*)
指向将被转换的unicode字符串。
lpMultiByteStr
参数类型:LPSTR (指针类型,char*)
指向接收被转换字符串的坐标。
lpDefaultChar和pfUsedDefaultChar
lpDefaultChar 参数类型:LPCCH (指针类型,const char*)
pfUsedDefaultChar 参数类型:LPBOOL (指针类型,int*)
只有当WideCharToMultiByte函数遇到一个宽字节字符,而该字符在uCodePage参数标识的代码页中并没有它的表示法时,WideCharToMultiByte函数才使用这两个参数。(通常都取值为NULL)
如果宽字节字符不能被转换,该函数便使用lpDefaultChar参数指向的字符。如果该参数是NULL(这是大多数情况下的参数值),那么该函数使用系统的默认字符。该默认字符通常是个问号。这对于文件名来说是危险的,因为问号是个通配符。
pfUsedDefaultChar参数指向一个布尔变量,如果Unicode字符串中至少有一个字符不能转换成等价多字节字符,那么函数就将该变量置为TRUE。如果所有字符均被成功地转换,那么该函数就将该变量置为FALSE。当函数返回以便检查宽字节字符串是否被成功地转换后,可以测试该变量。
返回值
下面我自己封装了几个函数以供字符编码转换使用:
最终结果:
是不是看着舒服多了~
参考文档:
1.https://blog.csdn.net/qq_37415550/article/details/104894225
2.https://www.jb51.net/article/35778.htm
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。