字符串拆分的中文处理问题
容健行@2007年7月
转载请注明出处
原文出处:http://www.devdiv.net/home/space.php?uid=125&do=blog&id=365
概述:
拆分一个字符串在程序中使用非常广泛,特别是我们经常跟表格打交道的程序员们。所谓拆分字符串,就是将一个字符串中间以某个(或某些)字符为分隔,拆分成多个字符串。如 std::string s = "abc | ddd | 中国"; 如果以竖线“|”拆分,可以将这个字符串拆分成三个字符串。
当然字符串拆分还包括通过正则表达式来拆分,为了简化问题,我们以单个字符做分隔的拆分,因为这种拆分用得最多。代码使用C++来讲解。
问题:
问题来源于实际,是之前我们组和其他组都有遇上的。先看一个例子,使用"|"拆分以下字符串,看起来怎么数都是分为48列,但我看到好几个版本的字符串拆分函数却报有49列:
"AGZGY1000004|200|刘瓅||20100101||OPRT10|1|0||AAGZ0Y100|0|0|24|0|0|0|0||-1|20030101|0|20991231||AGZGK6172888|200|曾晓翔||20100101||OPRT10|1|0||AAGZ0K617|0|0|24|0|0|0|0||-1|20061215|1|20061215||"
原因分析:
让我们先把以上字符串放到UltraEdit中,并切换到16进制的编辑模式,看看它的编码。
原因是原来的字符串拆分函数只是简单的查找“|”(编码为0x7c),而没有考虑到中文的处理(源代码太多,且有好几个版本,这里略去)。
在boss中,c++程序使用的编码方式几乎全为ansi,而在ansi中,表示中文是用两个字符,且第一个字符是一个大于0x80的字符(字符的第一位为1),第二个字符为任意字符。这里引起一个问题:
当我们要分割字符串时,假如用"|"(0x7c)作为分割符,当分析上面这个字符遇到"瓅"(编码为0xad,0x7c)这个字符时,会把它第二个字符作为了分割符,结果就多出了一列。
解决方案:
问题原因找到了,重新写了一下字符串拆分函数-Split,这里使用的方法是:找到分隔符后,再向前查找字符看一下它前一个字符是否为东亚文字的第一个字符编码(编码大于0x80)。
考虑到以后支持unicode,这里使用了模板。以下可能不是最高效简单的实现,但如果以后遇上这种问题,可以参考一下。
#include "stdafx.h"
#include <stdio.h>
#include <tchar.h>
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <fstream>
// unicode 分割策略
inline
bool __SplitPolicy(
const std::wstring& s,
const std::wstring& splitchar,
std::wstring::size_type& pos)
{
pos = s.find_first_of(splitchar, pos);
return pos != std::string::npos;
}
// ansi 分割策略
inline
bool __SplitPolicy(
const std::string& s,
const std::string& splitchar,
std::string::size_type& pos)
{
pos = s.find_first_of(splitchar, pos);
if (pos != std::string::npos)
{
// 如果前一个字符的第一位为1,且当前字符是在东亚文字的第二个字符,
// 则认为该字符是东亚字的其中一个字符,要跳过,不作为分割符。
std::string::size_type i = 1;
for (; i < pos; ++i)
{
if (!((char)(s[pos - i]) & 0x80)) // 判断第一位是否为1。(0x80的二进制为 10000000)
break;
}
if (!(i % 2)) // 看一下当前字符是否为东亚文字的第二个字符
{
++pos;
__SplitPolicy(s, splitchar, pos);
}
}
return pos != std::string::npos;
}
template<typename char_type> inline
int Split(
const std::basic_string<char_type>& s,
const std::basic_string<char_type>& splitchar,
std::vector<std::basic_string<char_type> >& vec)
{
typedef std::basic_string<char_type> string_t;
typedef typename string_t::size_type size_t;
string_t tmpstr;
size_t pos = 0, prev_pos = 0;
vec.clear();
while (__SplitPolicy(s, splitchar, pos))
{
tmpstr = s.substr(prev_pos, pos - prev_pos);
vec.push_back(tmpstr);
prev_pos = ++pos;
}
size_t len = s.length() - prev_pos;
if (len > 0)
vec.push_back(s.substr(prev_pos, len));
return static_cast<int>(vec.size());
}
// ansi版本测试
void testSplit()
{
std::vector<std::string> vec;
const std::string str = "AGZGY1000004|200|刘瓅瓅||20100101||OPRT10|1|0||AAGZ0Y100|0|0|24|0|0|0|0||-1|20030101|0|20991231||AGZGK6172888|200|曾晓翔||20100101||OPRT10|1|0||AAGZ0K617|0|0|24|0|0|0|0||-1|20061215|1|20061215||a";
const std::string sp = "|";
int count = Split(str, sp, vec);
for (std::vector<std::string>::const_iterator it = vec.begin(); it != vec.end(); ++it)
std::cout << *it << " ";
}
// unicode版本测试
void testSplitW()
{
std::vector<std::wstring> vec;
const std::wstring str = L"AGZGY1000004|200|刘瓅||20100101||OPRT10|1|0||AAGZ0Y100|0|0|24|0|0|0|0||-1|20030101|0|20991231||AGZGK6172888|200|曾晓翔||20100101||OPRT10|1|0||AAGZ0K617|0|0|24|0|0|0|0||-1|20061215|1|20061215||";
const std::wstring sp = L"|";
Split(str, sp, vec);
const char head[3] = {0xff, 0xfe, 0};
const wchar_t line[3] = L" ";
// 控制台输出不了unicode字符,使用输出到文件的方式
std::ofstream fileOut("C:/out.txt");
fileOut.write(head, 2);
for (std::vector<std::wstring>::iterator it = vec.begin(); it != vec.end(); ++it)
{
fileOut.write((const char*)it->c_str(), it->length() * 2);
fileOut.write((const char*)line, 2);
}
}
int main()
{
testSplit();
testSplitW();
}
参考:
1.http://unicode.org/
2.《谈谈Unicode编码,简要解释UCS、UTF、BMP、BOM等名词》