C++初级语法
Cpp整个语法的核心都在围绕封装和权限进行发展- 因此我们看待
Cpp绝大语法的使用 要从封装和权限两个角度进行理解
- 因此我们看待
引子
- 初阶
Cpp主要是解决C中的不足而设计 C++重要的成熟版本分别为:C++98、C++11C和C++的应用场景:主要是服务端开发然后是嵌入式,C++一般适用于底层开发C++特性C++语法兼容C语法
- 前置知识:标识符是用来命名变量、函数、类、结构体、枚举、命名空间等用户定义实体的名称
- 也就是说变量名、类名、函数名、枚举类型名、结构体名、命名空间名、...都属于标识符
C++关键字
C++的关键字是在C的基础上新增了32个关键字
C++关键字大全
cpp
asm do if return try continue
auto double inline short typedef for
bool dynamic_cast int signed typeid public
break else long sizeof typename throw
case enum mutable static union wchar_t
catch explicit namespace static_cast unsigned default
char export new struct using friend
class extern operator switch virtual register
const false private template void true
const_cast float protected this volatile while
delete goto reinterpret_cast
命名空间
C存在标识符命名冲突的问题
C 语言命名冲突演示
报错结果:
ERROR,命名冲突报错,出现重复定义问题出现原因:核心在于:同名标识符不可以出现在同一个域 ,因此
<stdlib.h>内部的rand函数名就会和用户定义的rand变量名在全局域下出现重名冲突
c
#include<stdlib.h>
#include<stdio.h>
int rand=10; //这里会报错,命名冲突错误
int main()
{
return 0;
}
Cpp解决方案:引入命名空间,本质就是引入新的域,冲突的标识符分别被不同的命名空间包含起来,通过将冲突的标识符分别封装在不同的域,完美的规避了命名冲突- 和命名空间处于同一作用域的
代码要想使用命名空间内的标识符
- 和命名空间处于同一作用域的
Cpp解决方案演示
cpp
#include<stdlib.h>
#include<stdio.h>
namespace Bit
{
int rand=10;
}
int main()
{
printf("%d\n",Bit::rand);
return 0;
}
- 命名空间域理解
- 封装角度:命名空间可以理解为对指定资源的封装
- 权限角度:对 命名空间内资源的
标识符进行 权限设计 - 使用角度
[域名]::[标识符]的方式,编译器会直接使用命名空间中资源的标识符控制资源- 同一作用域下 并且
using namespace [域名]下方的代码 才可以直接使用 命名空间内资源标识符控制资源
- 命名空间特性
- 不会改变所包含对象的生命周期,不会改变所包含对象的生命周期性质,没包含之前是生命周期是什么包含之后依旧是不变
- 当同一作用域下定义了两个同名命名空间,这两个命名空间就会被合并
- 命名空间内可以定义函数、类、对象、等资源
- 命名空间内部的函数、类、对象 这三者对应的标识符不冲突 就可以随便定义
- 扩展:命名空间内可以继续定义命名空间
编译器访问对应对象优先级
- 编译器默认查找原则:
向上查找,编译器执行到对应代码时寻找资源时,会根据此代码的访问权限先访问此代码 所处在的域 进行向上查找,没找到 就向上去包含此域的域内继续向上查找,逐渐向上一层一层查找,直至全局域内向上查找完为止,就不向外查找了 - 如果对应的
标识符被指定对应的域了,那么编译器直接去指定的域进行获取标识符对应的对象、函数、类 执行后续操作了
标识符被指定域进行使用
[域名]::[标识符]表示 这个代码有权限 并且 指定去该命名空间 使用此标识符- 指定全局域方式:
::[标识符] - 指定命名空间域方式
[命名空间]::[标识符] - 指定类域方式:
[类名]::[标识符]
展开命名空间
- 展开语法:
using namespce [命名空间域]; - 命名空间外面且下方位置 和命名空间 处于同一作用域的指令 可以直接 获取和使用该命名空间的标识符
- 展开命名空间后,编译器会默认 认为展开名空间这段代码 下面位置且被同一作用域包住 的所有代码
有权限直接使用命名空间内资源的标识符 进行操作
- 展开命名空间后,编译器会默认 认为展开名空间这段代码 下面位置且被同一作用域包住 的所有代码
- 展开命名空间慎用
只展开命名空间的指定标识符
- 展开语法:
using namespace [命名空间域]::[标识符]
知名命名空间
- 全局作用域下存在一个知名命名空间叫做
std命名空间,这个命名空间包含C++绝大部分标准方法、对象、类
C++输入和输出
C语言的标准输入和标准输出需要printf进行指定类型,Cpp优化方案:设计了cin、cout自动识别类型,免去手动指定类型
cout 和 cin
cout、cin存在于iostream头文件里cout、cin都属于输入流对象,这些对象用于输入输出操作
cout
cout是Cpp用于标准输出的对象,是ostream类型的对象Cpp中的cout对<<进行了复用,<<和内置类型的对象一起使用就是左移操作,但在Cpp这里cout搭配<<就是标准输出的操作- 一个
cout搭配多个<<就是连续标准输出 - 语法:
cout << [可打印的数据]
cout 使用
cpp
#include<iostream>
using namespace std;
int main()
{
cout << "hello world" << '\n';
return 0;
}
cin
cin是Cpp用于标准输入的对象,是istream类型的对象Cpp中的cin对>>进行了复用,>>和内置类型的对象一起使用就是右移操作,但在Cpp这里cin搭配>>就是标准输入操作- 一个
cin搭配多个>>就是连续标准输入 - 语法:
cin >> [被赋值的对象]
cin 使用
cpp
#include<iostream>
using namespace std;
int main()
{
int a;
cin >> a;
return 0;
}
缺省参数
C调用自定义函数必须调用时填写实参,Cpp优化方案:定义自定义函数时,给指定的形参提供缺省值,当用户不给指定的形参传递实参数据时,默认该形参的缺省值就会填充给该形参
缺省参数使用
cpp
#include<iostream>
using namespace std;
void Func(int a = 0)
{
cout << "a = " << a << endl;
}
int main()
{
Func(10); //不触发缺省参数,标准输出为 a = 10
Func(); //触发缺省参数,标准输出为 a = 0
return 0;
}
缺省参数的规则
- 当用户自定义函数时,给形参填写缺省参数时,必须从
最右至左,连续的填写缺省参数 - 用户提供的实参数据,从自定义函数的参数列表的
最左至右依次传递给自定义函数的形参 - 缺省参数:必须是常量或者全局变量
缺省参数正确使用
cpp
int Func(int a,int b,int c=2)
int Func(int a,int b=1,int c=2)
int Func(int a=0,int b=1,int c=2)
缺省参数错误的使用
cpp
int Func(int a,int b=1,int c)
int Func(int a=0,int b,int c)
int Func(int a=0,int b,int c=2)
函数根据缺省进行的分类
- 全缺省:自定义的函数的所有参数都存在缺省值
- 半缺省:自定义的函数部分参数存在缺省值
声明和参数分离场景下的缺省参数用法
- 函数声明和定义分离的情况下,缺省参数规定 缺省参数只能在声明位置提供
- 声明定义分离就是只函数的声明和函数的定义不在同一个文件下,函数声明和函数调用在一个文件,函数定义单独一个文件
- 如果不在声明位置给缺省参数,那么项目会在编译器的编译阶段报错,因为调用函数的代码触发了缺省参数,编译器会进行识别,发现声明位置不存在缺省参数,编译器就会报错
编译器生成可执行程序的四个阶段
预处理
- 预处理阶段:会对
.cc、.c进行展开头文件、宏替换、条件编译、去注释操作 - 形成的文件为
.i文件
编译阶段
- 编译阶段:会对预处理形成后的
.i文件进行语法检查,然后生成对应的汇编指令放到.s文件中 - 语法检查中函数检查过程中,编译器会在主函数所在下 搜索与
调用函数的代码匹配的函数头,看看调用函数的代码是否符合函数头的返回值和参数- 这个函数头 就是 主函数所在文件下 定义的函数声明
- 编译阶段会为每个
.i文件形成符号表,符号表会记录当前.i文件下所有的自定义函数地址和函数名+参数类型的映射关系
符号表
cpp
Func(int,int) : 0AA12F211
Test() : 1FDD76551
Func(char,int) : 9ADD21985
汇编
- 将汇编代码进行解析,解析成二进制机器码放到
.o文件中
链接
- 将上阶段的
.o文件合并成一个可执行文件,编译阶段的所有符号表也会在此阶段合成一张符号表 供给 可执行程序
声明定义分离场景下的编译器的四个阶段
cpp
______________________ _________________________ ___________________
|Func.h | |Func.cc | |Main.cc |
| void Func(int a=10); | |void Func(int a) | |#include"Func.h" |
|______________________| |{ | |int main() |
| cout<<"a ="<<a<<endl; | |{ |
|} | | Func(1); |
|_________________________| | return 0; |
|} |
|___________________|
预处理
Main.cc 头文件展开
cpp
________________________ _________________________
|Main.i | |Func.i |
|void Func(int a=10); | |void Func(int a) |
|int main() | |{ |
|{ | | cout<<"a ="<<a<<endl; |
| Func(1); | |} |
| return 0; | |_________________________|
|} |
|________________________|
编译
语法检查形成汇编代码,因为
Main.i存在函数声明,因此可通过语法检查因为
Main.i只有声明没有定义,Main.i通过了语法检查,编译器对调用函数对应的汇编指令暂时设为call Func(?),后续在链接阶段?会变成函数地址顺便根据每个文件的全部函数形成对应的符号表,每个文件对应一个符号表
cpp
______________________________ ______________________________________ :.o文件
|Main.o | |Func.o |
|movl $10, %esi | |0A01357h jmp Func(int)(0A01770h) |
|00A0191F call Func(int)(?) | | |
|...... | |void Func(int a) |
|______________________________| |{ |
|0A01770 push ebp |
|0A01771 mov ebp,esp |
|0A01773 push esp,0C0h |
|... |
|} |
|______________________________________|
_____________________ _____________________ :符号表
|Func(int):? | |Func(int) : 0A01770h |
|_____________________| |_____________________|
汇编
这里就是将
Main.o和Func.o转成两个.s文件,内部逻辑没变我们这里进行反汇编,观察到这两个文件和上一阶段没任何区别,所以就不用在伪代码模拟了
- 省略
链接
这里会将
.s两个文件合并成一个可执行程序文件,多个文件的符号表会合成一张大的符号表
call Func(?)部分编译器会查询符号表进行对应地址的补充为了可观性这里进行反汇编表达
cpp
______________________________________
|exe |
|Main() |
|{ |
|... |
|00A0191F call Func(int)(0A01770h) |:? 被替换成 真函数地址
|... |
|} |
|... |
|0A01357h jmp Func(int)(0A01770h) |
|... |
|void Func(int a) |
|{ |
|0A01770 push ebp |
|0A01771 mov ebp,esp |
|0A01773 push esp,0C0h |
|... |
|} |
|... |
|______________________________________|
函数重载
C中存在一个函数名只能对应一个函数定义,这样就太单一了,C++解决方案:设置函数重载,函数名相同只要参数不同,就可以定义多个同名函数- 函数名相同,参数类型不同 或者 参数类型顺序不同,都可构成函数重载
- 注意:只有返回值不同无法构成重载
函数重载使用
一下四个函数都可以构成重载
cpp
void Func(int a)
{
cout << endl;
}
void Func(int a,char b)
{
cout << endl;
}
void Func(char b,int a)
{
cout << endl;
}
void Func()
{
cout << endl;
}
C++支持函数重载的原理
C编译器区分函数是单纯通过函数名来进行区分,Cpp编译器区分函数是通过函数名+函数的参数类型进行区分- 此时同一作用域就可以定义相同函数名的不同函数了
Cpp中函数名+参数类型来标识一个函数
引用
C中通过指针变量来控制指向的对象比较繁琐,Cpp解决方案:引入引用类型,给标识符对应的资源或者单纯的资源添加新的标识符,直接对新的标识符操作就可以控制对应的资源Cpp对&进行扩展复用,类型结合&形成[类型]&就是引用类型- 只要新标识符和原标识符不在同一作用域就可以相同
引用的使用
这里是给
a标识符对应的资源添加了新的标识符b,a和b标识符对应的是同一份内存资源
cpp
#include<iostream>
int main()
{
int a=0;
int& b=a; //b是a对应资源的另一个标识符
cout<<&a<<endl;
cout<<&b<<endl;
}
引用做参数
- 引用做参数,形参名就变成了实参对象的新标识符,
Cpp会为引用类型的形参 生成指针对象 来维护 形参名 作为 实参对象 的新标识符,此时形参的开销仅仅为指针大小,引用类型作为形参类型 有效控制了形参的开销,如果形参类型 是实参类型,当实参对象过大时,形成的形参对象开销也会变大
引用做参数
这里引用做参数,直接用新的别名来控制对应的资源,大大提高了可观可用性
引用做参数,那么这个参数叫做输出型参数
输出型参数 可以 将函数内部的处理结果,通过参数送出函数 被外部接收并使用
cpp
void Swap(int& a,int& b) //a是i的别名,b是j的别名
{
int c=a;
a=b;
b=c;
}
int main()
{
int i=1;
int j=2;
cout<<"i="<<i<<"j="<<j<<endl;
Swap(i,j);
cout<<"i="<<i<<"j="<<j<<endl;
return 0;
}
引用特性
- 引用特性
- 引用必须被初始化,也就是说
[引用类型] [标识符];这个代码是错误的,必须[引用类型] [标识符]=[资源]; - 引用不能改变指向,也就是说引用一旦成为了某个资源的标识符,那么该引用在当前作用域会一直都作为这个资源的标识符
- 引用必须被初始化,也就是说
- 引用和指针是相辅相成的,虽然引用的产生是为了解决指针部分带来的不便,其不是为了替代指针而产生的
引用底层原理
Cpp引用类型底层采用的是C中的指针实现出来的,编译器会对 引用对象名 在底层就是创建一个指针变量 来维护和实现 引用对象名 作为新标识符 的行为
引用类型做返回值类型
C中,函数返回都会产生保存返回值对象数据的临时变量,此时产生的临时对象就是内存开销,当大型结构体类型为返回值类型,那么开销也会随之变大,成本变高,Cpp解决方案:使用引用类型作为返回值类型- 函数返回时,一定会产生临时变量,当临时变量类型不是引用类型时,这个临时变量就会被开辟成返回值类型大小的空间,然后存储函数返回值对象的数据,
函数(参数)这段调用代码 也会变成这个临时变量的标识符,当返回值类型为引用类型时,这个临时变量就变成了指针变量,存储返回值对象的地址,对地址进行引用层面维护,让函数(参数)这段调用代码 变成 返回值对象 的新标识符 - 注意:引用作为返回类型,那么作为返回值的对象 通常是 静态、全局、堆上的,
函数(参数)这段代码也会变成返回值对象的新标识符,如果返回值对象被回收了,那么这个新标识符对应的内存空间就是非法空间
C 演示大型结构体做返回值类型的开销
此时内存就会开辟一个
sizeof(struct BIG)大小的空间,这个空间就是临时变量,也是一种内存开销
c
struct BIG
{
int a;
bool b;
char* c;
double d;
}
struct BIG Func()
{
struct BIG big;
return big;
}
int main()
{
struct BIG b=Func(); //此时会产生临时对象接受这个big对象,这个临时对象就是很大的开销
return 0;
}
C中,函数返回值逻辑图
cpp
__________________________ :内存
| ______________________ :Func函数栈帧
| |return [struct BIG big] :big对象作为返回值
| |___________|__________
| data copy
| |
| V
| [struct BIG] :[struct BIG]表示临时生成的临时变量,用于接收big对象的数据,这个临时变量大小为 sizeof(struct BIG)
| ________________|______ :main函数栈帧
| | |
| |[struct BIG b]=Func(); :b是main函数中用于接收[struct BIG]内部数据的对象,Func()会成为[struct BIG]的标识符
| |______________________
|___________________________
Cpp 解决 C 中函数返回值出现开销的问题
Cpp引用的底层是指针变量,产生的临时变量是struct BIG&类型,这个临时变量内存会开辟struct BIG*指针来维护这个引用对象,虽然都开辟了响应的内存空间,但指针类型相比于大结构类型,开销此时就大大减少了
cpp
#include<iostream>
struct BIG
{
int a;
bool b;
char* c;
double d;
}
struct BIG& Func()
{
struct BIG* big=(struct BIG*)malloc(sizeof(struct BIG));
return *big;
}
int main()
{
struct BIG b=Func();
return 0;
}
Cpp中,函数返回值逻辑图
txt
__________________________ :内存
| ______________________ :Func函数栈帧
| |return [struct BIG big] :big对象作为返回值
| |___________________|__
| |
| [struct BIG*]-maintain->| :[struct BIG*]表示临时生成的指针变量,用于维护Func()这段代码 作为big对象新标识符的行为,这个临时指针变量大小为 sizeof(struct BIG*)
| ___________________|___ :main函数栈帧
| | |
| |[struct BIG b]=Func(); :b是main函数中用于接收[struct BIG]内部数据的对象,Func()作为[struct BIG]的标识符
| |______________________
|___________________________
const 关键字
const用于修饰标识符,使用 被修饰的标识符 是无法修改其对应的对象内部数据的- 临时变量具有常性,意思就是 临时变量对应的标识符 会被
const修饰 - 定义时,
const只会修饰*+标识符的组合 或者标识符const本质就是修饰标识符的,*+标识符的组合 本质就是一个新的标识符
内联函数
C中函数调用是存在开销的,每次调用就要创建一次函数栈帧,Cpp解决方案:内联函数,执行到调用内联函数位置不进行创建函数栈帧了,而是编译时直接将此函数逻辑在调用位置直接展开,展开在调用位置所处的栈帧中- 虽然
C中宏函数使用后的效果跟内联函数相同,但宏函数操作复杂使用性差并且debug下无法被调适,因此Cpp就产生了内联函数 - 函数过大被
inline修饰,编译器并不会将此内联函数展开,因为函数过大,调用位置展开后就会导致形成的代码段变大,此时展开的开销大于创建的开销,编译器就会避免这种情况,不展开- 代码段过大(代码膨胀)就会导致执行效率变慢,代码段大(代码膨胀)对应的可执行程序也会很大这空间也会出现损耗
- 内联函数不支持声明定义分离,并且可以被展开的内联函数是不会被加入到符号表中的
C 中,不断创建函数栈帧场景
此场景会创建大量函数栈帧,对内存的占用和
CPU的调度有很大的消耗
c
#include<stdio.h>
int gval=1;
void Func()
{
printf("%d\n",++gval);
}
int main()
{
for(int i=0;i<=100;i++)
{
Func();
}
return 0;
}
Cpp 解决方案:内联函数
此时
Func函数的逻辑会被编译器解析时,将Func逻辑展开在main函数中
cpp
#include<iostream>
using namespace std;
inline void Func()
{
printf("%d\n",++gval);
}
int main()
{
for(int i=0;i<=100;i++)
{
Func();
}
return 0;
}
.h 文件包含函数定义,并且被多个.c 文件包含,函数表重定义解决方案
普通的函数定义 被放到.h 文件,存在链接时符号表合并后函数名冲突问题
txt
____________________ ___________________ ____________________
|Func.h | |Func.c | |Main.c |
|void Func() | |#include"Func.h" | |#include"Func.h" |
|{ | | | |#include<stdio.h> |
| printf("Func\n"); | | | |int main() |
|} | | | |{ |
|____________________| |___________________| | return 0; |
|} |
|____________________|
| 预处理:头文件展开
V
____________________ ____________________
|Func.c | |Main.c |
|void Func() | |void Func() |
|{ | |{ |
| printf("Func\n"); | | printf("Func\n"); |
|} | |} |
|____________________| |... |
|int main() |
|{ |
| return 0; |
|} |
|____________________|
| 编译:形成符号表
V
____________________ ____________________
|Func.c | |Main.c |
|void Func() | |void Func() |
|{ | |{ |
| printf("Func\n"); | | printf("Func\n"); |
|} | |} |
|____________________| |... |
|int main() |
|{ |
| return 0; |
|} |
|____________________|
:符号表
_______________ ________________
|Func():0xfffff | |Func():0x0000f |
|_______________| |________________|
| 汇编
| 链接:合并符号表,此时报错,合并后的符号表出现重名函数
V
- 解决方案:static 修饰的函数定义
- 被
static修饰后的函数,这个函数的链接属性会被改变,这个函数只被预处理后,其所处的.cc/.c文件自己可用,其他文件访问不到这个static修饰的函数- 本质就是被
static修饰的函数不会放到符号表,但可直接被使用,可执行程序只使用主函数文件的 static 修饰函数
- 本质就是被
static 修饰函数解决方案
txt
____________________ ___________________ ____________________
|Func.h | |Func.c | |Main.c |
|static void Func() | |#include"Func.h" | |#include"Func.h" |
|{ | | | |#include<stdio.h> |
| printf("Func\n"); | | | |int main() |
|} | | | |{ |
|____________________| |___________________| | return 0; |
|} |
|____________________|
| 预处理:头文件展开
V
____________________ ____________________
|Func.c | |Main.c |
|static void Func() | |static void Func() |
|{ | |{ |
| printf("Func\n"); | | printf("Func\n"); |
|} | |} |
|____________________| |... |
|int main() |
|{ |
| return 0; |
|} |
|____________________|
| 编译:形成符号表,因为Func被static修饰所以不会放到符号表,并且每个.c的static修饰函数只会被该.c自己使用
V
____________________ ____________________
|Func.c | |Main.c |
|static void Func() | |static void Func() |
|{ | |{ |
| printf("Func\n"); | | printf("Func\n"); |
|} | |} |
|____________________| |... |
|int main() |
|{ |
| return 0; |
|} |
|____________________|
:符号表
_____________________ ______________________
|OtherFuncA():0x3427ff| |OtherFuncB():0x9826ef |
|_____________________| |______________________|
| 汇编
| 链接:合并符号表
V
___________
|Execute |
|___________|
Cpp 解决方案:内联函数函数
内联函数函数凭借展开时,不会加入符号表的特性完美的解决了,符号表函数名冲突的问题
内联函数函数可展开时,其只在预处理后其所在的文件可用
- Cpp 的 inline 函数解决方案
txt
____________________ ___________________ ____________________
|Func.h | |Func.c | |Main.c |
|inline void Func() | |#include"Func.h" | |#include"Func.h" |
|{ | | | |#include<stdio.h> |
| printf("Func\n"); | | | |int main() |
|} | | | |{ |
|____________________| |___________________| | return 0; |
|} |
|____________________|
| 预处理:头文件展开
V
____________________ ____________________
|Func.c | |Main.c |
|inline void Func() | |inline void Func() |
|{ | |{ |
| printf("Func\n"); | | printf("Func\n"); |
|} | |} |
|____________________| |... |
|int main() |
|{ |
| return 0; |
|} |
|____________________|
| 编译:形成符号表,因为Func被inline修饰所以不会放到符号表,并且每个.c的inline修饰函数只会被该.c自己使用
V
____________________ ____________________
|Func.c | |Main.c |
|inline void Func() | |inline void Func() |
|{ | |{ |
| printf("Func\n"); | | printf("Func\n"); |
|} | |} |
|____________________| |... |
|int main() |
|{ |
| return 0; |
|} |
|____________________|
:符号表
_____________________ ______________________
|OtherFuncA():0x3427ff| |OtherFuncB():0x9826ef |
|_____________________| |______________________|
| 汇编
| 链接:合并符号表
V
___________
|Execute |
|___________|
auto 关键字
- C 中定义变量都需要
具体类型 标识符 = 值的形式,Cpp为了省劲就借鉴了Python,直接auto 标识符=值- auto 是很灵活的推导
- 自动根据 赋的值 来 推导标识符的类型
- auto 无法做函数的参数和函数的返回值,但 C++14 后 auto 可以做函数的返回值类型,auto 不可以定义数组
- auto 的弊端就是当你自己本身不了解返回类型时,你就无法使用返回值,auto 存在的意义就是你清楚值的类型使用基础上直接用
auto,你要不清楚值的类型就会很糟糕
auto 关键字的使用
cpp
#include<iostream>
using namespace std;
int main()
{
int i=0;
auto p1=i; //auto->int
auto& p2=i; //auto&->int&
auto* p3=&i; //auto*->int*
auto p4=&i; //auto=int*
return 0;
}
auto 关键字衍生的范围 for 语法
Cpp的for循环高级用法
范围 for 使用演示
for(auto e:arr),auto 类型的 e 会自动依次从循环中取 arr 数组的元素对 e 初始化,然后取完就自动结束循环
cpp
#include<iostream>
using namespace std;
int main()
{
int arr[10]={1,2,3,4,5,6,7,8,9,10};
for(auto e:arr)
{
printf("e\n");
}
return 0;
}
nullptr
- Cpp98 中的 NULL 是 0 的 define,并不是 C 中的 NULL(空指针),因此 Cpp11 中就设置了 nullptr 来代表 C 中的 NULL
权限
- 被
const修饰的标识符属于读权限,反之没被const修饰的标识符就具有读写权限 - 读写权限的指针或引用类型 的标识符 不可以被 读权限的标识符 初始化,这种行为叫做权限不可被放大