目录
一:关键字
二:命名空间
1.引入
2.命名空间的定义
<1>:命名空间中定义变量/函数/类型
<2>:命名空间可以嵌套
<3>:同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中
3.命名空间的使用
<1>:加命名空间名称及作用域限定符
<2>:使用using namespace 命名空间名称引入
<3>:使用using将命名空间中某个成员引入
三:输入、输出
四:缺省参数
1.缺省参数的概念
2.缺省参数的分类
<1>:全缺省参数
<2>:半缺省参数
<3>:注意
五:函数重载
1.函数重载概念
2.C++支持函数重载的原理 --- 名字修饰
<1>:C语言不支持函数重载的原因
<2>:两者编译后对比
六:引用
1.引用的概念
2.引用的特性
3.引用作返回值
<1>:传值返回
<2>:传引用返回
4.引用做参数
<1>引用做参数
<2>:引用做参数和做返回值的对比
5.常引用
6.引用和指针的区别
七:内联函数
1.内联函数概念
2.特性
八:auto 关键字
九:范围for
十:指针空值
一:关键字
C++总计63个关键字,C语言32个关键字
二:命名空间
1.引入
在打印 rand 的值时,无法得到我们想要的结果。
在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存 在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。
2.命名空间的定义
定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{} 中即为命名空间的成员。
<1>:命名空间中定义变量/函数/类型
#include <iostream>
namespace rsg
{// 命名空间中可以定义变量/函数/类型int rand = 10;//定义变量int Add(int left, int right)//定义函数{return left + right;}struct Node//定义结构体类型{struct Node* next;int val;};
}
<2>:命名空间可以嵌套
#include <iostream>
namespace rsg
{int a;int b;int Add(int left, int right){return left + right;}namespace zrsg{int c;int d;int Sub(int left, int right){return left - right;}}
}
程序可以正常退出,上述代码没有问题。命名空间可以嵌套。
<3>:同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中
#include <iostream>
namespace rsg
{// 命名空间中可以定义变量/函数/类型int rand = 10;//定义变量int Add(int x, int y)//定义函数{return x + y;}
}
namespace rsg
{int a = 3;int b = 4;int c = 5;
}
两个 rsg 的命名空间最后会合并在一起。
3.命名空间的使用
观察下述代码,猜测程序运行的结果???
#include <iostream>
namespace rsg
{int a = 10;//命名空间定义变量int Add(int x, int y)//命名空间定义函数{return x + y;}
}
int main()
{printf("%d\n", a);return 0;
}
通过调试运行代码,我们得到下述结果:
我们发现,a 为未定义的标识符 ---> 命名空间的内容是如何使用的呢 ???
<1>:加命名空间名称及作用域限定符
: : 域作用限定符
我们可以采用“命名空间 : : 命名空间成员”的方式访问命名空间内的相应的成员。
<2>:使用using namespace 命名空间名称引入
该方法在某些情况下会出现问题,如我们在命名空间内定义了一个 printf 变量,当我们使用该命名空间时,会造成 printf 为不明确符号:
为此,添加了一种新的方法。
<3>:使用using将命名空间中某个成员引入
a --- 未定义的标识符,b 是正常的 :
三:输入、输出
#include <iostream>
//std 是C++标准库的命名空间
using namespace std;
int main()
{cout << "Hello World" << endl;return 0;
}
程序运行结果为:
std命名空间的使用惯例:std是C++标准库的命名空间,如何展开std使用更合理呢?1. 在日常练习中,建议直接using namespace std即可,这样就很方便。2. using namespace std展开,标准库就全部暴露出来了,如果我们定义跟库重名的类型/对象/函数,就存在冲突问题。该问题在日常练习中很少出现,但是项目开发中代码较多、规模大,就很容易出现。所以建议在项目开发中使用,像std::cout这样使用时指定命名空间 +using std::cout展开常用的库对象/类型等方式。cout --- 控制台输出(控制台)<< --- 流插入运算符>> --- 流提取运算符endl --- 换行符,等价于 ' \n ' (此处为字母l,不是数字1)cin --- 标准输入对象(键盘)使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream >头文件以及按命名空间使用方法使用std。
四:缺省参数
1.缺省参数的概念
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实 参则采用该形参的缺省值,否则使用指定的实参。
2.缺省参数的分类
<1>:全缺省参数
#include <iostream>
//std 是C++标准库的命名空间
using namespace std;
//全缺省 --- 可以一个都不传
void Func(int a = 10, int b = 20, int c = 30)
{cout << "a= " << a << endl;cout << "b= " << b << endl;cout << "c= " << c << endl << endl;
}
int main()
{Func(1, 2, 3);Func(1, 2);Func(1);Func();//从左向右(缺省参数传参)显示传参return 0;
}
程序运行结果为:
<2>:半缺省参数
注:下属图片中,半缺省为从右向左给缺省值
部分图片有误。
#include <iostream>
//std 是C++标准库的命名空间
using namespace std;
//半缺省 --- 从左向右,给缺省值
void Func(int a, int b = 20, int c = 30)
{cout << "a= " << a << endl;cout << "b= " << b << endl;cout << "c= " << c << endl << endl;
}
int main()
{//至少传一个Func(1, 2, 3);Func(1, 2);Func(1);//Func();return 0;
}
至少传一个参数:
程序运行结果为:
<3>:注意
1. 半缺省参数必须从右往左依次来给出,不能间隔着给2. 缺省参数不能在函数声明和定义中同时出现当声明和定义同时出现时(缺省参数):在声明中给,在定义中就不需要再给了,正确情况下为:
3. 缺省值必须是常量或者全局变量4. C语言不支持(编译器不支持)
五:函数重载
1.函数重载概念
函数重载(一个词有多重含义):是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。<1>:C语言不允许同名函数(重定义)<2>:cpp可以,但是要求构成函数重载类型不同:<3>:函数名相同,参数不同(类型不同、个数不同、顺序不同)在 <2> 中我们已经使用了参数类型不同,接下来举例后面两种情况个数不同:
顺序不同:
2.C++支持函数重载的原理 --- 名字修饰
<1>:C语言不支持函数重载的原因
在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。示例:Test.cpp// 预处理 :<1>:头文件的展开 <2>:宏替换 <3>:去掉注释 <4>:条件编译Test.i// 编译 (检查语法)生成汇编代码(指令级代码 eg:push、sub....)<1>:语法分析 <2>:词法分析 <3>:语义分析 <4>:符号分析Test.s// 汇编 --- 将汇编代码转换成二进制指令(机器码) 形成符号表 同名函数出错Test.o// 链接 合并链接,生成可执行程序 符号表的合并和重定位Test.exe 二进制的指令(机器指令)
<2>:两者编译后对比
测试用例:
C语言编译器编译结果(linux 平台下):
C++编译器编译结果(linux 平台下):
六:引用
1.引用的概念
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
如上,b 为 a 的引用,即为 a 的别名( a , b 为同一块内容),所以取地址a 和 取地址b 的地址值相同。对b进行++,a 也会变。
使用示例:
x 为 a 的引用, y 为 b 的引用,改变 x,y的值,a,b的值会被被改变。
2.引用的特性
<1>:引用在定义时必须初始化
<2>:一个变量可以有多个引用
<3>:引用一旦引用了一个实体,再不能引用其他实体
3.引用作返回值
<1>:传值返回
n 出函数栈帧 --- 销毁 != 空间没了
返回值 n ,通过一个临时变量(可为寄存器)传给ret 。
<2>:传引用返回
函数调用,先传参,后建立栈帧。
传引用返回时,可能会出现栈帧覆盖情况。
空间被清 --- 随机值
空间不被请 --- 1(下图示例)
返回的值是 a 的别名(引用)
通过下述的示例,让我们更好的理解空间覆盖。
观察下述代码,分析结果???
#include <iostream>
using namespace std;
int& Add(int x, int y)
{int z = x + y;return z;
}
int main()
{int& ret = Add(3, 4);Add(5, 5);cout << "Add(3,4) is " << ret << endl;return 0;
}
显而易见 3 + 4 = 7,让我们看一下程序运行的结果:
我们发现程序的最终的结果与我们预想的不一样???
z 为返回的对象,但出了Add的函数栈帧,z 就会被销毁(空间仍然存在),在下一次函数调用时,会将该空间覆盖掉(栈帧),返回一个新的 z 的值。此情况非常不安全。
如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。
4.引用做参数
<1>引用做参数
<2>:引用做参数和做返回值的对比
传引用传参(任何时候都可以使用)
*1 提高效率
*2 输出型参数(形参的修改,影响实参)
传引用返回(出了函数作用域对象还在才可以使用)
*1 提高效率
*2 修改返回对象
5.常引用
观察下面三个示例:
被 const 修饰的值在引用过程中不能给不被 const 修饰的值,该情况属于权限的放大。
const 修饰的值在引用过程中给被 const 修饰的值,该情况属于权限的平移。
不被 const 修饰的值在引用过程中给被 const 修饰的值,该情况属于权限的缩小。
在引用过程中:
权限可以平移,可以缩小,但不能放大。
查看下述代码:
从反汇编我们可以看出, i 赋值给一个临时变量,然后赋值给a
临时变量具有常性。
在此处,并非为权限的平移,参数类型不同。
解决方法,在double 前面加上 const :
6.引用和指针的区别
观察 引用和指针的反汇编:
我们发现两者反汇编语言相同,由此得出结论:
底层只有指针,没有引用(汇编一层,没有引用的概念)
七:内联函数
1.内联函数概念
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。
原来的非内联函数:
如果在上述函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的调用。
上述结果的查看方式:
2.特性
1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。2. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。3. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。
假如Func是一个100行的一个函数。我们在100个位置进行函数调用 :
是内联(inline)合计多少行指令??? // 1000 行
不是内联(inline)合计多少行指令??? // 200 行 --- call Func 每个地方100行函数调用+Func(100行的一个函数)。
test.h文件
#pragma once#include <iostream>
using namespace std;void Func(int i);//1
//void F();//2
//inline void Func(int i);//3
在情况1 --- 正常
在只有情况3 --- 报错,无法解析的外部符号,连接错误。
只包含了 test.h ,只有声明。
情况1和2 --- 可以正常运行,F函数既有声明,又有定义。
情况2和3 --- 可以正常运行 -> F函数存在(F函数调到了Func函数???在只有情况3的时候,我们没有掉到Func函数)
原因:2和3情况下,F函数中的Func(1),既有函数(Func)的声明,又有定义,在此处并没有函数调用,而是在此处直接展开,它不需要地址。
解决上述问题问题的方法为:声明+内联 且不可分离
#pragma once#include <iostream>
using namespace std;inline void Func(int i)
{cout << "inline void Func(int i)" << i << endl;
}
八:auto 关键字
示例:
查看 c 的类型,为 int* 指针类型。
注意:
<1>: auto 不能作为函数的参数
<2>: auto 不能直接用来声明数组
九:范围for
打印一个数组乘2后的数组:
橙色方框与黄色方框的最终结果相同。黄色方框使用了范围 for
范围 for :
<1>: 依次读取数组中的数据赋值给e
<2>: 自定判断结束
<3>: 自动迭代(++、--)
在此处如果使用 for (auto &x : arr) 会产生怎样的结果???
由上述可知,传入的 arr 的值为第一次乘2后的结果,修改 x , arr 的值被改变。
for (auto x : arr)
修改 x , arr 的值不受影响。
十:指针空值
NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量
不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦。
在此处,我们可以使用 nullptr 来取代 NULL 。
注意:1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。