自学内容网 自学内容网

C++入门基础

我们在学习完C语言之后,开始踏入新的领域——C++。C++顾名思义就是C语言的进阶版,它是基于C语言的,但是也有许多C语言中所没有的,这节我们就来学习一些C++的一些入门基础知识。

文章目录

前言

一、命名空间

1.1 命名空间的价值

1.2 namespace的定义

1.3 namespace的性质

1.4 命名空间的使用

二、C++的输入输出

三、缺省参数

四、函数重载

五、引用

5.1 引用的概念

5.2 引用的特性

5.3 引用的辨析

5.4 引用的使用

5.5 const引用

5.6 指针和引用的关系

六、内联函数

七、nullptr

总结


前言

最近5年里,编程语言的发展可谓是进入了井喷时期,每年都会诞生超过50多种新的编程语言,但是C++语言始终在编程语言排行榜中位居前三,还不时地挑战榜首。由此可见C++的含金量了,但是它的学习难度也很大,但是我们不要害怕,正如翁恺老师所说的:“计算机的所有东西都是人做出来的,别人能想出来的,我也一定能够想出来,在计算机里没有任何黑魔法,所有的东西只不过是我现在不知道而已。总有一天,我会把所有细节,所有内部的东西全都搞明白。“


一、命名空间

命名空间是我们刚开始接触C++所要接触的第一个新东西,它是在C语言中所没有的,但是它在C++中是必备的,就像我们每次在写C语言时都要先写一个头文件,它的地位就跟它差不多,可以说是每次写C++时的一个框架。(当然C++中也有自己的头文件,我会在后面介绍)

1.1 命名空间的价值

我们知道在C/C++中变量,函数,类都是大量存在的,这些类,变量,函数一般都创建在全局域或者函数局部域,如果我们在写一个变量时恰好和C++标准库中自带的函数重名时,是不是很难绷?为了避免这种命名冲突或名字污染的情况,于是我们就引入了命名空间这一概念。

1.2 namespace的定义

我们在创建一个命名空间时,首先要先写一个关键字namespace,然后再接一个命名空间的名字(这个名字可以由使用者自己任意创建,不过在后面我们会介绍命名空间中一个标准库的名字std)最后在接一对{ },{ }内即为命名空间的成员,在命名空间中我们可以定义变量/函数/类型等等,我们在命名空间里面所定义的变量/函数名即使是与全局域或函数局部域中所定义的变量重名也不会报错,因为他们之间存在域隔离,每个域之间都是独立的。

C++中有全局域,函数局部域,命名空间域,类域,域影响的是编译器在查找一个变量/函数/类型的出处(声明和定义)的逻辑,因此有了域隔离之后,名字冲突的问题就解决了。全局域和局部域除了会影响编译器查找时的逻辑还会影响变量的生命周期(即变量在编译器中存在的时间),而命名空间域和类域不会影响变量的生命周期。我们需要知道的是:在不做任何操作的时候,编译器会默认访问全局域与局部域中的变量。如果想要访问命名空间域或者类域(这个我们后面会着重介绍,这节就简单提及一下),就要通过C++中的范围运算符(::),我们可以通过这个运算符来访问修改命名空间或类中的成员。

1.3 namespace的性质

1.namespace只能定义在全局域中,也可以嵌套定义。这条性质其实很好理解:我们将几个范围差不多的空间是不是应该放在并列位置,但是全局域是包含整个编译器的,那么它的空间是最大的包含其他几个域,至于其他域的空间大小都是差不多的,如果我们将一个空间放到和它差不多的空间中定义实现是不是感觉怪怪的呢?至于嵌套定义就更好理解了,命名空间域在自己的空间中定义自己,应该合情合理的,只不过比最先的那个小一点,这就跟我们小时候玩的套娃是一个道理。如下图所示,嵌套定义完全是可行的,只不过我们在调用其成员时,要多使用几次范围运算符喽~

2.项目中多文件定义的同名namespace,会认为是一个namespace而不会发生报错。我们之前在学习数据结构的时候,经常要创建好几个文件,一个文件用来存放存放一些函数的声明,一个文件用来存放实现代码,还有一个文件用来存放测试代码。对于这些一个项目使用多个文件来实现时,我们在不同的文件中定义命名空间,如果我们将那些命名空间的名字都定义一样的话,那么编译器就会认为它们是属于一个命名空间的,大概意思就是同名命名空间可以跨文件共处一个空间~

3.C++的标准库都放在一个叫做std(standard)的命名空间中。我们在刚开始就说过了,命名空间的创建是要有个namespace关键字的,然后再加一个自己任意创建的命名空间名字还有一对{ }。这里我们来介绍一下C++中一个标准库的命名空间名字(std)。这个命名空间中会自带一些对象(变量/函数/类型),我们可以不用自己定义了可以直接调用了,具体哪些对象我会在后面慢慢介绍的~

1.4 命名空间的使用

我们在编译查找某个变量或函数时,编译器会默认到全局域或局部域中去找,不会去命名空间域中去找。那么下面编译程序就会发生报错,那么我们如果要使用命名空间中的对象,我们有以下三种方式:

1.指定命名空间访问,在我们后面做一些项目比较适合。这种方式我们,是通过直接指定是哪个命名空间,然后再去那个命名空间中去访问,这种在那些大型项目中,不乏有许多同名对象,我们如果使用指定命名空间访问,就不用担心命名冲突或名字污染的问题了。

2.using将命名空间中的某个成员进行展开,这种方式通常适用于项目中一些经常使用而且没有命名冲突的成员。这种方式,我们就不用像上面那样在main函数中如果要使用哪个对象时就不用再通过命名空间使用范围运算符(::)来查找命名空间中的对象,但是我们要提前在main函数外将要使用的那个对象从命名空间中展开出来。注意的是,我们使用这种方式的时候要确保,全局域或函数局部域中没有同名对象,不然会出现重命名的报错。

3.using将命名空间中的所有成员都进行展开,这种方式在一些大型项目中不太适合,在我们日常写一些小代码比较适合。这种方式不像上面两种方式那样,需要哪个对象就将哪个对象放开,这种方式直接将所有的成员都放开,于是我们在main函数中就可以调用了,但是这种方式容易发生命名冲突的问题,使用前提和第二种方式是一样的。

二、C++的输入输出

介绍完C++中的命名空间,现在我们再来学习一下另外几个和C语言中有所区别的东西。在C语言中我们在实现输入输出是使用scanf和printf来实现的。但是在使用这两个函数时还有点麻烦:比如使用scanf,我们要传递变量的地址过去,想当年在刚开始学习C语言时不少人应该都掉进这个坑过,半天找不到错误。还有printf,这个函数虽然是我们学习C语言时第一个使用的函数,但是它的输出格式属实有点麻烦,对于不同数据类型的变量,我们要使用相对应的输出格式。于是那些研究C++的大佬们就研究出属于C++的输入输出。

在介绍C++中的输入输出之前,我们要学习一下C++中的一个库<iostream>,iostream是 Input Output Stream的缩写,是标准的输入,输出流库,这个库里面定义了标准的输入,输出对象。除此之外,<iostream>中还兼容C语言中的输入输出。上面在介绍命名空间时我们已经小小地提及到了C++中的一个标准库命名空间std(standard),我们的输入,输出就是从这里面来的。

• std::cin是istream类的对象,它主要面向窄字符(narrow characters (of type char))的标准输入流。

• std::cout是ostream类的对象,它主要面向窄字符的标准输出流。

• std::endl是⼀个函数,流插入输出时,相当于插入⼀个换行字符加刷新缓冲区。

• >>是流提取运算符,<<是流插入运算符,搭配上面两个输入输出流进行使用。(C语言还用这两个运算符做位运算右移/左移)

另外再提及一下,在一些竞赛题中我们经常使用C++做的话,有时候我们可以输入如下代码,我们知道C++是基于C语言,那么它也是兼容C语言的,下面的代码是为了取消与C语言的联系,那样我们在编译一些题目时所花费的时间开销就会少一点。

#include<iostream>
using namespace std;
int main()
{
 // 在io需求⽐较⾼的地⽅,如部分⼤量输⼊的竞赛题中,加上以下3⾏代码 
 // 可以提⾼C++IO效率 
 ios_base::sync_with_stdio(false);
 cin.tie(nullptr);
 cout.tie(nullptr);
 return 0;
}

三、缺省参数

缺省参数是在函数声明或定义时给函数参数的指定一个缺省值。我们在调用函数时,如果没有指定实参就使用形参中的缺省值,否则使用指定实参。缺省参数分为全缺省和半缺省(有些地方把缺省参数叫做默认参数)。

定义一个函数时,全缺省参数就是给函数的每一个形参都指定缺省值(就是对形参进行一下初始化),而半缺省参数就是部分形参指定缺省值。但是我们在给半缺省参数指定参数时要注意顺序,我们要按照从右往左的顺序进行指定缺省值,我们也不可以间隔跳跃给缺省值

调用一个函数时,C++规定必须从左往右传实参,也不可以跳跃给实参

当函数声明与函数定义不在一个地方时(不在一个文件中),我们规定不能同时给函数声明与函数定义都指定缺省值,我们只能给函数声明指定缺省值~

四、函数重载

在C语言中,我们知道如果定义两个同名的函数就会发生重命名的报错,但是C++中引入了函数重载这一概念,这一方法使得C++变得更加灵活。函数重载就是一个函数(函数名)可以有好几个功能。这是什么意思呢?我们知道,编译器是看菜吃饭的,我们在函数的形参中给了哪个数据类型,那么这个函数就实现什么数据类型的功能。C++正是利用这个特点,通过给予同一个函数不同的形参,那么这个函数不就有好几个功能了嘛。对于函数重载中有三种方式:

1.形参数据类型不同;

2.形参的个数不同;

3.形参的数据类型顺序不同。

对于上面两种方式很好理解,对于第三种方式我要多讲一句。我们知道传递的形参是一个变量,何为变量就是可以变化的量嘛,如果我们仅仅将两个变量进行交换顺序,那有什么意义呢?所以我强调的是形参的数据类型顺序不同。还有一个容易踩雷的坑就是函数的返回值不同不会构成函数重载(如果可以的话,我不就往上面写了嘛,哈哈哈~)。

五、引用

5.1 引用的概念

引用不是重新定义一个变量,而是给那个变量取一个别名。编译器不会为引用变量开辟一个新的内存空间,它和它所引用的对象共用一个内存空间。我们可以这样理解:引用这个东西就是给你取一个别的名字,对你没啥实质性的改变,你叫张三,在家里家里人喊你小三子,在公司里同事们喊你老张,老板喊你小张。不管别人怎么喊你,你还是你,只不过是不同人不同的叫法而已。

 这是引用的书写格式 :  类型&引用别名=引用对象; 

这里我们需要注意的是,在C++中为了避免使用太多的运算符,会重用C语言中的一些运算符,比如前面的流插入,流提取运算符,与C语言中的左移右移运算符就进行了重用。而这里的引用也重用了C语言中取地址操作符&,因此我们要注意方法角度进行区分。

5.2 引用的特性

1.引用在定义时必须要初始化。引用的前提就是要有一个被引用对象,如果没有被引用对象,咱们去引用谁,给谁去取别名呀~

2.一个对象可以有多个引用即可以取多个别名,我们还可以给别名取别名。

3.引用一旦引用一个实体后,就不可以在引用其他实体了,引用不可以改变指向。

5.3 引用的辨析

我们在C语言中学习过 typedef 和宏,可能会有人将这两个与引用弄混。typedef是给一个类型重新命名一个名字,我们一般是在一个类型太长的时候,使用typedef重命名一个更加简洁的类型名,它所针对的是类型;至于宏,它一般是在全局域中所定义的,宏是我们单独定义的,然后在编译器进行编译时,把我们定义的宏将我们main中的变量或函数直接替换掉。这么说来,typedef还是比较有点像引用,但是两种的作用对象不同,我们要注意啦~

5.4 引用的使用

我们一般使用引用传参和引用做返回值来减少拷贝提高效率和改变引用对象同时改变被引用对象。我们在C语言中经常使用指针传参,其实引用传参和指针传参本质都是一个的,两者最终都是指向所在的内存空间的,不过引用传参更加方便点,这对于指针没有学好的小伙伴有福了,我们在传参时就不用再纠结是传递一级指针还是二级指针啦,一个引用就能搞定了。我们在C语言中学习的经典交换函数,我们之前传递的参数就是指针,本质传递地址,现在我们使用引用传参,那么我们在函数中对形参进行的操作,同样也会影响到实参的值,因为两者是共用一个内存空间的。如下我们就是两种传参的方式,这么一看是不是引用传参方便多了。

引用和指针在实践中相辅相成,在功能上具有重叠性,但是各有特点,互相不可替代。C++的引用跟其他语言的引用如Java是有很大的区别的,除了用法,最大的点,C++引用定义后不能改变指向, Java的引用可以改变指向。这种问题我们后面如果要学习java可要注意喽,不要直接将C++生搬硬套上去噢~

5.5 const引用

const我们在C语言中就已经学习过了,它是一个修饰符,它放在变量前面就会让这个变量的权限缩小,本来这个变量既可以读又可以修改,当我们使用const修饰之后,这个变量就只能够读了不可以对其进行修改了。这里在引用中,我们再次将const拿过来了。我们引用一个const对象时,就必须使用const引用。而对于一个普通对象,我们既可以使用普通引用,也可以使用const引用。因为对象的访问权限在引用时只能够被缩小不能够被放大。

除此之外,还有一种情况需要使用const引用,类似 int& rb = a*3; double d = 12.34; int& rd = d; 这样⼀些场景下a*3的和结果保存在⼀个临时对象中, int& rd = d 也是类似,在类型转换中会产生临时对象存储中间值,也就是时,rb和rd引用的都是临时对象,而C++规定临时对象具有常性,所以这里就触发了权限放大,必须要用常引用才可以。所谓临时对象就是编译器需要⼀个空间暂存表达式的求值结果时临时创建的⼀个未命名的对象, C++中把这个未命名对象叫做临时对象。

5.6 指针和引用的关系

C++中指针和引用就像两个性格迥异的亲兄弟,指针是哥哥,引用是弟弟,在实践中他们相辅相成,功能有重叠性,但是各有自己的特点,互相不可替代。

• 语法概念上引用是⼀个变量的取别名,不开空间,指针是存储⼀个变量地址,要开空间。

• 引用在定义时必须初始化,指针建议初始化,但是语法上不是必须的。

• 引用在初始化时引用⼀个对象后,就不能再引用其他对象;而指针可以在不断地改变指向对象。

• 引用可以直接访问指向对象,指针需要解引用才是访问指向对象。

• sizeof中含义不同,引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节,64位下是8个字节)

• 指针很容易出现空指针和野指针的问题,引用很少出现,引用使用起来相对更安全⼀些。

六、内联函数

用关键字inline修饰的函数叫做内联函数,编译时C++编译器会在调用的地方展开内联函数,这样调用内联函数就不需要建立栈帧了,可以提高效率。用inline修饰函数只是给编译器一个建议,最终是否展开内联函数,还得看编译器自己。这个什么情况展开,C++中没有一个标准,inline一般适用于一个频繁使用且比较短小的函数,对于递归函数和一些代码量比较大的代码,编译器则会忽略掉inline,即不会展开内联函数。其实内联函数展开就是将用inline修饰的函数代码带入到主函数中,对于那些短小的函数还可以,这样就可以减少创建与销毁栈帧的时间开销了,但是那些代码量比较大的函数进行内联函数展开,可能会导致代码膨胀,这是我们所不愿意看到的,因此我们要避免。我们如果想要判断内联函数是否展开了,可以在编译器中转到反汇编,看是否有[call地址]这条汇编指令,如果有则说明没有展开,没有的话就说明已经展开了。总的来说,内联函数是C语言中宏的平替升级版,它展开时也是替换代码,但是它不像宏那样,在定义宏的时候会有许多坑,有时候一不小心就会掉下去了,而且也不好进行调试修改。

• vs编译器debug版本下面默认是不展开inline的,这样方便调试,debug版本想展开需要设置⼀下以下两个地方。

• inline不建议声明和定义分离到两个文件,分离会导致链接错误。因为inline被展开,就没有函数地址,链接时会出现报错。

七、nullptr

这里我们再来介绍一个C++中与C语言不同的一点,我们之前在C语言中认为NULL是空指针,但是在C++中则把NULL定义为字面常量0。为了弥补这个坑,C++研究的人又定义了一个空指针nullptr,之后我们在C++中如果要使用空指针就使用这个。如下,我用两个重载函数验证了一下。


总结

这节内容是给之前学习C语言中所留下的坑埋坑,这节我们初步了解一下C++,后面我们才正式开始学习C++,同志们任重而道远啊,最后给大家看一个漫画放松放松:


原文地址:https://blog.csdn.net/H2X7_/article/details/145177473

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!