中文乱码问题

导入

做项目时遇到一个问题,使用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)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是最通用的信息交换标准。

  1. 空格 0x20
  2. 数字 0x30
  3. 大写字母 0x41
  4. 小写字母 0x61

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转换表:

  1. 1字节 0xxxxxxx
  2. 2字节 110xxxxx 10xxxxxx
  3. 3字节 1110xxxx 10xxxxxx 10xxxxxx
  4. 4字节 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
  5. 5字节 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
  6. 6字节 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

举例说明,现在有一个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规则吗,

  1. 3字节 1110xxxx 10xxxxxx 10xxxxxx

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)

一组位标记用以指出是否未转换成预作或宽字符(若组合形式存在),是否使用象形文字替代控制字符,以及如何处理无效字符。你可以指定下面是标记常量的组合,含义如下:
在这里插入图片描述
注意:

  1. 组合字符由一个基础字符和一个非空字符构成,每一个都有不同的字符值。
  2. 每个预作字符都有单一的字符值给基础/非空字符的组成。在字符è中,e就是基础字符,而重音符标记就是非空字符。
  3. 函数的缺省动作是转换成预作的形式。如果预作的形式不存在,函数将尝试转换成组合形式。
  4. 标记MB_PRECOMPOSED和MB_COMPOSITE是互斥的,而标记MB_USEGLYPHCHARS和MB_ERR_INVALID_CHARS则不管其它标记如何都可以设置。

对于下列代码页,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. 如果函数运行成功,并且cchMultiByte不为零,返回值是由 lpMultiByteStr指向的缓冲区中写入的字节数。
  2. 如果函数运行成功,并且cchMultiByte为零,返回值是接收到待转换字符串的缓冲区所必需的字节数。(此种情况用来获取转换所需Char的个数)
  3. 如果函数运行失败,返回值为零。
  4. 若想获得更多错误信息,请调用GetLastError函数。它可以返回下面所列错误代码:
    ERROR_INSUFFICIENT_BJFFER ERROR_INVALID_FLAGS
    ERROR_INVALID_PARAMETER
    ERROR_NO_UNICODE_TRANSLATION。

下面我自己封装了几个函数以供字符编码转换使用:

《中文乱码之编码格式转换》

解决问题

最终结果:
在这里插入图片描述
是不是看着舒服多了~

参考文档:
1.https://blog.csdn.net/qq_37415550/article/details/104894225
2.https://www.jb51.net/article/35778.htm

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

只会C++啊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值