彻底弄懂UTF-8、Unicode、宽字符、locale( 二 )
locale环境变量有何作用
以LC_TIME为例,该变量会影响strftime()等函数 。size_t strftime(char *str, size_t maxsize, const char *format, const struct tm *timeptr)
strftime根据format中定义的格式化规则,格式化结构timeptr表示的时间,并把它存储在str中 。
#include <locale.h>#include <stdio.h>#include <time.h>int main () { time_t currtime; struct tm *timer; char buffer[80]; time( &currtime ); timer = localtime( &currtime ); printf("Locale is: %s", setlocale(LC_TIME, "en_US.iso88591")); strftime(buffer,80,"%c", timer ); printf("Date is: %s", buffer); printf("Locale is: %s", setlocale(LC_TIME, "zh_CN.UTF-8")); strftime(buffer,80,"%c", timer ); printf("Date is: %s", buffer); printf("Locale is: %s", setlocale(LC_TIME, "")); strftime(buffer,80,"%c", timer ); printf("Date is: %s", buffer); return(0);}编译后运行结果如下:
Locale is: en_US.iso88591Date is: Sun 07 Jul 2019 04:08:39 PM CSTLocale is: zh_CN.UTF-8Date is: 2019年07月07日 星期日 16时08分39秒Locale is: zh_CN.UTF-8Date is: 2019年07月07日 星期日 16时08分39秒可以看到对LC_TIME设置不同的值后,调用strftime()会产生不同的结果 。
char* setlocale (int category, const char* locale);可以用来对当前程序进行地域设置 。
category:用于指定设置影响的范围,LC_CTYPE影响字符分类和字符转换,LC_TIME影响日期和时间的格式,LC_ALL影响所有内容 。
locale:用于指定变量的值,上例中分别使用了"en_US.iso88591","zh_CN.UTF-8"和空字符串"",""表示使用当前操作系统默认的区域设置 。
参考资料:
setlocale()
为什么需要宽字符类型
“你好”对应的Unicode分别为"U+4f60"和"U+597d”,对应的UTF-8编码分别为“0xe4 0xbd 0xa0”和“0xe5 0xa5 0xbd”
多字节字符串在编译后的可执行文件以UTF-8编码保存
#include <stdio.h>#include <string.h>int main(void) { char s[] = "你好"; size_t len = strlen(s); printf("len = %d", (int)len); printf("%s", s); return 0;}编译后执行,输出如下:
len = 6你好od编译后的可执行文件,可以发现"你好"以UFT-8编码保存,也就是“0xe4 0xbd 0xa0”和“0xe5 0xa5 0xbd”6个字节 。
strlen()函数只管结尾的0字节而不管字符串里存的是什么,所以len是6,也就是“你好”的UFT-8编码的字节数 。
printf("%s ", s);相当于将“0xe4 0xbd 0xa0”和“0xe5 0xa5 0xbd”6个字节write到当前终端的设备文件,如果当前终端的驱动程序能识别UTF-8编码就能打印汉字,如果当前字符终端的驱动程序不能识别UTF-8就打印不出汉字 。
宽字符串在编译后可执行文件中以Unicode保存
#include <wchar.h>#include <stdio.h>#include <locale.h>int main(void) { setlocale(LC_ALL, "zh_CN.UTF-8"); //设置locale wchar_t s[] = L"你好"; size_t len = wcslen(s); printf("len = %d", (int)len); printf("%ls", s); return 0;}编译后执行,输出如下:
len = 2你好对编译后的可执行文件执行od命令,可以找到如下这些字节:
193 0003020 001002` O} Y194 00020001 00004f60 0000597d 0000000a00004f60正是“你”对应的Unicode,0000597d是“好”对应的Unicode 。所以对于宽字符串是按Unicode保存在可执行文件中的 。
wchar_t是宽字符类型 。在字符常量或者字符串前加L就表示宽字符常量或者宽字符串 。所以len是2 。
wcslen()和strlen()不同,不是见到0字节就结束而是要遇到UCS编码为0的字符才结束 。
目前宽字符在内存中以Unicode进行保存,但是要write到终端仍然需要以多字节编码输出,这样终端驱动程序才能识别,所以printf在内部把宽字符串转换成多字节字符串,然后write出去 。这个转换过程受locale影响,setlocale(LC_ALL, "zh_CN.UTF-8");设置当前进程的LC_ALL为zh_CN.UTF-8,所以printf将Unicode转成多字节的UTF-8编码,然后write到终端设备 。如果将setlocale(LC_ALL, "zh_CN.UTF-8");改为setlocale(LC_ALL, en_US.iso88591):打印结果中将不会输出"你好" 。
一般来说程序在内存计算时通常以宽字符编码,存盘或者网络发送则用多字节编码 。
多字节字符串和宽字符串相互转换
c语言中提供了多字节字符串和宽字符串相互转换的函数 。
#include <stdlib.h>size_t mbstowcs(wchar_t *dest, const char *src, size_t n);size_t wcstombs(char *dest, const wchar_t *src, size_t n);
推荐阅读
- Java源码中>>,>>>的区别是啥?我给你彻底讲清
- 一篇文章彻底搞懂base64编码原理
- 吕秀才喻恩泰彻底翻车 喻恩泰八卦
- 一分钟弄懂什么是分布式和微服务
- 仅需 5 分钟,彻底理解 cookie、session、token
- 汽车上常见的几种悬挂,你分清了吗?三分钟教你弄懂
- 这样瘦肚子最快每天10分钟彻底摆脱小肚腩,做个人人羡慕的美腹婆
- 彻底理解cookie,session,token
- 家里有蟑螂怎么能彻底消灭 卧室有蟑螂怎么办
- 记住:永远不要在 MySQL 中使用 UTF-8