前情提要

本章节是C++入门基础语法的相关知识~

接下来我们即将进入一个全新的空间,对代码有一个全新的视角~

以下的内容一定会让你对C++有一个颠覆性的认识哦!!!

以下内容干货满满,跟上步伐吧~


作者介绍:

作者: 热爱编程不起眼的小人物
作者的Gitee:代码仓库
系列文章&专栏推荐: 《刷题特辑》、 《C语言学习专栏》、《数据结构_初阶》 、《C++轻松学_深度剖析_由0至1》

我和大家一样都是初次踏入这个美妙的“元”宇宙 希望在输出知识的同时,也能与大家共同进步、无限进步


导航小助手

  • 本章重点
  • 一.命名空间
    • Ⅰ.域作用限定符
    • Ⅱ.总结
  • 二.缺省参数
    • Ⅰ.总结
  • 三.函数重载
    • Ⅰ.面试真题
    • Ⅱ.总结
  • 四.引用
    • Ⅰ.权限问题
    • Ⅱ.常引用
    • Ⅲ.使用场景
      • 1.做参数
      • 2.做返回值(少数情况)
    • Ⅳ.总结
  • 五.内联函数
    • Ⅰ.总结
  • 六.C++11特性
    • Ⅰ.auto关键字
    • Ⅱ.范围for
    • Ⅲ.指针空值nullptr
    • Ⅳ.总结
  • 总结

本章重点

  • 了解C++入门基础语法

  • 了解C++11的新特性


一.命名空间

命名空间:

  • C++中,变量、函数和类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突

  • 使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染namespace关键字的出现就是针对这种问题的

简单来说:

  • 就是很好的解决了C语言中的命名冲突的问题【Eg:自己写的函数名字与官方库里函数名字相冲突】

  • 做到了名字隔离的作用

➡️命名空间定义: namespace➕命名空间名字➕{命名空间的成员}

  • namespace:为C++中的关键字

  • 命名空间的成员:可以是函数、变量、类……

  • 命名空间整体组成了一个域:命名空间域

示例:

通过上述的示例不难看出:

  • 红色框框住的为std库中的库函数,打印出来的便是库函数的函数地址

  • 黄色框框住的为Dream_Y_ocean这个命名空间域的strlenscanf这两个变量,打印出来便是这两个变量的地址

这里便涉及两个知识点:

  • 1️⃣在搜索变量的时候,程序遵循的是就近原则:即先从局部域开始寻找,如果没有匹配的再去全局域【包括:全局变量、头文件(库)】中寻找

  • 但并不会进命名空间域里寻找,即命名空间域对于程序来说是个独立的空间,没有使用者的允许,是不会主动进去寻找的,这也就是为什么说命名空间域很好的解决了命名冲突的问题,起到了名字隔离的作用

  • 这也解释了示例中为什么前两个打印的是库中函数的函数地址

  • 2️⃣域作用限定符(::


Ⅰ.域作用限定符

域作用符:

  • 要想要调用命名空间域中的成员的话,我们需要用到域作用限定符::

  • 简单来说:就是限定域作用限定符::)后面的成员是来自前面空间名字对应的命名空间域

  • 一个命名空间就相当于定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中

➡️用法: 想要调用的命名空间域的空间名➕::➕调用的命名空间域的成员

❓这里我们便可以思考一下如下代码经常出现在C++语言中的含义

using namespace std;

如上代码就代表着:

  • cpp为了防止命名冲突,把自己库里面的东西都定义在一个叫std的命名空间中

  • 而我们如果要调用std库里的成员,就有三种方式:

  1. std::➕要调用的成员 :此方式是最规范的写法

  2. using namespace std;:此方式相当于把std这个命名空间域里所有成员展开到全局域

  • 虽然方便日常的我们使用,但会使我们后续的命名可能会与标准库中的命名相冲突
  • 所以在规范的工程项目中是不推荐这种方式的
  1. 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️⃣权限放大问题

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


Ⅳ.总结

综上:

  • C++11还增加了其它更多好玩的特性,期待大家去挖掘呀~

总结

综上,我们基本了解了C++中的 “入门基础语法” 的知识啦~

恭喜你的内功又双叒叕得到了提高!!!

感谢你们的阅读

后续还会继续更新,欢迎持续关注哟~

如果有错误❌,欢迎指正呀

✨如果觉得收获满满,可以点点赞支持一下哟~✨