网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
🍞三.函数重载
💡函数重载:
- 是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数
- 这些同名函数的形参列表(参数个数 或 类型 或 顺序)必须不同,常用来处理实现功能类似数据类型不同的问题
👆简单来说:
- 即实现了允许可以同时有多个同名函数的存在,且同名函数可以用来解决不同的实际问题【即赋予了函数不同的意义】
- 本质是为了解决C语言中不允许同名函数问题而出现的
❗特别注意:
-
构成函数重载,必须满足(三者满足其一即可):
- 函数参数的个数不同
- 函数参数的类型不同
- 函数参数的顺序不同
👉示例:
👆从示例中不难发现:
-
我对写了两个名字同为
Add
的函数,它们之间构成函数重载(因为函数参数的类型不同) -
可以看出编译器会根据我们函数参数的类型去匹配相对应的函数进行调用
-
其中之所以第二个
Add
函数也输出2
,是因为其返回值的类型为int
,使其原本返回类型为double
的值被强制转换类型为int
- 这也侧面反映了:构成函数重载的因素只与
参数
有关,与返回值得类型
无关
- 这也侧面反映了:构成函数重载的因素只与
❓想必同学们都会产生如下例子中的问题:下列两个函数是否构成函数重载
⭐答案是:构成函数重载,但并不能通过编译
- 这是因为这种代码在执行的时候有可能产生歧义,导致编译器不知道该执行哪个函数,最终调用不明确,编译失败
🥯Ⅰ.面试真题
💡在面试中,会经常出现如上知识点的相关问题:
- C语言为什么不支持函数重载
- C++又是怎么支持函数重载的
👆以上问题,可以都归结于一个原因:
- 函数名修饰规则
➡️简单来说:
-
我们调用函数,在汇编时期本质是通过指令
call 函数实现的汇编指令的地址
的 -
而对于函数是声明和定义分开在不同的文件时,并不会一开始就找到函数实现的汇编指令的地址:
-
1️⃣而是因为有了
函数声明
,在编译阶段就让这个指令暂时通过(即地址处暂时空出),此时编译器会认为函数定义
在其它地方,后续链接
时再找函数定义的地址 -
2️⃣
链接
的时候:拿着函数名去找其函数实现的汇编指令的地址,具体是在其它文件的符号表中搜索地址- 只要找到就放入地址
- 找到不到就相当于
链接失败
-
-
⭐所以这也就为什么:
- C语言中不支持函数重载,是因为C语言中函数实现的汇编指令的地址是根据函数名称去匹配的,面对同名函数编译器链接的时候无法区分,所以链接失败
- 而C++支持函数重载,是因为函数名经过了
函数名修饰规则
【即现在的函数名不是直接拿原函数名作为名字,而是通过_Z+原函数名的长度+原函数名+函数参数的类型的首字母
的规则进行修饰,这也就为什么构成重载函数的三个要求都与函数参数
有关】,这样就可以同名函数从而链接到地址
🥯Ⅱ.总结
⭐综上: 正是有函数名修饰规则
的加持下,让C++相较于C语言上有了更加丰富的实现
🍞四.引用
💡引用:
- 引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间
👆简单来说:
- 就是对一个已有的变量起一个别名【类似于指针,只不过指针是指向变量的地址,但需要给指针这个变量开辟空间】
- 而引用本质还是自己,只是自己有了一个新的名字,但本体还是自己,所以对引用的修改会影响自身
❗特别注意:
- 引用类型必须和引用实体是同种类型的
- 引用在定义时必须初始化
- 一个变量可以有多个引用
- 引用一旦引用一个实体,再不能引用其他实体
👉示例:
👆从示例中不难发现:
- 我将变量
MoonCake
起了一个别名MoonCake1
- 通过打印地址也可以证明它们其实是在同一块空间,这也就证明了
引用
本质就相当于对引用对象的那块空间起多一个变量名字而已
🥯Ⅰ.权限问题
1️⃣权限放大问题
const int MoonCake = 10;
int& YueBing = MoonCake;
❓同学们觉得如上操作是正确的的吗
❗其实是错误的,这是因为:
MoonCake
这个变量被const
所修饰,变成常变量,拥有常属性,在这种情况下后续的操作中是不允许对MoonCake
这个变量的值进行修改的- 而如果用
int&
进行引用的话,那YueBing
这个别名就表示可以对MoonCake
本身就行读和写 - 即
const
修饰的变量只允许读,不能写,而现在别名
竟然可以对自身读和写,这样就属于对自身权限放大
问题
👆那我们该如何修改呢:只需要匹配权限即可
- 即在引用类型将
int&
改为const int&
即可权限相匹配
2️⃣权限缩小问题
int MoonCake = 10;
const int& YueBing = MoonCake;
❓同学们觉得如上操作是正确的的吗
❗是正确的,这是因为:
- 变量本体是允许读和写,而引用后这个
别名
的权限只有读
- 但这样并不会影响本体,因为
别名
的权限小于本体
✨综上:
- 只要引用后的
别名
权限<=
本体权限即可
🥯Ⅱ.常引用
❓同学们觉得如下操作是正确的的吗
int MoonCake = 10;
double& YueBing = MonnCake;
👆在解答上述问题前,我们先了解一下这个机制:以如下代码为例子
int c = 10;
double d = 1.11;
d = c;
➡️同学们肯定知道上述代码在d = c
中会发生隐式类型转换
- 但
隐式类型转换
并不是将c
的值直接转换后赋值给d
- 而是编译器会自动产生一个临时变量(我们看不见),类型为转换后的类型,最后再将这个临时变量的值赋值给
d
【即我们接收的是临时变量的值】
✨综上:
- 不仅仅是
隐式类型转换
,还是强制类型转换
or函数值返回
……只要会发生类型偏差的,本质并不是对本体产生影响,而是产生一个值相同、转换类型后的临时变量(具有常属性)
✊有了以上的了解补充后,我们再看回最初的疑问:操作其实是错误的
- 因为
double&
引用的是隐式类型转换后的临时变量,而非MoonCake
- 又因为临时变量具有
常属性
,所以这属于权限放大
问题,只有引用类型为const double&
才匹配权限 - 且这样的引用后续如果修改
MoonCake
的值,对YueBing
这个别名是没有任何影响的,因为我们本质是引用一个临时变量
而非MoonCake
🥯Ⅲ.使用场景
💡引用常见的使用场景有两个:
- 做参数
- 做返回值
🧇1.做参数
- 以上这种情况属于做
输出型参数
🧇2.做返回值(少数情况)
💡在深入了解前,我们先作补充:
❗我们可知:
- 函数的返回值返回并不是直接赋值给接收值(ret)
- 而是先赋值给
临时变量
,再由临时变量
赋值给接受值(ret),类似于前面类型转换的模式 - 这样的原因是:返回值
c
这个变量一旦出函数栈帧,生命周期就结束了,变量就会销毁,所以为保证正常返回想要的值,返回的便是对象C
的临时拷贝
👆有了以上补充,我们再来看看引用返回:
- 一般多作用于对象的生命周期不随着
函数栈帧
的结束而销毁的引用返回 - 即如果函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回
🥯Ⅳ.总结
⭐综上:
- 如果函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回
- 如果已经还给系统了,则必须使用传值返回
🍞五.内联函数
💡内联函数:
- 以
inline
修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数提升程序运行的效率
➡️简单来说:
- 就是C语言中的
宏函数
➕函数
的合体 - 即
宏函数
的作用是和内联函数
的作用是一样的
❓既然c语言已经解决了,为什么c++还提供inline
函数呢
❗这是因为宏函数
具有如下缺点:
- 不支持调试【在预处理阶段就替换了】
- 宏语法复杂,容易出错【涉及符号优先级的问题】
- 没有类型安全的检查【即直接就替换了,没有检查类型是否匹配】
✨所以C++为了完美解决上述问题,就提出了inline
函数
-
但使用
inline
函数的场景多推荐于将频繁调用的小函数定义成内联函数,这就涉及到inline
函数的特性:- inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长或者有循环/递归的函数不适宜使用作为内联函数
- inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等等,编译器优化时会忽略掉内联
- inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到
🥯Ⅰ.总结
⭐综上:
- C++可以利用
内联函数
替代宏函数
🍞六.C++11特性
💡一些方便且重要的新特性:
- auto关键字
- 基于范围的for循环
- 指针空值nullptr
🥐Ⅰ.auto关键字
💡auto关键字:
- auto作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得
- 简单来说: 就是自动帮助我们推导变量的类型,且简化我们对于类型的书写
➡️特别注意:
- 使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型
- 因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型
- 意思就是:
auto
的使用前提是已知返回值的类型,从而auto
才能推导出类型从而定义变量去接收返回值
👉示例:
auto a = &x; //推出来a的类型:int\*
auto\* b = &x; //推出来b的类型:int\*
int& y = x;
auto c = y; //推出来c的类型:int
//auto在同一行定义多个变量的时候,这些变量必须是相同的类型
//否则编译器会报错,因为auto实际只会对第一个类型进行推导
//【利用推到出来的类型 定义剩下的全部变量】
auto a = 1, b = 2;
❗auto不能推导的场景:
- auto不能作为函数的参数
- auto不能直接用来声明数组
✨综上:
auto
在实际中最常见的优势用法就是根后续讲到的范围for
搭配使用
🥐Ⅱ.范围for
💡基于范围的for循环:
- 是一种更加简单、简洁遍历数组的方法
👉示例:
➡️上述操作可采分为:
- 依次取出
arr
数组中的每一个数据 - 然后由
auto
关键字推导出e
的类型 - 从而定义
e
这个变量并接收arr
中的每一个数据进行初始化,并打印出来
🤜如果想在上述示例的基础上,修改数组里的每一个值,我们可以: 加上引用
操作
🥐Ⅲ.指针空值nullptr
💡指针空值nullptr:
nullptr
相较于C语言中的NULL
更加规范了
➡️这是因为:
NULL
实际是一个宏
,会被替换成字面常量0
或者被定义为无类型指针(void*
)的常量,但使用起来有时候难免会发生歧义nullptr
就很好的解决了这一歧义
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
97ff587448e20d.png)
🥐Ⅲ.指针空值nullptr
💡指针空值nullptr:
nullptr
相较于C语言中的NULL
更加规范了
➡️这是因为:
NULL
实际是一个宏
,会被替换成字面常量0
或者被定义为无类型指针(void*
)的常量,但使用起来有时候难免会发生歧义nullptr
就很好的解决了这一歧义
[外链图片转存中…(img-u47H4iXc-1715597268315)]
[外链图片转存中…(img-JT4zPrwY-1715597268315)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!