← 课程目录
第 17 讲 模块五:批量处理数据

字符串常用操作

17 / 24
💡
本节课目标
把这几讲学到的下标遍历用到 string 上:用 .length() 取字符串长度、用下标访问单个字符、 用循环遍历字符串查找特定字符。
开场

字符串其实很像“字符的数组”

📖 理论讲解

回忆第 4 讲

第 4 讲学过 string,当时重点是拼接cin / getline 的区别。今天从另一个角度看它—— 一个字符串就是一串连续排列的字符,这一点和数组、vector 非常像:都可以用下标访问,都可以用循环遍历。

第一段

.length():字符串有多长

📖 理论讲解

和 vector 的 .size() 是同一类东西

对这里使用的英文字符串来说,字符串变量.length() 返回这个字符串一共有多少个字符:

length_demo.cpp CPP
#include <iostream>
#include <string>

int main() {
    std::string name = "Hello";
    std::cout << "长度:" << name.length() << std::endl;
    return 0;
}
💡
“Hello” 的长度是 5
Hello, 正好 5 个字符,空格、标点也都算一个字符。
⚠️
中文提醒:std::string 数的是字节
常见的 UTF-8 编码中,一个英文字母通常占 1 个字节,一个汉字通常占 3 个字节。 因此 std::string text = "你好";text.length() 通常是 6,而不是 2;用下标取到的也是其中一个字节, 不是完整汉字。本节练习先使用英文、数字和英文标点。
第二段

用下标访问单个字符

📖 理论讲解

下标同样从 0 开始

字符串变量[下标] 取出对应位置的单个字符—— 规则和数组、vector 完全一致:第一个字符下标是 0, 长度为 5 的字符串,最后一个字符下标是 4

index_demo.cpp CPP
#include <iostream>
#include <string>

int main() {
    std::string name = "Hello";
    std::cout << name[0] << std::endl;   // H
    std::cout << name[4] << std::endl;   // o
    return 0;
}
⚠️
有效字符下标仍然是 0 到长度减 1
"Hello" 来说,能代表实际字符的下标只有 0~4。 C++17 对字符串有一个特殊规则:只读 name[name.size()] 会得到结尾的空字符 '\0',但它不属于字符串内容; 使用比 size() 更大的下标仍然是未定义行为。
想让越界明确报错,用 .at()
name.at(4) 会得到 'o'name.at(5) 则会抛出 std::out_of_range。 这和第 16 讲的 vector.at() 是同一套规则。
📖 理论讲解

下标不仅能读,还能改

字符串变量[下标] 不只是用来读取字符的, 也可以直接对某个位置赋值,单独把这一个字符换掉:

mutable_demo.cpp CPP
#include <iostream>
#include <string>

int main() {
    std::string name = "Hello";
    name[0] = 'J';   // 直接改掉下标 0 的字符
    std::cout << name << std::endl;   // Jello
    return 0;
}
Python

Python:字符串不能这样改

name = "Hello"

name[0] = "J" # 报错!字符串不可变

VS
C++

C++:可以直接改某个字符

std::string name = "Hello";

name[0] = 'J'; // 没问题

💡
Python 的字符串“锁死”了,C++ 的没有
Python 里字符串是不可变的,想改只能整个重新赋值; C++ 的 string 更像数组、vector——每个位置都能单独改, 这也是为什么把它归到“批量数据”这一类里讲的原因。
第三段

遍历字符串

📖 理论讲解

循环条件换成 .length()

把上节课 vector 的遍历写法搬过来,把 .size() 换成 .length() 就行:

traverse_demo.cpp CPP
#include <cstddef>
#include <iostream>
#include <string>

int main() {
    std::string name = "Hello";

    for (std::size_t i = 0; i < name.length(); i++) {
        std::cout << name[i] << std::endl;
    }
    return 0;
}
只需要字符,不需要下标?
也可以使用上一讲的范围 forfor (char c : name) { std::cout << c << std::endl; }。 需要比较左右位置或记录位置时,再使用下标循环。
⚡ 动手实操 约 10 分钟

数一数某个字母出现几次

跑一下下面这段程序,输入一句包含好几个字母 a 的话, 确认统计结果正确:

count_char.cpp CPP
#include <cstddef>
#include <iostream>
#include <string>

int main() {
    std::string text;
    std::cout << "请输入一句话:";
    std::getline(std::cin, text);

    int count = 0;
    for (std::size_t i = 0; i < text.length(); i++) {
        if (text[i] == 'a') {
            count++;
        }
    }

    std::cout << "字母 a 出现了 " << count << " 次。" << std::endl;
    return 0;
}

注意 text[i] == 'a' 用的是单引号—— 单个字符用单引号,字符串用双引号,第 1 讲提过这个区别。

实战

动手写四个字符串程序

练习 17-1

打印长度

读入一个字符串(用 cin >> 即可,不含空格),打印它的长度。

练习 17-2

逐字符打印

读入一个字符串,遍历它,把每个字符单独打印一行。

练习 17-3

统计指定字符

读入一句话(用 getline,允许包含空格),再读入一个想统计的字符 (char 类型),打印这个字符在句子里出现了几次。

提示:照抄 count_char.cpp 的思路,把写死的 'a' 换成读入的字符变量。

练习 17-4

判断回文

读入一个单词,判断它是不是回文(正着读、反着读都一样,比如 "level")。补全下面的骨架:

palindrome_skeleton.cpp CPP
std::string word;
std::cin >> word;

bool isPalindrome = true;
std::string::size_type left = 0;
std::string::size_type right = word.length();   // 先指向末尾字符的后一个位置

while (left < right) {
    right--;   // 先退一步,再访问 word[right]
    // 比较 word[left] 和 word[right]
    // 不相等就能确定不是回文,可以提前 break
    left++;
}

// 根据 isPalindrome 输出“是回文”或“不是回文”
💡
想一想
left 从头往后走,right 从尾往前走,两人往中间靠拢—— 只要中途遇到一对字符不相等,就已经能确定不是回文了, 不用等两人真正相遇。骨架先让 right 指向末尾字符的后一个位置, 每轮先执行 right-- 再访问,可以避免空字符串时计算“长度减 1”发生下溢。
收尾

小结

📖 理论讲解

这节课你掌握了

能力用到的工具
取字符串长度.length()
访问单个字符字符串[下标],从 0 开始
遍历整个字符串下标循环,或范围 for
统计 / 查找字符遍历 + if 判断 s[i]

数组、vector、字符串,这三种“一批数据”的容器, 用的是同一套下标和遍历规则——学会一种,另外两种就顺理成章了。

💡
下节课预告
模块五最后一讲:把数组、vector、循环遍历、累加、找最大值全部用进一个完整程序—— 用 vector 录入全班成绩,输出总分、平均分、最高分。