HOWTO · C++

如何在 C++ 中从函数中返回一个字符串

本文演示了如何在 C++ 中从函数中返回一个字符串。

本页内容

一般的 C++ 函数应按值返回 std::string。这样调用方会获得独立的字符串,并且 C++ 可以在适用时进行返回值优化。只有在返回由其他对象管理生命周期的现有对象时,才使用引用;仅当接口明确要求时,才使用指针或 C 风格字符串。以下示例使用 C++11 或更高版本。

按值返回字符串

当函数创建或转换字符串时,按值返回是最清晰的默认做法。调用方会获得自己的字符串值,可以独立保存或修改它。按值返回具名局部变量通常很高效:编译器可以通过具名返回值优化,直接在调用方的目标位置构造结果。如果无法进行这种优化,std::string 也可以移动。无论哪种情况,都不需要在 return 语句中添加 std::move。

运行以下示例:

本节中的代码以及显示的标签或输出仍为英文。

#include <iostream>
#include <string>

std::string makeGreeting() {
    std::string result = "Hello, C++!";
    return result;
}

int main() {
    std::string greeting = makeGreeting();
    std::cout << greeting << '\n';
}

函数的返回类型是 std::string,而 return result; 会返回这个局部值。函数结束时,局部变量会销毁,但返回的字符串仍可供调用方使用。与另外安排动态分配字符串的所有权相比,按值返回通常更简单、更安全。

返回现有字符串的引用

返回引用时,函数会引用一个已经存在的对象,而不会创建新的字符串。如果函数需要选择或公开一个由生命周期更长的对象所拥有的字符串,这种方式可能很有用。调用方访问的是同一个对象,因此通过非 const 引用所做的修改会影响原对象。若调用方只需读取而不应修改内容,可使用 const std::string&。

在调用方使用返回的引用期间,被引用的对象必须一直有效。绝不要返回局部变量的引用:函数退出时该变量会被销毁,调用方得到的引用也就悬空了。只有当调用方能确保实参持续有效时,才可以安全地返回引用参数,如下例所示:

本节中的代码以及显示的标签或输出仍为英文。

#include <iostream>
#include <string>

const std::string& sameString(const std::string& value) {
    return value;
}

int main() {
    std::string original = "Existing string";
    const std::string& alias = sameString(original);
    std::cout << alias << '\n';
}

通过指针返回字符串

返回指针可以表示可选访问,也可以遵循现有接口,但这会使生命周期和所有权成为接口约定的一部分。只有在现有字符串的生命周期长于指针的所有使用时,才适合返回它的地址。应说明对象由谁拥有,以及指针是否可能为空。

不要仅仅为了返回而使用 new 分配 std::string。这样会要求调用方记得释放对象,也会让提前返回和错误处理更难管理。若要返回新创建的字符串,应按值返回。下面的示例指向一个现有对象,并确保使用指针期间该对象仍然有效:

本节中的代码以及显示的标签或输出仍为英文。

#include <iostream>
#include <string>

const std::string* findGreeting(const std::string& value) {
    return &value;
}

int main() {
    std::string greeting = "Hello through a pointer";
    const std::string* result = findGreeting(greeting);
    if (result != nullptr) {
        std::cout << *result << '\n';
    }
}

该函数不会取得 value 的所有权。被引用的字符串销毁后,调用方不得继续保留并使用返回的指针。

仅在确有帮助时使用 std::move

std::move 是允许进行移动操作的类型转换;它本身并不会移动数据。在 return 语句中写 return std::move(local); 通常没有必要。这样做可能妨碍具名返回值优化,使编译器无法直接构造结果,而改为执行移动。对于局部结果,应优先使用 return local;,让语言规则和编译器选择适用的优化。

当对象不符合自动移动的条件时,显式移动可能有用,例如需要从生命周期更长的对象中移出一个成员。这会改变源对象,因此只有在之后不再需要源对象原有状态时才这样做。不要仅为了让返回看起来更快而添加 std::move。

使用 char* 返回 C 风格字符串

C 风格字符串是以空字符结尾的字符序列,不是 std::string。如果接口要求字符指针,且结果是固定文本,可以返回指向字符串字面量的 const char*。字符串字面量具有静态存储期,因此该指针在整个程序运行期间都有效。这里的 const char* 表示“指向 const 字符的指针”:不能通过该指针修改字符,但指针本身并非 const,可以重新赋值。这与 char* const 不同;后者是指向可修改字符的 const 指针。不得修改字符串字面量。

本节中的代码以及显示的标签或输出仍为英文。

#include <iostream>

const char* greeting() {
    return "Hello from C-style text";
}

int main() {
    std::cout << greeting() << '\n';
}

不要返回指向局部字符数组的指针,因为函数返回时该数组就不再存在;也不要返回指向局部 std::string 内部的指针。c_str() 的结果取决于所属 std::string 对象的生命周期,而且修改该字符串的操作可能使这个结果失效。如果调用方需要独立拥有且可修改的结果,应改为返回 std::string。