C++ 学习笔记
完整整理版· 含原理解析 / 易错对比 / 速查表
涵盖:数据类型· 函数与作用域 · 容器 · 字符串 · 二叉树 · 排序 · 类结构
二、函数
2.1 函数返回值规范
C++ 对返回值有严格的类型和存在性要求,违反时会编译报错或产生未定义行为。
void 函数
▸不能有具体返回值,可以不写 return 语句
▸在循环或递归中需要提前退出时,写return;(空 return)即可终止函数
void printAll(vector<int>& v, int i) { if (i >= v.size()) return; // 空 return,终止递归 cout << v[i] << " "; printAll(v, i + 1); } |
有返回值的函数(int / bool / string 等)
▸函数体每条可能的执行路径都必须有 return 语句
▸仅在分支内写 return 不够——编译器不会分析 p 的值域,哪怕逻辑上已穷举所有情况
▸return 后面必须接值,不能空 return
// ❌ 错误:缺少全局 return bool check(int p) { if (p == 0) { return true; } if (p == 1) { return false; } // 编译器报错:not all control paths return a value } // ✅ 正确:补上兜底 return bool check(int p) { if (p == 0) { return true; } if (p == 1) { return false; } return false; // 兜底,消除编译警告/错误 } |
变量必须在函数开头定义,不能在分支内定义后在外部使用
C++ 的作用域规则:在 {} 内定义的变量,出了 {} 就销毁。如果最终需要返回某变量,务必在函数顶部声明它。
| // ❌ 错误示例:在 if 块内定义,块外返回 int compute(bool flag) { if (flag) { int result = 42; // result 仅在此 if 块内有效 } return result; // ❌ 编译错误:result 已超出作用域 } // ✅ 正确:在函数开头声明 int compute(bool flag) { int result = 0; // 先在外部声明并给默认值 if (flag) { result = 42; // 赋值而非定义 } return result; // ✅ } |
⚠️ 注意 if / for / while 的 {} 都适用同样规则。在循环体内定义的变量,每次迭代结束都会销毁,下一次迭代重新创建。 |
2.2 全局变量与局部变量的作用域规则
C++ 的变量查找规则:优先从当前作用域向外查找,最外层是全局作用域。函数参数和函数内部定义的局部变量会"屏蔽"(Shadow)同名的全局变量。以下四种情况务必分清:
情况 1:函数无同名参数,直接读写全局变量
函数内可直接访问和修改全局变量,修改是永久的:
int num = 10; // 全局变量 void modify() { num = 20; // 直接修改全局变量 } int main() { modify(); cout << num; // 输出 20,全局变量已被真正修改 } |
情况 2:函数参数与全局变量同名 → 参数屏蔽全局
函数内只认参数,全局变量被屏蔽。函数结束后全局变量不受影响:
int num = 10; void f(int num) { // 参数名与全局变量重名 num = 999; // 只修改了参数的副本 cout << num; // 输出 999 } int main() { f(50); cout << num; // 输出 10,全局变量未变 } |
情况 3:只是参数名重了,但调用时没传值 → 直接编译报错
参数名与全局变量同名不会自动传入全局值。参数就是参数,必须显式传入:
int x = 10; void func(int x) { cout << x; } int main() { func(); // ❌ 编译错误:too few arguments // 不会自动把全局 x=10 传进去 } |
情况 4:函数内部定义同名局部变量 → 局部屏蔽全局
与情况 2 类似,局部变量屏蔽全局变量,全局变量不受影响:
int num = 10; void f() { int num = 666; // 局部变量,与全局变量同名 cout << num; // 输出 666(局部) } int main() { f(); cout << num; // 输出 10,全局变量未变 } |
情况 5:函数间的局部变量互不可见
函数 A 中的局部变量,即使在 A 调用函数 B 时,B 也看不见:
| void func() { int y = x; // ❌ 编译错误:x 未声明 } int caller() { int x = 10; // x 只属于 caller func(); // func 内部看不到 caller 的 x } |
��说明这是 C++ 的"词法作用域"(Lexical Scope)规则:变量的可见性由源码位置决定,而非调用栈。若需跨函数共享数据,应用全局变量、引用传参或将数据封装在类中。 |
2.3 引用传参(&)——修改调用方的实参
C++ 函数默认是"值传递":把实参复制一份给形参,函数内对形参的修改不影响实参。若要修改调用方的变量,需要在形参前加 & 符号,改为"引用传递"。
| 值传递(默认) | 引用传递(加 &) |
形参定义 | void f(int x) | void f(int& x) |
操作对象 | 实参的副本 | 实参本身 |
修改是否影响外部 | ❌ 不影响 | ✅ 影响 |
调用方写法 | f(a); // 普通 | f(a); // 完全相同,不加 & |
适用场景 | 只读、计算结果 | 排序、修改数组、大对象避免拷贝 |
| // 值传递:函数内修改不影响外部 void doubleByVal(int x) { x *= 2; } // 引用传递:函数内修改直接作用于外部变量 void doubleByRef(int& x) { x *= 2; } // vector 引用传递(避免拷贝大数组,同时允许修改) void merge(vector<int>& nums, int left, int right, vector<int>& tem) { // 对 tem 的修改会同步到调用方的 tem tem[i++] = nums[l++]; } int main() { int a = 5; doubleByVal(a); cout << a; // 输出 5,未变 doubleByRef(a); cout << a; // 输出 10,已变 // 调用时写法完全相同,不需要在实参上加 & } |
��提示大型对象(如 vector、string)传参时,即使不需要修改,也建议用 const 引用 const vector<int>& 避免拷贝开销:既高效,又安全。 |
2.4 返回多种类型的值 → 结构体
C++ 函数只能有一个返回类型。若需返回多个不同类型的值(如同时返回一个 int 和一个 bool),可通过结构体包装,或使用 C++17 的 pair/tuple。
// 方法 1:定义结构体(最直观) struct Result { int index; bool found; }; Result search(vector<int>& v, int target) { for (int i = 0; i < v.size(); i++) { if (v[i] == target) return {i, true}; } return {-1, false}; } // 方法 2:pair(两个值) pair<int,bool> search2(vector<int>& v, int target) { // ... return {idx, true}; } auto [idx, found] = search2(v, 5); // C++17 结构化绑定 // 方法 3:返回结构体指针(动态分配,记得 delete) Result* searchPtr(vector<int>& v, int target) { Result* r = new Result(); // ... return r; // 调用方需要 delete r } |