跳转至

易错点

字符串

类型 推荐方法 说明
int a[5](裸数组) std::size(a) C++17,最清晰
int a[5] sizeof(a)/sizeof(a[0]) 经典,但仅限未退化时
int a[5] 传进函数 ❌ 拿不到 必须额外传 size
std::array<int,5> a a.size() 任何时候都行
std::vector<int> v v.size() 任何时候都行
std::string s s.size() 任何时候都行
char c[](C 字符串) strlen(c) 不含 '\0'

string与char数组的区别

简单一句话回答:在“访问方式”上很像,在“本质和规则”上完全不同。

作为初学者,你可以把string理解为带有各种高级功能的超级 char 数组,但千万别把它当成真正的 char 数组去用(这就是你上一题报错的原因)。

我帮你把它们的相似点和致命区别彻底理清楚:

✅ 相似点:为什么用起来像数组?

  1. 支持下标访问:std::string 重载了 [] 运算符。
    1
    2
    3
    string str = "hello";
    cout << str[0]; // 输出 'h'
    cout << str[1]; // 输出 'e'
    
  2. 内存是连续的:在 C++11 及以后的标准里,string 的底层确实是一块连续的字符内存,所以你可以用 &str[0] 拿到首地址。
  3. 可以无缝转换:你可以通过 str.c_str() 或 str.data() 拿到 C 风格的 const char* 指针,直接传给那些只认 char 数组的老函数。

❌ 本质区别:为什么不能直接等同?

1. 身份不同:一个是“类”,一个是“原始内存”

  • char arr[10] 是一块光秃秃的内存,它本身没有大小记录,没有自带方法。
  • std::string 是一个完整的类(对象)。它内部不仅存了字符,还存了“字符串长度”、“内存容量”等信息,并且自带了几十个好用的函数(比如 .size(), .substr(), .find())。

2. 赋值规则不同(你上题报错的核心)

  • char arr[10] 是极其死板的。你不能在定义之后用 = 给它赋值,更不能写成 char arr[str.size()] = str;。
  • std::string 是非常智能的,它支持直接赋值、拼接:
1
2
3
string a = "hello";
string b = a;        // 直接拷贝,没问题
a = a + " world";    // 自动扩容,没问题

3. 大小可变性不同

  • char arr[10] 的大小在定义时就固定死了。你无法让它自动变长,装多了就会内存溢出(越界)。
  • std::string 是动态扩容的。你随时可以 push_back 或 +=,它内部会自动去申请更大的内存。

4. 安全性不同

  • char arr[10]; arr[20] = 'a'; 编译器不会报错,但程序运行会崩溃或产生极其隐蔽的 Bug(这叫缓冲区溢出)。
  • string str; str[20] = 'a'; 如果你用 str.at(20) 会直接抛出异常报错;如果用 str[20],虽然不报错,但通常也是越界,不过 string 本身会帮你管理内存,不容易造成灾难性崩溃。

💡 形象比喻

  • char 数组:就像一块划好格子的水泥地。大小固定,你只能往里放东西,不能直接把另一块地“赋值”给它。想复制内容,必须用 strcpy 一个个搬。
  • std::string:就像一个带有智能收纳功能的箱子。它自带标签(记录长度),能自动变大变小,你可以直接把另一个箱子里的东西倒进来(=),还能随意在中间掏个洞(substr)。

🎯 初学者的实战建议

  1. 日常写题、写项目,无脑用 std::string。它安全、方便、功能强大。
  2. 什么时候必须用 char[]? 只有当你需要调用一些老旧的 C 语言 API(比如 fopen 打开文件),或者嵌入式开发极度抠内存时。
  3. 不要写 char arr[n] = str;。如果非要把 string 转成 char 数组,正确的做法是:
    1
    2
    3
    string str = "hello";
    char arr[100];
    strcpy(arr, str.c_str()); // 把 str 的内容拷贝到 arr
    

把 std::string 当成一个完整的对象,而不是单纯的数组。记住这一点,你以后就不会再踩 char 数组的那些坑了!

char 数组与 std::string 对比表

特性 char 数组(C 风格字符串) std::string
本质 就是一块 char 内存,以 '\0' 结尾 一个类,封装了字符缓冲区
头文件 <cstring>(函数) <string>
长度 不知道,靠 '\0' 判断结尾 内部存了长度,.size() O(1)
内存管理 手动,固定大小,可能溢出 自动扩容,不用操心
赋值 不能直接 =,要 strcpy 直接 =,+ 拼接
比较 strcmp 直接 ==、<、>
安全性 容易越界、溢出 相对安全
是否对象 不是,是原始内存 是,有成员函数

一句话:char 数组是”裸内存 + 约定”,std::string 是”封装好的字符串类”。

指针

赋值操作总结表

写法 是否合法 含义 说明
p = &a; ✅ 让 p 指向 a 赋变量地址,最常用
p = q; ✅ 让 p 指向 q 指向的地方 指针互相赋值,共享地址
p = nullptr; ✅ 让 p 不指向任何地方 空指针,安全
p = arr; ✅ 数组名退化为首地址 数组名本身就是首地址
p = new int(5); ✅ 动态内存 记得用 delete 释放
p = 10; ❌ 不能把值赋给指针 语法错误,类型不匹配
int* p = pd;(类型不同) ❌ 类型必须匹配 两个指针类型不同
p = (int*)10; ⚠️ 语法可过,极度危险 强制转换不安全
*p = 100; ✅ 把 p 指向的变量改成 100 看等号左边有没有 *,有就是改“指向的内容”,没就是改“指针自己”
*p = &a; ❌ *p 是 int,不能赋地址 类型不匹配
int* p = &a; ✅ a 的地址赋给指针 p int* ← int*

定义时:int* p = &a; —— 可以拿地址初始化指针 非定义时:p = &a; —— 不行,p 已经是它指向的那个变量了,只能给它赋值 区别就在于 前面有没有 int: int p → p 是指针,可以接地址 *p → p 指向的那个变量,只能接值

核心:看等号左边有没有*有, * 就是改“指向的内容”,没 * 就是改“指针自己”

指针安全警告

操作 是否合法 说明
使用未初始化的指针 ❌ 未定义行为,直接用会崩溃
使用已释放/越界的指针 ❌ 未定义行为,会破坏内存
越界指针 ❌ 指针跑到数组外面,写进去会破坏别的内存
悬空指针 ❌ 指向的内存已经被释放,写进去会破坏已归还的内存

共同点:指针看起来”还有效”,其实已经无效,C++语法不会提醒你。

函数内无法用 sizeof 得到数组长度,在函数内部,sizeof(arr) 拿不到数组长度,因为数组作为参数传进来时已经退化成指针了,必须额外传 size