第六章 字符串与输入输出

← 返回目录

本章目标:std::string(最常用的类型)、string_view(C++17)、 格式化输出 print/format、读取输入。 熟练字符串,你的 C++ 就算入门一半了。

6.1 std::string:会自己长的字符串

#include <string>
std::string s = "hello";
std::string t = "world";
std::string u = s + " " + t;   // 拼接,u == "hello world"
s += "!";                      // 追加
s.size();                      // 长度(字符个数)
s.empty();                     // 是否为空
s[0];                          // 第 0 个字符 'h'
s.at(0);                       // 带边界检查,越界会抛异常

对比 C 风格的 char[],string 的三大好处:

  1. 自动管理内存,不用担心长度
  2. 拼接 +、比较 == 直接写
  3. 附带一堆现成方法

6.2 string 常用方法速览(用到查,不必背)

s.substr(pos, len);        // 截取子串
s.find("lo");              // 找子串位置,找不到返回 npos
s.starts_with("he");       // [C++20] 是否以 xx 开头
s.ends_with("lo");         // [C++20] 是否以 xx 结尾
s.contains("ell");         // [C++23] 是否包含子串
s.replace(pos, len, str);  // 替换一段
s.insert(pos, str);        // 插入
s.erase(pos, len);         // 删除一段
s.push_back('x');          // 末尾加一个字符
s.pop_back();              // 删掉末尾一个字符

find 的返回值是 size_t 类型的位置;找不到时返回 std::string::npos(一个巨大的数)。用法:

auto pos = s.find("lo");
if (pos != std::string::npos) {   // 找到了
    std::println("位置在 {}", pos);
}

6.3 数字 ↔ 字符串 互转

字符串转数字(C++17 起推荐 std::from_chars,性能最好, 先记 from_chars 简单版 / 或 to_number 在 C++26 可用):

int n = std::stoi("42");        // string → int(stod→double)
// C++17 高效版:
int n2;
auto [p, ec] = std::from_chars("42", "42"+2, n2);  // 不抛异常

数字转字符串(C++20 起推荐 std::format,最优雅):

std::string s = std::format("{}", 42);        // "42"
std::string s2 = std::format("{:06.2f}", 3.14159); // "003.14"

老写法 s = std::to_string(42) 也行,但 format 更强大。

6.4 [C++17] std::string_view:不拷贝的"视野"

string_view 是"别人字符串的一段视图"——不拥有数据、不拷贝。

std::string s = "hello world";
std::string_view sv(s.data() + 6, 5);  // "world" 的视图
sv = s;                    // 整个 s 的视图

为什么要它?性能。 函数接收 string 参数时,传值会拷贝整个字符串。 传 string_view 则零拷贝——只记录"从哪开始、多长"。

现代 C++ 最佳实践:

   std::string_view bad() {
       std::string s = "x";
       return s;   // 函数结束 s 销毁,视图悬空!编译会警告
   }

6.5 输出:print / println(C++23,推荐)

#include <print>
std::println("{} + {} = {}", 1, 2, 3);   // 1 + 2 = 3
std::print("不换行");

格式化语法(大括号里的内容):

{}                默认格式
{:d}              十进制整数
{:x} {:X}         十六进制
{:b}              二进制(bool 也能用,输出 true/false)
{:f}              浮点:默认 6 位小数
{:.2f}            保留 2 位小数
{:>10}            右对齐,宽度 10({:<10} 左对齐)
{:05d}            宽度 5,不足用 0 补
{:%Y-%m-%d}       日期格式(配合 chrono)

例:

std::println("{:>6} {:.2f}", "price", 3.14159);
// 输出: price 3.14

6.6 输出:printf(老风格,读旧代码要懂)

printf("%s %d %.2f\n", "值", 42, 3.14);
// %s 字符串 %d 整数 %f 浮点 %.2f 两位小数

printf 的危险:类型写错会"未定义行为"(打印乱码甚至崩溃)。 新代码一律用 println,不用 printf。

6.7 输入:读终端

#include <iostream>
std::string name;
int age;
std::cout << "名字和年龄:";
std::cin >> name >> age;        // 空格/换行分隔,逐个读

一行读一整行(含空格):

std::string line;
std::getline(std::cin, line);   // 读一整行

读数字直到失败/结束:

int n;
while (std::cin >> n) {         // 读不到整数时返回 false
    // 处理 n
}

注意 cin 与 getline 混用时的坑:cin >> 后按回车留下的换行符 会被 getline 直接读走。解决办法:cin.ignore() 清掉一个字符, 或全程用 getline 再手动解析。新手常见坑,遇到再说。

6.8 文件读写(IO 的完整版)

#include <fstream>
// 写文件
std::ofstream out("data.txt");
out << "hello " << 42 << std::endl;
// 读文件
std::ifstream in("data.txt");
std::string word;
while (in >> word) { /* 处理 */ }

读整个文件到字符串(C++17 风格):

std::ifstream in("data.txt");
std::string content((std::istreambuf_iterator<char>(in)),
                     std::istreambuf_iterator<char>());

文件打开失败怎么办?检查:

if (!out) { std::println("无法打开文件!"); return 1; }

本章小结

练习题


  1. 读入一行英文,统计其中字母、数字、空格的个数。
  2. 读入两行字符串,判断第一行是不是第二行的子串,并打印位置。
  3. 打印一个数字的十六进制和二进制表示。
  4. 把字符串 "3.14" 转成 double,再格式化打印保留 3 位小数。
  5. 把用户输入的若干行写入文件,再读出来打印。