HOWTO · C++

如何在 C++ 中反转字符串

本文介绍了如何在 C++ 中对字符串进行反转。

本页内容

在 C++ 中反转字符串,先决定是否要修改原字符串。若要保留源字符串,可以用反向迭代器构造新的 std::string;若适合原地修改,则可以对字符串调用 std::reverse。std::copy 可以填充目标字符串,也可以将反转后的字符直接写入流。循环能清楚展示逐个处理元素的过程;递归主要适合作为学习练习。

选择一种字符串反转方法

对于大多数代码,可以用反向迭代器创建单独的结果,或者用 std::reverse 重排已有字符串中的元素。这两种方法操作的都是 std::string 元素,也就是 char 值。如果文本采用 UTF-8,反转这些字节可能会破坏多字节字符;将这些方法用于自然语言文本之前,请先阅读最后一节。

使用反向迭代器反转字符串

字符串的 rbegin() 和 rend() 迭代器会从最后一个元素向第一个元素遍历。将它们传给 std::string 构造函数,可以创建反转后的副本,同时保留源字符串不变。当需要同时保留两个字符串时,这是一种直接的做法。

运行此示例。输出会显示原字符串和反转后的字符串;其中 Original: 和 Reversed: 标签保持英文:
本节中的代码以及显示的标签或输出仍为英文。

#include <iostream>
#include <string>

int main() {
    const std::string original = "Hello, C++!";
    const std::string reversed(original.rbegin(), original.rend());
    std::cout << "Original: " << original << '\n';
    std::cout << "Reversed: " << reversed << '\n';
}

构造函数接收一对迭代器,用它们确定新字符串中元素的顺序。原字符串仍可供后续使用,而新字符串则保存反转后的序列。示例中的 Original: 和 Reversed: 标签以及输入文本保留英文,因为代码和输出块必须与英文原文逐字一致。

使用 std::reverse 原地反转字符串

std::reverse 是在 <algorithm> 中声明的算法。它会交换半开区间中的元素:从第一个迭代器指向的位置开始,到第二个迭代器指向的位置之前结束。该算法会修改给定范围,不会返回反转后的副本。下面的程序先对副本调用此算法,因此可以输出原字符串和修改后的字符串。输出标签 Original: 和 Changed: 保持英文:
本节中的代码以及显示的标签或输出仍为英文。

#include <algorithm>
#include <iostream>
#include <string>

int main() {
    const std::string original = "Hello, C++!";
    std::string changed = original;
    std::reverse(changed.begin(), changed.end());
    std::cout << "Original: " << original << '\n';
    std::cout << "Changed: " << changed << '\n';
}

如果要直接修改原字符串,可以对非 const 字符串调用 std::reverse(original.begin(), original.end())。此操作不需要分配第二个字符串。不要认为该算法会报告内存分配失败:它只会重排范围内的元素,本身不执行分配操作。

使用 std::copy 创建反转后的字符串

std::copy 会将输入范围中的元素复制到目标范围;它不会调整目标字符串的大小。复制前要确保目标范围有足够的元素。反向迭代器会让复制后的顺序倒过来。如果只需要显示字符,可以使用输出迭代器,省去存储另一个字符串。

示例会先输出存入目标字符串的反转结果,再将反转后的字符直接写入流。输出标签 Copy: 和 Stream: 保持英文:
本节中的代码以及显示的标签或输出仍为英文。

#include <algorithm>
#include <iostream>
#include <iterator>
#include <string>

int main() {
    const std::string original = "Hello, C++!";
    std::string reversed(original.size(), '\0');
    std::copy(original.rbegin(), original.rend(), reversed.begin());

    std::cout << "Copy: " << reversed << '\n';
    std::cout << "Stream: ";
    std::copy(original.rbegin(), original.rend(),
              std::ostream_iterator<char>(std::cout));
    std::cout << '\n';
}

字符串构造函数会创建具有所需元素数量的目标字符串,因此输出范围足以容纳源字符串。std::ostream_iterator 会将复制的每个字符写入 std::cout;它由 <iterator> 声明。算法名称加上 std:: 限定符,可以明确表示它们属于该命名空间。

使用循环反转字符串

循环可以从源字符串末尾向开头逐个追加元素。使用 std::string::size_type 进行迭代,可以避免将字符串长度转换为有符号整数。下面的递减并检查写法也会在从零继续递减之前停止:
本节中的代码以及显示的标签或输出仍为英文。

#include <iostream>
#include <string>

int main() {
    const std::string original = "Hello, C++!";
    std::string reversed;
    reversed.reserve(original.size());

    for (std::string::size_type i = original.size(); i > 0; --i) {
        reversed.push_back(original[i - 1]);
    }

    std::cout << reversed << '\n';
}

预留容量是一种可选优化,可以避免追加元素时反复扩容。循环有助于理解反转过程,也适合加入逐元素处理逻辑;对于普通的反转操作,标准库方法更简洁,也更容易辨认。示例输出直接显示反转后的字符串,不带标签。

使用递归反转字符串

递归可以通过取出最后一个元素、再处理前面的子字符串来表达反转过程。下面的版本会返回一个新字符串,用来展示这种定义。示例输出直接显示反转后的字符串,不带标签:
本节中的代码以及显示的标签或输出仍为英文。

#include <iostream>
#include <string>

std::string reverseString(const std::string& value) {
    if (value.size() < 2) {
        return value;
    }
    return value.back() + reverseString(value.substr(0, value.size() - 1));
}

int main() {
    const std::string original = "Hello, C++!";
    std::cout << reverseString(original) << '\n';
}

每一步递归都会创建一个子字符串,因此这种方法可能复制大量数据并进行许多次函数调用。输入较长时,还会需要很深的调用栈。除非递归本身就是教学内容,否则在生产代码中优先使用反向迭代器或 std::reverse。

理解空字符串和字符编码

对于空字符串,反向迭代器构造函数会创建另一个空字符串,而传给 std::reverse 的范围不包含任何元素。循环不会执行任何迭代。递归示例会在基准情形下返回。因此,处理空输入时,这些方法都不需要特殊输出或单独的反转逻辑。

std::string 存储的是 char 元素,而不是抽象的 Unicode 字符。对于 UTF-8 文本,一个可见字符可能占用多个字节,而一个用户感知的字符也可能由多个 Unicode 码点组成。反转字节可能破坏编码;即使反转码点,也仍可能拆开由字母和重音符号组合而成的字素簇。处理多语言文本时,应先解码,并使用支持 Unicode 的库,按应用所需的单位进行反转,通常是字素簇。