使用非 ASCII 字符反转字符串

Posted

技术标签:

【中文标题】使用非 ASCII 字符反转字符串【英文标题】:Reverse string with non-ASCII characters 【发布时间】:2013-10-28 15:09:01 【问题描述】:

我想用这样的特殊字符更改字符串中的顺序:

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

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

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

【问题讨论】:

FWIW,您不需要为 std::reverse 使用反向迭代器。 (并不是说它会改变结果,但很高兴知道) @KerrekSB:不,不会,如果字符被分解。我开始怀疑,因为 \304 看起来很像 U+0304,这是一个分解的变音符号。 @MSalters:确实,好点。 老实说,从 Unicode 开始,这是一个非常困难的问题。 C++ 在那里一点帮助也没有。见***.com/questions/16629183/… @MSalters IMO 仅仅正确地说明问题就够难了。 【参考方案1】:

您的代码确实正确地反转了字符串中的字节,这里没有错。然而,问题在于您的编译器存储了您的文字字符串“ZAŻÓŁĆ GĘŚLĄ JAŹŃ!” UTF-8 编码。

并且 UTF-8 将除匹配 ASCII 的字符之外的所有字符存储为可变长度的序列字节。这意味着一个char(一个字节)不再是一个字符,因此反转char 现在与反转字符不同。

要实现您的目标,您至少有两种选择:

    使用一些 utf-8 库,可以让您迭代字符而不是字节。一个例子是http://utfcpp.sourceforge.net/ 不知何故(这在很大程度上取决于您使用的编译器和操作系统)切换到 utf-32 编码,该编码具有恒定的字符长度并具有良好的旧的恒定字符大小字符串,而没有所有这些疯狂的可变字符大小麻烦.

UPD:一个不错的链接:http://www.joelonsoftware.com/articles/Unicode.html

【讨论】:

utf32 只是将问题委托给 32 位,坚持使用 utf8 应该是正确的事情 “将问题委托给 32 位”是什么意思? @R.MartinhoFernandes 一些字符序列对于 utf32 来说太长了 @Dieter Lücking 目前 UTF-32 是唯一的固定字符长度的 Unicode 编码,在最近的 1000 年内人类不太可能需要更多的 Unicode 字符。所以我不认为它会在最近的将来变成可变长度。如果它没有变成可变长度,它确实解决了 OP 的字符反转问题。 FWIW,Joel 的 Unicode 文章已经过时,关于 UTF-8 最多可达 6 个字节。十年来,它还没有达到 6 个字节。 (即使它是正确的,这 6 个字节也只覆盖了 31 位空间,而不是更多。)【参考方案2】:

您可以自己编写一个 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/

【讨论】:

此代码有多个问题。请在提交之前修复它的语法问题【参考方案3】:

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

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

下一个问题是单个字素可能包含多个 Unicode 代码点。例如,“é”可能被编码为两个代码点 U+0065 后跟 U+0301。如果您颠倒这些顺序,则会破坏它,因为组合字符 U+301 现在将与不同的基本字符相关联。所以“神奇宝贝”以这种方式反转会变成“noḿekoP”,重音在“m”而不是“e”。

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

然后您的语言中的字符会根据上下文改变形式,而我对如何处理这些语言还不太了解。

它可能更接近您想要反转的字素簇。见UAX29: Unicode text segmentation。

【讨论】:

【参考方案4】:

您是否尝试过逐个交换字符。 例如,如果字符串长度为奇数,则将第一个字符与最后一个字符交换,将第二个字符与倒数第二个字符交换,直到剩下中间字符。如果字符串长度为偶数,则将第一个与最后一个交换,第二个与第二个最后交换,直到两个中间字符都交换。这样,字符串将被反转。

【讨论】:

这正是std::reverse 所做的,只是它不会浪费您的时间编写代码、测试代码和修复代码中的问题。

以上是关于使用非 ASCII 字符反转字符串的主要内容,如果未能解决你的问题,请参考以下文章

反转字符串

字符串反转

力扣——反转字符串

344. 反转字符串

反转字符串

LeetCode|344. 反转字符串