C++相关概念与语法基础——C基础上的改进与优化
一、C/C++对比
C语言作为一门经典的编程语言,语言的结构化是C的一大特点,C语言在结构上可分为顺序、分支、循环三大结构,C相比其他语言,较贴近底层,在各大编程和开发领域都有较为广泛的应用,是很多编程学习者的入门语言,当然C语言在广泛运用的同时,也存在一些不足之处,例如在C语言中,不允许定义同名变量和函数,可见下图:
(1)
(2)
图(1)定义了一个整形变量rand,并初始化为10,printf函数在标准输出流上打印rand的值。图(2)与图(1)类似,不同在于图(2)包含了一个头文件<stdlib.h>,编译时却发生错误,错误信息为rand的重定义,原因在于<stdlib.h>头文件中包含了rand指针,与我们定义的整形变量rand命名冲突,因此导致编译不通过,这个例子就很好地说明了C中不允许定义同名变量和函数。此外,C中若想通过函数来改变实参的值,则必须通过传实参的地址即一级指针才能改变实参,若想改变一级指针,则必须通过传二级指针,即C中只能通过函数传址调用才能改变实参,而不能通过直接传值调用改变实参,传址调用常常会涉及多级指针,往往会较复杂,若能通过直接传值调用,则就不需要考虑多级指针的问题了,这也是C可以考虑优化的一个方面。
此外,C不允许同名函数,即函数名不能相同,与上述变量命名冲突情况类似。那么在实践使用C进行编程时,要考虑的情况就多了,需要考虑变量不能同名,函数传址调用往往要考虑多级指针,且函数不能同名,这些情况往往会导致C的代码量较大,且较繁琐。针对上述情况,C++创始人比雅尼 斯特劳斯特鲁普1979年在C语言的基础上对其进行了优化,创立了C++,C++在C的基础上对C进行了改进,针对C语言的命名冲突问题,C++语法允许变量和函数同名,解决了命名冲突的问题,针对C中函数传址改变实参往往涉及多级指针的问题,C++引入了引用的概念,引用使得C++允许函数进行直接传值调用,代替了多级指针,总得来说,C++是在C基础上的改进与优化,同时C++也兼容C的语法,是在C基础上优化和改进的版本。
二、C++基本语法
1、初识C++
C语言的文件,文件名通常以.c为后缀,C++文件以.cpp为后缀,我们先看一个简单的C++程序,实现在屏幕上输出字符串“hello world”。
C++编译需包含的头文件为iostream,为标准输入输出流文件,using namespace std能够将头文件中的std命名域内容展开,后面我们将会详细介绍,cout为标准输出流,<<在C语言中为左移位操作符,在C++可表示将要输出的内容以标准输出流的形式输出到标准输出平台,且<<会自动识别输出内容的格式,不需要像C语言中需手动指定输出格式,endl底层相当于换行符,类似于C的“\n”。
2、域
(1)域的概念
为了解决C语言变量和函数命名冲突的问题,C++引入了域的概念,在C++中分为4个域:全局域,局部域,命名空间域,类域,每个域的变量相互独立,即不同的域的变量就算同名,也不会有命名冲突,即每个域的变量允许同名。
再回头看上面rand命名冲突的问题,我们可以用域来解决这个问题,namespace为定义一个命名域的关键字,yzk为用户自定义域名,我们在该域定义一个整形rand变量,并初始化为10,这时整形变量rand在命名域yzk中,与全局域<stdlib.h>头文件的rand不在同一个域,不会命名冲突,但是调用整形变量rand需要域引用符::,同时也需指明rand所在的域,所以整形变量rand的表示方式为yzk::rand,若直接使用rand,则表示全局域即<stdlib.h>头文件的rand指针。
namespace命名域中除了可以定义变量,也可以定义函数、结构体。
命名域中的函数、结构体与其他域中的函数、结构体也是相互独立的,对它们进行访问同样也需要域访问符::
若有两个整形变量同名,其中一个为局部变量,该变量所在域为局部域,一个为全局变量,该变量所在域为全局域,则C++优先访问局部变量,遵循就近原则,若想访问全局变量,则需要在变量前加入域访问符::,即可访问全局变量。
如上图,a遵循就近原则,表示局部变量a,其值为0,::a表示全局变量a,其值为1。
此外,命名域可存在相互嵌套,即命名域中可再包含命名域,不同命名域中也可再定义同名变量,如下图:
namespace yzk域中包含了三个命名域y、z、k,三个域中分别定义了三个整形变量rand,三者相互独立,互不影响。
若想访问三个域中的rand,则需进行两层域访问,若想访问y域的rand,则需表示为yzk::y::rand,同理访问z、k域中的rand,需表示为yzk::z::rand,yzk::k::rand,如下图所示:
(2)域的展开
命名域也可进行展开,命名域的内容展开一般有两种方式,第1种展开方式为全展开,即将命名域的所有内容展开,如下图所示:
定义了一个命名域K,using namespace K表示将K中的内容全部展开,这时访问命名域K的成员就不需要域访问符::,直接使用变量名即可,例如想访问K中的整形变量a,就不需要域访问符::
第2种展开方式为部分展开,即只展开域的部分成员,如下图:
上图中我们只部分展开了命名域K中的成员a,using K::a表示展开K中的成员a,这时访问成员a就不需要域访问符::,直接访问即可。
3、cin、cout、endl
cin、cout是C++的标准字符输入、输出流,类似于C的scanf、printf函数,不同在于C++的cin、cout会自动识别输入、输出内容的格式,而C的scanf、printf需要我们自己设置输入、输出格式,可见在这一方面cin、cout相比scanf、printf要有优势,endl底层类似于C的“\n”,即换行,cin、cout在iostream文件的std域中,使用时需先将std域展开,即using namespace std。
如上图,定义了三个整形变量a,b,c,由于using namespace std已将std的内容全部展开,故cin、cout可不加域访问符::,也可加std::,cin获取a、b、c的输入内容,并自动识别输入格式,与>>搭配,cout也会自动识别输出格式,并输出a、b、c,与<<搭配,“ ”表示空格,表示a、b、c之间用空格隔开。
a、b、c由cin输入1、2、3,则cout在屏幕上输出1 2 3。
由上图可知,cout会自动识别输出内容格式,而printf需要手动设置输出格式,对于double类型,cout默认保留5位小数,对于&a,即a的地址,cout也会自动识别其输出格式。
4、缺省参数
C++函数相比C的函数多了缺省参数这个概念,缺省参数指的是函数参数在没有实参对它传递时,函数参数所默认的值,若有实参传递,则参数为实参的值,函数缺省可分为全缺省和半缺省,全缺省即函数的所有参数都是缺省参数,每个函数参数都有默认值,半缺省即函数参数部分缺省,C++规定半缺省函数的参数必须从右往左依次缺省,不能中间、跳跃缺省,全缺省、半缺省可见下图:
如上图所示,fun1函数的参数都有默认值,即都为缺省参数,故fun1为全缺省函数,fun2函数的参数b、c有默认值,故b、c为缺省参数,a没有默认值,故a不为缺省参数,且函数从右到左,不存在跳跃、间隔缺省,故fun2函数为半缺省。
上图fun1函数三个函数参数分别进行2,3,4实参传递,故a、b、c的值为2、3、4,fun2函数只传递了一个实参,即a=10,b、c没有实参,则b、c的值默认为缺省参数的值,即b=4,c=5,。
5、函数重载
C++函数较C的函数有了很大的不同,第1个不同就是在C中,不允许函数同名,而在C++中,允许函数同名,可参考下图Add函数的实现。
上图实现两个Add函数,一个是实现整形的相加,另一个则是double类型的相加,两个函数的函数名均为Add,可见C++允许函数同名,这是C++在C基础上的进一步优化,函数同名,就不用考虑命名冲突的问题了,主函数main直接调用Add函数,Add(1,1),Add(1.1,1.1),编译器会自动识别并调用相应的函数,我们一般将其称为函数重载。
C++函数重载允许函数名相同,函数的参数可不相同,如函数参数类型不同
如上图,Swap函数的函数参数类型不同,返回值类型相同,Add函数的函数参数类型与返回值类型均不相同。
此外,还包括函数参数的有无
如上图,第一个f()无函数参数,第二个f()有函数参数,且缺省参数a=10。
函数参数顺序也可不相同
需要注意的是C++不允许函数名和函数参数相同,但函数返回类型不同,原因是这种情况下编译器无法区分两个函数,存在歧义,无法构成重载。若想区分,则必须将其中一个函数置于一个命名域中,这样函数间就相互独立了,如下图
k()两者函数名与函数参数类型都相同,但返回类型不同,无法构成重载,为了区分,可将其中一个k()置于命名域K中,这样二者就相互独立,互不影响。
调用上面的重载函数,只需直接调用函数名即可,编译器会自动识别并调用相应的函数,需要注意的是针对上面函数名和函数参数相同,但函数返回类型不同,无法构成重载的,需将二者置于不同的域中,调用时需域引用符::,即可完成函数的调用。
6、引用
C语言中通过调用函数来改变实参,需要通过该实参的地址才能改变,也就是只能通过传址调用才能改变实参,传址调用往往涉及多级指针,且往往较复杂,针对上述情况,C++引入了引用的概念,使得函数能够直接传值调用,就避开了多级指针的复杂情况,引用的含义即给变量取别名,通过引用变量的别名来直接修改变量,就不需要通过指针来修改变量,使用起来更加方便。
引用的一般格式为:(变量的数据类型)& (别名)=(变量名)
如上图,首先定义了一个整形变量a,并初始化为0,接着使用引用,可知b、c、d都是a的别名,它们的地址都是a的地址,b、c、d其实就是a,它们的地址都相同,对b、c、d的修改就是对a的修改,就不需要通过指针来对a进行修改,引用也可再引用,int& e=b,就是将e作为b的别名,b又是a的别名,那么e实际就等价于a,对e的修改也就是对a的修改。
对a、b、c、d、e分别取地址&,可以看出它们的地址是相同的,即都为a的地址,b、c、d、e就是a的别名,对它们的修改就是对a的修改。
Swap交换函数在C中是一个典型的需要进行传实参的地址来交换实参的函数,在C++中就可不必通过传址来交换了,我们可以借助引用,直接进行传值调用来交换实参,如上图,Swap函数参数为两个整形变量的引用,那么下面我们想要交换a,b的值,则需直接传值调用该函数Swap(a,b),即可完成交换,这时a的别名就是rx,对rx的修改就是对a的修改,b的别名是ry,对ry的修改就是对b的修改,就不需要通过指针、解引用来交换a、b的值了,直接传值调用相比传址调用更加方便,很好地优化了代码。
Swap函数通过引用参数rx、ry,对rx、ry的修改就是对a、b的修改,从而完成a、b的交换,引用不仅可以作为函数参数,也可作为函数的返回类型,如下图
引用的底层还是相当于指针,但引用与指针不同在于引用必须初始化,而指针可不初始化,此外,指针可改变其指向,而引用一旦初始化,就不能再改变其指向。
引用初始化后就不能再改变其指向,如上图的d初始化为b的别名,则就不能再改变d的指向,故d=e并不是改变d的指向,而是将e的值赋值给d,改变了d的值,而不改变d的指向,d还是b的引用。
由上图可知,b、c、d都为a的引用,故a、b、c、d的地址相同,d不为e的引用,只是将e的值赋值给d,故e的地址与a、b、c、d不相同。
7、const修饰引用
对于const修饰的对象,对它进行引用时,也需const修饰引用。
若对象没用const修饰,则也可用const修饰引用。
如上图,rb为b的引用,此时rb用const修饰,则rb对b只能读不能写,故rb++是错误的写法,b++是正确的写法,因为b并没有用const修饰,这时rb作为b的引用,由于const修饰rb,则引用rb权限缩小,对b只能读不能写。
此外,const修饰的引用也可表示常量,比如const int& c=30,这时c就为常量30的引用,可读不可写。const修饰的引用也可表示表达式,const int& rd=(a+b),a+b表达式的结果会以临时变量的形式保存起来,在C++中,临时变量是可读不可写的,故引用需用const修饰,即const int& rd=(a+b),此外,隐式类型转化也需用const修饰引用,如上图的x为double类型,const int& m=x,首先需将x强制类型转化为整形,强制类型转化的值也需const修饰,即const int& m=x。由此可知,const修饰的引用可表示变量,常量,表达式,以及强制类型转化的值,可见const修饰的引用可表示多种数据类型,所以const修饰的引用常常作为函数参数。
如上图,func的函数参数为const修饰的引用,则调用func,函数参数可以为常量30,表达式a+b,以及强转类型x,以及浮点数1.1,可见const修饰的引用作为函数参数,大大提高了函数的灵活性。
8、inline内联函数
C++函数与C函数不同之处还包括C++引入了内联函数的概念,C++内联函数会在函数调用处将函数全部展开,这样就不需要创建函数栈帧,开辟空间,可以提高函数调用的效率,内联函数声明只需在函数名前加入inline关键字,则该函数就为内联函数,如下图
如上图,Add为内联函数,则函数将在调用处展开,无需创建栈帧。
内联函数对于编译器仅仅是个建议,也就是说在函数代码量较大,或者函数递归深度太深,编译器也可选择不展开内联函数,内联函数适用于代码量少、且频繁调用的函数,该函数编译器会在函数调用处展开。
9、类
C中有自定义类型struct结构体类型,C++在结构体的基础上引入了类的概念,定义类的关键字为class,类相比于结构体,在结构体的基础上,类中可以定义函数,此外,类也有自己独立的域,我们称为类域,类中的成员类型有3种,public、private、protected,三种成员类型的作用域为从当前类型开始到下一个成员类型结束,若无下一个成员类型,则作用到类结束。public类型的成员是公开的,允许外界访问,private、protected类型的成员归为类私有。
如上图,定义了一个简单的类class,类名为stack,函数Init为公开类型。
上图定义了一个类名为date的类class,函数Init为public类型,允许外界访问,year、month、day为private类型,time、minutes、seconds为protected类型,为类私有。
访问类的成员,与C中访问结构体成员的方式类似,用“.”操作符即可访问。
如上图,定义了一个类class,类名为stack,创建一个类型为stack的类st,访问类的成员,与结构体一致,使用.操作符,st.pop(),st.push(1),即可完成对类成员的访问。
此外,C++还进一步优化了语法,在C中若想对结构体命名,则需使用typedef关键字来进行命名,而C++无需typedef关键字,直接使用结构体名即可,如上图的struct ListnodeC,用C的语法命名需typedef关键字,C++语法则无需typedef,直接使用结构体名ListnodeCpp Cpp即可。
此外,类中函数的声明和定义可以分离,但需要域引用符::,指明函数所在类域,如上图的Init函数,其声明与定义分离,声明在类域中,则定义需域引用符::,即date::Init。
三、结语
以上就是本文有关C/C++的介绍,首先分析对比了C/C++,C++是在C基础上的进一步优化,针对C语法一些不足的地方,例如C中不允许变量和函数同名,C++引入了域的概念,使得变量可以同名,针对函数不能同名的情况,C++引入了函数重载,使得函数能够同名,解决了C的命名冲突问题,此外,C中函数只能通过传址调用来改变实参,往往涉及多级指针,且较为复杂,C++引入了引用的概念,使得函数能够直接传值调用,使得代码变得较为简单,此外,我们也进一步分析了const修饰引用的情况,const修饰的引用可表示多种数据类型,引用的权限可以缩小,但不能放大,const修饰的引用常常作为函数参数。此外,C++函数还引入了缺省参数,缺省参数为函数无实参传递时默认的参数值,以及内联函数,内联函数会在函数调用处直接展开,无需创建栈帧,提高了函数调用的效率,适合于代码量较少、频繁调用的函数。C++还引入了类的概念,类可理解为在结构体基础上的进一步改进,类中可定义函数,以及类有3种成员类型,public、private、protected,public成员可被外界访问,private、protected成员为类私有,以上这些都是C++的语法基础内容,C++较其他编程语言,语法更为复杂,且学习难度也较高,需要我们打牢这些语法基础,C++的学习,语法是这条路的起点,掌握了C++的基础语法,我们才能在这条路上越走越远,与其他语言不同,C++有较高的自由去操纵内存和硬件资源,在接下去的不断深入学习中,我们将会领略它所带来的高性能快感!