C C++最新【中秋特辑-代码解析月饼节】C++比C语言更加规范、方便(4),妈妈再也不用担心我的面试

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上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就很好的解决了这一歧义

👉示例:

在这里插入图片描述

👆第二个调用f函数的代码,我们传NULL本想调用的是参数为int* p的函数,但却调用了参数为int的函数,这就是因为:

  • 此过程中NULL被替换成0了,因为这是编译器默认情况下的选择,而并非将其看作为无类型的指针(void*)【即(void*)0
  • 所以建议用nullptr来消除歧义【因为nullptr是直接被转换为(void*)0

综上:

  • 在C++11中,sizeof(nullptr)sizeof((void*)0)所占的字节数相同
  • 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

这就是因为:**

  • 此过程中NULL被替换成0了,因为这是编译器默认情况下的选择,而并非将其看作为无类型的指针(void*)【即(void*)0
  • 所以建议用nullptr来消除歧义【因为nullptr是直接被转换为(void*)0

综上:

  • 在C++11中,sizeof(nullptr)sizeof((void*)0)所占的字节数相同
  • 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr

[外链图片转存中…(img-RhJ0kcLe-1715725706664)]
[外链图片转存中…(img-M2xJl13R-1715725706665)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

  • 14
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值