MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C++ string类I/O用法详解

2021-02-274.2k 阅读

C++ string 类输入输出基础

标准输入流读取 string

在 C++ 中,最常见的从标准输入读取 string 的方式是使用 cin。例如:

#include <iostream>
#include <string>

int main() {
    std::string str;
    std::cout << "请输入一个字符串: ";
    std::cin >> str;
    std::cout << "你输入的字符串是: " << str << std::endl;
    return 0;
}

在上述代码中,cin >> str 从标准输入读取一个字符串并存储到 str 中。这里需要注意的是,cin 是以空白字符(空格、制表符、换行符等)作为分隔符的。也就是说,如果输入 hello worldcin 只会读取到 helloworld 会留在输入缓冲区中。

读取整行字符串

如果我们希望读取包含空白字符的整行字符串,可以使用 std::getline 函数。getline 函数有两种常见的重载形式:

  1. std::getline(std::istream& is, std::string& str):从输入流 is 中读取一行数据到 str 中,默认以换行符 \n 作为行结束标志。
  2. std::getline(std::istream& is, std::string& str, char delim):从输入流 is 中读取数据到 str 中,直到遇到指定的分隔符 delim

示例代码如下:

#include <iostream>
#include <string>

int main() {
    std::string line;
    std::cout << "请输入一行字符串: ";
    std::getline(std::cin, line);
    std::cout << "你输入的整行字符串是: " << line << std::endl;

    std::string part;
    std::cout << "请输入以逗号分隔的部分字符串: ";
    std::getline(std::cin, part, ',');
    std::cout << "你输入的部分字符串是: " << part << std::endl;
    return 0;
}

在第一个 getline 调用中,它会读取整行输入,包括空格。而第二个 getline 调用中,当遇到逗号时就会停止读取。

格式化输入输出

使用 printf 和 scanf 家族函数处理 string

虽然 C++ 有自己的输入输出流库,但由于历史原因,C 语言的 printfscanf 家族函数仍然可以在 C++ 程序中使用。不过,这些函数并没有原生支持 std::string 类型。要使用它们,我们需要将 std::string 转换为 C 风格的字符串(const char*)。

对于输出,printf 函数使用 %s 格式说明符来打印 C 风格字符串。例如:

#include <iostream>
#include <string>
#include <cstdio>

int main() {
    std::string str = "Hello, printf!";
    printf("%s\n", str.c_str());
    return 0;
}

这里使用了 std::stringc_str 成员函数,它返回一个指向以空字符结尾的 C 风格字符串的指针。

对于输入,scanf 函数可以读取 C 风格字符串到一个字符数组中,然后再将其转换为 std::string。示例如下:

#include <iostream>
#include <string>
#include <cstdio>

int main() {
    char buffer[100];
    std::cout << "请输入一个字符串: ";
    scanf("%s", buffer);
    std::string str(buffer);
    std::cout << "你输入的字符串是: " << str << std::endl;
    return 0;
}

需要注意的是,scanf 存在缓冲区溢出的风险,如果输入的字符串长度超过 buffer 的大小,就会导致未定义行为。

使用字符串流进行格式化输入输出

std::stringstream 类模板在 <sstream> 头文件中定义,它允许我们像操作标准输入输出流一样操作字符串。这在进行格式化输入输出时非常有用。

字符串流输出

#include <iostream>
#include <sstream>
#include <string>

int main() {
    int num = 42;
    double dbl = 3.14159;
    std::string str;
    std::stringstream ss;

    ss << "数字: " << num << ", 浮点数: " << dbl;
    str = ss.str();
    std::cout << str << std::endl;
    return 0;
}

在上述代码中,stringstream 对象 ss 首先像 cout 一样将数据格式化输出到自身,然后通过 str 成员函数获取最终的字符串。

字符串流输入

#include <iostream>
#include <sstream>
#include <string>

int main() {
    std::string input = "10 20.5";
    int intValue;
    double doubleValue;
    std::stringstream ss(input);

    ss >> intValue >> doubleValue;
    std::cout << "整数: " << intValue << ", 浮点数: " << doubleValue << std::endl;
    return 0;
}

这里 stringstream 对象 ssinput 字符串为输入源,像 cin 一样从字符串中按顺序提取数据并转换为相应的类型。

向文件中读写 string

写入文件

要将 std::string 写入文件,我们使用 <fstream> 头文件中的 std::ofstream 类。示例如下:

#include <iostream>
#include <fstream>
#include <string>

int main() {
    std::string content = "这是要写入文件的内容。";
    std::ofstream file("example.txt");

    if (file.is_open()) {
        file << content << std::endl;
        file.close();
        std::cout << "内容已成功写入文件。" << std::endl;
    } else {
        std::cout << "无法打开文件。" << std::endl;
    }
    return 0;
}

在上述代码中,首先创建了一个 ofstream 对象并尝试打开名为 example.txt 的文件。如果文件成功打开,就将 content 字符串写入文件,并在末尾添加换行符。最后关闭文件。

从文件读取

从文件读取 std::string 同样使用 <fstream> 头文件,这次使用 std::ifstream 类。

#include <iostream>
#include <fstream>
#include <string>

int main() {
    std::string line;
    std::ifstream file("example.txt");

    if (file.is_open()) {
        while (std::getline(file, line)) {
            std::cout << "从文件读取的内容: " << line << std::endl;
        }
        file.close();
    } else {
        std::cout << "无法打开文件。" << std::endl;
    }
    return 0;
}

这里使用 ifstream 打开文件,如果成功打开,通过 getline 逐行读取文件内容并输出到控制台。

内存管理与 string 输入输出

string 在输入输出中的内存分配

当我们使用 cingetline 等函数读取字符串到 std::string 对象时,std::string 会根据输入的内容动态分配内存。例如,当使用 getline 读取一个很长的字符串时,std::string 会分配足够的内存来存储整个字符串。

#include <iostream>
#include <string>

int main() {
    std::string longStr;
    std::cout << "请输入一个很长的字符串: ";
    std::getline(std::cin, longStr);
    std::cout << "字符串长度: " << longStr.size() << ", 容量: " << longStr.capacity() << std::endl;
    return 0;
}

在这个例子中,size 成员函数返回字符串的实际长度,capacity 成员函数返回当前分配的内存容量。当输入的字符串长度超过当前容量时,std::string 会重新分配内存,通常会以一定的策略增加容量,例如翻倍。

避免不必要的内存分配

在某些情况下,我们可以提前知道要读取的字符串的大致长度,这时可以使用 reserve 成员函数预先分配足够的内存,以避免多次重新分配内存。

#include <iostream>
#include <string>

int main() {
    std::string str;
    str.reserve(100); // 预先分配 100 个字符的空间
    std::cout << "请输入字符串: ";
    std::cin >> str;
    std::cout << "字符串长度: " << str.size() << ", 容量: " << str.capacity() << std::endl;
    return 0;
}

这样,如果输入的字符串长度不超过 100,就不会发生额外的内存重新分配。

高级输入输出场景

自定义输入输出操作符

我们可以为自定义类型重载输入输出操作符,以便与 std::string 进行交互。例如,假设有一个简单的 Point 类:

#include <iostream>
#include <string>
#include <sstream>

class Point {
public:
    int x;
    int y;

    // 重载输出操作符
    friend std::ostream& operator<<(std::ostream& os, const Point& p) {
        os << "(" << p.x << ", " << p.y << ")";
        return os;
    }

    // 重载输入操作符
    friend std::istream& operator>>(std::istream& is, Point& p) {
        std::string temp;
        std::getline(is, temp, ')');
        std::stringstream ss(temp.substr(temp.find('(') + 1));
        ss >> p.x >> p.y;
        return is;
    }
};

int main() {
    Point p;
    std::cout << "请输入点的坐标 (格式: (x, y)): ";
    std::cin >> p;
    std::cout << "输入的点: " << p << std::endl;
    return 0;
}

在上述代码中,我们重载了 <<>> 操作符,使得 Point 类型可以方便地进行输入输出,并且在输入输出过程中与 std::string 进行了有效的转换。

处理国际化字符串输入输出

在处理国际化字符串时,std::wstring 用于宽字符字符串,而 <codecvt> 头文件提供了在不同字符编码之间转换的工具。例如,将 std::string(多字节字符)转换为 std::wstring(宽字符)以便处理 Unicode 字符。

#include <iostream>
#include <string>
#include <codecvt>
#include <locale>

int main() {
    std::string mbString = "你好";
    std::wstring_convert<std::codecvt<wchar_t, char, std::mbstate_t>> converter;
    std::wstring wideString = converter.from_bytes(mbString);
    std::wcout.imbue(std::locale("en_US.UTF - 8"));
    std::wcout << L"宽字符字符串: " << wideString << std::endl;
    return 0;
}

这个例子展示了如何将多字节字符串转换为宽字符字符串,并在控制台输出。在实际应用中,这种转换在处理不同编码的文件输入输出以及与国际化 API 交互时非常有用。

性能优化在 string 输入输出中的应用

  1. 减少拷贝:在输入输出操作中,尽量减少 std::string 的拷贝。例如,使用 std::move 语义来避免不必要的深拷贝。
#include <iostream>
#include <string>

void processString(std::string&& str) {
    std::cout << "处理字符串: " << str << std::endl;
}

int main() {
    std::string str = "测试字符串";
    processString(std::move(str));
    return 0;
}

在上述代码中,std::movestr 的所有权转移给 processString 函数,避免了一次拷贝。

  1. 批量操作:如果需要进行多次输入输出操作,可以考虑批量处理以减少系统调用开销。例如,在文件写入时,可以先将多个字符串拼接成一个大字符串,然后一次性写入文件。
#include <iostream>
#include <fstream>
#include <string>

int main() {
    std::ofstream file("batch.txt");
    std::string batchStr;
    std::string str1 = "第一行";
    std::string str2 = "第二行";
    std::string str3 = "第三行";

    batchStr = str1 + "\n" + str2 + "\n" + str3 + "\n";
    file << batchStr;
    file.close();
    return 0;
}

这种方式减少了文件写入的次数,提高了性能。

通过以上详细的讲解和丰富的代码示例,相信你对 C++ 中 string 类的输入输出用法有了深入的理解,无论是日常编程还是处理复杂的项目需求,都能更加得心应手地运用这些知识。