当前位置: 首页 > news >正文

C++ 入门基础(3)

目录

1. 函数重载

1.1 概念

1.2 函数重载的条件

1. 参数个数不同

2. 参数类型不同

3. 参数类型顺序不同

1.3 注意事项

1.4 函数重载的优点

1.5 总结

2. 引用

2.1 概念

2.2 语法格式

2.3 基础用法

2.4 进阶用法

1. 作为函数参数(传引用)

2. 传引用返回

1. 基础语法

2. 核心原理

3. 典型场景

4. 安全用法

5. 对比

7. 总结

3. 常引用(const 引用)

4. 指针的引用

2.5 常见误区

2.6 总结

在上篇C++入门基础(2)中,小编主要讲解了命名空间,C++输入和输出以及缺省参数的用法,在这篇文章我们将继续讲剩余内容。

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 概念

引用 不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共同用同一块内存空间。比如:<<水浒传>>中李逵,宋江叫"铁牛",江湖上人称"黑旋风";林冲,外号豹子头;

核心本质:给已存在的变量/对象取一个“别名”,和原变量共享同一块内存空间,操作引用就等同于操作原变量。

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];  // 输出 10  

3. 结构体/类成员的引用

给结构体、类的成员取别名,简化访问:
 

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 22  

2.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 等价于修改 valcout << func();       // 输出 20(val 被改了)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];  // 输出 100return 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 总结

传引用返回的核心价值是 “高效 + 灵活”:


 - 高效:避免拷贝大对象,提升程序性能。
- 灵活:让返回值支持左值操作(赋值、链式调用等),扩展函数用法。


但必须注意 “生命周期” 问题:返回的引用必须指向“生命周期足够长”的变量(全局、静态、堆内存、函数参数等),绝对不能返回局部变量的引用。
只要避开“返回局部变量”这个雷区,传引用返回能让代码更简洁、高效,尤其在操作大对象、实现链式调用时,非常实用。

3. 常引用(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 变量(值为 3),b 绑定到临时变量  
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 不会被修改 

4. 指针的引用

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++ 里简化代码、提升效率的实用工具,核心是给变量/对象取别名。常用场景:
 
- 传引用替代指针,让函数传参更简洁、安全。
- 用常引用保护数据、接收临时值。
- 返回引用实现函数返回值当左值,或简化数组/对象成员访问。
 
只要记住“引用是别名、必须初始化、绑定后不能换”这些核心点,再结合传参、返回值、常引用等用法,就能灵活运用。

以上便是函数重载和引用部分的有关内容,感谢大家的观看!

http://www.lryc.cn/news/608871.html

相关文章:

  • 自私挖矿攻击
  • C++引用:高效安全的别名机制详解
  • RPG增容3:尝试使用MVC结构搭建玩家升级UI(一)
  • Claude Code入门学习笔记(四)--Claude Code的使用
  • [硬件电路-150]:数字电路 - 数字电路与模拟电路的异同
  • 志邦家居PMO负责人李蓉蓉受邀为PMO大会主持人
  • Rust:开发 DLL 动态链接库时如何处理 C 字符串
  • 2025-0803学习记录21——地表分类产品的精度验证
  • 多向量检索:lanchain,dashvector,milvus,vestorsearch,MUVERA
  • gemini-cli +llms
  • 嵌入式硬件篇---Openmv
  • 问题集000
  • 对接古老系统的架构实践:封装混乱,走向有序
  • ⭐CVPR2025 FreeUV:无真值 3D 人脸纹理重建框架
  • 专网内网IP攻击防御:从应急响应到架构加固
  • 第十章:如何真正使用Java操作redis
  • 语义分割--deeplabV3+
  • 洛谷——P1048 [NOIP 2005 普及组] 采药
  • 在 macOS 上通过 Docker 部署DM8 (ARM 架构)
  • 关于Hugging Face【常见问题解决方案】
  • Linux网络编程 ---五种IO模型
  • 12.Redis 主从复制
  • LabVIEW驱动点阵实时控制系统
  • 力扣热题100----------141.环形链表
  • Spring MVC 九大组件源码深度剖析(一):MultipartResolver - 文件上传的幕后指挥官
  • 如何查看SoC线程的栈起始地址及大小
  • Mysql的MVCC是什么
  • 主成分分析法 PCA 是什么
  • 2、RabbitMQ的5种模式基本使用(Maven项目)
  • kafka 是一个怎样的系统?是消息队列(MQ)还是一个分布式流处理平台?