#include <cctype>
#include <iostream>
#include <string>
using namespace std;
int main() {
string str;
getline(cin, str);
int whitespace = 0;
int digits = 0;
int chars = 0;
int others = 0;
// write your code here......
for(int i=0; i<str.length(); i++){
if(isalpha(str[i])){
chars++;
}else if(isdigit(str[i]))
{
digits++;
}else if(isspace(str[i])){
whitespace++;
}else {
others++;
}
}
cout << "chars : " << chars
<< " whitespace : " << whitespace
<< " digits : " << digits
<< " others : " << others << endl;
return 0;
}
核心问题:为什么 string 对象可以直接用 str[i]?
能直接用 str[i] 访问第i个字符,最核心的原因只有一个,也是 C++ 的核心特性:
C++ 标准库中的
std::string类,对「数组下标运算符[]做了运算符重载(operator [] 重载)」
✅ 关于「运算符重载」的通俗解释
在 C++ 中,[] 本来是给「原生字符数组」用的,比如:
cpp
运行
// 原生char数组,天生支持 arr[i] 访问 char arr[] = "hello"; cout << arr[0]; // 输出 'h',合法
而 string 本质是一个类 (class),不是原生数组,类的对象本身是不能直接用 [] 的。
C++ 的设计者为了让 std::string 用起来像原生字符数组一样简洁、易用,就在 std::string 类的内部,提前实现了 operator[] 这个重载函数,把「下标访问字符」的逻辑封装好了。
✅ string::operator[] 重载的具体作用
这个重载的 [] 运算符,规则是:
- 接收一个非负整数下标
i作为参数; - 返回字符串中「第
i个字符的引用(char&)」; - 下标从
0开始计数(和原生数组一致):str[0]是第一个字符,str[1]是第二个字符,以此类推; - 既能读取字符,也能修改字符。
举个合法使用的例子:
cpp
运行
#include <iostream>
#include <string>
using namespace std;
int main() {
string str = "hello"; // 初始化带内容的字符串
// 1. 读取:通过[]访问指定下标字符
cout << str[0] << endl; // 输出 'h'
cout << str[3] << endl; // 输出 'l'
// 2. 修改:通过[]直接修改指定下标字符
str[1] = 'i';
cout << str << endl; // 输出 "hillo",修改生效
return 0;
}
三、底层原理:string 为什么能实现这个重载?
你可能会好奇,string 内部是怎么存字符的,才能支持下标访问?
std::string的底层本质,就是封装了一个「动态可扩展的 char 类型字符数组」
字符串的所有字符,在内存中是连续存储的,string 类帮我们管理了这个数组的内存申请、扩容、释放。
而 operator[] 重载函数的底层逻辑,就是拿着你传入的下标 i,去访问这个内部 char 数组的第 i 个元素,再把结果返回给你。
简单说:string 是「披着类的外衣,内部是字符数组」,再通过运算符重载,让我们能用数组的语法访问,兼顾了易用性和安全性。
三、⚠️ 重中之重:str[i] 的「致命坑点」+ 必知规则(新手 99% 踩坑)
这是你提问里隐含的最关键注意事项,也是 C++ 中使用 str[i] 最容易出错的地方,一定要牢记:
规则 1:
std::string的operator[]运算符,不做下标越界检查!
什么是「下标越界」?
对于一个字符串 str,合法的下标范围是:0 ≤ i < str.size()
- 字符串的有效字符数量是
str.size(),下标最大只能到str.size()-1; - 如果你的下标
i < 0或者i ≥ str.size(),就属于越界访问。
越界访问的后果是什么?
C++ 编译器不会报错,程序能编译通过,但运行时会触发 未定义行为 (Undefined Behavior):
- 可能输出乱码、随机字符;
- 可能导致程序直接崩溃(闪退);
- 可能程序暂时没事,但埋下隐藏 bug,后续莫名其妙出错。
✅ 你最可能踩的坑:string str; 空字符串的 str[i] 绝对非法!
回到开头的 string str; —— 这是一个空字符串,它的 str.size() = 0,合法的下标范围是空的(没有任何合法下标)。
此时任何形式的 str[i] 都是越界访问,比如:
cpp
运行
string str; // 空字符串,size=0 cout << str[0]; // 非法!越界访问,未定义行为 str[0] = 'a'; // 非法!越界赋值,未定义行为
这是新手最容易犯的错误,一定要避免!
四、安全的替代方案:str.at(i) 成员函数
既然 str[i] 不做越界检查,很危险,那有没有安全的下标访问方式?
答案是:有!使用 std::string 的成员函数 str.at(i)
str.at(i) 和 str[i] 的对比
✅ 相同点:
- 下标计数规则一致:从 0 开始,
str.at(0)是第一个字符; - 功能一致:既能读取字符,也能修改字符;
✅ 核心不同点(关键优势):
str.at(i)会做「严格的下标越界检查」!
越界时的差异演示:
cpp
运行
#include <iostream>
#include <string>
using namespace std;
int main() {
string str = "hello"; // size=5,合法下标0~4
// 情况1:str[i] 越界,无检查,程序运行崩溃/乱码
cout << str[10]; // 越界,未定义行为,危险!
// 情况2:str.at(i) 越界,主动抛出异常,程序可控
cout << str.at(10); // 编译器检测到越界,直接抛出 std::out_of_range 异常
return 0;
}
结论:如何选择?
- 追求极致效率,且能 100% 保证下标合法 → 用
str[i]; - 日常开发、新手学习、不确定下标是否合法 → 优先用
str.at(i),虽然效率略低一点点,但能避免致命的越界 bug,性价比极高。
✅ 总结(所有知识点浓缩,建议收藏)
string str;→ 对std::string做默认初始化,得到一个空字符串(长度 0,无字符);str[i]可用的核心原因 →std::string类重载了数组下标运算符operator[];string底层本质 → 封装了连续存储的 char 类型动态数组,[]本质是访问这个数组的第 i 个元素;- 核心禁忌 → 空字符串(
size=0)的任何str[i]都非法;下标i >= str.size()也是非法; - 安全原则 → 新手用
str.at(i)替代str[i],能自动检查下标越界,规避 99% 的下标错误; - 下标规则 → 永远从 0 开始,最后一个字符的下标是
str.size()-1。
希望以上内容能帮你彻底理清这个知识点,这是 C++ string 最基础也是最核心的用法之一 ✨

京公网安备 11010502036488号