数据结构后续补上,先从c++开始慢慢补
先了解一下c++创始人,本贾尼·斯特劳斯特卢普博士
可以看出是强者的发型。
C语言是结构化和模块化的语言,适合处理较小规模的程序。对于复杂的问题,规模较大的
程序,需要高度的抽象和建模时,C语言则不合适。为了解决软件危机, 20世纪80年代, 计算机
界提出了OOP(object oriented programming:面向对象)思想,支持面向对象的程序设计语言
应运而生。
1982年,Bjarne Stroustrup博士在C语言的基础上引入并扩充了面向对象的概念,发明了一
种新的程序语言。为了表达该语言与C语言的渊源关系,命名为C++。因此:C++是基于C语言而
产生的,它既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的
程序设计,还可以进行面向对象的程序设计。
C++还在不断的向后发展。但是:现在公司主流使用还是C++98和C++11,所有大家不用追求最
新,重点将C++98和C++11掌握好,等工作后,随着对C++理解不断加深,有时间可以去琢磨下更
新的特性。
操作系统以及大型系统软件开发
所有操作系统几乎都是C/C++写的,许多大型软件背后几乎都是C++写的,比如:
Photoshop、Office、JVM(Java虚拟机)等,究其原因还是性能高,可以直接操控硬件。
服务器端开发
后台开发:主要侧重于业务逻辑的处理,即对于前端请求后端给出对应的响应,现在主流采
用java,但内卷化比较严重,大厂可能会有C++后台开发,主要做一些基础组件,中间件、
缓存、分布式存储等。服务器端开发比后台开发跟广泛,包含后台开发,一般对实时性要求
比较高的,比如游戏服务器、流媒体服务器、网络通讯等都采用C++开发的。
游戏开发
PC平台几乎所有的游戏都是C++写的,比如:魔兽世界、传奇、CS、跑跑卡丁车等,市面上
相当多的游戏引擎都是基于C++开发的,比如:Cocos2d、虚幻4、DirectX等。三维游戏领
域计算量非常庞大,底层的数学全都是矩阵变换,想要画面精美、内容丰富、游戏实时性
搞,这些高难度需求无疑只能选C++语言。比较知名厂商:腾讯、网易、完美世界、巨人网
络等。
嵌入式和物联网领域
嵌入式:就是把具有计算能力的主控板嵌入到机器装置或者电子装置的内部,能够控制这些
装置。比如:智能手环、摄像头、扫地机器人、智能音响等。
谈到嵌入式开发,大家最能想到的就是单片机开发(即在8位、16位或者32位单片机产品或者
裸机上进行的开发),嵌入式开发除了单片机开发以外,还包含在soc片上、系统层面、驱动
层面以及应用、中间件层面的开发。
常见的岗位有:嵌入式开发工程师、驱动开发工程师、系统开发工程师、Linux开发工程
师、固件开发工程师等。
知名的一些厂商,比如:以华为、vivo、oppo、小米为代表的手机厂;以紫光展锐、乐鑫为
代表的芯片厂;以大疆、海康威视、大华、CVTE等具有自己终端业务厂商;以及海尔、海
信、格力等传统家电行业。
随着5G的普及,物联网(即万物互联,)也成为了一种新兴势力,比如:阿里lot、腾讯lot、京
东、百度、美团等都有硬件相关的事业部。
数字图像处理
数字图像处理中涉及到大量数学矩阵方面的运算,对CPU算力要求比较高,主要的图像处理
算法库和开源库等都是C/C++写的,比如:OpenCV、OpenGL等,大名鼎鼎的Photoshop
就是C++写的。
人工智能
一提到人工智能,大家首先想到的就是python,认为学习人工智能就要学习python,这个
是误区,python中库比较丰富,使用python可以快速搭建神经网络、填入参数导入数据就
可以开始训练模型了。但人工智能背后深度学习算法等核心还是用C++写的。
分布式应用
近年来移动互联网的兴起,各应用数据量业务量不断攀升;后端架构要不断提高性能和并发
能力才能应对大信息时代的来临。在分布式领域,好些分布式框架、文件系统、中间组件等
都是C++开发的。对分布式计算影响极大的Hadoop生态的几个重量级组件:HDFS、
zookeeper、HBase等,也都是基于Google用C++实现的GFS、Chubby、BigTable。包括分
布式计算框架MapReduce也是Google先用C++实现了一套,之后才有开源的java版本。
除了上述领域外,在:科学计算、浏览器、流媒体开发、网络软件等都是C++比较适合的场景,
作为一名老牌语言的常青树,C++一直霸占编程语言前5名,肯定有其存在的价值。
好了了解完c++大体情况现在正式开始入门知识了解。
C++首先是在C语言基础上进行一些功能优化,之后又不断迭代逐渐演变为C++。
所以C++可以兼容C语言90%及以上的语法,但并不是完全兼容,在一些特殊情况下还是会冲突但大部分情况C语言和C++可以混合编写。
#include
using namespace std;
int main()
{
printf("hello world\n");
cout << "hello world" << endl;
return 0;
}
可以看出printf在c++领域中依然有效, < iostream >是c++中标准头文件类似于C语言中的stdio.h
其命名基础是两种命名为istream和ostream的类型,分别是输入流和输出流,这里了解一下,后续会深入。
在了解一下c++关键字:
不需要死记硬背,后续边学边敲边记就好。
c++的第一个知识点
如上图所示全局变量rand和库中的rand函数命名冲突,而C语言没办法解决类似这样的命名问题,所以c++提出了namespace来解决
再说命名空间时,先回顾一下C语言作用域相关知识点,C语言分为局部域和全剧域,其对函数,变量,参数的影响在于使用和生命周期
看一下这张图f1,f2还有全局的a在各自的作用域使用并不影响,f1和f2的a在函数结束后失效,全局的a则在其定义之后到程序结束之前都有效。那能不能在f1或者f2中使用全局的a?答案是可以的,接下来我们认识一个新符号 :: 两个冒号,此符号叫做域作用限定符,他可以指定的去访问某一个作用域
如上图所示域作用限定符左侧是要访问的域吗,右侧是需要的变量等,空格表示全局域但是这个预作用限定符并不能在一个函数中去访问另一个函数的域例如f1中不能访问f2中的a。
(注意域作用限定符不存在于C语言中)在编程中函数名也容易冲突,所以c++引入了命名空间的概念。命名空间域只影响使用,但不影响生命周期
如上图,其关键字是namespace,具体定义是namespace + 域名+花括号(结尾无需分号),花括号所包含的地方就是该命名空间所有域。
在程序编译过程中,使用变量时默认先在局部找,再去全局找 ,编译自己不会去命名空间找
但如上图可以人为去指定去命名空间找。
两个不同的命名空间内可以定义同名的函数,类型,变量,使用时加上域作用限定符,不会冲突
在多个文件定义同名的命名空间,系统会将他们合并,这时候不同文件中相同名字的命名空间中相同的变量,函数,类型就会冲突,这时候要么去改名,要么实行命名空间的嵌套
命名空间嵌套时使用变量,函数,类型就要这样使用了
这种类型和函数在一个命名空间里,我们只需要指定类型的命名空间,在调用与其相关的同一命名空间函数时就不需要域作用限定符来限制了,但稳一点最好还是指定一下命名空间
当我们觉得挨个指定命名空间的时候太过麻烦,就可以像上图这样命名空间全局展开,我们上面说系统不会去命名空间搜索,当我们展开时,系统就会去展开的命名空间中搜索要使用的东西
但是说实话全局展开这种方法虽然比较省事但是不太好,因为它违背了最后开始的意愿,命名空间制定就是要防止冲突,要是全展开了,就相当于建立的命名空间白费了,何时使用全局展开靠程序猿自己定夺了。
当然还有一种比较保守的办法就是命名空间部分展开如下图所示
如上图所示,这样的话在main函数中使用cout和endl就不用在多余的编写域作用限定符了。
最后一幅图做总结:
首先我们了解两个符号,分别是流插入<<,和流提取>>,与它们相连使用的分别是cout和cin,我们在这里看一下他们与C语言的不同:
这里的 cin>>n 等价于C语言的 scanf(“%d”,&n) , 同理 cout< 当然在c++编程中流插入与流提取和C语言中标准输入输出是可以穿插得用,什么时候用流插入提取,什么时候用C语言输入输出要具体情况具体分析。这里简单了解就好,后续还会深入研究。
这个是c++前期一个比较重要的知识点
如上图为C语言,在main函数中调用func函数必须给参数1,但有时候我们只是想看看一个函数的作用,我们不知道传递什么参数类型,传错了可能会报错,这个时候c++就引入了缺省参数这个概念。
如上图所示,在c++中定义函数时我们可以先给其形参赋一个参数,这个参数就叫缺省参数,当在main函数中调用函数没有给定参数时,就会自动使用缺省参数,当在main函数中给了参数,就会调用给的这个参数,如下图所示:
缺省参数也有全缺省和部分缺省,我们先来看全缺省
全缺省顾名思义就是在函数定义时给每个形参都赋了缺省参数
如上图所示,函数定义时给每个参数都上了缺省值
而当我们不想使用缺省参数时,我们要从右往左连续的传参比如下图
我们一旦在一个参数位置上使用了形参,那么就要在它的左边所有形参位置都赋值,否则就会报错。
效果图如下:
顾名思义就是没有给每个参数都赋上缺省值,当然半缺省赋缺省参数时也是一个道理,也要从右往左连续的赋缺省值,下图举一个错误示例:
上图为正确的半缺省
最后的缺省值知识点就是当一个函数声明在一个xxx.h的文件中,而函数的定义在xxx.cpp中时,不能在声明和定义中同时给缺省值
先来看函数重载定义
如上图所示,上图这种情况在c++允许,但C语言不行,上图就是函数重载
函数重载分为三种情况:1、函数参数类型不同 2、参数个数不同 3、参数类型顺序不同
关于编译器如何区别函数重载不同类型的函数:
每个编译器对c++都有自己的函数修饰规则
这个是linux下对函数的编译规则_Z是指令编码,3是函数名长度,ii是函数参数类型
上图所示的规则了解一下就好。
这里在强调一下返回值不同不构成重载
如上两幅图所示,要是只有返回值不同,那么调用时就会产生歧义。
这个知识点比较重要,它在前期没有什么地位,但后期会频繁的使用引用,还是先看定义。
记住引用其实就是给一个变量xx取了个别名yy,就像一个人有全名,有小名,但无论叫那个名字,归根结底都是同一个人
如图所示(画的比较丑见谅),b是a的引用(就相当于a的别名),它与a公用一块空间。没错看到这大家肯定觉得它和指针很像,其实底层就是指针,只不过语法上变成了引用,使用起来更方便。
(这里在区分一个点,特别注意,int&这种在类型后面放取地址符的是引用,&i这种放在变量前面是取地址 )这里可以看到k是i的引用而他俩地址是一样的。
如上图所示,他们都是引用,也就是说可以给一个空间取多个别名,也可以给一个空间别名的别名取别名
如上图所示,在函数中如果希望形参的改变影响实参就可以用引用,在一些情况就不用二级指针了
当然了引用相比于指针也是有一些局限性的,如下图所示:
如上两幅图所示,c不能成为x的别名,c++引用不能改变指向,这也是c++引用的一个局限性,举个例子,如下图所示
当我们手动实现链表的时候,next用引用,当我们要删除一个节点,改变next的指向是不行的,所以指针和引用是互补的关系,让一些场景变简单一点,而不是完全替代指针。
这里总结一些引用的特点
1、引用在定义时必须初始化 2、一个变量可以有多个引用 3、引用一旦引用一个实体,再不能引用其他实体 4、引用类型必须和引用实体是同种类型
如上图所示,1、做参数
再说第二个作用之前先补充一下一些其他的知识点:
如上图所示,这个所展示的过程就是main函数和count函数创建栈帧,而变量n在count函数的栈帧中,调用count函数完成后count栈帧销毁的一个过程,那么有个问题就是我们需要count函数的返回值,当count销毁了,那ret怎么接收n的返回值呢?这里就引出了临时变量这个概念,在count销毁之前会先生成一个临时变量(当返回值内存较小时临时变量由寄存器充当,当返回值较大时临时变量将在main函数中生成,并且临时变量具有常性不可修改),先将返回值拷贝给临时变量,之后函数销毁就不会影响返回值了。
再看这个场景,这里的n是存在静态区,但是编译器是比较傻瓜的所以返回时还需要临时变量
这时候问题就来了,我们都知道,我们不断的写程序或者日常的各种电子设备的软硬件更新,无非都是对其工作进行优化,那么上述两个场景可以优化吗?答案是第一个场景我们已经无法去优化了,但第二个场景明明不需要临时变量,但是编译器却额外消耗了内存,这个是可以优化的。(顺带一提这种需要临时变量的叫做传值返回)
这就是引用的第二个作用,做返回值 这里就叫传引用返回,编译器会给n去一个别名来返回,归根结底就是反回了n,不需要额外开空间。这里可能还有个疑问就是前面说函数不是销毁了吗,为什么还能返回n?这是因为编译器在程序运行过程中的函数销毁有时并不是立即销毁而是作为码农的我们失去了这个函数栈帧的使用权,并不是像灭霸打了个响指一样,直接抹除了这片区域,我们要是想用还是可以用的,虽然这种方法严格来说属于非法行为,但是编译器对非法行为的检查也不一定都可以检查到,而我们这种方法也没有造成任何损失,所以编译器不会报错。(函数栈帧是否会立即清理要看编译器,vs不会第一时间销毁)
如上两幅图所示传引用返回我们甚至可以去直接修改这个数组
所以传引用返回优点:1、可以减少拷贝 2、调用者可以修改返回对象
这里传引用返回还是小打小闹,以后的类和运算符重载会大量运用到传引用返回
当然我们也说了世界上没有完美的东西,如下图所示
传引用返回如果使用不当也可能造成这种结果,这是因为ret也是个引用它指向的也是c的别名,第一次调用的时候函数栈帧并没有销毁,但第二次调用时函数栈帧已经销毁了,所以此时是垃圾值
(这段话要接着上面的话看,上面没仔细看这里会发懵)
这里权限仅涉及指针和引用,不涉及其他知识点。
在对指针或者引用赋值或者初始化的时候,权限可以缩小或者平移但不能放大。
像上图的c是const常量整形,但d是整形引用,c不可更改,但若是赋值给引用d,那么就可以更改d来改变c的值,这就是引用的权限放大,这是不允许的,指针p1赋值给p2也是同理。
解决方法就是像上图给被赋值的对象也进行权限缩小,再赋值,那么这个时候c,d以及p1,p2他们的权限相等,都是不允许被修改的,这就是权限平移。
像这种x可修改,但引用y不可修改,就是y的权限小于x,这种情况是可以赋值的,这是权限的缩小。
看上两幅图其报错原因也是权限放大,因为函数返回值是临时变量,而临时变量具有常性,所以这里想用引用接收必须加const。
这里报错的原因是类型转换时也会出现临时变量。
类似于上图,类型转换并不改变i的类型,只是产生了一个临时变量。
接下来一张图总结一下引用和指针的区别。
上图可以看见,宏函数有时候会出现比较无厘头的错误,相信让大家突然写个add宏函数和上图差不多,还有一点,很多喜欢调试的同学可以编写一个宏,在调试界面是没有的。
当然也不是说宏没有优点,宏的优点就是不用开辟栈帧。
那接下来看一下c++中的内联函数。关键字是 inline
上图就是内联函数,inline就是内联函数的关键字,内联函数也不会开辟栈帧,而且在相关配置之后的debug和普通的release版本下都是可以调试的,在一些特定场景下可以提高效率。BUT内联也并不是完美的。
内联函数是一种以空间换时间的做法,如果编译器将函数当成内联函数处理(注意哈,不是你用inline定义了内联函数他就是内联,编译器需要对你的行为做出判断,至于通过编译器判断的条件后续再说),在编译阶段会用函数本体替换函数调用,其缺点就是可能会使目标文件变大,优点就是少了调用开销,在一定程度上可以提高运行效率。
上图就是可能存在的缺点。因为内联函数是直接展开函数,而不是建立栈帧,所以也会导致编译时程序会比较大。
函数小于75行,且有关键字inline就可以作为内联函数。
内联函数对编译器只是一个建议,假如说有一个函数是1000行,而你使用了1000个这个函数,而恰好这个函数又是内联函数,如果编译器听了你的岂不是需要1000*1000行代码空间,This is a disaster.所以并不是所有内联函数都不创建栈帧。
还有一点就是内联函数的定义和声明要搁在一个文件,不然就会报错,因为inline被展开时,没有函数地址,所以分开定义和声明会导致链接错误。
这里就是说在func.h进行内联函数声明,在func.cpp进行内联函数定义,这样在test.cpp里调用内联函数会产生链接错误,这里也不需要深究,记住就好了,内联函数声明和定义不要分离。
auto b=a和被注释掉的一行含义是相同的,auto关键字的作用就是自动推导类型。
这里稍微记一下typeid(变量).name()这个就是显示变量的类型
上图就是auto的最终形态,所以auto的价值就是简化代码(这里看不懂没关系,以后会看懂的,知道auto的作用就行)。
auto也有点缺点,例如你直接用auto可能会导致别人看不懂你的代码因为缺少了类型说明,比如在一个大项目中,你写的成分用了大量的auto,虽然可以运行,但在代码可读性上会有一些瑕疵,所以建议初学者还是少用auto,等成为大牛的时候想怎么用就怎么用。
像这种的意思就是不管是什么类型但必须是个指针,加了&就必须是引用
这里在插个语法糖,上述两个方法的效果是一样的,e不是固定的也可以死i,j,k等等任意名称。
范围for并不改变数组的值因为这是自动取数组的数据赋值给e。
这样加上引用就可以改变数组的值
上图这种情况是不允许用范围for的,因为array就是个地址,范围for比较傻瓜,它只会去找值,但不会去找地址。
这里额外补充一点,c++中NULL是0,c++中空指针是nullptr(注意大小写)。
上述就是此篇博客全部内容了,c++在语法学习上要比Java难一些,建议还是多看视频多做题,最后用书或者他人优秀博客来复习,最后制作不易求个三连,若是有问题欢迎在评论区互相探讨(笔者也是学者,有些知识点也不甚清楚)。