目录1. 函数重载1.1 概念1.2 函数重载的条件1. 参数个数不同2. 参数类型不同3. 参数类型顺序不同1.3 注意事项1.4 函数重载的优点1.5 总结2. 引用2.1 概念C 引用底层原理2.2 语法格式2.3 基础用法2.4 进阶用法1. 作为函数参数传引用2. 传引用返回2.1 基础语法2.2 核心原理2.3 典型场景2.4 安全用法2.5 对比2.6 总结传值返回和传引用返回的返回值拷贝问题​编辑3. 常引用const 引用4. 举例:5. 指针的引用2.5 常见误区2.6 总结二次复习:1. 函数重载1.1 概念C 里的函数重载Function Overloading简单说就是允许在同一作用域中定义名字相同、但参数不同的多个函数让函数能更灵活适配不同使用场景。1.2 函数重载的条件想构成函数重载得满足“同名不同参” 具体看这些差异1. 参数个数不同比如void show() { cout 无参数的 show endl; } void show(int a) { cout 有 1 个 int 参数的 show endl; }调用时传参数量不同会匹配对应函数show(); // 调用 show() show(10); // 调用 show(int a)2. 参数类型不同参数类型有差异也能构成重载int add(int a, int b) { return a b; } double add(double a, double b) { return a b; }调用时传入参数的类型会决定调用哪个int res1 add(1, 2); // 调用 int add(int, int) double res2 add(1.1, 2.2); // 调用 double add(double, double)3. 参数类型顺序不同当参数类型组合顺序有变化也算重载常见于多类型参数场景void print(int a, char b) { cout 先 int 后 char: a , b endl; } void print(char b, int a) { cout 先 char 后 int: b , a endl; }调用示例print(10, A); // 调用 print(int, char) print(B, 20); // 调用 print(char, int)注意返回值不同不能单独作为重载条件因为调用函数时编译器不看返回值来匹配比如:// 编译报错仅返回值不同无法构成重载 int func() { return 0; } double func() { return 0.0; }1.3 注意事项容易踩的“坑”重载匹配歧义写重载函数时若调用代码让编译器无法明确匹配到唯一函数就会报错“歧义ambiguity” 。常见场景比如1. 默认参数导致歧义void func(int a) { cout func(int) endl; } void func(int a, int b 0) { cout func(int, int) endl; } // 调用时当传 1 个参数编译器分不清该选哪个 func func(10); // 编译报错歧义因为 func(10) 既可能匹配 func(int a) 也可能匹配 func(int a, int b 0) 用了默认缺省参数编译器无法确定就会报错。2. 隐式类型转换引发歧义当参数能被隐式转换比如 int 和 short 、 double 和 float 等也可能让编译器犯难void show(int a) { cout show(int) endl; } void show(short a) { cout show(short) endl; } // 调用传入 char可转 int/short 编译器不知道选谁 // show(A); // 编译报错歧义A 是 char 类型能隐式转 int 或 short 导致匹配歧义。1.4 函数重载的优点灵活适配场景同一功能如 “加法”“打印” 用不同参数调用时不用起不同函数名比如 addInt addDouble 代码更简洁、语义更直观。多态基础是 C 静态多态编译时多态的体现让函数调用在编译阶段就能确定具体执行哪个版本效率高。1.5 总结C函数重载允许同一作用域同名函数(同命不同参)靠参数个数、类型、顺序区分实现静态多态返回值不同不算。编译器靠名字修饰区分要避免调用时的匹配歧义 让代码更灵活。2. 引用2.1 概念引用 不是新定义一个变量而是给已存在变量取了一个别名编译器不会为引用变量开辟内存空间它和它引用的变量共同用同一块内存空间。比如水浒传中李逵宋江叫铁牛江湖上人称黑旋风林冲外号豹子头核心本质给已存在的变量/对象取一个“别名”和原变量共享同一块内存空间操作引用就等同于操作原变量。C 引用底层原理C 引用本质上是现有变量的别名编译器在底层并不会为引用单独分配一块内存空间存储数据而是会将引用处理成一个被常量修饰的指针这个隐藏的常量指针会存储目标变量的内存地址一旦引用完成初始化就永久绑定该地址无法再指向其他变量。定义引用时必须同步初始化正是因为底层对应的常量指针不能中途修改指向不存在无地址的空引用这也是引用和裸指针最核心的底层差异。当代码中操作引用比如读取、修改引用的值时编译器会自动完成解引用操作表面上写的是引用名直接读写数据底层等价于取出隐藏指针里存储的地址访问该地址对应的内存数据整个解引用过程由编译器隐式完成不需要开发者手动书写解引用符号。传引用作为函数参数时底层只是拷贝了目标变量的地址不会复制变量完整的数据因此可以省去大块对象拷贝带来的内存开销同时函数内部对引用的修改会直接作用于原始变量根源就是引用底层始终绑定着原始变量的内存地址。区分普通值传递和引用传递的底层逻辑十分清晰值传递会在栈上开辟新内存拷贝实参数据函数内修改的是栈上新副本引用传递仅在栈上创建一个隐藏常量指针存储实参地址全程操作原始变量的内存不会产生数据副本。另外常量引用底层同样是常量指针只是编译器会增加访问限制禁止通过该隐藏指针修改对应内存的数据以此实现只读绑定的效果所有引用的语法特性都依托于这套隐藏常量指针的底层实现规则。2.2 语法格式//类型 引用名 原变量; int a 10; int b a; // b 是 a 的别名此后操作 b 就等同于操作 a关键特性引用必须初始化定义时就得绑定原变量不能先定义再赋值。引用一旦绑定就不能再绑定其他变量“从一而终” 区别于指针。2.3 基础用法1. 普通变量的引用int、double 等最基础的用法给普通变量取别名int a 100; int b a; //给 a 取了一个别名 b b 200; // 修改 b 200, 等价于 a 也被修改为 200 cout a; // 输出 200不管操作 a 还是 b 改的都是同一块内存结果完全同步。2. 数组元素的引用给数组里的元素取别名方便操作int arr[3] {1, 2, 3}; int ref_arr0 arr[0]; // ref_arr0 是 arr[0] 的别名 ref_arr0 10; cout arr[0]; // 输出 103. 结构体/类成员的引用给结构体、类的成员取别名简化访问struct Person { string name; int age; }; Person p {Tom, 20}; string ref_name p.name; // ref_name 是 p.name 的别名 int ref_age p.age; // ref_age 是 p.age 的别名 ref_name Jerry; ref_age 22; cout p.name p.age; // 输出 Jerry 222.4 进阶用法1. 作为函数参数传引用作用替代指针实现“传址调用”让函数直接操作外部变量还能简化语法。对比指针的优势更简洁、更安全不用处理空指针且语法像传值。示例// 传引用直接操作原变量 void swap(int a, int b) { int temp a; a b; b temp; } int x 1, y 2; swap(x, y); cout x y; // 输出 2 1这里 swap 函数直接交换 x 和 y 的值因为 a 、 b 是 x 、 y 的引用。2. 传引用返回C 中传引用返回函数返回引用类型 是很实用的特性能让函数更灵活、高效。2.1 基础语法函数返回值类型是引用类型语法 类型 函数名(参数) 直接返回变量的引用别名。示例返回静态变量的引用int func() { static int val 10; // 静态变量存在全局区程序结束才销毁 return val; // 返回 val 的引用 } int main() { int ref func(); // ref 是 func 返回值 val 的引用 ref 20; // 修改 ref 等价于修改 val cout func(); // 输出 20val 被改了 return 0; }那这块为什么是返回静态变量的引用呢那返回普通局部变量引用可以吗如果返回普通局部变量的引用就会出现 “野引用” —— 引用指向的内存已经被释放后续操作这个引用时程序可能崩溃、行为异常比如输出随机值、触发段错误。错误示例 int badReturn() { int localVal 10; // 普通局部变量函数结束后销毁 return localVal; // 返回局部变量的引用 → 危险 } int main() { int ref badReturn(); //ref 是函数 badReturn 返回值 localVal 的别名 // 但是 localVal 已销毁ref 变成“野引用”操作它可能崩溃 cout ref; return 0; }普通局部变量比如 int localVal 10; 是 “栈上变量” 它的生命周期和所在函数或代码块绑定函数执行时栈会为它分配空间函数执行结束栈空间会被释放变量也就“销毁”了。静态变量用 static 修饰比如 static int val 10; 存储在 “全局/静态存储区” 它的生命周期和整个程序绑定从程序启动时分配内存直到程序结束才释放。所以静态变量的特殊之处生命周期长程序结束才销毁。返回静态变量的引用是安全的 — 即使函数结束静态变量的内存还在引用始终能正确指向它。2.2 核心原理内存角度返回“别名”而非拷贝普通函数返回值值返回会拷贝一份数据返回而引用返回不拷贝直接返回原变量的“别名”。调用者拿到的是原变量的引用操作它就等同于操作函数内的变量或全局/静态变量。底层实现类似指针但语法更简洁编译器对引用返回的处理底层和指针类似多数情况用指针实现但语法上无需解引用 * 更直观。可以理解为引用返回是“带语法糖的指针返回”。2.3 典型场景1. 让函数返回值支持“左值操作”普通值返回的函数返回值是临时常量不能被赋值左值要求是“可修改的变量”但引用返回的函数返回值是“变量的别名”可以当左值被赋值。示例模拟数组的 [] 运算符让函数返回值能被赋值int arr[5] {1,2,3,4,5}; // 返回数组元素的引用支持“左值赋值” int getNum(int i) { return arr[i]; } int main() { // 直接修改 arr[2] 的值getNum 返回引用可当左值 getNum(2) 100; cout arr[2]; // 输出 100 return 0; }这里 getNum(2) 返回 arr[2] 的引用所以能直接赋值实现类似数组 arr[2] 100 的效果。2. 避免大对象的拷贝提升效率如果函数返回的是大对象比如复杂的 struct 、 class 值返回会拷贝整个对象耗时、耗内存而引用返回不拷贝直接返回对象的引用效率更高。示例返回大结构体的引用struct BigData { int data[10000]; // 大数组拷贝成本高 }; // 静态变量存数据避免返回局部变量 BigData globalData; // 返回引用避免拷贝大对象 BigData getData() { return globalData; } int main() { BigData ref getData(); // 直接操作 ref等价于操作 globalData无需拷贝 ref.data[0] 100; return 0; }2.4 安全用法函数返回引用时返回的变量必须生命周期足够长在调用者使用引用时变量仍有效。安全的返回目标1. 全局变量整个程序运行期间都存在。2. 静态局部变量 static 修饰存在全局区函数结束后不销毁 。3. 堆内存变量用 new 分配手动管理生命周期需注意释放 。4. 函数参数的引用参数是调用者传入的调用者保证其生命周期 。2.5 对比传引用返回 vs 值返回 的对比特性传引用返回传值返回底层行为返回变量的“别名”不拷贝数据拷贝一份数据返回效率高无拷贝适合大对象低需拷贝适合小对象返回值能否当左值可以返回引用支持赋值)不可以返回临时常量赋值会报错安全风险若返回局部变量引用会导致“野引用”无此风险返回拷贝不关联原变量生命周期2.6 总结传引用返回的核心价值是 “高效 灵活”- 高效避免拷贝大对象提升程序性能。- 灵活让返回值支持左值操作赋值、链式调用等扩展函数用法。但必须注意 “生命周期” 问题返回的引用必须指向“生命周期足够长”的变量全局、静态、堆内存、函数参数等绝对不能返回局部变量的引用。只要避开“返回局部变量”这个雷区传引用返回能让代码更简洁、高效尤其在操作大对象、实现链式调用时非常实用。传值返回和传引用返回的返回值拷贝问题1. 值返回一定会产生临时拷贝当函数返回一个普通类型如 int 、 double 或对象时会触发一次拷贝函数执行到 return ret; 时会把 ret 的值拷贝到一个临时变量中。这个临时变量是一个右值生命周期只存在于表达式结束前。然后再把临时变量的值拷贝给 x 临时变量随即被销毁。需要注意的是对于 int 这类内置类型编译器可能会优化掉一次拷贝比如直接把 ret 的值赋给 x 但这个优化不改变语法规则。对于自定义对象比如 string 或你自己写的类值返回会调用拷贝构造函数来生成临时对象这是无法优化的。2. 引用返回完全没有拷贝当函数返回一个引用时不会产生任何临时变量因为它只是原变量的一个别名函数返回的 a 的引用直接指向 x 的内存地址。所以 y 就是 x 的别名修改 y 就是修改 x 全程没有任何拷贝。需要注意的是绝对不能返回局部变量的引用因为局部变量在函数结束时就被销毁了引用会变成“悬空引用”导致未定义行为。3. 为什么值返回会导致 SLAt(s, i) 1; 报错SLAt(s, i) 返回的是 sl.a[i] 的拷贝这个拷贝是一个临时变量右值。 要求左操作数是左值可以被修改的内存地址而临时变量没有持久的内存地址所以编译器会报错 “”: 左操作数必须为左值 。4. 引用返回为什么能解决这个问题SLAt(s, i) 返回的是 sl.a[i] 的引用这个引用直接指向原数组元素的内存地址左值。所以 1 可以直接修改原数组元素的值没有任何临时变量和拷贝。3. 常引用const 引用在 C 中 const 引用常量引用是指被 const 修饰的引用它的核心特性是不能通过该引用修改所指向的对象。这一特性让它在实际编程中非常有用尤其是在函数传参场景中。1. 语法const 类型 引用名 原变量;2. 作用让引用“只读”保护原变量不被修改还能接收不同类型的临时值隐式类型转换场景。场景 1保护原变量禁止通过引用修改int a 10; const int b a; // b 20; // 编译报错常引用不能修改原变量 a 20; // 允许直接修改原变量 cout b; // 输出 20原变量改了引用同步变化场景 2接收临时值隐式类型转换当用不同类型的值给常引用赋值时编译器会生成临时变量常引用绑定到临时变量普通引用无法绑定临时值会编译报错double a 3.14; // 普通引用int b a; 编译报错类型不匹配 // 常引用编译器生成临时 int 变量值为 3b 绑定到临时变量 const int b a; cout b; // 输出 3场景 3函数参数用常引用避免拷贝 保护数据如果函数不需要修改参数用 const 引用 传参既能避免拷贝大对象提升效率又能保证参数不被函数内部修改void print(const string str) { // str abc; // 编译报错常引用不能修改 cout str endl; } string s Hello; print(s); // 高效传参且 s 不会被修改3. 权限问题在 C 中 const 引用的核心规则可以概括为 “权限只能缩小或保持不能放大”。这里的“权限”指的是对对象的“修改权”具体来说(1) 基本权限逻辑当用引用绑定一个对象时引用的权限 不能超过原对象的权限。简单理解如果原对象是 可修改的非 const 可以用 const 引用绑定它权限缩小从“可改”变为“只读”。如果原对象是 不可修改的 const 则只能用 const 引用绑定它权限保持仍然“只读”不能用非 const 引用绑定否则会放大权限从“只读”变为“可改”这是禁止的。(2) 具体场景示例非 const 对象可修改原对象有“修改权”引用可以选择放弃修改权用 const 引用合法权限缩小。保留修改权用非 const 引用合法权限保持。int a 10; // 非 const 对象有修改权 int ref1 a; // 非 const 引用保留修改权→ 合法 const int ref2 a; // const 引用放弃修改权→ 合法 ref1 20; // 可以修改权限允许 // ref2 30; // 不能修改已放弃权限const 对象不可修改原对象没有“修改权”引用 必须也放弃修改权只能用 const 引用。如果用非 const 引用相当于“获得原对象没有的修改权”权限放大这是 禁止的。const int b 20; // const 对象无修改权 // int ref3 b; // 错误非 const 引用试图获得修改权权限放大 const int ref4 b; // 正确const 引用放弃修改权权限保持临时对象如字面量、表达式结果临时对象如 5 、 x y 是“只读”的默认无修改权因此只能用 const 引用绑定权限保持。不能用非 const 引用绑定权限放大。// 临时对象如字面量、表达式结果默认无修改权 const int ref5 5; // 正确const 引用权限保持 // int ref6 5; // 错误非 const 引用权限放大 int x 1, y 2; const int ref7 x y; // 正确xy 是临时对象const 引用绑定 // int ref8 x y; // 错误权限放大(3) 为什么禁止“权限放大”C 设计这一规则的核心目的是 保护对象的只读属性如果允许 const 对象被非 const 引用绑定就可以通过引用修改 const 对象违背了 const 的语义 const 本应保证对象不可修改。临时对象的生命周期短暂若允许非 const 引用修改可能导致不可预期的错误修改一个即将销毁的对象无意义且容易引发 bug。(4) 总结const 引用的权限规则本质是引用的“修改权限” ≤ 原对象的“修改权限”。非 const 对象 → 可绑定非 const 引用权限保持或 const 引用权限缩小。const 对象/临时对象 → 只能绑定 const 引用权限保持不能绑定非 const 引用禁止权限放大。这一规则是 C 类型安全的重要保障。4. 举例:理解这段代码中const 引用 作为函数参数以及相关常量、临时对象的关系我们可以从引用的权限规则、临时对象的常性这两个核心点展开分析为什么函数 func 的参数是 const int C 中引用的权限遵循“缩小不允许平移/放大允许”的规则普通引用如 int 要求权限放大或平移即实参的权限不能比引用小。而 const 引用const int 是权限缩小把可修改的权限限制为只读这种情况是允许的。func 用 const int 作为参数是为了兼容更多类型的实参包括普通变量、 const 变量、常量、临时对象等——这些实参要么本身是“只读”如 const 变量、常量要么会生成“只读的临时对象”如表达式结果、类型转换结果 const 引用 能安全地绑定到它们。我们逐个分析 main 中变量、常量、表达式传给 const 引用 或 func 的逻辑1. 绑定普通变量 int x int x 1; const int r1 x; func(x);对 r1 x 是普通 int 可修改) const int 是权限缩小把可修改变成只读允许绑定。对 func(x) func 的参数是 const int x 传参时权限缩小允许调用。2. 绑定 const 变量 const int y const int y 2; const int r2 y; func(y);对 r2 y 本身是 const int 只读 const int 是权限平移都是只读允许绑定。对 func(y) func 的参数是 const int 和 y 权限一致平移允许调用。3. 绑定常量如 10 const int r3 10; func(10);常量如 10 本身是“只读”的且是临时存在的。 const int 可以绑定到常量上——因为 const 引用 能延长临时对象的生命周期否则常量/临时对象会立即销毁。对 func(10) 10 是常量 func 的 const int 接收它权限平移允许调用。4. 绑定表达式结果临时对象const int r4 x * 10; func(x * 10);x * 10 是一个表达式会生成临时对象计算结果的临时 int 。临时对象默认是“只读”的具有常性所以必须用 const 引用 才能绑定普通引用 int 无法绑定临时对象。对 func(x * 10) 表达式生成临时对象 func 的 const int 接收它权限平移允许调用。5. 绑定类型转换的临时对象double d 1.1; const int r5 d; func(d);d 是 double 赋值给 const int 时会发生类型转换生成一个临时的 int 对象值为 1 这个临时对象具有常性所以必须用 const int 绑定。对 func(d) d 传参时先类型转换为临时 int 对象 func 的 const int 接收它权限平移允许调用。总结:const 引用 的核心作用是兼容“只读对象、临时对象、类型转换对象”通过“权限缩小/平移”的规则让函数或引用能安全绑定这些场景下的实参。而普通引用 int 无法做到这一点因为普通引用要求权限放大/平移且不能绑定临时对象。5. 指针的引用1. 语法类型* 引用名 指针变量;2. 用途在函数中修改指针本身比如让指针指向新的内存比二级指针更直观。示例函数内修改指针指向void resetPtr(int* ptr) { // ptr 是外部指针的引用修改 ptr 会影响外部 ptr new int(100); } int main() { int* p nullptr; resetPtr(p); // p 现在指向 new 出来的 int(100) cout *p; // 输出 100 delete p; return 0; }如果不用指针的引用用二级指针 int** 会更麻烦指针的引用让语法更简洁。3. 区别引用 vs 指针 的区别 : 虽然引用和指针都能间接操作变量但核心差异很大特性引用Reference指针Pointer初始化必须初始化定义时绑定变量可空 nullptr 可延迟初始化绑定关系一旦绑定不能换目标“从一而终”可随时指向不同变量/对象灵活语法复杂度像普通变量无需解引用 * 更简洁需解引用 * 、取地址 稍复杂安全性不会有空引用初始化后一定绑变量更安全可能有空指针 null 需检查底层实现多数编译器用指针实现语法糖但语义不同直接存储内存地址2.5 常见误区1. “引用是新变量”不是引用不占独立内存编译器不会为引用单独开空间它只是原变量的别名和原变量共享内存。2. “引用可以重新绑定”不行引用定义时必须绑定变量之后不能换绑其他变量。比如int a 1, b 2; int ref a; // ref b; // 这不是“换绑”而是把 b 的值赋给 a因为 ref 是 a 的别名3. 返回局部变量的引用绝对禁止局部变量在函数结束后会销毁返回它的引用会导致“野引用”程序运行时可能崩溃或出现未定义行为。2.6 总结引用是 C 里简化代码、提升效率的实用工具核心是给变量/对象取别名。常用场景传引用替代指针让函数传参更简洁、安全。用常引用保护数据、接收临时值。返回引用实现函数返回值当左值或简化数组/对象成员访问。只要记住“引用是别名、必须初始化、绑定后不能换”这些核心点再结合传参、返回值、常引用等用法就能灵活运用。以上便是函数重载和引用部分的有关内容感谢大家的观看二次复习:当我们需要使用其他文件中定义的命名空间时首先要引入存放该命名空间声明的头文件引入头文件后编译器就能识别这个命名空间内部的类、函数等标识此时即便不使用 using 语句我们也可以通过命名空间标识的完整写法正常调用里面的内容。如果在当前文件写下 using namespace 对应的命名空间那么在没有名称冲突的前提下当前文件内使用该命名空间的内容就不用再加命名空间前缀可以直接书写标识名称且这条 using 语句仅作用于当前文件不会对其他源文件产生任何影响。倘若当前文件同时 using 了两个不同的命名空间而这两个命名空间里存在名称完全相同的函数或者类此时直接裸写这个共用名称会触发编译歧义报错编译器无法判断我们想要调用哪一个命名空间内的内容想要解决这种冲突最简单的方式就是调用时手动补全命名空间前缀依靠命名空间标识的写法明确区分除此之外也可以选择不整体展开命名空间仅单独 using 需要用到的单个标识或是直接放弃 using 语句全程使用完整带双冒号的写法从根源避免命名冲突问题。包含头文件的作用头文件里放了命名空间、类、函数的声明只要 #include 这个头文件编译器就知道 bit::xxx、byte::xxx 这些符号真实存在不会报 “未定义标识符”。此时完整书写 A::init() 一定能正常编译运行。