序言
今天是2021年12月31日,也是2021年的最后一天,时间过得很快,也很慢,回想年初在江边玩耍的时候定下学习目标,仿佛就是昨天的事,细想来从定下目标的那一天至今我已完成了很多课程的学习,复盘今年,好像我走哪都只有学习的那种感觉给我留下的印象最深刻,我都怀疑如果不是学习,也许我现在根本想不起今年都去到了哪里,干了些什么。今年出了两趟远门,去了趟上海出差,在酒店的桌子上学习链表。去了趟河南的丈母娘家,在书桌上学习动静态库的引用和作用域、链接属性等。年初时周末为了不被家里小孩打扰,在弟弟的新房里学习,那段时间学的是裸机,再后来周末带娃去恒大售房部的游乐元园玩,我在旁边的会客桌学的是uboot移植。再后来,孩子放暑假送回了老家,开始过二人世界,炎热的夏天,二人在卧式开着空调,学习到12点,那段时间学了linux驱动,再后来每个周末都在公司学习,开始过上了全年无休的生活,还是公司呆的时间比较长,学的知识也比较多,多到我没办法把它们都一一列出来。
有理想且为自己的理想奋斗的过程是幸福的,这一年我虽然有过迷茫,有过工作低谷,即使现在的我也没有完全从迷茫和低谷中走出来,但是我心里也有些许的踏实,这种踏实是学习给我带来的。今年部门人员变动很大,该走的不该走的都走了,我再次夯实了老油条这个称号,看着别人的离开我的内心也很动荡,要不是我有自己的计划,我现在也应该已经离职了。
很庆幸我年初为自己制定了学习目标,很感激一路坚持学习的自己,至少学习让我内心动荡的期间,有一个精神支柱,使我内心的桥梁没有崩塌,没有在迷茫的时候愁白头。
学完linux核心课程,紧接着从11月1日踏上了这条注定只有开始没有结束的c++征途,为期2个月的时间把c++学了个七七八八,学习过程中,越学越发现c++的博大精深,以前觉得c语言是门大学问,现在想来比起c++它简直是容易死了,到现在所使用的c还是98版的标准,而c++去年才更新了,而且后续还会有更新,所以不敢说把c++学完了,只是把现有的课程学习完了,也不敢说精通c++,只能说c++略知一二吧。至此c++的学习暂告于段落,接下来要做的就是通过学习qt去应用c++,在应用中去领悟c++。
今日写下这篇总结目的,一是借此将这2个月以来的c++课程进行一个回顾。二是对所学知识进行梳理将零散的知识进行串联,也算是对2021年的一个总结吧。
截止2021年10月31日,完成了嵌入式核心课程的学习,我本应该多做些项目去巩固课程所学,但是我厌倦了工作中的各种开发流程,所以要快刀斩乱麻,快速的学完c++和qt后转向做软件开发,因为软件开发的流程简单很多,我不必花很多的时间来应付那些看似合规实则没有意义的事情。这一遍的学习我并没有准备去和每一个知识点死磕到底,通过之前学习c语言的过程中我发现,技能这个东西需要循序渐进,先有一个知识架构,再在使用中去慢慢研究,必要时还会再回来重复看一看视频教程。好了废话不多说,开始我的主题。
c++概述
c++的由来
c++由c发展而来,早期有一种叫法叫做带类的c,后来随着要解决的问题越来越复杂,c++增加了很多新的语法和特性,导致今天的c++已看不到太多当初c的身影,虽然变复杂了,但是还是完全兼容c的,所以他们还是好兄弟。c++应该这么来看,它并不是一门更好c,而另外的一门语言,c有少量的库,而c++的库多了很多,特别是强大的STL库,c之所以成功,是因为他有指针,c++之所以成功,我想他除了面向对象外,还有很多可以高效率的完成工作有关,c之所以没被淘汰,是因为c的用户多,c的效率比c++更高,所以在linux底层都是使用的c或效率更高的汇编,只是汇编有点费人。到目前为止c在单片机领域仍然处于霸主地位,因为单片机业务相对简单些,所以对语言的要求也简单些,c适合用来做小规模的程序,适用处理性能一般的cpu,而c++则是凌驾于系统之上,有着更高级的语法特性,来解决更复杂业务逻辑。
c++到底是什么
c++是一门半面向对象的语言,之所以说是半,是因为他为了完全兼容c,也多了很多的包袱。c++中的面向对象的语法特性的权重相当于指针在c中的地位,c和c++都是编译型语言,最终的可执行文件的运行,与运行平台有关,c++文件的扩展名典型的是.cpp。记得课程上老师用了一个难度守恒定律来描述c++,就是说语言简单了,那么程序员需要考虑更多,语言难度增加了,那么解决业务问题的编程就变简单了。因为c++难,所以更适合编写大程序,编写一些架构等。
c++是面向对象语言之一,面向对象并不是c++独有的,很多高级语言都有面向对象,况且c++并非纯面向对象,他有很多非面向对象语言,c++能够经久不衰,是因为他是面向对象语言中效率最高的语言,他的效率高主要归功于他的多态、封装、继承、面向对象的设计模式。使用c也能实现面向对象只是要实现面向对象要程序员做很多额外工作,额在面向对象的语言中,这些额外的工作已经被语言本身实现,程序员的工作就变少了。
非面向对象、面向对象、框架设计、设计模式是一件顺利成章的事。
要学习面向对象应该先学习面向过程。明白实现过程的步骤再去体会面向对象的解决问题的思路这样的学习来得更结实,更透彻。
面向对象是相对面向过程来说的,编程由面向过程转向关注对象,面向对象其实是一种编程的架构组织方式,实际上干活的还是那些代码,干的还是那些活,只是编写思路发生了变化。面向对象是一种分装数据和看待问题的更高层次和视角。面向对象是应对复杂问题更有效的方法,语言由面向过程到面向对象,是一个自然的成长之路。和人的成长之路一样。
面向对象编程的工作主要分我两大块,一个是编写类库,另一个是使用类库来完成具体的业务,这两大块通常由两拨人来分工完成,大部分人通常是应用类库实现业务。
c++该怎么学
c++相较于c来说要无论是关键字还是其内容的抽象度和语法细节都要复杂很多,关系不止2倍,面向对象是其核心,有了面向对象的思维,也就会了c++,模板和泛型是精髓,只是刚入门一般用得很少,只有大神在结构、框架编写中才会遍地使用,老师曾不止一次提到,不要出去吹自己精通c++,这门学问太大,精通容易被打脸。
c++的学习不要试图去记住所有,根本不现实,要以理解为主,通过写代码去吸收语法的特性,形成自己的理解,达到在解决问题时大概知道用声明语法,即使你连关键字都不会拼写,语法细节也记不住了,完全可以通过查阅资料,来编程。写多了自然也就记住了。
c++基本保持在3-5年更新一次,每次更新都是增加一些关键字,其实这些增加,也有很多是借鉴了其它类型的编程语言的一些优秀特性。
c++的学习分为4重境界,
第一层:语法层面,对语法比较熟悉,会使用c++的语法来建模编程。
第二层:能使用c++解决具体业务问题,大部分人停留在这一步。
第三层:编写类库供别人使用,出了问题能快速解决,具有一定的框架思维。
第四层:理解c++设计背后的原因,有自己独立思考的能力,能品出c++的魅力,思考问题的方式和c++的设计者具有一定的同步思想,把c++上升到哲学境界。
c++的应用场景
c++就是用来干业务复杂的开发的,类似于qt、opencv等软件虽然应用编程是使用c++实际上这些软件本身也是使用c++开发而来,c++更适合用来做后台业务逻辑,而前台的开发并不是c++的强项,在当下c++最大的优势就是在视觉领域做AI开发,游戏和图像引擎、网络服务引擎等开发。
c++程序员的发展前景
c++的学习难度大,通过率低,只要是使用c++编程的人无论是做什么方向,待遇都不会差,相较于其他语言,c++的程序员的辉煌周期更长,天花板也更高。c++对内功的要求比较高,很多人根本都入不了门就被淘汰,当然c++的岗位需求也比java、python要少,但整个行业的c++的大神非常稀缺。
总的来说,语言没有好坏之分,只有适合与不适合。c适合资源拮据的平台,如单片机,而c++适合资源中产的平台,如手机、中控屏等,像java、python就适合资源富裕云端,只在乎开发效率,不在乎资源的平台。
c++的语言特性
语言特性来源于实际需求,因为需求变的得越来越复杂,所以语言必须跟着变复杂,每一种语法都是为了解决实际需求,都对程序员有帮助,语法特性越多,某种程度上也可以说明这门语言也更厉害,同时学习难度也更大,本质上语法特性是靠编译工具链的支持,我们使用的各种语法,其实都是在按照规则,调用编译器的各种功能,所以高级语言的背后真正强大的是编译器。语言的本身变迁无非是关键字新增或变更一些语法特性,学习过程中的重点就是掌握这些语法,而真正解决问题还是要靠编程思想。
c的源文件扩展名.c 头文件扩展名.h。c++的源文件扩展名有.cpp、.cxx、.cc、.c、.c++ 头文件扩展名有.hpp、hxx、.h
在c++中完全兼容c,在c++中包含头文件一般没有扩展名,如”#include<iostream>” 当然也支持c中的.h的写法,其实c++为了支持c的.h写法,c++的编译器为了兼容c在背后做了很多工作,这些工作是对高效的一种破坏,所以我们在c++的程序中应尽可能的避免c中的一些特殊语法。
cpluspluse前后双下划线,是c++编译器提供的一个环境变量,在程序中我们无需定义可以直接使用,该变量内部记录了c++的版本,而这个变量在c的编译环境下木有,利用这个特点,很多时候我们使用一个宏来判断该变量是否存在,如果不存在就按照c的方法来处理,存在就按c++的方法来处理。
c与c++混合编程
c有很多优秀的代码和库在c++中重写没有必要,丢了也可惜,另外就是一些底层实现时比较注重效率,所以通常会出现在c++中引用c的静态库或者c++的静态库在c源码中引用。c和c++的程序最终都是被编译成.o文件,所以在.o文件将不同类型的源代码链接在一起理论上完全是可行的,然鹅还是有一些细节的东西有区别,如c++支持函数重载,所谓函数重载就是允许函数名相同参数类型不同的函数存在,编译器可以通过对参数对别仍然可识别程序员的意图,编译器底层其实是对重载函数的名称做了更改,添加了一些关于传参类型的标识,来实现函数名重载,然而c不支持函数重载,自然也不会修改名称,所以如果在c涉及调用c++的函数,就无法找到函数,因为编译器已经把函数名进行了修改。导致无法链接成功。解决方法就是让c++安装c的方式来命名。实现方法就是extern “C” { },括号内的元素使用c规则编译,extern是c++中才有的关键字,在c编译器下无法编译,为了通用,我们又添加了#ifdef _cpluspuls,来对extern“c”进行选择编译,__cpluspulus是c++编译器自带的环境变量,用来存储c++的版本,利用对该变量的判断我们即可知道当前编译器是c还是c++。
#ifdef __cplusplus
c++源码引用c静态库混编
这种情况通常出现在供应商给我的文件时.a库,由于编译器编译时会把源文件中的函数名进行修改,所以我们调用的函数名肯定是错误的,处理办法就是加上extern “C”,按照c规则不改变函数名称。
c源码与c++静态库混编
该混编的问题也出在我们不知道c++编译的函数名,解决思路就是我们另外建立一个c++的函数,函数中去引用原来 .hpp中给出的函数,而我们自己写的函数再添加extern”c”来实现名称不改变,编译成另外一个库,其原理就是内部调用名称被改变的函数,对外的封装函数名称没有改变。
gcc 1xxx -c -o 2xxx.o //将1xxx只编译不链接,命名为2xxx.o
objdump -d 2xxx.o > 3xxx.i //将2xxx.o文件反编译成.i文件。
.o文件内部全部是二进制,本身也带有符号信息,只是我们看不懂二进制,所以要使用反编译器将其转换成我们能看懂的格式。
gcc 1xxx -c -o 2xxx.o //将1xxx只编译不链接,后文件名命名为2xxx.o
ar -r lib1xx.a 2xxx.o //ar是工具链,-r静态库,lib1xxx.a库名,lib开头。
g++ main.cpp -l1xxx -L. //-l指定库名,-L指定当前路径
c++编译器版本指定 -std
在linux的gcc编译器中集成了多个c++的版本,不同版本的linux默认使用的c++的版本也不同,但是我们可以使用-std来指定。示例如下
g++ aa.cpp -std=c++11 //指定c++11版本来编译aa.cpp源程序
C++语法库
valgrind工具查看内存泄漏
之所以c++难,其实主要是我们要对内存操心,当我们程序太大后内存申请后容易忘记释放,导致程序吃内存,所以检测程序是否有内存泄漏比较重要,在此介绍一个工具来做内存检测valgrind。
valgrind工具介绍
Memcheck是valgrind应用最广泛的内存检查器,能够发现开发中绝大多数内存错误使用情况,除了内存检查还包了以下功能:
Callgrind—用于检查程序中函数调用过程中出现的问题。
Cachegrind—用于检查程序中缓存使用出现的问题。
Helgrind—用于检查多线程程序中出现的竞争问题。
Massif—用于检查程序中堆栈使用中出现的问题。
命令:sudo apt-get install valgrind
memcheck使用
编译:重新编译要检查的源码,编译时添加-g生成dbug版本目标文件。
g++ person.cpp main.cpp -g -o apptest
检查内存:实际上该工具就是对代码运行过程的内存申请和释放统计。./后面跟被检查的可执行文件,
valgrind --tool=memcheck --leak-check=full --show-reachable=yes --trace-children=yes ./app
命名空间就是用来解决变量、函数、结构体、枚举等重名的问题,在一个庞大的程序中,重名很难避免,所以很多高级语言都有命名空间。在c中我们解决重名主要有几个方面,文件内的重名由程序员自己解决,需要做全局变量的变量名通常会以模块或开发团队缩写字母来做前缀来区分,文件内的函数则使用static来将作用域限定在本文件内,而枚举在c中压根就没能解决重名的问题,无论在c中用了什么方法来解决重名的问题,总的来说都是程序员自己定义的一些潜规则,然而很多人也根本不遵守这些规则,语言本身并没有解决重名问题。
在c++中解决重名使用了命名空间来对任何元素统一做链接属性的限定,包括宏、枚举、结构体、联合体等。命名空间的定义使用namespace,紧跟xxx来表示空间名称,以{ }大括号来限定范围,括号内可以有函数、变量、枚举等任何东西。在括号内大家可以相互访问,而括号外要访问括号内必须使用“空间名::变量名”方式来访问,这种方式看起来也是一种前缀。
关键在于关键字namespace示例如下:
namespace people
{
int a;
void func();
}
- 方式一
外部引用命名空间内的成员,使用::示例如下
people :: a=10;
people :: func();
- 方式二
使用using关键字,将空间内的成员进行声明,声明后的访问就不在需要加空间名,函数声明时不需要使用括号,调用时需要使用括号,示例如下:
using func;
using a;
func();
a=10;
- 方式三
使用using将整个命名空间的所有成员都进行声明,相当于把指定空间内的所有成员都拿出来共享。示例如下:
using namespace pople;
a=10;
func();
没有特意指定命名空间的函数或变量会被编译器统一归类为一个命名空间,这个空间就是默认命名空间,如我们的man函数。默认命名空间我们在访问时也不需要指定空间,直接调用即可,典型的就是我们的c程序使用c++编译器编译。
匿名也就是没有名字,在c中我们想要把一个函数的作用域限定在文件内,在函数前加static,但是static不能用来修饰enum、struct,而匿名命名空间就是用来解决这个问题的。默认命名空间没有名称,自然无法访问内部成员,典型使用就是用来做拒绝跨文件引用。只能在空间内部即文件内部访问。实现方法就是在定义时不用写命名空间的名称,示例如下
namespace
{
int a;
func();
}
命名空间也可以嵌套,即一个命名空间中包含另一个命名空间,在外部访问时需要使用“::”一层一层的向内剥离,如下示例
namespace aa
{
int a;
namespace bb
{
int b=10;
a=b;
}
}
aa::bb::b=10;
嵌套的命名空间内部可以直接访问上层所有成员,但上层不能访问下层成员,如果两空间成员名称相同,那么在命名空间内部优先使用同级命名空间成员,类似于全局变量与局部变量同名时优先使用同级别的成员。
c++标准库的使用
在学习c的时候我们用到了一些库,如stdio.h,c的库少的可怜,往往越是高级的语言库就越多,如java学习完基础的语法才算是刚刚入门,而真正的进阶是库的学习。c的头文件在c++中做了一些优化,其名称只是在前面添加了一个c,而引用时也不在是原来的方式,而是没有了.h如 “#include<cstdio>”,c++为了兼容c,其实是牺牲效率换来的成果,如我们在c++中包含头文件使用“#include<stdio.h>”实际上在背后会被转换成“#include<cstdio>”,所以为了效率我们应该尽可能的使用c++的语法。
iostream引用
iostream是c++非常常用的库之一,和c中的stdio.h一个重量级,为了防止重名,该库使用了命名空间,空间名称为std,在访问iostream内部成员时必须在前面添加空间名称和域操作符,由于使用得太过频繁,所以我们通常会直接对命名空间的所有元素统一进行声明,常用的方式如下:
using namespace std;
在c中标准输出使用printf在c++中使用cout,cout属于std命名空间中的成员,标准输出的典型格式为
cout << “hudaizhou” <<endl;
其中“<<”是流操作符,可以多个元素的接续输出,输入输出涉及ostream和istream两个类,他们都继承了iostream,cout本质上是osteam的一个对象。<<则是在ostream中对<<进行的运算符重载。可以简单的把<<看成一个箭头,cout关键字看成一个显示设备,我们将字符移动到设备就是输出,endl也是osteam中的一个对象,用来做换行操作,等效于\n\r,
cin是c++中的标准输入,他是isteram的一个对象,定义于std命名空间。示例如下
cin>>a>>b;
其中箭头的方向与cout相反,我们同样可以将cin看成一个输入设备“键盘”,箭头指向的对象就是用来接收输入的内容,他同样可以级联,如上例,输入第一个存放在a中第二个放在b中,输入内容之间以空格为分割。如果输入的元素超出接收的变量,那么后面的会被丢弃。
在c找那个文件读写使用了open()、read、write等在c++中同样也有这一套,只不过把名称进行了修改。
open函数
将名为 filename 的文件打开并与文件流关联。
示例:fstream fs; //创建一个文件流指针
fs.open(pach); //打开pach文件
is_open函数
检查文件流是否有关联文件,检查是否打开成功。
if(fs.is_open()==true)//打开成功
close函数
功能:关闭打开的文件
示例:fs.close(); //关闭文件
read函数
功能:读取文件
示例:char read_buff[20]={0};
fs.read(read_buff,sizeof(read_buff));
write函数
功能:写文件
示例:fs.write(