CC++ & Algorithm

字符串的分割、合并与格式化

较难2
语言版本:通用
概述:学会如何把一个长字符串按指定分隔符切成小块(分割),以及如何将多个小块用分隔符连起来(合并),还能控制数字的显示格式。

字符串的分割、合并与格式化——让文字像乐高一样拆拼

你有没有遇到过这样的场景:老师发来一串学生名单,中间用逗号隔开,你要把它拆成一个个名字;或者你想把几个数字拼成“12-34-56”这样的格式;又或者你要把计算出来的价格保留两位小数显示?编程中,字符串的分割、合并与格式化就是专门干这些事的。它们能帮你把长字符串按规则切开(分割)、把多个小块连起来(合并)、还能控制数字或文本的显示样式(格式化)。学会它们,你就能像变魔术一样处理文字数据!

下面我们分三招来学,每一招都配生活例子和代码,最后还有完整的运行示例和避坑指南。


一、字符串分割(Split)——把一根长绳子剪成短截

生活例子:过年奶奶剪彩带,或者你把一串电话号码“138-1234-5678”按“-”切开,得到区号、前四位和后四位。编程中,分割就是把一个字符串按照指定分隔符(比如逗号、空格、破折号)变成字符串列表(数组)。

做法对比

  • C++:标准库没有直接的split函数,你需要自己动手。最常用的方法是:用istringstream(输入字符串流)配合getline按分隔符逐段读取。这种方法简单可靠,适合单字符分隔符。如果分隔符复杂(比如多个字符或正则),可以用std::regex或Boost库。
  • Python:内置str.split(sep)方法,直接返回列表,超级方便。

代码细节

C++ 手动实现 split(基于 istringstream + getline)

#include <iostream>
#include <sstream>   // istringstream, ostringstream
#include <string>
#include <vector>
using namespace std;

// 自定义 split 函数:按单个字符分隔
vector<string> split(const string& s, char delim) {
    vector<string> tokens;          // 存放分割后的每个片段
    istringstream iss(s);           // 把字符串包装成输入流
    string token;                   // 临时存储读到的片段
    while (getline(iss, token, delim)) {  // 每次读取到分隔符为止
        tokens.push_back(token);    // 将片段加入vector
    }
    return tokens;
}

关键点getline(iss, token, delim)会从流中读取字符直到遇到delim,把读到的内容存入token,然后丢弃分隔符。循环直到流结束。注意:如果原始字符串以分隔符结尾(如"a,b,"),最后一次getline会读到空字符串,这是正常行为。如果你不希望出现空串,可以在添加前判断token是否为空。

Python 的分割

# 直接使用 split 方法
data = "apple,banana,orange,grape"
fruits = data.split(',')   # 返回列表 ['apple', 'banana', 'orange', 'grape']
print(fruits)

常见错误与注意事项

  1. C++ 忘了包含头文件#include <sstream>#include <string> 必须写。
  2. 分隔符是多个字符getline只支持单字符分隔符。若分多个字符(如"**"),需要用其他方法,比如用std::string::find手动查找,或者用正则。
  3. 空字符串问题:如果字符串以分隔符开头或结尾,getline会产生空字符串片段。比如",a,b"分割后第一个是空串。需要的话可以手动过滤。
  4. Python 中 split 的默认行为split()不带参数时,会按任意空白字符(空格、制表符、换行等)分割,并且自动去掉开头和结尾的空串。但如果指定分隔符,则不会自动去除。

二、字符串合并(Join)——把积木拼成一串

生活例子:你有三个数字“12”“34”“56”,想用“-”连起来变成“12-34-56”。编程中,合并就是把一个列表(或数组)的所有元素用指定的分隔符拼接成一个字符串。

做法对比

  • C++:可以用循环逐个加+,但效率低(每次拼接都创建新字符串)。推荐用ostringstream(输出字符串流)高效合并,或者用std::accumulate(但性能不如ostringstream)。
  • Python"分隔符".join(可迭代对象) 一行搞定。

代码细节

C++ 手动实现 join(基于 ostringstream)

#include <iostream>
#include <sstream>
#include <string>
#include <vector>
using namespace std;

// 自定义 join 函数:用分隔符连接字符串列表
string join(const vector<string>& parts, const string& sep) {
    ostringstream oss;                      // 流式输出,高效拼接
    for (size_t i = 0; i < parts.size(); ++i) {
        if (i > 0) oss << sep;              // 除了第一个元素,前面都加分隔符
        oss << parts[i];                    // 输出当前元素
    }
    return oss.str();                       // 返回拼接后的字符串
}

为什么用 ostringstream 而不是直接 +?假设有100个字符串,用+拼接时,每次都会生成新字符串,导致大量内存分配。而ostringstream像流水线一样,把内容依次写入缓冲区,最后一次性生成字符串,性能好很多。

Python 的 join

parts = ["12", "34", "56"]
joined = "-".join(parts)   # 结果 "12-34-56"
print(joined)

注意:join是字符串的方法,分隔符字符串写在前面。它要求参数是可迭代对象(列表、元组、生成器等),并且元素必须是字符串。如果元素是数字,需要先转成字符串。

常见错误与注意事项

  1. C++ 中直接把vector<string>传给join:确保元素是string类型,如果是int,需要先转成字符串(用to_string)。
  2. Python 中元素类型错误"-".join([1,2,3])会报错,因为数字不能自动转字符串。要写成"-".join(map(str, [1,2,3]))
  3. 合并时空列表/空向量:C++ 的join处理空向量时,循环不执行,返回空字符串。Python 的join处理空列表时也返回空字符串,合理。

三、字符串格式化(Format)——给数字穿上漂亮衣服

生活例子:考试成绩要显示“92.50”,或者时间显示“08:05”,又或者商品价格保留两位小数“¥19.90”。格式化就是控制数字或文本的显示方式,比如:补零、设置宽度、对齐、保留小数位数等。

做法对比

  • C++
    • C风格:sprintf(不安全,可能缓冲区溢出,不推荐在新代码中使用)。
    • C++风格:用ostringstream配合<iomanip>中的操控符(setwsetfillfixedsetprecisionleftright等)。
  • Python
    • 旧式:"%10.2f" % value
    • 现代:str.format() 或 f-string(最推荐,Python 3.6+)。

详细控制符解释

C++ 常用操控符(需要#include <iomanip>

操控符作用例子
setw(n)设置下一个输出的字段宽度为nsetw(8) 表示输出至少占8个字符
setfill(c)设置填充字符(默认空格)setfill('0') 用0填充空白位置
fixed浮点数以定点格式显示(即保留小数位数)fixed << setprecision(2) 保留2位小数
setprecision(n)设置浮点数精度(小数位数)setprecision(3) 显示3位小数
left / right / internal对齐方式(左/右/内部)left << setw(10) << "hello" 左对齐占10格

重要setw只对紧跟着的一个输出有效,每次都需要重新设置。而fixedsetprecision是持久化的,设置后影响后续所有浮点数输出,直到被改变。

C++ 示例:打造完美格式

#include <iostream>
#include <sstream>
#include <iomanip>
using namespace std;

int main() {
    double pi = 3.1415926535;
    int num = 42;

    // 1. 保留两位小数
    ostringstream oss1;
    oss1 << fixed << setprecision(2) << pi;
    cout << "pi保留2位小数: " << oss1.str() << endl;   // 输出 3.14

    // 2. 宽度8,不足补0(用于学号、编号)
    ostringstream oss2;
    oss2 << setw(8) << setfill('0') << num;
    cout << "编号(补零): " << oss2.str() << endl;     // 输出 00000042

    // 3. 左对齐宽度10
    ostringstream oss3;
    oss3 << left << setw(10) << "Hello" << "!";
    cout << "左对齐: " << oss3.str() << endl;         // 输出 "Hello     !"

    // 4. 混合使用:学号4位补零,成绩保留1位小数
    ostringstream oss4;
    oss4 << "学号: " << setw(4) << setfill('0') << 88;
    oss4 << " 成绩: " << fixed << setprecision(1) << 92.36;
    cout << oss4.str() << endl;   // 输出 "学号: 0088 成绩: 92.4"
}

Python 的格式化(f-string)

pi = 3.1415926535
num = 42

# f-string 方式(最推荐)
print(f"pi保留2位小数: {pi:.2f}")          # "3.14"
print(f"编号(补零): {num:08d}")           # "00000042"
print(f"左对齐: {'Hello':<10}!")          # "Hello     !"
print(f"学号: {88:04d} 成绩: {92.36:.1f}") # "学号: 0088 成绩: 92.4"

# 或者用 format 方法
print("pi: {:.2f}  num: {:04d}".format(pi, num))

格式说明符格式{变量名:填充字符?对齐?宽度.精度?类型}

  • 对齐:<左对齐,>右对齐,^居中。
  • 类型:d整数,f浮点数,s字符串等。
  • 宽度和精度放在一起:08d表示宽度8补0,.2f表示2位小数。

常见错误与注意事项

  1. C++ 中忘记#include <iomanip>setw等操控符无法使用。
  2. C++ 中setw只生效一次:如果连续输出多个字段需要多次设置,否则后面的字段会使用默认宽度。
  3. C++ 中浮点数精度设置不持久fixedsetprecision是持久化的,但如果后面输出另一个浮点数,可能被影响。建议每次格式化前清空ostringstream并重新设置。
  4. Python 中格式字符串写错:比如{num:08d},如果num是浮点数会报错。确保格式符匹配变量类型。
  5. 缓冲区溢出(C 风格 sprintf):sprintf不检查缓冲区大小,容易写越界,建议用snprintf或C++流代替。

完整可运行示例(C++ 和 Python 都带详细注释)

下面给出一个综合例子,模拟一个学生信息处理场景:按逗号分割学生名字和成绩,然后按格式输出。

C++ 版本

#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <iomanip>   // setw, setfill, fixed, setprecision
using namespace std;

// 分割函数
vector<string> split(const string& s, char delim) {
    vector<string> tokens;          // 存放分割结果
    istringstream iss(s);           // 字符串流
    string token;                   // 每次读到的片段
    while (getline(iss, token, delim)) {
        if (!token.empty()) {       // 避免空字符串(可选)
            tokens.push_back(token);
        }
    }
    return tokens;
}

// 合并函数
string join(const vector<string>& parts, const string& sep) {
    ostringstream oss;              // 高效拼接
    for (size_t i = 0; i < parts.size(); ++i) {
        if (i > 0) oss << sep;
        oss << parts[i];
    }
    return oss.str();
}

int main() {
    // 模拟原始数据:学生姓名和成绩用逗号分隔
    string raw_data = "张三,92.5,李四,88.3,王五,76.8";
    // 第一步:按逗号分割原始数据
    vector<string> fields = split(raw_data, ',');
    // 现在 fields = ["张三","92.5","李四","88.3","王五","76.8"]

    // 第二步:将姓名和成绩配对,并格式化输出
    cout << "学生成绩单(格式化版):" << endl;
    for (size_t i = 0; i < fields.size(); i += 2) {
        string name = fields[i];
        double score = stod(fields[i+1]);   // 字符串转double

        // 使用ostringstream格式化:姓名左对齐宽度10,成绩保留1位小数且宽度6
        ostringstream line;
        line << left << setw(10) << name;                 // 左对齐宽度10
        line << " " << right << setw(6) << fixed << setprecision(1) << score;
        cout << line.str() << endl;
    }

    // 第三步:将格式化后的各行合并成一个表格字符串(用换行符分隔)
    vector<string> formatted_lines;
    for (size_t i = 0; i < fields.size(); i += 2) {
        ostringstream line;
        line << left << setw(10) << fields[i];
        line << " " << right << setw(6) << fixed << setprecision(1) << stod(fields[i+1]);
        formatted_lines.push_back(line.str());
    }
    string table = join(formatted_lines, "\n");
    cout << "\n完整表格:\n" << table << endl;

    // 额外演示:数字补零(学号)
    int student_id = 123;
    ostringstream oss_id;
    oss_id << setw(6) << setfill('0') << student_id;
    cout << "\n学号: " << oss_id.str() << endl;   // 输出 000123

    return 0;
}

输出

学生成绩单(格式化版):
张三               92.5
李四               88.3
王五               76.8

完整表格:
张三               92.5
李四               88.3
王五               76.8

学号: 000123

Python 版本

# 原始数据
raw_data = "张三,92.5,李四,88.3,王五,76.8"

# 第一步:分割
fields = raw_data.split(',')   # ['张三','92.5','李四','88.3','王五','76.8']

# 第二步:格式化输出
print("学生成绩单(格式化版):")
for i in range(0, len(fields), 2):
    name = fields[i]
    score = float(fields[i+1])
    # f-string 格式化:姓名左对齐宽度10,成绩右对齐宽度6保留1位小数
    print(f"{name:<10} {score:>6.1f}")

# 第三步:合并成表格字符串
lines = [f"{name:<10} {float(score):>6.1f}" for name, score in zip(fields[::2], fields[1::2])]
table = "\n".join(lines)
print("\n完整表格:\n" + table)

# 额外演示:学号补零
student_id = 123
print(f"\n学号: {student_id:06d}")   # 输出 000123

避坑指南(新手最容易犯的错)

  1. C++ 分割时处理空字符串:如果输入是"a,,b",getline会产生一个空字符串。视情况决定是否要过滤掉。
  2. C++ 分割复杂分隔符:不要用getline去分割多个字符的分隔符,它会失败。需要改用find+substr循环或正则。
  3. C++ 合并时不要用+连接大量字符串:效率低,使用ostringstream
  4. Python 中splitjoin搞混了:记住split是字符串的方法(str.split),join也是字符串的方法(sep.join(iterable)),不是列表的方法。
  5. 格式化字符串中的类型不匹配:比如对整数使用{:02f}会报错,整数应该用{:02d}
  6. 忘记包含头文件:C++ 中#include <sstream>#include <iomanip>#include <string>#include <vector>都得写全。
  7. C++ 中setw只影响下一个输出:如果你连续输出多个字段,每个都要单独设setw

相关指引

  • 想要更强大的字符串处理(正则表达式)?可以学习 std::regex(C++11)或 Python 的 re 模块。
  • 想了解 C++ 字符串流的更多用法?搜索“std::istringstreamstd::ostringstream 详解”。
  • 如果你需要对数字进行格式化输出(如科学计数法、十六进制),C++ 的 <iomanip> 还有很多操控符,比如 hexdecoctscientific 等。
  • Python 中 f-string 的完整语法可以参考官方文档:“PEP 498 – Literal String Interpolation”。

掌握了分割、合并、格式化这三招,你就能像搭积木一样任意改造字符串,处理成绩单、日志、配置文件都得心应手。快去试试吧!

例题精讲

1单选题

在Python中,以下哪个方法用于将字符串按指定分隔符拆分为列表?

Asplit()
Bjoin()
Cformat()
Dreplace()
2单选题

在Python中,将列表['a','b','c']合并成字符串'a,b,c'应使用哪个方法?

Ajoin()
Bsplit()
Cformat()
Dconcat()
3判断题

在Python中,字符串格式化可以使用f-string,例如f"Hello {name}"。

4填空题
给定字符串s = "apple,banana,cherry",将其按逗号分割成列表,代码:result = ___
5填空题
将列表words = ["Hello", "World"]用空格连接成字符串,代码:result = ___