【中秋特辑 - 代码解析月饼节】C++ 比 C 语言更加规范、方便?是因为增加了如下特性 | C++98 & C++11 | C++ 难学?带领大家一步一步深度剖析 | 简单易懂
💛 前情提要💛
本章节是C++
的入门基础语法
的相关知识~
接下来我们即将进入一个全新的空间,对代码有一个全新的视角~
以下的内容一定会让你对C++
有一个颠覆性的认识哦!!!
以下内容干货满满,跟上步伐吧~
💡本章重点
了解
C++
入门基础语法了解
C++11
的新特性
🍞一.命名空间
💡命名空间:
在
C++
中,变量、函数和类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,
namespace
关键字的出现就是针对这种问题的
👆简单来说:
就是很好的解决了
C语言
中的命名冲突的问题【Eg:自己写的函数名字与官方库里函数名字相冲突】做到了名字隔离的作用
➡️命名空间定义: namespace
➕命名空间名字➕{命名空间的成员}
namespace
:为C++
中的关键字命名空间的成员:可以是函数、变量、类……
👉示例:
👆通过上述的示例不难看出:
红色框框住的为
std库
中的库函数,打印出来的便是库函数的函数地址黄色框框住的为
Dream_Y_ocean
这个命名空间域的strlen
、scanf
这两个变量,打印出来便是这两个变量的地址
❗这里便涉及两个知识点:
1️⃣在搜索变量的时候,程序遵循的是
就近原则
:即先从局部域
开始寻找,如果没有匹配的再去全局域
【包括:全局变量、头文件(库)】中寻找但并不会进
命名空间域
里寻找,即命名空间域
对于程序来说是个独立的空间,没有使用者的允许,是不会主动进去寻找的,这也就是为什么说命名空间域
很好的解决了命名冲突的问题,起到了名字隔离的作用这也解释了示例中为什么前两个打印的是库中函数的函数地址
2️⃣域作用限定符(
::
)
🥐Ⅰ.域作用限定符
💡域作用符:
要想要调用
命名空间域
中的成员的话,我们需要用到域作用限定符(::
)简单来说:就是限定域作用限定符(
::
)后面的成员是来自前面空间名字对应的命名空间域即一个命名空间就相当于定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中
➡️用法: 想要调用的命名空间域的空间名➕::
➕调用的命名空间域的成员
❓这里我们便可以思考一下如下代码经常出现在 C++语言中的含义
如上代码就代表着:
cpp 为了防止命名冲突,把自己库里面的东西都定义在一个叫
std
的命名空间中而我们如果要调用 std 库里的成员,就有三种方式:
std::
➕要调用的成员 :此方式是最规范的写法using namespace std;
:此方式相当于把std
这个命名空间域里所有成员展开到全局域中
虽然方便日常的我们使用,但会使我们后续的命名可能会与标准库中的命名相冲突
所以在规范的工程项目中是不推荐这种方式的
using std::
➕要调用的成员:此方式就是介于前两种之间,可以用于对常用的成员进行展开,这样后续再调用这些成员就更加方便,也更加规范了
❗特别注意:
若单独
::➕要调用的成员
,这样子表示在全局域中寻找要调用的这个成员
🥯Ⅱ.总结
⭐综上:
命名空间中的内容,既可以定义变量,也可以定义函数
命名空间是可以嵌套的【相对应的:调用的时候也需要嵌套调用】
同一个工程中允许存在多个相同名称的命名空间,编译器最后会自动合成同一个命名空间中
🍞二.缺省参数
💡缺省参数:
缺省参数是声明或定义函数时为函数的参数指定一个默认值
在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参
👉示例:
1️⃣全缺省参数【即给函数的所有参数都给上缺省值】
👆从示例中不难发现:
我们对
MoonCake
函数的缺省值为0
若调用
MoonCake
函数的时候没有传参数,则函数会利用缺省参数的值(0
)充当参数,进而在函数内部被使用若调用
MoonCake
函数的时候有传参数,则函数会利用实参的值赋值给形参(100
),进而在函数内部被使用
2️⃣半缺省参数【即给函数的部分参数给上缺省值】
👆从示例中不难发现:
我们对
MoonCake
函数的形参nums
没有给缺省值,形参size
给的缺省值为10
若调用
MoonCake
函数的时候没有传形参size
,则函数会利用其缺省参数的值(10
)充当形参size
参数,进而在函数内部被使用若调用
MoonCake
函数的时候有传参数,则函数会利用实参的值赋值给形参(100
),进而在函数内部被使用
❗特别注意:
半缺省参数必须从右往左依次来给出,不能间隔着给【这是因为参数是从左往右传给形参的,若中间隔着给缺省值的话,程序便不知道哪个实参对应哪个形参(但实际形参入栈的时候:是从右往左入栈)】
缺省参数最好不要在函数声明和定义中同时出现【若两个位置提供的缺省值不同,会给编译器造成歧义不知道该用哪个缺省值】
缺省值必须是常量或者全局变量
🥯Ⅰ.总结
⭐综上: 缺省参数是 C++中新添加的语法,使调用函数时变得更加灵活了
🍞三.函数重载
💡函数重载:
是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数
这些同名函数的==形参列表(参数个数 或 类型 或 顺序)必须不同==,常用来处理实现功能类似数据类型不同的问题
👆简单来说:
即实现了允许可以同时有多个同名函数的存在,且同名函数可以用来解决不同的实际问题【即赋予了函数不同的意义】
本质是为了解决 C 语言中不允许同名函数问题而出现的
❗特别注意:
构成函数重载,必须满足(三者满足其一即可):
函数参数的个数不同
函数参数的类型不同
函数参数的顺序不同
👉示例:
👆从示例中不难发现:
我对写了两个名字同为
Add
的函数,它们之间构成函数重载(因为函数参数的类型不同)可以看出编译器会根据我们函数参数的类型去匹配相对应的函数进行调用
其中之所以第二个
Add
函数也输出2
,是因为其返回值的类型为int
,使其原本返回类型为double
的值被强制转换类型为int
这也侧面反映了:构成函数重载的因素只与
参数
有关,与返回值得类型
无关
❓想必同学们都会产生如下例子中的问题:下列两个函数是否构成函数重载
⭐答案是:构成函数重载,但并不能通过编译
这是因为这种代码在执行的时候有可能产生歧义,导致编译器不知道该执行哪个函数,最终调用不明确,编译失败
🥯Ⅰ.面试真题
💡在面试中,会经常出现如上知识点的相关问题:
C 语言为什么不支持函数重载
C++又是怎么支持函数重载的
👆以上问题,可以都归结于一个原因:
函数名修饰规则
➡️简单来说:
我们调用函数,在汇编时期本质是通过指令
call 函数实现的汇编指令的地址
的而对于函数是声明和定义分开在不同的文件时,并不会一开始就找到函数实现的汇编指令的地址:
1️⃣而是因为有了
函数声明
,在编译阶段就让这个指令暂时通过(即地址处暂时空出),此时编译器会认为函数定义
在其它地方,后续链接
时再找函数定义的地址2️⃣
链接
的时候:拿着函数名去找其函数实现的汇编指令的地址,具体是在其它文件的符号表中搜索地址只要找到就放入地址
找到不到就相当于
链接失败
⭐所以这也就为什么:
C 语言中不支持函数重载,是因为 C 语言中函数实现的汇编指令的地址是根据函数名称去匹配的,面对同名函数编译器链接的时候无法区分,所以链接失败
而 C++支持函数重载,是因为函数名经过了
函数名修饰规则
【即现在的函数名不是直接拿原函数名作为名字,而是通过_Z+原函数名的长度+原函数名+函数参数的类型的首字母
的规则进行修饰,这也就为什么构成重载函数的三个要求都与函数参数
有关】,这样就可以同名函数从而链接到地址
🥯Ⅱ.总结
⭐综上: 正是有函数名修饰规则
的加持下,让 C++相较于 C 语言上有了更加丰富的实现
🍞四.引用
💡引用:
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间
👆简单来说:
就是对一个已有的变量起一个别名【类似于指针,只不过指针是指向变量的地址,但需要给指针这个变量开辟空间】
而引用本质还是自己,只是自己有了一个新的名字,但本体还是自己,所以对引用的修改会影响自身
❗特别注意:
引用类型必须和引用实体是同种类型的
引用在定义时必须初始化
一个变量可以有多个引用
引用一旦引用一个实体,再不能引用其他实体
👉示例:
👆从示例中不难发现:
我将变量
MoonCake
起了一个别名MoonCake1
通过打印地址也可以证明它们其实是在同一块空间,这也就证明了
引用
本质就相当于对引用对象的那块空间起多一个变量名字而已
🥯Ⅰ.权限问题
1️⃣权限放大问题
❓同学们觉得如上操作是正确的的吗
❗其实是错误的,这是因为:
MoonCake
这个变量被const
所修饰,变成常变量,拥有常属性,在这种情况下后续的操作中是不允许对MoonCake
这个变量的值进行修改的而如果用
int&
进行引用的话,那YueBing
这个别名就表示可以对MoonCake
本身就行读和写即
const
修饰的变量只允许读,不能写,而现在别名
竟然可以对自身读和写,这样就属于对自身权限放大
问题
👆那我们该如何修改呢:只需要匹配权限即可
即在引用类型将
int&
改为const int&
即可权限相匹配
2️⃣权限缩小问题
❓同学们觉得如上操作是正确的的吗
❗是正确的,这是因为:
变量本体是允许读和写,而引用后这个
别名
的权限只有读
但这样并不会影响本体,因为
别名
的权限小于本体
✨综上:
只要引用后的
别名
权限<=
本体权限即可
🥯Ⅱ.常引用
❓同学们觉得如下操作是正确的的吗
👆在解答上述问题前,我们先了解一下这个机制:以如下代码为例子
➡️同学们肯定知道上述代码在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 不能推导的场景:
auto 不能作为函数的参数
auto 不能直接用来声明数组
✨综上:
auto
在实际中最常见的优势用法就是根后续讲到的范围for
搭配使用
🥐Ⅱ.范围 for
💡基于范围的 for 循环:
是一种更加简单、简洁遍历数组的方法
👉示例:
➡️上述操作可采分为:
依次取出
arr
数组中的每一个数据然后由
auto
关键字推导出e
的类型从而定义
e
这个变量并接收arr
中的每一个数据进行初始化,并打印出来
🤜如果想在上述示例的基础上,修改数组里的每一个值,我们可以: 加上引用
操作
🥐Ⅲ.指针空值 nullptr
💡指针空值 nullptr:
➡️这是因为:
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
🥯Ⅳ.总结
⭐综上:
C++11 还增加了其它更多好玩的特性,期待大家去挖掘呀~
🫓总结
综上,我们基本了解了 C++中的“入门基础语法”的知识啦~
恭喜你的内功又双叒叕得到了提高!!!
感谢你们的阅读
后续还会继续更新,欢迎持续关注:哟~
版权声明: 本文为 InfoQ 作者【Dream-Y.ocean】的原创文章。
原文链接:【http://xie.infoq.cn/article/df3591f1f5a95df4ec671cd06】。未经作者许可,禁止转载。
评论