彻底解密C++宽字符(二)
彻底解密C++宽字符(二)
转:http://club.topsage.com/thread-2227977-1-1.html
4、利用codecvt和use_facet转换
locale和facet
C++ 的locale框架比C更完备。C++除了一个笼统本地策略集locale,还可以为locale指定具体的策略facet,甚至可以用自己定义的 facet去改造一个现有的locale产生一个新的locale。如果有一个facet类NewFacet需要添加到某个old_loc中形成新 new_loc,需要另外一个构造函数,通常的做法是:
std::locale new_loc(old_loc, new NewFacet);
标准库里的标准facet都具有自己特有的功能,访问一个locale对象中特定的facet需要使用模板函数use_facet:
template <class Facet> const Facet& use_factet(const locale&);
换一种说法,use_facet把一个facet类实例化成了对象,由此就可以使用这个facet对象的成员函数。
codecvt
codecvt就是一个标准facet。在C++的设计框架里,这是一个通用的代码转换模板——也就是说,并不是仅仅为宽窄转换制定的。
templat <class I, class E, class State> class std::codecvt: public locale, public codecvt_base{...};
I表示内部编码,E表示外部编码,State是不同转换方式的标识,如果定义如下类型:
typedef std::codecvt<wchar_t, char, mbstate_t> CodecvtFacet;
那么CodecvtFacet就是一个标准的宽窄转换facet,其中mbstate_t是标准宽窄转换的State。
内部编码和外部编码
我们考虑第1节中提到的C++编译器读取源文件时候的情形,当读到L"中文abc"的时候,外部编码,也就是源文件的编码,是GB2312或者UTF-8的 char,而编译器必须将其翻译为UCS-2BE或者UTF-32BE的wchar_t,这也就是程序的内部编码。如果不是宽字符串,内外编码都是 char,也就不需要转换了。类似的,当C++读写文件的时候 ,就会可能需要到内外编码转换。事实上,codecvt就正是被文件流缓存basic_filebuf所使用的。理解这一点很重要,原因会在下一小节看到。
CodecvtFacet的in()和out()
因为在CodecvtFacet中,内部编码设置为wchar_t,外部编码设置为char,转换模式是标准宽窄转换mbstate_t,所以,类方法in()就是从char标准转换到wchar_t,out()就是从 wchar_t标准转换到char。这就成了我们正需要的内外转换函数。
result in(State& s, const E* from, const E* from_end, const E*& from_next, I* to, I* to_end, I*& to_next) const;
result out(State& s, const I* from, const I* from_end, const I*& from_next, E* to, E* to_end, E*& to_next) const;
其中,s是非const引用,保存着转换位移状态信息。这里需要重点强调的是,因为转换的实际工作交给了运行时库,也就是说,转换可能不是在程序的主进程中完成的,而转换工作依赖于查询s的值,因此,如果s在转换结束前析构,就可能抛出运行时异常。所以,最安全的办法是,将s设置为全局变量!
const的3个指针分别是待转换字符串的起点,终点,和出现错误时候的停点(的下一个位置);另外3个指针是转换目标字符串的起点,终点以及出现错误时候的停点(的下一个位置)。
代码如下:
头文件
//Filename string_wstring_cppcvt.hpp
#ifndef STRING_WSTRING_CPPCVT_HPP
#define STRING_WSTRING_CPPCVT_HPP
#include <iostream>
#include <string>
const std::wstring s2ws(const std::string& s);
const std::string ws2s(const std::wstring& s);
#endif
实现:
#include "string_wstring_cppcvt.hpp"
mbstate_t in_cvt_state;
mbstate_t out_cvt_state;
const std::wstring s2ws(const std::string& s)
{
std::locale sys_loc("");
const char* src_str = s.c_str();
const size_t BUFFER_SIZE = s.size() + ;
wchar_t* intern_buffer = new wchar_t[BUFFER_SIZE];
wmemset(intern_buffer, , BUFFER_SIZE);
const char* extern_from = src_str;
const char* extern_from_end = extern_from + s.size();
const char* extern_from_next = ;
wchar_t* intern_to = intern_buffer;
wchar_t* intern_to_end = intern_to + BUFFER_SIZE;
wchar_t* intern_to_next = ;
typedef std::codecvt<wchar_t, char, mbstate_t> CodecvtFacet;
CodecvtFacet::result cvt_rst =
std::use_facet<CodecvtFacet>(sys_loc).in(
in_cvt_state,
extern_from, extern_from_end, extern_from_next,
intern_to, intern_to_end, intern_to_next);
if (cvt_rst != CodecvtFacet::ok) {
switch(cvt_rst) {
case CodecvtFacet::partial:
std::cerr << "partial";
break;
case CodecvtFacet::error:
std::cerr << "error";
break;
case CodecvtFacet::noconv:
std::cerr << "noconv";
break;
default:
std::cerr << "unknown";
}
std::cerr << ", please check in_cvt_state."
<< std::endl;
}
std::wstring result = intern_buffer;
delete []intern_buffer;
return result;
}
const std::string ws2s(const std::wstring& ws)
{
std::locale sys_loc("");
const wchar_t* src_wstr = ws.c_str();
const size_t MAX_UNICODE_BYTES = ;
const size_t BUFFER_SIZE =
ws.size() * MAX_UNICODE_BYTES + ;
char* extern_buffer = new char[BUFFER_SIZE];
memset(extern_buffer, , BUFFER_SIZE);
const wchar_t* intern_from = src_wstr;
const wchar_t* intern_from_end = intern_from + ws.size();
const wchar_t* intern_from_next = ;
char* extern_to = extern_buffer;
char* extern_to_end = extern_to + BUFFER_SIZE;
char* extern_to_next = ;
typedef std::codecvt<wchar_t, char, mbstate_t> CodecvtFacet;
CodecvtFacet::result cvt_rst =
std::use_facet<CodecvtFacet>(sys_loc).out(
out_cvt_state,
intern_from, intern_from_end, intern_from_next,
extern_to, extern_to_end, extern_to_next);
if (cvt_rst != CodecvtFacet::ok) {
switch(cvt_rst) {
case CodecvtFacet::partial:
std::cerr << "partial";
break;
case CodecvtFacet::error:
std::cerr << "error";
break;
case CodecvtFacet::noconv:
std::cerr << "noconv";
break;
default:
std::cerr << "unknown";
}
std::cerr << ", please check out_cvt_state."
<< std::endl;
}
std::string result = extern_buffer;
delete []extern_buffer;
return result;
}
最后补充说明一下std::use_facet<CodecvtFacet>(sys_loc).in()和 std::use_facet<CodecvtFacet>(sys_loc).out()。sys_loc是系统的locale,这个 locale中就包含着特定的codecvt facet,我们已经typedef为了CodecvtFacet。用use_facet对CodecvtFacet进行了实例化,所以可以使用这个 facet的方法in()和out()。
5、利用fstream转换
C++的流和本地化策略集
BS在设计C++流的时候希望其具备智能化,并且是可扩展的智能化,也就是说,C++的流可以“读懂”一些内容。比如:
std::cout << << "ok" << std::endl;
这句代码中,std::cout是能判断出123是int而"ok"是const char[3]。利用流的智能,甚至可以做一些基础类型的转换,比如从int到string,string到int:
std::string str("");
std::stringstream sstr(str);
int i;
sstr >> i;
int i = ;
std::stringstream sstr;
sstr << i;
std::string str = sstr.str();
尽管如此,C++并不满足,C++甚至希望流能“明白”时间,货币的表示法。而时间和货币的表示方法在世界范围内是不同的,所以,每一个流都有自己的 locale在影响其行为,C++中叫做激活(imbue,也有翻译成浸染)。而我们知道,每一个locale都有多个facet,这些facet并非总是被use_facet使用的。决定使用哪些facet的,是流的缓存basic_streambuf及其派生类basic_stringbuf和 basic_filebuf。我们要用到的facet是codecvt,这个facet只被basic_filebuf使用——这就是为什么只能用 fstream来实现宽窄转换,而无法使用sstream来实现的原因。
头文件:
//filename string_wstring_fstream.hpp
#ifndef STRING_WSTRING_FSTREAM_HPP
#define STRING_WSTRING_FSTREAM_HPP
#include <string>
const std::wstring s2ws(const std::string& s);
const std::string ws2s(const std::wstring& s);
#endif
实现:
#include <string>
#include <fstream>
#include "string_wstring_fstream.hpp"
const std::wstring s2ws(const std::string& s)
{
std::locale sys_loc("");
std::ofstream ofs("cvt_buf");
ofs << s;
ofs.close();
std::wifstream wifs("cvt_buf");
wifs.imbue(sys_loc);
std::wstring wstr;
wifs >> wstr;
wifs.close();
return wstr;
}
const std::string ws2s(const std::wstring& s)
{
std::locale sys_loc("");
std::wofstream wofs("cvt_buf");
wofs.imbue(sys_loc);
wofs << s;
wofs.close();
std::ifstream ifs("cvt_buf");
std::string str;
ifs >> str;
ifs.close();
return str;
}
在窄到宽的转化中,我们先使用默认的本地化策略集(locale)将s通过窄文件流ofs传入文件,这是char到char的传递,没有任何转换;然后我们打开宽文件流wifs,并用系统的本地化策略集(locale)去激活(imbue)之,流在读回宽串wstr的时候,就是char到wchar_t的转换,并且因为激活了sys_loc,所以实现标准窄到宽的转换。
在宽到窄的转化中,我们先打开的是宽文件流wofs,并且用系统的本地化策略集 sys_loc激活(imbue)之,这时候,因为要写的文件cvt_buf是一个外部编码,所以执行了从wchar_t到char的标准转换。读回来的文件流从char到char,不做任何转换。
6、国际化策略
硬编码的硬伤
我们现在知道,C/C++的宽窄转换是依赖系统的locale的,并且在运行时完成。考虑这样一种情况,我们在简体中文Windows下编译如下语句:
const char* s = "中文abc";
根据我们之前的讨论,编译器将按照Windows Codepage936(GB2312)对这个字符串进行编码。如果我们在程序中运行宽窄转换函数,将s转换为宽字符串ws,如果这个程序运行在简体中文环境下是没问题的,将执行从GB2312到UCS-2BE的转换;但是,如果在其他语言环境下,比如是繁体中文BIG5,程序将根据系统的locale执行从BIG5到UCS-2BE的转换,这显然就出现了错误。
补救
有没有补救这个问题的办法呢?一个解决方案就是执行不依赖locale的宽窄转换。实际上,这就已经不是宽窄转换之间的问题了,而是编码之间转换的问题了。我们可以用GNU的libiconv实现任意编码间的转换,对于以上的具体情况,指明是从GB2312到UCS-2BE就不会出错。(请参考本人前面的章节:win32下的libiconv),但这显然是一个笨拙的策略:我们在简体中文Windows下必须使用GB2312到UCS-2BE版本的宽窄转换函数;到了BIG5环境下,就必须重新写从BIG5到UCS-2BE的宽窄转换函数。
Windows的策略
Windows的策略是淘汰了窄字符串,干脆只用宽字符串。所有的硬编码全部加上特定宏,比如TEXT(),如果程序是所谓Unicode编译,在编译时就翻译为UCS2-BE——Windows自称为Unicode编程,其本质是使用了UCS-2BE的16位宽字符串。
Linux的策略
Linux下根本就不存在这个问题!因为各种语言的Linux都使用UTF-8的编码,所以,无论系统locale如何变化,窄到宽转换的规则一直是UTF-8到UTF32-BE 。
跨平台策略
因为在16位的范围内,UTF32-BE的前16位为0,后16位与UCS2-BE是一样的,所以,即使wchar_t的sizeof()不一样,在一般情况下,跨平台使用宽字符(串)也应该是兼容的。但是依然存在潜在的问题,就是那些4字节的UTF32编码。
gettext策略
以上都是将ASCII及以外的编码硬编码在程序中的办法。GNU的gettext提供了另外一种选择:在程序中只硬编码ASCII,多语言支持由gettext函数库在运行时加载。(对gettext的介绍请参考本人前面的章节:Win32下的GetText)。 gettext的多语言翻译文件不在程序中,而是单独的提出来放在特定的位置。gettext明确的知道这些翻译文件的编码,所以可以准确的告诉给系统翻译的正确信息,而系统将这些信息以当前的系统locale编码成窄字符串反馈给程序。例如,在简体中文Windows中,gettext的po文件也可以以UTF-8储存,gettext将po文件翻译成mo文件,确保mo文件在任何系统和语言环境下都能够正确翻译。在运行是传给win32程序的窄串符合当前locale,是GB2312。gettext让国际化的翻译更加的方便,缺点是目前我没找到支持宽字符串的版本(据说是有ugettext()支持宽字符串),所以要使用gettext只能使用窄字符串。但是gettext可以转换到宽字符串,而且不会出现宽窄转换的问题,因为gettext是运行时根据locale翻译的。例如:
const char* s = gettext("Chinese a b c");
其中"Chinese a b c"在po中的翻译是"中文abc"
使用依赖locale的运行时宽窄转换函数:
const std::wstring wstr = s2ws(s);
运行时调用该po文件对应的mo文件,在简体中文环境下就以GB2312传给程序,在繁体中文中就以BIG5传给程序,这样s2ws()总能够正常换算编码。
更多
在本文的最后,我想回到C++的stream问题上。用fstream转换如此的简单,sstream却不支持。改造一个支持codecvt的string stream需要改造basic_stringbuf。basic_stringbuf和basic_filebuf都派生自 basic_streambuf,所不同的是basic_filebuf在构造和open()的时候调用了codecvt,只需要在 basic_stringbuf中添加这个功能就可以了。说起来容易,实际上是需要重新改造一个STL模板,尽管这些模板源代码都是在标准库头文件中现成的,但是我还是水平有限,没有去深究了。另外一个思路是构建一个基于内存映射的虚拟文件,这个框架在boost的iostreams库中,有兴趣的朋友可以深入的研究。
(完)
彻底解密C++宽字符(二)的更多相关文章
- 彻底解密C++宽字符(一)
彻底解密C++宽字符(一) 转:http://club.topsage.com/thread-2227977-1-1.html 1.从char到wchar_t “这个问题比你想象中复杂” 从字符到整数 ...
- SQL注入之Sqli-labs系列第三十二关(基于宽字符逃逸注入)
开始挑战第三十二关(Bypass addslashes) 0x1查看源代码 (1)代码关键点 很明显,代码中利用正则匹配将 [ /,'," ]这些三个符号都过滤掉了 function che ...
- 宽字符,Ansic和Unicode
电脑发展的初期,只是在美国等英文国家使用,英文只有26个字母和其它字符,一个字节最多可以表示256个字符,如字母"A"用0x41(二进制01000001)表示,字母"a& ...
- gcc编译器对宽字符的识别
最早是使用VC++工具来学习C++,学的越多就越对VC挡住的我看不见的东西好奇,总想多接触一些开发环境,今日抽空摸索了一下CodeBlocks这个开源的IDE使用方法,配置的编译器是MinGW的gcc ...
- [c/c++] programming之路(25)、字符串(六)——memset,Unicode及宽字符,strset
一.memset #include<stdio.h> #include<stdlib.h> #include<memory.h> void *mymemset(vo ...
- C语言小程序——推箱子(窄字符和宽字符)
C语言小程序——推箱子(窄字符Version) 推箱子.c #include <stdio.h> #include <conio.h> #include <stdlib. ...
- 宽字符wchar_t和窄字符char——putwchar、wprintf
宽字符wchar_t 与 窄字符char 先说下窄字符char,这个大部分读者应该很清楚,char类型的变量占一个字节(byte)(也就是8个bit(比特)),能表示256个字符,那char的范围有两 ...
- 关于MultiByteToWideChar与WideCharToMultiByte代码测试(宽字符与多字节字符的转换)以及字符串的转换代码测试
#pragma once #include <stdio.h> //getchar() #include <tchar.h> #include <stdlib.h> ...
- 通过编写串口助手工具学习MFC过程——(三)Unicode字符集的宽字符和多字节字符转换
通过编写串口助手工具学习MFC过程 因为以前也做过几次MFC的编程,每次都是项目完成时,MFC基本操作清楚了,但是过好长时间不再接触MFC的项目,再次做MFC的项目时,又要从头开始熟悉.这次通过做一个 ...
随机推荐
- windows10添加开机自启动程序
1. 将要启动的程序创建快捷方式 2. 将创建的快捷方式放到以下文件夹中 C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup
- URL存在http host头攻击漏洞-修复方案
URL存在http host头攻击漏洞-修复方案 spring boot使用注解的方式 -- 第一步:在自定义filter类上添加如下注释 package com.cmcc.hy.mobile.con ...
- HDU 1712 ACboy needs your help(分组背包入门题)
http://acm.hdu.edu.cn/showproblem.php?pid=1712 题意: 有个人学习n门课程,a[i][j]表示用j分钟学习第i门课程所能获得的价值,背包容量为一共有m时间 ...
- HDU 4745 Two Rabbits(最长回文子序列)
http://acm.hdu.edu.cn/showproblem.php?pid=4745 题意: 有一个环,现在有两只兔子各从一个点开始起跳,一个沿顺时针,另一个沿逆时针,只能在一圈之内跳,并且每 ...
- 简单购物车的实现,session的使用
购物车浏览商品界面代码<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http:// ...
- lua if 流程控制
Lua认为false和nil为假,true和非nil为真. 要注意的是Lua中 0 为 true --[ 为 true ] ) then print("0 为 true") end ...
- Rails-Treasure chest3 嵌套表单; Ransack(3900✨)用于模糊查询, ranked-model(800🌟)自订列表顺序; PaperTrail(5000✨)跟踪model's data,auditing and versioning.
自订列表顺序, gem 'ranked-model' 多步骤表单 显示资料验证错误讯息 资料筛选和搜寻, gem 'ransack' (3900✨); 软删除和版本控制 数据汇出(csv), 自订列表 ...
- ES6学习笔记(一)——扩展运算符和解构赋值
前言 随着前端工程化的快速推进,在项目中使用ES6甚至更高的ES7等最近特性早已不是什么新鲜事.之前还觉得既然浏览器支持有限,那了解一下能看懂就好,然而仅仅了解还是不够的,现在放眼望去,那些成熟框架的 ...
- 关于java的讲座有感
今天晚上闲着看了下李兴华老师的java + 大数据 讲座.做一个屌爆的全能型技术人才,感觉有点收获,有兴趣的可以看看 链接地址:https://study.163.com/course/introdu ...
- vue: register and import
components/header-nav/menu-nav.vue <template> <div> menu nav </div> </template& ...