一、字符集总结

其实大多数的知识在这篇文章里已经讲得非常清楚了。这里只是讲一下自己的感悟。

1. UTF-8虽然是以UTF(unicode transfermation format)开头的,但是他并不是真正意义上的Unicode。他是在UCS上的再编码。而且,这是一个变长的编码方式。

2. 根据这篇文章的说法,在ISO制定UCS(Universal Character Set)的同时,另一个由厂商联合组织也在着手制定这样的编码,称为Unicode,后来两家联手制定统一的编码,但各自发布各自的标准文档,所以UCS编码和Unicode码是相同的。

3. 当我们在Linux下采用“locale –a”命令查看所有可用字符集时,出现的描述其实就是都是属于Native ANSI,因为他们都是标准ANSI的超集。

4. 当我们在Windows下将文件以UTF-8的方式进行存储时,Windows会将一个叫做UTF-8 Signature的三个字节写在文件的最开头,这样就可以表示,这个文件的编码方式就是UTF-8,而不是其他字符集。由于UTF-8是变长编码,所以这样就便于我们去区分如下的一种情况,即当文件中只有标准ANSI字符集中规定的字符时,我们还认为这是一个UTF-8编码,这样以后如果再出现CJK(Chinese,Japan,Korea)里面的汉字时,就可以顺利解码了。同样,也是因为它,当我们在windows下另存为UTF-8格式时,直接FTP到Linux下进行编译,即使将gcc的-finput-charset=utf-8设定,也会出现编译错误(因为有不认识的字符了,具体表现如此)。

5. 通常来说,我们所存储的文件(这里主要是指配置文件,代码文件)需要使用UTF-8编码。一方面是因为gcc的默认finput-charset选项是utf-8,另外一方面utf-8编码支持所有的字符(中文,英文)。 *.Java文件就是这样存储的。

6. 注意到一个专有名词叫做“C Locale”,他是一般C语言程序进入main之后的默认字符集(可以通过setlocale(LC_ALL, NULL)进行查看)。这个字符集就是ANSI字符集,因为所有的C程序都支持这个字符集,且有了这个字符集就可以运行程序了,所以就给了一个名字,叫做“C Locale”。

 

二、关于gcc对字符集的支持

这里所述的内容,主要是参考了这篇文章和man gcc。这里做一个总结。在整个编译过程中有如下几个关键的字符集。

  • 代码文件的字符集A
  • gcc内部处理的字符集B(UTF-8)
  • gcc输出的二进制文件的字符集C(默认是UTF-8,可以使用-fexec-charset选项进行指定)

具体来说,当我们运用gcc命令将代码文件进行编译,它就直接认为代码文件的字符集是finput-charset选项中所指定的字符集(默认是utf-8),也就是说他也许根本就不知道你的字符集是字符集A。他根据finput-charset选项中的字符集向自己的内部所使用的字符集B进行转码。经过编译之后,就再次将二进制输出从内部字符集B转为字符集C。图示为,

 

 

学过编译原理的同学应该会知道,在二进制文件中最多的应该是指令,那么什么是需要使用字符集C来表示的?当然是我们硬编码的字符串。直接嫁接这里的例子,如果有如下代码,

#include <stdio.h>

int main(void)
{
printf("你好\n");
return 0;
}

 

且我们假设,源文件是UTF-8编码,我们也使用默认的gcc –fexec-charset。那么这个“你好”就需要使用字符集C进行编码,通过命令查看

$ od -tc nihao.c
0000000 # i n c l u d e < s t d i o .
0000020 h > \n \n i n t m a i n ( v o i
0000040 d ) \n { \n \t p r i n t f ( " 344 275
0000060 240 345 245 275 \ n " ) ; \n \t r e t u r
0000100 n 0 ; \n } \n
0000107

 

其中八进制的344 375 240(十六进制e4 bd a0)就是“你”的UTF-8编码,八进制的345 245 275(十六进制e5 a5 bd)就是“好”。

由此可以得到的结论是,尽量使用UTF-8来编写我们的源程序,这样就可以不用显式设置-finput-charset和-fexec-charset了,便于移植。

三、程序运行与字符集

1. printf(“%s”)到底做了什么?参考文档。

当我们在程序里面使用了printf(“%s”),他其实就是把字符串首地址到第一个“\0”处的字节write到当前终端的设备文件。如果当前终端的驱动程序能够识别UTF-8编码就能打印出汉字,如果当前终端的驱动程序不能识别UTF-8编码(比如一般的字符终端)就打印不出汉字。也就是说,像这种程序,识别汉字的工作既不是由C编译器做的也不是由libc做的,C编译器原封不动地把源文件中的UTF-8编码(假设这个源文件就是用UTF-8编码且没有另外指定-finput-charset)复制到目标文件中,libc只是当作以0结尾的字符串原封不动地write给内核,识别汉字的工作是由终端的驱动程序做的

我一开始以为终端会帮我们做转码,因为我们设置了“LANG”这个环境变量(他会转而设置LC_ALL),所以做了如下实验。

#include <iostream>
#include <locale.h>
using namespace std; int main(int argc, char **argv)
{
string s = "你好";
cout << s << endl; char buff[10] = "你好";
for (int i = 0; i < 10; i++)
{
printf("%2X ", buff[i]);
}
cout << endl; return 0;
}

现在我保证输出的二进制是UTF-8编码的。实验如下,

 

可以看到,输出和当前终端的字符集无关。因为从程序里面输出的字节流就是“你”和“好”的UTF-8编码,所以还是被设备驱动程序给正确解析了。

 

2. setlocale()到底用来做什么?

在做上面的实验的时候,其实我还对“终端字符集对输入和输出会进行转码”而有所期待。所以在代码里,我还特意尝试了个中setlocale(LC_ALL, “xxxx”)的调用,尝试看结果。但是结果总是和上面所说的一样,总是没有出现乱码输出。

经过一段分析和朋友的点拨,终于将setlocale与wcstombs(宽字符串转换到多字节串)、mbstowcs(多字节串转换到宽字符串)结合起来了。

要讲清楚这个事情,必须先从宽字符串(wide-character string)与多字节串(multibyte string)讲起。为什么要引入宽字符串,这里有比较好的讲法。摘抄如下。

“最根本的原因是,ANSI下的字符串都是以’\0’来标识字符串尾的(Unicod以“\0\0”束),许多字符串函数的正确操作均是以此为基础进行。而我们知道,在宽字符的情况下,一个字符在内存中要占据一个字的空间,这就会使操作ANSI字符的字符串函数无法正确操作。以”Hello”字符串为例,在宽字符下,它的五个字符是:

0x0048 0x0065 0x006c 0x006c 0x006f 
在内存中,实际的排列是:

48 00 65 00 6c 00 6c 00 6f 00  (这里应该是源代码是UTF-8编码的,所以高位是00——Aicro注)

于是,ANSI字符串函数,如strlen,在碰到第一个48后的00时,就会认为字符串到尾了,用strlen对宽字符串求长度的结果就永远会是1! ”

 

其实这里所说的宽字符串,就是我们通常所说的Unicode串,也就是UCS-2。

mbstowcs就是将,反之是wcstombs。

 

  • mbstowcs的具体工作流程

我们先通过在线转换工具了解到“你好”这两个汉字的宽字符(Unicode)表示是\u4f60\u597d。

 

下面的程序使用gcc编译的,使用各种默认选项。

#include <locale.h>
#include <iostream>
#include <stdlib.h>
#include "string.h" int main()
{
char* source = "你好"; setlocale(LC_ALL, "zh_CN.utf8"); // 获取长度
size_t wcs_size = mbstowcs(NULL, source, 0); // 申请内存并初始化
wchar_t* dest = new wchar_t[wcs_size + 1];
wmemset(dest, L'\0', wcs_size + 1); // 多字节串转换到宽字符串,注意,第三个参数是byte数
mbstowcs(dest, source, strlen(source) * sizeof(char)); // 验证一下
for (int i = 0; i < wcs_size; i++)
{
printf("%2X ", dest[i]);
} printf("\n"); return 0;
}

 

输出结果就是4F60 597D。可见,mbstowcs的作用过程是

 

重点是,mbstowcs把LC_CTYPE认作是source(多字符字串)的编码。

 

  • wcstombs的具体工作流程

我们先通过在线转换工具了解到“你好”这两个汉字的gbk编码是C4 E3 BA C3 。

实验程序

#include <locale.h>
#include <iostream>
#include <stdlib.h>
#include "string.h" int main()
{
char* source = "你好"; setlocale(LC_ALL, "zh_CN.utf8"); // 获取长度
size_t wcs_size = mbstowcs(NULL, source, 0); // 申请内存并初始化
wchar_t* dest = new wchar_t[wcs_size + 1];
wmemset(dest, L'\0', wcs_size + 1); // 多字节串转换到宽字符串,注意,第三个参数是byte数
mbstowcs(dest, source, strlen(source) * sizeof(char)); // 转回gbk编码
setlocale(LC_ALL, "zh_CN.gbk"); // 获取长度
size_t mbs_size = wcstombs(NULL, dest, 0); // 申请内存并初始化
char* buf_mbs = new char [mbs_size + 1];
memset(buf_mbs, '\0', mbs_size + 1); // 宽字符串转换到多字节串,注意,第三个参数是byte数
wcstombs(buf_mbs, dest, wcs_size * sizeof(wchar_t)); // 验证一下
for (int i = 0; i < mbs_size; i++)
{
printf("%2X ", buf_mbs[i]);
} printf("\n"); return 0;
}

 

输出结果与预期一样。这说明了wcstombs的流程。

重点是,wcstombs把LC_CTYPE认作是destination(多字符字串)的编码。

 

总结:setlocale需要与wcstombs域mbstowcs联合使用。根据这篇文章的说法,程序在做内部计算时通常以宽字符编码,如果要存盘或者输出给别的程序,或者通过网络发给别的程序,则采用多字节编码。这就让我想到了原来读第四版《windows via c/c++》,好像第二章就讲到过这个问题,一直没有实践过,所以就忘记了。

字符集与Mysql字符集处理(一)的更多相关文章

  1. 字符集与Mysql字符集处理(二)

    接着上篇文章继续讲字符集的故事.这一篇文章主要讲MYSQL的各个字符集设置,关于基础理论部分,参考于这里.   1. MYSQL的系统变量 – character_set_server:默认的内部操作 ...

  2. 9.Mysql字符集

    9.字符集9.1 字符集概述 字符集就是一套文字符号及其编码.比较规则的集合. ASCII(American Standard Code for Information Interchange)字符集 ...

  3. 如何修改MySQL字符集

    首先,MySQL的字符集问题主要是两个概念,一个是Character Sets,一个是Collations,前者是字符内容及编码,后者是对前者进行比较操作的一些规则.这两个参数集可以在数据库实例.单个 ...

  4. Mysql字符集设置

    转 基本概念 • 字符(Character)是指人类语言中最小的表义符号.例如’A'.’B'等:• 给定一系列字符,对每个字符赋予一个数值,用数值来代表对应的字符,这一数值就是字符的编码(Encodi ...

  5. MySQL字符集

    字符集的选择 1.如果数据库只需要支持中文,数据量很大,性能要求也很高,应该选择双字节定长编码的中文字符集(如GBK).因为相对于UTF-8而言,GBK"较小",每个汉字只占2个字 ...

  6. ubuntu系统修改mysql字符集

    1.进入mysql,查看默认字符集: mysql>show variables like 'char%'; 2.退出mysql; 3.输入命令:sudo gedit /etc/mysql/con ...

  7. mysql5.5字符集设置的一点变化(对于中文乱码问题,需要设置mysql字符集)

    工作中因为字符集问题没少头疼,还犯过一次错误,还好拯救及时,没有发生重大事故,唉,弄清楚点还是非常有必要的: 例如我的工作环境为CTR+redhat5+mysql5.5 在导入sql语句的时候必须要注 ...

  8. Mysql字符集知识总结

    字符集&字符编码方式 字符集(Character set)是多个字符的集合,字符集种类较多,每个字符集包含的字符个数不同,这里的字符可以是英文字符,汉字字符,或者其他国家语言字符. 常见字符集 ...

  9. MySQL字符集的修改和查看

    1.关于MySQL字符集 MySQL的字符集支持(Character Set Support)有两个方面: 字符集(Character set)和排序方式(Collation). MySQL对于字符集 ...

随机推荐

  1. windows必备,命令行工具cmder

    下载cmder并安装! 64位可下,亲测: http://www.wmzhe.com/soft-31133.html 创建文件夹并进入文件夹命令: mkdir web-server && ...

  2. JSP内置对象总结

    前几天学习了javaee中的jsp(Java Server Pages),即java服务器页面,其实就是在html里面写java代码. 一.概述前言 在总结九大对象之前,有必要先搞清楚几个概念:请求, ...

  3. js系列(10)js的运用(二)

        本节继续介绍在html页面中js的运用.   (1)数码时钟:(http://files.cnblogs.com/files/MenAngel/text05.zip) <!DOCTYPE ...

  4. vertex compression所遇到的问题

    对于数据压缩,其实就是把浮点的32位精度,改用16位定点数来表达. 例如0.0 = 0,1.0 = 32767,-1.0 = -32767 这是一种有损压缩,会丢失一些精度,一般情况下是可以接受的. ...

  5. easy datagrid 按钮控制

    onBeforeLoad : function() {// 这里是紧接着你的修改按钮的 // 注意ID为你初始化工具栏按钮对应的ID var adminid=<%=Admin_Id%>+' ...

  6. 集群: 如何在spring 任务中 获得集群中的一个web 容器的端口号?

    系统是两台机器, 跑四个 web 容器, 每台机器两个容器 . nginx+memcached+quartz集群,web容器为 tomcat . web 应用中 用到spring 跑多个任务,任务只能 ...

  7. Lotus Domino中使用Xpage技术打造通讯录

    我们来完成一个类似通讯录的功能,我们可以添加人员,查看和修改,删除人员,我们假设我们的通讯录中只记录人员的名字和年龄字段.先看看完成后的效果吧 点击New可以到新增人员页面,如下图: 编辑按钮后进入编 ...

  8. MinStack

    Design a stack that supports push, pop, top, and retrieving the minimum element in constant time. pu ...

  9. IOS越狱开发之——进程通讯

    Mac OS下的IPC方式种类很多,大约有下面几种. 1. Mach API 2. CFMessagePort 3. Distributed Objects (DO) 4. Apple events  ...

  10. mybatis 3.2.3 maven dependency pom.xml 配置

    <dependency>      <groupId>org.mybatis</groupId>      <artifactId>mybatis< ...