一、C++关键字
下面展示C++的关键字,不作说明,后续会逐一讲到。C++在C语言的基础上,容纳了面向对象的编程思想,增加了许多有用的库以及编程范式,对C语言进行了修正和内容的增加,使功能更加全面与完善。
二、命名空间
C语言有两个域:全局域和局部域,创建的全局变量和函数就存在于全局域当中,在主函数等大括号中创建的变量就存在于局部域中,在C语言的全局变量创建时,我们有时会遇到命名与某些调用的库中的函数名发生冲突的情况如下:
上述两图比较,我们不难发现,由于调用了stdlib.h,库中的rand函数与我们命名的rand变量冲突导致了编译器报错。
同理在实际工作中,我们也会遇到两个程序员可能使用了相同的命名,因此就需要对这些情况进行一些更正。
那么在说明C++对命名冲突的完善之前,如果有两个同名的局部变量x和全局变量x,我们在C语言中如何打印出全局变量呢?
首先我们知道,如果不做任何限制在主函数内进行打印变量x,那么编译器会优先搜索主函数这个局部域中有无变量x,若无,转而搜索全局域。因此,如果不作出调整,我们是难以在主函数中打印出全局变量x的。那么就有两种方式能够达成目的:
①全局变量封装在函数内部,调用函数打印。
②使用 :: 域作用限定符
左侧是域名,右侧是变量名,表示指定编译器搜索该域名中的该变量。
左侧若想代表全局域,域名省略即可。
编译器的搜索原则:
1、当前局部域;2、全局域;
如果使用 :: 域作用限定符对域进行了指定,那么直接去指定域搜索变量。
回到正题,对于C++而言,如何解决命名冲突的问题呢? ===> 创建命名空间域。
在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。
namespace---创建命名空间域
关键字namespace,字如其意,使用它能够创建一个命名空间域,使用方法类似结构体定义如下:
namespace name//名字任意取
{;//写入需要创建的变量/函数/结构体类型
}
如我们可以创建一个命名空间域,名字为Uika,在其中创建一个变量a,一个Add函数,一个结构体类型Book:
namespace Uika
{int a = 10;int Add(int x, int y){return x + y;}struct Book{float price;char name[20];int code;};
}
接下来我们可以在主函数中使用域作用限定符 :: 对这些变量/函数/结构体进行使用:
值得注意的是,:: 这个域作用限定符在指定结构体时,所放的位置有一些不同,左侧域名与 :: 需要放在struct之后,放在结构体类型名Book之前:
那么能不能使用同一个名字创建两个不同的命名空间域呢?
答案是可以的,编译器会将这两个命名空间域合并,只要内部不产生重定义或者命名冲突,就不会报错。
如下举例所示:我们创建两个命名空间域,名称均为Uika,但是创建的变量/函数/结构体类型名各不相同:
namespace Uika
{int a = 10;int Add(int x, int y){return x + y;}struct Book{float price;char name[20];int code;};
}namespace Uika
{int b = 10;int Add1(int x, int y){return x + y;}struct Book1{float price;char name[20];int code;};
}
在主函数中使用 :: 对Uika中的变量等进行搜索打印:
我们发现,无论是a、Add还是b、Add1,都能够被找到并打印出正确结果。
说明了编译器是允许创建命名空间域的名称相同的,编译器会将相同名称的命名空间域合并。
但是如果合并有命名冲突,在后续使用过程中就会出现报错!
using namespace---展开命名空间域
一般在日常学习、写代码过程中,使用using namespace展开命名空间域,是方便使用、便于我们学习的,但是在正式场合、工作项目中,一般不使用using展开自己的命名空间域,而是展开某个域中的某个需要的成员:
平时学习过程中,为了方便学习,我们可以直接展开std命名空间域:
注意:原std命名空间域中是没有cout与endl成员的,我们需要包含输入输出流的头文件:
#include<iostream>
注:C++库中的头文件都不带 .h
命名空间域的嵌套
如果公司要求将所有代码写到一个business的命名空间域,那么不用担心会有命名冲突,可以在命名空间域中嵌套命名空间域。
三、C++输入输出cin&cout
使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream >头文件以及按命名空间使用方法使用std;cin和cout是全局的流对象/变量,它们都包含在<iostream>头文件中,调用该头文件后被存于std命名空间域中,因此需要展开std命名空间域才能使用它们。
我们也可以对比一下printf/scanf函数与cin/cout对象:
对于cout的输出和cin的输入而言,编译器会自动识别对象类型,而使用printf、scanf需要手动控制格式。
cout的作用类似于控制台,将<<右侧的内容从左到右依次打印出来。
endl的作用就是换行 == \n" :
上图中输出结果可以看出:endl的作用与换行操作符\n相同,因此通常在C++中,我们使用endl来搭配cout使用,当然也可以使用换行操作符 '\n' 。
cin也可以同时进行多次输入:
注意:早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应头文件即可,后来将其实现在std命名空间下,为了和C头文件区分,也为了正确使用命名空间,规定C++头文件不带.h;旧编译器(vc 6.0)中还支持<iostream.h>格式,后续编译器已不支持,因此推荐使用<iostream>+std的方式。
对于cout输出指定小数位数的数据,涉及到后面的C++的函数,比较复杂且代码较长。推荐直接使用printf来完成,如两位小数输出double类型:%.2lf即可。
四、缺省参数
缺省参数是声明或定义函数时为函数的参数指定一个默认值(缺省值)。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。
我们在C语言阶段所学习的函数,在函数的形参定义上,是不能对其进行赋值的,是通过传入实参,形参拷贝一份代入函数:
而在C++中,有了缺省参数的概念,即,在函数定义的形参部分,可以对其赋予默认值,如果传入实参没有指定该对象,那么就使用该对象的缺省值:
上图将函数中的所有参数都赋予初始值的方式,我们称之为全缺省。
对于传入的实参而言,不能跳过传参,必须要从左到右!
跳过某个参数传参的错误示范如下,编译器是不会通过的:
半缺省,即函数参数中部分采取缺省参数的形式,缺省参数从右往左连续给,不能跳跃:
跳跃,不从右到左设置缺省参数的错误示范如下:
说的通俗一点,Func(int a , int b , int c),如果半缺省,那么当a是缺省参数时,b就一定得是缺省参数;b是缺省参数时,c就一定得是缺省参数。缺省参数的设置是从右到左设置的,可以只有c是缺省参数,a、b不是缺省参数;可以b、c是缺省参数,a不是缺省参数;也可以三个都不是缺省参数。
注意事项总结:
①半缺省参数必须从右往左依次来给出,不能间隔着给;
②缺省参数不能在函数声明和定义中同时出现;同时出现编译器无法确定到底使用哪个缺省值。
如果声明和定义分离,缺省参数给在声明处。
③缺省值必须是常量或者全局变量;
④C语言不支持(编译器不支持)。
缺省参数的实际运用举例
数组栈的空间开辟:
五、函数重载
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数或参数类型或参数类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。
通俗地来讲,C++可以存在同名函数(其 参数类型 / 参数个数 / 参数类型顺序 不同),但是C语言不能够存在同名函数。
参数个数不同的同名函数举例:
参数类型不同的同名函数举例:
参数类型顺序不同的同名函数举例:(其实本质还是参数类型不同)
在C语言中,不论声明与定义是否分离,不论要查找的函数是在包含的头文件中还是就在本身的.c文件下,都不允许存在同名函数!原因就是:C语言查找函数的地址是通过函数名去查找的,在翻译环境下的编译阶段会将代码转换为汇编指令,对于函数而言,它的地址就是函数内部执行的代码转换为汇编指令的首条指令的地址,在链接过程中,编译器会通过函数名在符号表(汇编过程形成符号表)中寻找对应的函数名,然后就找到了地址从而调用函数的执行指令。
既然C语言中只是通过简单的函数名去查找函数地址,那么理所应当地不能够使用同名函数,不然的话就能够找到多个函数的地址,那是要使用哪一个呢?因此C++对函数名的修饰进行了优化,从而能够使用同名函数,这种情况称之为函数重载。
C++下的函数名修饰(name Mangling)
函数名称 + 参数类型 构成了C++的函数名字修饰。
C++中,在声明与定义未分离时,编译器能够直接通过定义找到对应函数并调用,因为名字修饰使同名函数在汇编形成的符号表中的名称并不相同!
如下图,我们就能够看到VS下的名称修饰规则:
Linux下g++编译的名字修饰就更加简洁直观:
C语言中找函数名,不进行特别的名字修饰,因此不能存在同名函数:
正是因为C++有自己的函数修饰规则,只要参数不同,修饰出来的名字就不同,因此才能够有同名函数的存在。
注:两个函数函数名和参数一样,返回值不同是不构成重载的,因为调用时编译器没办法区分!