非ASCII字符的字符串反转

4

我想要改变带有特殊字符的字符串的顺序,就像这样:

ZAŻÓŁĆ GĘŚLĄ JAŹŃ

变成

ŃŹAJ ĄŁŚĘG ĆŁÓŻAZ

我尝试使用std::reverse。

std::string text("ZAŻÓŁĆ GĘŚLĄ JAŹŃ!");
std::cout << text << std::endl;
std::reverse(text.rbegin(), text.rend());
std::cout << text << std::endl;

但输出结果显示如下:

ZAŻÓŁĆ GĘŚLĄ JAŹŃ!

!\203Ź\305AJ \204\304L\232Ř\304G \206āœû\305AZ <- 反转字符串

所以我试着手动做这个操作:
std::string text1("ZAŻÓŁĆ GĘŚLĄ JAŹŃ!");
std::cout << text1 << std::endl;
int count = (int) floorf(text1.size() /2.f);
std::cout << count  << "  " << text1.size() << std::endl;

unsigned int maxIndex = text1.size() - 1;
for (int i = 0; i < count ; i++)
{
    char tmp = text1[i];
    text1[i] = text1[maxIndex];
    text1[maxIndex] = tmp;
    maxIndex--;
}
std::cout << text1 << std::endl;

但在这种情况下,我在text1.size()中有一个问题,因为每个特殊字符都被计算两次:

ZAŻÓŁĆ GĘŚLĄ JAŹŃ!

13 27 <- 第二个数字是text1.size()

!\203Ź\305AJ \204\304L\232Ř\304G \206āœû\305AZ

如何正确地反转带有特殊字符的字符串?


1
顺带一提,您在使用std::reverse时不需要使用反向迭代器。(虽然这不会改变结果,但了解这一点也是好的) - R. Martinho Fernandes
@KerrekSB:如果字符被分解,它就不会了。我开始怀疑那个\304看起来很像U+0304,这是一个分解的变音符号。 - MSalters
@MSalters:确实,说得好。 - Kerrek SB
1
老实说,在Unicode中,这是一个非常棘手的问题。C++在这方面一点也不帮助你。请参见http://stackoverflow.com/questions/16629183/fully-correct-unicode-visual-string-reversal - MSalters
@MSalters 在我看来,仅仅正确陈述问题就已经够难的了。 - R. Martinho Fernandes
真的,只需考虑反转“ae”与反转“æ”。 - MSalters
4个回答

1
你的代码确实可以正确地反转字符串中的字节,这里没有问题。然而,问题在于你的编译器将字面字符串“ZAŻÓŁĆ GĘŚLĄ JAŹN!”以UTF-8编码存储。
UTF-8将除了匹配ASCII的字符之外的所有字符都存储为可变长度的字节序列。这意味着一个char(一个字节)不再是一个字符,因此反转char不同于反转字符。
要实现你的目标,你至少有两个选择:
1. 使用一些支持迭代字符而非字节的utf-8库。一个例子是http://utfcpp.sourceforge.net/ 2. 以某种方式(这在很大程度上取决于你使用的编译器和操作系统)切换到具有恒定字符长度并且具有好老的常量字符大小字符串的utf-32编码,避免所有这些疯狂的可变字符大小问题。
更新:这是一个不错的链接:http://www.joelonsoftware.com/articles/Unicode.html

utf32只是将问题委托给32位,坚持使用utf8应该是正确的做法。 - user2249683
“将问题委托给32位”是什么意思? - R. Martinho Fernandes
虽然我同意理解UTF-8并坚持使用它在大多数情况下是好的和有用的。阅读我们讨论的人可能会发现这个链接很有用:http://www.utf8everywhere.org/ - Anton
所有这些说法,UTF-8或者无论是什么UTF都不重要,因为组合标记才是最重要的。 - R. Martinho Fernandes
3
顺便提一句,乔尔的 Unicode 文章关于 UTF-8 可以使用 6 字节已经过时。现在 UTF-8 最多只能使用 4 字节。(即使这个信息是正确的,这 6 个字节也只能覆盖 31 位空间,而不是更多。) - R. Martinho Fernandes
显示剩余3条评论

0
你可以自己编写一个 reverseUt8 函数:
std::string getMultiByteReversed(char ch1, char ch2)
{  
   if (ch == '\xc3') // most utf8 characters
      return std::string(ch1)+ std::string(ch2);
   } else {
      return std::string(ch1);
   }
}

std::string reverseMultiByteString(const std::string &s)
{
    std::string result;
    for (std::string::reverse_iterator it = s.rbegin(); it != s.rend(); ++it) {
       std::string reversed;
       if ( (it+1) != rbegin() && (reversed = getMultiByteReversed(*it, *it+1) ) {
          result += reversed;
          ++it;
       } else {
          result += *it;
       }
  }
  return result;
}

您可以在此链接查找utf8代码:http://www.utf8-chartable.de/


这段代码存在多个问题。请在提交之前修复其语法问题。 - Mohammad f

0

这里有几个问题。答案很复杂,取决于你要做什么。

首先是(如其他答案所述),如果你的字符串是UTF-8编码,一个Unicode代码点可能由多个字节组成。如果你只是反转字节,你会破坏UTF-8编码。最简单的(虽然不一定是最好的)解决方法是将字符串转换为UTF-32,并反转32位代码点而不是字节。

下一个问题是,一个单独的音素可能由多个Unicode代码点组成。例如,“é”可能被编码为两个代码点U+0065后跟U+0301。如果你颠倒这些顺序,那么它就会被打破,因为组合字符U+301现在将与不同的基字符相关联。因此,“Pokémon”以这种方式反转将变成“noḿekoP”,重音在“m”上而不是“e”上。

现在你可能认为你可以通过先将字符串规范化为组合形式来解决这个问题。然而,这也有它自己的问题,因为并不是每个字形都可以用单个代码点表示。例如,加拿大国旗表情符号()由代码点U+1F1E8和代码点U+1F1E6表示。它没有单一的代码点。如果你颠倒它的代码点,你会得到阿森松岛()的旗帜。

然后你有一些语言,其中字符根据上下文改变形式,我还不太了解如何处理这些语言。

反转字形簇可能更接近于你想要的。请参见UAX29:Unicode文本分段


-4
你尝试过逐个交换字符吗?例如,如果字符串长度为奇数,则将第一个字符与最后一个字符交换,第二个字符与倒数第二个字符交换,直到剩下中间字符。如果字符串长度为偶数,则交换第一个和最后一个字符,第二个和倒数第二个字符,直到两个中间字符都被交换。通过这种方式,字符串将被反转。

2
这正是std::reverse所做的事情,但它可以让你不浪费时间编写代码、测试代码和修复代码中的问题。 - R. Martinho Fernandes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接