命名空间
什么是命名空间?
我们先来看一个案例!
在校内,有一天,4班的小胖摔了一跤,于是a同学对b同学说:“小胖摔了一跤”。
b同学说:“哪个小胖”?a同学又说,就是4班的那个小胖啊!
注意看上面的案例,我们可以把上面的案例代入到代码中
小胖:变量
a同学:程序员
b同学:编译器
在校内:在全局范围内
4班:其实就是对应c++中的命名空间
分析:当一个项目中,我们可能要定义两个/以上的同名变量,但是如果同名对象,程序就会报错,因为编译器不会像b同学一样问a同学是哪个?它只有对与不对之分!而人与人之间可以加限定词来修饰一个变量,程序又如何限定呢?由此我们引出了命名空间!
关键字:namespace+空间名
如果要访问的话就是:空间名::变量名
namespace修饰的空间其实我们可以看成一个作用域,里面的内容的作用域被限制在了命名空间中,命名空间外除非用特定的方式,不然无法访问
这里的"::"表示的是域访问符
![](https://img-blog.csdnimg.cn/img_convert/ab52923b5f04ee80ad3d9d4a52006c57.png)
了解了以上内容我们写个代码试试
![](https://img-blog.csdnimg.cn/img_convert/be909d4c8990a00642b7a800c70f8c99.png)
再看下面
![](https://img-blog.csdnimg.cn/img_convert/9cf7057134dbb7dde62726aa5d960348.png)
三个注意事项
-
命名空间可以用来放函数/变量/结构体....它就是一个域,把命名空间与命名空间之外的东西隔离开来
![](https://img-blog.csdnimg.cn/img_convert/9adfabddf099ac04d7527a4b6cf3078b.png)
-
命名空间里的变量那些都是全局的
-
命名空间中变量不允许重复赋值
![](https://img-blog.csdnimg.cn/img_convert/e5408ac6458d6271c56e53c52db2f33b.png)
命名空间的三种访问形式
第一种:空间名:: 变量名
![](https://img-blog.csdnimg.cn/img_convert/57db00726b0ecc5f10ee1c71edf6b5af.png)
这个上面介绍过,如果命名空间中的某些东西用的次数很频繁,为了方便一点我们引入第二种
第二种:using 空间名::函数名/变量名/结构体
![](https://img-blog.csdnimg.cn/img_convert/85f8d265b284f5f841eb85658776d52e.png)
这里的意思是展开S1命名空间中的a变量,展开后a变量就失去了命名空间的隔离效果
要注意的是,这里的using要放在定义命名空间之后,因为using展开以后是向上进行查找,如果在定义命名空间之前,则会导致using无法找到命名空间
但有时候在我们在平时练习时有些库函数要用,但一个一个展开又太麻烦了,有什么办法可以一次性展开整个命名空间吗?由此我们引入第三种访问形式
第三种:using namespace 空间名
![](https://img-blog.csdnimg.cn/img_convert/38acb07c85f426ca1fc0792f8b61fc72.png)
这个的意思是展开命名空间中的所有东西,展开后整个命名空间中的内容都失去了命名空间的隔离效果
但要注意的是这个展开方式在实际工程中不推荐使用,因为当代码量过大且很多人一起操作一个项目时容易与命名空间中的某些函数重复。
在实际工程中,推荐使用第一种或第二种!
关于命名空间的一些问题
-
命名空间可以嵌套使用吗?
答:可以,看如下代码
![](https://img-blog.csdnimg.cn/img_convert/b3e8aec5802a7d453bdb7906f4ec6e98.png)
-
命名空间如果也是一样的名字该怎么办?
答:如果命名空间是一样的名字,那么编译器就是把两个命名空间的内容合为一个!
例如
![](https://img-blog.csdnimg.cn/img_convert/ff4f6d7a3df5a1984389c218bb4f61a3.png)
这个代码就跟如下的代码是相等的
![](https://img-blog.csdnimg.cn/img_convert/8b46d044733c138a6fda7fb13e226955.png)
3.命名空间和结构体有什么不同?
命名空间:是一个域,他限制了命名空间外面的访问必须要通过特定的方式
结构体:是一个自定义的类型,本质上跟int这种内置类型一样,区别就是结构体是你自己实现的类型
一个图总结命名空间,你能否全部答上来呢?
![](https://img-blog.csdnimg.cn/img_convert/a7ecf3ea5080eedb8d916f39e5cade26.png)
std命名空间及cin&cout
简介
我们先看一段代码
![](https://img-blog.csdnimg.cn/img_convert/dd573745e0d13e5d78af2db1a005b3e9.png)
我们一段一段来解释这个代码
using namespace std:由上一个命名空间章节可知,std是一个命名空间, std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中,using是展开的意思,所以这里的意思是展开命名空间中所有的定义
#include<iostream>:#include功能是包含对应的头文件/库函数,而iostream是c++中的库函数,这个库函数叫做"输入输出流",他的定义是在命名空间std的
cout:实际上是std中的一个类型为istream的全局变量,他放在std中,这里大概理解成cout就是一个控制台这样就够了,c++后期会学IO流的原理
"<<"运算符:官方名称为流插入运算符,上述代码你可以翻译为:变量x中的字符串流向cout控制台所以控制台上就打印出来了
cin:是一个类型为ostream的全局变量,这里也可以把cin理解成一个控制台,他放在std中
">>":官方名称为流提取操作符,上述代码可以翻译为把cin这个控制台上的内容(也就是你输入的内容)流向x这个变量
endl:这个可以简单理解为是对应c++中的换行
注意:cin输入也是以空格/换行为分隔符,这一点和scanf一致
关于cin/cout有更复杂的语法(如控制浮点数输出精度,控制整形输出进制格式等 等。)这里用起来没有c语言的方便,而且也不经常用所以不加以赘述。
c++输入和输出比起c语言的有什么优势?
c++的可以自动识别类型,不用再写%d,%s,%lf....,而c语言是一定要写的
一张图理清std及cin&cout的知识点,你能答出来吗?
![](https://img-blog.csdnimg.cn/img_convert/31a4cb9bcb10c94b575ebedd08f6fe04.png)
缺省参数
初识缺省参数
理解缺省参数之前,让我们回顾一下c语言中的函数调用
![](https://img-blog.csdnimg.cn/img_convert/9347763fc3c3e24c252ac162a7b2629a.png)
这里传了一个1赋给形参a,2赋给形参b,但当我们不写参数时是会报错的
![](https://img-blog.csdnimg.cn/img_convert/e2321e64274f248a978ca662d5629dc3.png)
那在c++中有没有办法让他不报错呢?答案是有的,于是引进了缺省参数
![](https://img-blog.csdnimg.cn/img_convert/9facf03c44fe887a6cfcde7d1f3a0152.png)
这里两个形参都给上了缺省参数,设置为0,意思就是当我们不传值得时候a默认等于0,b默认等于0
![](https://img-blog.csdnimg.cn/img_convert/7b5a615956af788f531b541cd98d19cf.png)
而给了参数以后调用得是我们给得参数
缺省参数的分类
缺省参数除了上面那样给还分为几类
全缺省:就是上面给的
半缺省:准确的说就是部分缺省,例如
![](https://img-blog.csdnimg.cn/img_convert/ec74f55b3a144bde51ac53156f90e8ec.png)
可以看到我们的参数是给到了a,而b用的缺省参数
注意①:c++11之前的语法不允许中间的形参or传参 缺省 而两边的形参or传参不缺省
缺省参数只允许以下三种形式的
![](https://img-blog.csdnimg.cn/img_convert/0c24ae42b8e1597cd29a9d2ed6a8b109.png)
注意②:如果函数有声明那么缺省参数建议放到声明中(为了可读性),不允许声明定义同时有缺省参数
例如
![](https://img-blog.csdnimg.cn/img_convert/e6bef17b48e85be7a24b0070e9e387a0.png)
正确如下
![](https://img-blog.csdnimg.cn/img_convert/319bb1bcb13fa3509b5c6434e121392d.png)
缺省参数的总结
![](https://img-blog.csdnimg.cn/img_convert/8563828ff02ae0c39421b4fb40d1abed.png)
函数重载
定义
以前我们在写c语言的时候会有一个很麻烦的点,例如:写了一个整数和整数相加的函数,但我们在用的时候可能又是浮点型的相加,于是就要再写一个函数,但再写一个函数跟原来的函数还无法重名!而c++完善了这一点,提供了函数重载的功能
函数重载就是编译器对同一个名字的函数,进行区分不同
例如
![](https://img-blog.csdnimg.cn/img_convert/cb2482a521a7f2830ddb97383428fd4f.png)
可以看到,这里编译器会自动识别传过去的参数类型,而找到对应的同名函数
函数的重载到底有什么条件呢?
-
参数类型不同
例如:
![](https://img-blog.csdnimg.cn/img_convert/4690088c6f172ec7254f9ee1efde8b16.png)
-
参数个数不同
![](https://img-blog.csdnimg.cn/img_convert/cdf09c356589b7c41b3a9290aa7815d4.png)
-
参数顺序不同
![](https://img-blog.csdnimg.cn/img_convert/95434afd8427733498922dbe25d0a0f6.png)
特别要注意:函数重载与返回值无关,只与参数有关
函数重载例子
为什么c语言不支持函数重载?而c++支持函数重载
要理解这个问题我们先看看编译器对c语言同名函数的报错信息是什么!
![](https://img-blog.csdnimg.cn/img_convert/05e08e59482123702361e481a62732ca.png)
这里说明编译器无法识别这两个函数的不同,那为什么无法识别呢?
这就要说到c语言程序执行到编译阶段时会生成一个符号表,这个符号表记录了一些特殊的符号,例如main....
当符号表中有两个f函数时,编译器无法识别哪个对应哪个,所以会报错
而当函数的声明和定义分离时,我们再看
头文件存放函数声明
![](https://img-blog.csdnimg.cn/img_convert/aa19395b358e82efc04f2c1fd5ac6c11.png)
源文件存放函数实现
![](https://img-blog.csdnimg.cn/img_convert/212c900c706477fa98fe227e83fa5621.png)
报错信息
![](https://img-blog.csdnimg.cn/img_convert/1dc88fb1e86b48d1ceb4f1188a779a79.png)
为什么这里会报错呢?
分析:我们知道c程序在预处理阶段会进行头文件展开,然后编译器会向上进行查找,而查找到声明时不会产生地址因为程序只有遇到定义时才会产生地址,但会产生的是一个 函数名(?) 这样的一个东西,这个是什么意思呢?其实就是你先跟编译器声明有这么个东西的实现,然后在链接阶段会合并所有文件的符号表,而原来的 函数(?) 也会从其他文件中的符号表中找到他的实现,并把定义的地址替换掉原来的"?",但是如果编译的时候发现有两份同名实现时,编译器就无法识别该填哪个地址
但是为什么c++支持函数重载呢?而且函数重载的要求还一定是参数的差别呢?
分析:那是因为c++编译器在符号表中的名称添加了函数的修饰规则,这个修饰规则是根据编译器的不同而不同的,但是都或多或少与参数有关
比如:linux上的g++编译中void f(int a)产生的符号表对应符号名称为_Z1fi
这个符号表对应符号名称的意思是:_Z是编译器前缀,1是函数名字符个数,f是函数名,i是参数的类型为int
所以在以上void f(double b)和void f(int a)在c++linux上的符号表对应符号就分别是_Z1fd和_Z1fi
总结一下:c语言不能实现函数重载的原因就是因为他的符号表里的函数名就单纯是个函数名,而无法区分出两个同名函数有什么不同,而c++能实现函数重载的原因就是因为他的符号表对应的符号把参数带上了,这样编译器就能根据他的参数不同而找到对应的同名函数
一张图总结函数重载
![](https://img-blog.csdnimg.cn/img_convert/063d462cd027f58b33a1f2826dd5d918.png)
引用
引用概念
引用其实就是给变量取了一个别名,引用不会创建变量!,比如我们每个人除了自己得本名之外,在小得时候有人会叫你得乳名,在上学得时候有人会叫你得外号,其实不管是乳名还是外号形容得人都是你,并没有多出来一个人
引用的格式:类型& 新变量名 = 变量名
比如下面得代码
![](https://img-blog.csdnimg.cn/img_convert/d5f64399a9150c271ec36a5f60f02c77.png)
这里b和a得地址相同,说明这个地址除了叫做a以外还叫作b,而b没有创建新得变量
引用的特性
1、要注意的是取得外号(引用)的类型一定要跟实体相同
![](https://img-blog.csdnimg.cn/img_convert/caf390696225300a117d398876eccba1.png)
2、引用必须初始化
![](https://img-blog.csdnimg.cn/img_convert/500ed52aa4846177da8f91f575e72849.png)
3、一个变量可以有多个引用,并且引用变量也能引用
![](https://img-blog.csdnimg.cn/img_convert/15822211b858db20da7278332cc81fff.png)
4、引用一旦引用一个变量,就不能再引用另一个变量了
![](https://img-blog.csdnimg.cn/img_convert/0c88f36573639297de8a1f8df3d69278.png)
如图,当b引用了一个实体以后,b=c就是把c的值赋给b,而不是b是c的引用
5、引用可以给引用取别名
![](https://img-blog.csdnimg.cn/img_convert/a09c9a5f8eb5668c7ee1d88dec22a0c4.png)
引用的使用场景
-
做参数
以前我们写c语言时的有些函数,例如:swap函数,实参传给形参时交换的是形参的结果,无法交换实参的结果,当时的解决办法就是传地址调用如图代码
![](https://img-blog.csdnimg.cn/img_convert/57da02f2aa42e7abdd390f67aabe542f.png)
正确调用方式,如图
![](https://img-blog.csdnimg.cn/img_convert/d42f4ae3eadb86ccb0dbf3651a82266a.png)
现在还有一种方式,就是传引用调用
![](https://img-blog.csdnimg.cn/img_convert/c2384b7995d8cd2273abbf235977366f.png)
做参数的好处有什么?
第一,当大变量传参时(如结构体)可以减少拷贝,提高效率
第二,输出型参数可以把指针替换为引用
第三、某些场景下,引用有指针代替不了的作用,这个我们后面了解
-
做返回值
为什么会产生传返回值引用呢?
先看如图(这里还有一个bug,先往后看)
![](https://img-blog.csdnimg.cn/img_convert/695a08cb43caed3fd2b1f5514f923fbe.png)
当代码执行到return n 时,n会生产一个临时变量我们把它叫做tmp,于是代码返回的逻辑就变成了如下
![](https://img-blog.csdnimg.cn/img_convert/ecd8dbc915fb927303dc3410afdc3dc3.png)
那这么设计是为了什么呢?为什么不直接返回n要根据n的返回值生成一个临时变量呢?
这其实跟函数栈帧有关
首先,我们先看一下两个函数的栈帧
![](https://img-blog.csdnimg.cn/img_convert/68d37416e8e27ae4f1a8a7e62978cc87.png)
我们知道,当函数返回时,栈帧的使用权就已经给了系统,那么我们返回的时候n就已经被销毁了,如果不反回拷贝,直接返回n的话就会造成越界
那么我们回到刚开始的bug代码中,如图
![](https://img-blog.csdnimg.cn/img_convert/695a08cb43caed3fd2b1f5514f923fbe.png)
这里其实传返回值引用的时候就已经造成了越界。
这里的越界解决办法是把n改成静态变量,这样他就存储在静态区了,这样函数销毁的时候就不会影响n的值
![](https://img-blog.csdnimg.cn/img_convert/9e7cb2769fb8db7f39721685005e3062.png)
总结一下:传返回值引用,如果是出了函数的作用域之后销毁的变量则一定不能传引用返回,如果出了函数作用域不销毁的变量则可以传引用返回减少拷贝
问:以下代码输出结果是?
#include <iostream>
int& Add(int a,int b)
{
static int c = a + b;
return c;
}
int main()
{
int& ret = Add(1,2);
std::cout << "Add(1,2) is:" << ret << endl;
Add(3,4);
std::cout << "Add(1,2) is:" << ret << endl;
return 0;
}
答案:3,3
原因:局部静态变量只被初始化一次,而ret是c的别名,第二次进add函数内部时没有再次初始化
那下面这个呢?
#include <iostream>
int& Add(int a,int b)
{
static int c;
c = a + b;
return c;
}
int main()
{
int& ret = Add(1,2);
std::cout << "Add(1,2) is:" << ret << endl;
Add(3,4);
std::cout << "Add(1,2) is:" << ret << endl;
return 0;
}
答案:3,7
原因:上述代码是对c重新赋值,不是初始化,ret是c的别名,c改变ret改变
传值和传引用的效率对比
![](https://img-blog.csdnimg.cn/img_convert/e2ec3d9fd8a4b3998581e3cdaba0e478.png)
可以看到,在图上场景中,效率差距百倍不止
值和引用的作为返回值类型的性能比较
![](https://img-blog.csdnimg.cn/img_convert/a6c538e461809379a106c04400c6a890.png)
引用和指针有什么区别
虽然指针和引用很像,而引用能完成的功能指针基本上也能完成,但他们却也有着一些不同
1.语法上引用是变量的别名,不开空间,而指针存储一个变量地址。但是引用的底层是用指针实现的。
这里虽然从语法上来说引用是一个变量的别名,而指针是一个存储一个变量的地址,但在指令级语言中其实不然,先看下图代码
![](https://img-blog.csdnimg.cn/img_convert/54d629bc2e0c0812b1813e0c0587ab52.png)
可以看到,转到反汇编时,调用指针改变一个值和调用引用改变一个值的反汇编是完全相等的,所以由这一点我们也可以知道其实引用就是通过指针实现的,所以说从底层来说引用不开空间,指针才开空间是错误的
注意:但我们从语法/概念来讲的时候把引用当做不开空间就行,因为如果要纠结底层可能会造成很多的困惑
2.引用必须初始化并且在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何 一个同类型实体
这一点也就造成了指针能完成的引用完成不了,但引用做不成的指针一定能做
例如,数据结构中的链表需要不断指向前驱后继结点,所以进入c++后链表也还是通过指针来完成的
而因为引用必须初始化的原因,所以也导致在链表中的前驱和后继无法使用引用只能使用指针
3.没有NULL引用,但有NULL指针
因为引用必须初始化,而既然必须初始化,那么他的类型就是引用实体的类型,可能有人会说
int*& a 这样的引用一个实体会是空指针嘛?那就让我们看看吧
![](https://img-blog.csdnimg.cn/img_convert/99db246b4a624165b92fcc4b06df55ae.png)
可以看到,虽然int*&看似是空引用但其实是一个int*的类型
4在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32 位平台下占4个字节)
5.引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
6. 有多级指针,但是没有多级引用
7.访问实体方式不同,指针需要显式解引用,引用编译器自己处理
总的来说,引用比指针可读性更高,安全性更好,指针比引用更强大,但也更危险
一张图总结引用,你能答出来嘛?
![](https://img-blog.csdnimg.cn/img_convert/d9a46a914ce4f2d9e061a791d38c5882.png)
内联函数
定义
内联函数就是在调用的时候直接进行展开,而不创造函数栈帧,跟宏很像,现在我们看看效果
在开始实验之前我们先调一些东西,因为内联函数在debug版本下不会展开因为要可以进行调试,但release下如果变量不用的话又会自动优化掉,优化会很大所以我们先设置一点,首先右键源文件打开属性然后点开以下两个设置
![](https://img-blog.csdnimg.cn/img_convert/ff8a53fc9945f75c768bc8786e3236cb.png)
![](https://img-blog.csdnimg.cn/img_convert/2dc960aeed29f186c2fffa3b52670130.png)
把以上两张图中的画红框的设置成如图所示,现在开始调试吧
![](https://img-blog.csdnimg.cn/img_convert/c7a8141d96c59228624e8236fc60b7f8.png)
我们先看看反汇编,我们知道如果函数调用了函数栈帧,对应的反汇编指令就是call ...,如果没有说明函数以及被展开了
![](https://img-blog.csdnimg.cn/img_convert/36c56e9e44fd921a63e828512c6964c0.png)
我们看到,这里没有call...说明函数展开成功!
特点
1、内联函数只适用于小而频繁调用的函数,例如swap,add等,并且内联函数如果汇编指令过长(一般超过10行)则内联函数还是会自动调用函数栈帧,如图我们看看
![](https://img-blog.csdnimg.cn/img_convert/c5920e3398fc63b20e096c7d2ff6a49f.png)
![](https://img-blog.csdnimg.cn/img_convert/1087c985cff5f9cd7c83f7b260a56d30.png)
2、内联函数不能进行定义和声明分离
如图
.cpp文件
![](https://img-blog.csdnimg.cn/img_convert/1c8f12191f0636e28ddd3de6b848968e.png)
.h文件
![](https://img-blog.csdnimg.cn/img_convert/ad56335114cc73482bf4b372204f0e93.png)
原理:这里是造成了链接错误,我们函数重载时说过,声明在还没找到定义时,会只有函数名而没有地址,等到链接阶段时再把实现的地址填上,但这里的内联函数因为是直接展开的,所以造成这里的声明不会进入符号表,合并符号表的阶段也就不会把他的地址找到,也就造成了链接错误
总结一下:内联函数其实就是为了替换c语言中的宏函数而设计的,因为我们知道宏函数本身就非常的难用并且不能调试,也就会造成了很容易出现误用的情况,所以进入以后建议不要用宏函数用内联函数就好了!
总结
![](https://img-blog.csdnimg.cn/img_convert/87493df7f3d5515560e377b5eeb6ed98.png)
auto
定义
auto是c++中新加的一个关键字,他的作用是可以根据赋给他的变量自动推导出类型
如下
![](https://img-blog.csdnimg.cn/img_convert/5ef834e04d8b6f757215e8724e7e38f4.png)
常用场景
-
当有一个类型非常长的时候,用auto是非常方便的例如(特别是当我们学到c++中的stl时)
-
当要遍历一个数组...这种结构时,c++11支持使用范围for,格式为:for(auto e : 数组名)
如下
![](https://img-blog.csdnimg.cn/img_convert/6e1b7eb26ae2915593fb9e233d6ba3c6.png)
而当我们要修改的时候试试
![](https://img-blog.csdnimg.cn/img_convert/37960eca8cc0eb57de0e917d09f65438.png)
好像并没有成功,为什么?
因为一个变量赋值给另一个变量会创建一个临时拷贝,所以这里改变e的值并不会改变a的值,要改变a的值我们就要加上引用,如下
![](https://img-blog.csdnimg.cn/img_convert/472d16c61f6669c82a96592c849e801f.png)
![](https://img-blog.csdnimg.cn/img_convert/192b41f4444d62839a7c036798d74221.png)
总结
![](https://img-blog.csdnimg.cn/img_convert/b51c7f7344ef91b1e58c159e73cfaaed.png)
c++语法入门总结
总结并不会直接告诉答案,需要对应问题去原文中找,并且没有标准答案,试试拿着这些问题讲给自己,能讲出来就代表已经掌握了
![](https://img-blog.csdnimg.cn/img_convert/ac4ca6ff7153e4a652e9e122b6774de1.png)