从今天开始就步入C++的学习道路了,又是一条漫长的学习道路。
目录
C++关键字
命名空间
命名空间的定义
命名空间的使用
C++输入和输出
缺省参数
全缺省参数
半缺省参数
函数重载
extern "C"
引用
引用的特性
常引用
使用场景
引用和指针的区别
C语言中有关键字,比如sizeof等,在C++中也同样有关键字,而且C++兼容C语言,所以C语言中的关键字在C++中也可以使用。
C++总计63个关键字,C语言32个关键字:虽然在这里把关键字都罗列出来,但是并不会去挨个介绍,留到后面再去讲解,有了例子介绍这些关键字也方便记忆。
在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。namespace也是我们要介绍的第一个关键字。简单来说,在以后的工作中,每个人写一个模块,最后把这些拼到一起时,总会出现变量名或者函数名的冲突,那个人也不愿意改,因为很麻烦,那怎么办呢?这样命名空间就起到了作用,每个人把自己的命名放到自己创建的命名空间中就不会冲突了,在写代码之前先讲清楚命名空间的名字,不要起冲突,但是C语言就做不到,所以C++也可以说是填补C语言的坑,或者把C语言中不好用的改进。讲到这里看不懂不要紧,认真看下面的讲解你就明白了。在这之前多多少少都会看到这样的C++代码:
定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名空间的成员。
定义完了就该讲讲怎么用了
这里报错了,可是我上面明明定义了一个a,在命名空间n1中啊,那下面就介绍命名空间的使用方式。
第一种:加命名空间名称及作用域限定符
这个符号 " :: " 就是作用域限定符, 在变量前加上就可以使用这个命名空间中的变量了.
第二种: 使用using将命名空间中成员引入
第三种: 使用using namespace 命名空间名称引入
在最后还要说一点就是,在以后的工作中不要直接把std标准命名空间直接展开,可以用哪个展开哪个, 展开常用的。
Hello World!!!又和我们见面了,同样在C++中也来写一个简单的代码吧。
说明:
1. cout是标准输出(控制台), cin是标准输入(键盘), 使用这两个必须包含< iostream >头文件以及std标准命名空间。2. 使用C++输入输出更方便, 不需增加数据格式控制, 也就是在C语言中, 想要输入和打印必须使用%d之类的, 输入的时候的&也不能忘, 但是C++就要方便很多。而且C++还可以自动识别类型
但是这样写就不能像printf一样可以指定域宽和小数点后几位了。
缺省参数,一个好笑的讲法就是:C++函数参数的备胎。
缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参。
全缺省参数必须从左往右依次来给出,不能说给b的值不给a
这样函数传参至少要传一个 。
注意:
1. 半缺省参数必须从右往左依次来给出,不能间隔着给,比如b是缺省参数,a和c都不是,这样就不行。2. 缺省参数不能在函数声明和定义中同时出现,比如在头文件中声明的缺省参数是10,在.cpp文件中定义的缺省参数是20, 这样编译器就不知道该用哪个值了。3. 缺省值必须是常量或者全局变量4. C语言不支持(编译器不支持)
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表( 参数个数 或 类型 或 顺序 )必须不同,常用来处理实现功能类似数据类型不同的问题。判断函数重载最重要的就是上面标红的: 形参的 参数个数、 类型和 顺序这些必须不同。
为什么C++支援函数重载,而C语言不可以了?
这里我们要回顾一下以前的知识,在运行到执行文件前,要经过:预编译,编译,汇编,链接这些阶段其实问题就出在编译完之后的汇编阶段,因为在这里C++和C语言有着些许的不同,这里使用linux操作系统可以明确地看出区别。下面我们来看看:
采用C语言编译器编译下:
注意红色的箭头
采用C++编译器编译下
注意红色箭头
可以看出, 用C语言编译的函数名字的修饰没有发生改变; 而C++编译的函数名字的修饰发生了改变。
这就得提到Linux下的修饰规则:
C语言函数修饰不变, 而C++的函数修饰后变成了【_Z+函数长度+函数名+类型首字母】
这里看不懂没有关系简单来说就是: C语言的函数名字没有修饰, C++的函数名字会修饰。
通过这里就理解了C语言没办法支持重载,因为同名函数没办法区分。而C++是通过函数修饰规则来区 分,只要参数不同,修饰出来的名字就不一样,就支持了重载。另外我们也理解了, 为什么函数重载要求参数不同了, 而跟返回值没关系,因为返回值的类型也不会像参数类型一样被修饰到函数名上。
有时候在C++工程中可能需要将某些函数按照C的风格来编译,在函数前加extern "C",意思是告诉编译器,将该函数按照C语言规则来编译。所以当我们使用C语言来编译的时候, 使用到了C++的接口, 那么这个问题就可以使用entern "C"来解决。
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。类型& 引用变量名(对象名) = 引用实体&这个符号在这里就不是取地址的意思了。定义一个变量a, 然后b是a的别名, 可以看到一开始都是0, 改变a, b也跟着改变, 改变b, a也跟着改变, 最后两个变量的地址是一样的, 也就是共用同一块内存空间。引用类型必须和引用实体是同种类型。
1. 引用在定义时必须初始化
如果不初始化就相当于起了一个别名不知道是给谁的。
2. 一个变量可以有多个引用
3. 引用一但引用了一个实体, 再也不能引用其他实体
首先我们来看一下这段代码为什么会出错
看最下面那个错误,无法从"const int"转换为"int &",const修饰的a只有读的权限,而引用的ra具有读写的权限,这是明显的权限被放大了,改为const int& ra = a;就可以了。
权限放大不可以,那权限缩小可不可以呢?
答案是可以的,从可读可写变成只读,所以权限不能放大,但是可以缩小。
这个改为const int& b = 10; 就可以了,因为10是常量,所以用const修饰一下就可以用了。
这里改为const double& rd = i; 就可以了,这是为什么呢?
因为整型转换成浮点型会有一个隐式类型转换,在这个过程中i会产生一个临时变量,这个临时变量是double类型的,再把这个临时变量赋值给rd,临时变量是具有常性的,常性的权限是只读的,如果不加const是可读可写的,所以必须用const修饰才可以使用。
常引用和之前讲解的const常量修饰是一个道理,在函数传参的时候,如果参数不改变,那么尽量用const引用传参。
1. 做参数
2. 做返回值
在介绍这个使用场景之前,先来看一下这两个程序
左图调用函数, 在静态区创建了n, 因为static定义的全局变量, 当Count函数调用完之后, 这块函数栈帧会被销毁, 返回位于静态区的n。
右图在调用完函数后, n的值记录在寄存器中, Count函数栈帧被销毁, 再返回寄存器中的值。
上面这两段属于传值返回, 都会生成一个返回对象, 返回这个对象中拷贝的返回值。
再看一下这两段程序
左图Count函数的返回类型是引用, 下图的tmp相当于n的别名, 当Count函数栈帧销毁后, n这块空间就返还给操作系统了, 操作系统清理栈帧的时候会置成随机值, 但这块儿空间还是被返回到了ret中, 形成了越界访问, 虽然最后的值是正确的, 原因可能还没有来得及置成随机值。所以这段程序使用引用返回本质上是不对的。
右图的意思是tmp和ret都是n的别名, tmp是在返回的时候定义的, Count函数栈帧销毁的时候, 还是把这块空间返回到了main函数的栈帧中。
和左图说的一样, 这里打印的还是这块已经还给操作系统空间的值, 第一次打印, 操作系统可能没来得及置成随机值, 但第二次就是随机值。
所以, 出了函数作用域, 返回对象就销毁了的一定不能用引用返回, 必须用传值返回。
综合以上观点,这样写才能用引用返回,因为创建的全局变量在静态区,函数栈帧在栈区创建,它的作用域不会销毁。
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率非常低下,尤其是当参数或者返回值类型非常大时,效率就更低。
在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。但在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。
例如这段代码,我们可以通过调试来看看,转到反汇编的界面
左边是用引用的,右边是用指针的,不需要看懂它是怎么操作的,但是可以看出左右两边的操作都是一样的。
从语法角度而言,引用没有开辟空间,指针开了4或8字节;从底层角度而言,引用底层是用指针实现的。
引用和指针的不同点:从介绍引用到现在,已经提到过这两者很多的不同点。介绍引用的特性时:1. 引用在定义时必须初始化,指针没有要求。2. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体。指针和引用的用途基本是相似的1. 引用结果为引用类型的大小,但指针始终是地址空间占4或8字节。引用比指针使用起来相对更安全:指针更强大,更危险,更复杂;引用相对局限一些,但是更安全。