复习笔记

前言

以Q&A的形式贯穿整篇文章
每天进步一点点,像胡春旭说的,怕什么真理无穷,进一寸有进一寸的欢喜。
分为几个模块,本模块的内容主要为计算机组成结构与原理。还有关于系统设计及需求实现相关内容。
写这篇文章,更多地是希望自己以应用的角度去看待知识点,码农时间较短,很多地方自然没有大神那么强。
也是自己的需求吧,虽然都是码农,但是程序员的核心能力在于解决问题的能力,但是在解决问题的时候,用什么样的办法去切入问题解决问题,如何漂亮地解决问题,而不是硬生生凑出一个demo,这些需要的不仅仅是时间的积累,也有一个程序员最基本的基础。
Blog会不定期更新,最近有时间闲下来了就会努力更新。文章会随着内容不断增多,开始按照模块进行划分。

笔记

1. 计算机组成与网络基础

1.1 为什么要使用补码

参考网址 https://blog.csdn.net/TheSkyLee/article/details/109543418?spm=1000.2115.3001.4373.
在计算机中是只有加法器,没有减法器的,所以7-3会被系统转化成7+(-3)
在计算中像int float char都是以补码的形式存储的。
正数的补码就是其本身,负数的补码是每位取反然后再加一。

当在运行的程序中声明了一个变量
int a=0;
即在内存中申请了一个地址来存放变量。
1.2 寄存器

通俗地说,寄存器就是你的口袋。身上只有那么几个,只装最常用或者马上要用的东西。
内存就是你的背包。有时候拿点什么放到口袋里,有时候从口袋里拿出点东西放在背包里。1

1.3 为什么使用Linux

首先每一个新人都会问一个问题,为什么要选择Linux,不是因为Linux开源,Linux比windows更加稳定,同时支持小内核程序的嵌入系统。并且有更加安全的权限策略。

1.4 开源协议
  1. GNU GPL(GNU General Public License,GNU通用公共许可证)
    必须开源免费,不能闭源收费,协议并不适合商用软件。
特点说明
复制自由允许把软件复制到任何人的电脑中,并且不限制复制的数量。
传播自由允许软件以各种形式进行传播。
收费传播允许在各种媒介上出售该软件,但必须提前让买家知道这个软件是可以免费获得的;因此,一般来讲,开源软件都是通过为用户提供有偿服务的形式来盈利的。
修改自由允许开发人员增加或删除软件的功能,但软件修改后必须依然基于GPL许可协议授权。
  1. BSD(Berkeley Software Distribution,伯克利软件发布版)协议
    ROS遵循此开源协议
    2.1 BSD 协议基本上允许用户“为所欲为”,用户可以使用、修改和重新发布遵循该许可的软件,并且可以将软件作为商业软件发布和销售,前提是需要满足下面三个条件:
    2.2 如果再发布的软件中包含源代码,则源代码必须继续遵循 BSD 许可协议。如果再发布的软件中只有二进制程序,则需要在相关文档或版权文件中声明原始代码遵循了 BSD 协议。
    2.3 不允许用原始软件的名字、作者名字或机构名称进行市场推广。
    2.4 BSD 对商业比较友好,很多公司在选用开源产品的时候都首选 BSD 协议,因为可以完全控制这些第三方的代码,甚至在必要的时候可以修改或者二次开发。
  2. Apache 许可证版本(Apache License Version)协议
    和BSD差不多,意思就是用了apache的,你得在下边注明我也是apache的,用了BSD的,也要在下边说我是BSD的。
  3. MIT(Massachusetts Institute of Technology)协议
    目前限制最少的开源许可协议之一(比 BSD 和 Apache 的限制都少)
1.5 关于socket套接字的定义

socket 被翻译为“套接字”,它是计算机之间进行通信的一种约定或一种方式,在UNIX/Linux 中的一切都是文件。

1.6 基于TCP传输控制协议的SOCK_STREAM

流格式套接字(Stream Sockets)也叫“面向连接的套接字”
SOCK_STREAM 是一种可靠的、双向的通信数据流,数据可以准确无误地到达另一台计算机,如果损坏或丢失,可以重新发送
SOCK_STREAM 有以下几个特征:

1.数据在传输过程中不会消失;
2.数据是按照顺序传输的;
3.数据的发送和接收不是同步的(也称“不存在数据边界”)。
使用了 TCP 协议(The Transmission Control Protocol,传输控制协议),TCP 协议会控制你的数据按照顺序到达并且没有错误。
比如实际需要接收100个字节数据,发送方可能会以30,50,20,分三次将数据发送过来,接收方在接收到100个完整数据后,返回。

  1. 流格式套接字的内部有一个缓冲区(也就是字符数组),通过 socket 传输的数据将保存到这个缓冲区。接收端在收到数据后并不一定立即读取,只要数据不超过缓冲区的容量,接收端有可能在缓冲区被填满以后一次性地读取,也可能分成好几次读取。
  2. 为了保证数据包准确、顺序地到达,发送端在发送数据包以后,必须得到接收端的确认才发送下一个数据包;如果数据包发出去了,一段时间以后仍然没有得到接收端的回应,那么发送端会重新再发送一次,直到得到接收端的回应。这样一来,发送端发送的所有数据包都能到达接收端,并且是按照顺序到达的。
  3. 发送端发送一个数据包,如何得到接收端的确认呢?很简单,为每一个数据包分配一个 ID,接收端接收到数据包以后,再给发送端返回一个数据包,告诉发送端我接收到了 ID 为 xxx 的数据包。
  4. 面向连接的套接字会比无连接的套接字多出很多数据包,因为发送端每发送一个数据包,接收端就会返回一个数据包。此外,建立连接和断开连接的过程也会传递很多数据包。
1.7 TCP/IP模型

这个网络模型究竟是干什么呢?简而言之就是进行数据封装的。
我们平常使用的程序(或者说软件)一般都是通过应用层来访问网络的,程序产生的数据会一层一层地往下传输,直到最后的网络接口层,就通过网线发送到互联网上去了。数据每往下走一层,就会被这一层的协议增加一层包装,等到发送到互联网上时,已经比原始数据多了四层包装。整个数据封装的过程就像俄罗斯套娃。
给数据加包装的过程,实际上就是在数据的头部增加一个标志(一个数据块),表示数据经过了这一层,我已经处理过了。给数据拆包装的过程正好相反,就是去掉数据头部的标志,让它逐渐现出原形。
我们所说的 socket 编程,是站在传输层的基础上,所以可以使用 TCP/UDP 协议,但是不能干「访问网页」这样的事情,因为访问网页所需要的 http 协议位于应用层。

1.8 两台计算机间的通信原则

两台计算机进行通信时,必须遵守以下原则:
必须是同一层次进行通信,比如,A 计算机的应用层和 B 计算机的传输层就不能通信,因为它们不在一个层次,数据的拆包会遇到问题。
每一层的功能都必须相同,也就是拥有完全相同的网络模型。如果网络模型都不同,那不就乱套了,谁都不认识谁。
数据只能逐层传输,不能跃层。
每一层可以使用下层提供的服务,并向上层提供服务。

1.9 层级关系图

Alt

1.10 端口号
  1. 有了 IP 地址和 MAC 地址,虽然可以找到目标计算机,但仍然不能进行通信。一台计算机可以同时提供多种网络服务,例如 Web 服务(网站)、FTP 服务(文件传输服务)、SMTP 服务(邮箱服务)等,仅有 IP 地址和 MAC 地址,计算机虽然可以正确接收到数据包,但是却不知道要将数据包交给哪个网络程序来处理,所以通信失败。
  2. 为了区分不同的网络程序,计算机会为每个网络程序分配一个独一无二的端口号(Port Number),例如,Web 服务的端口号是 80,FTP 服务的端口号是 21,SMTP 服务的端口号是 25。
  3. 端口(Port)是一个虚拟的、逻辑上的概念。可以将端口理解为一道门,数据通过这道门流入流出,每道门有不同的编号
  4. 0~1023 的端口一般由系统分配给特定的服务程序,例如 Web 服务的端口号为 80,FTP 服务的端口号为 21,所以我们的程序要尽量在 1024~65536 之间分配端口号
1.11 面向连接的套接字
  1. 面向连接的套接字实际上,会建立一条虚电路,然后只要路由器没有被破坏或者断电,这条通信路径是固定的,所以消息发送在这条管道中按照先后顺序,不会出现先发送的消息后到达,后发送的消息先到达。
  2. 为了保证数据包准确、顺序地到达,发送端在发送数据包以后,必须得到接收端的确认才发送下一个数据包;如果数据包发出去了,一段时间以后仍然没有得到接收端的回应,那么发送端会重新再发送一次,直到得到接收端的回应。这样一来,发送端发送的所有数据包都能到达接收端,并且是按照顺序到达的。
1.12 阻塞模式
  1. 阻塞模式针对于send()和write()函数,如果tcp协议正在向网络发送数据,会锁定发送缓存区,此时是无法将数据存入发送缓存区的,必须要等到数据发送完毕后缓存区解锁。如果需要发送的数据大于缓存区长度的话,数据会分批发送。
  2. 阻塞模式针对于recv()或者read()函数,首先会检查缓冲区,如果缓冲区中有数据,那么就读取,否则函数会被阻塞,直到网络上有数据到来。 如果要读取的数据长度小于缓冲区中的数据长度,那么就不能一次性将缓冲区中的所有数据读出,剩余数据将不断积压

2. C++基础知识提炼

2.1 动态链接库 .so和静态链接库 .a
2.2 核心转储文件core dumped

当进程意外终止时,系统可以将该进程的地址空间的内容以及其他信息保存到核心转储文件。

2.3 Linux下64位环境的用户空间内存分布情况

Linux下64位环境的用户空间内存分布情况
站在文件结构的角度,可执行文件包含了众多的段(Section),每个段都有不同的作用;站在加载和执行的角度,所有的段都是数据,操作系统只关心数据的权限,只要把相同权限的数据加载到同一个内存区域,程序就能正确执行。
在 Linux 下,会经常遇到过一种叫做Segment fault(段错误)的错误,这种错误发生在程序执行期间,在编译和链接时无法检测,一般都是代码的权限不足导致的。

2.4 堆栈溢出

第一种情况是频繁使用递归函数,一般给一个进程分配给栈空间大小是2M,超过了这个上限,就会导致递归函数执行终止。
第二种情况是如果你创建一个数组过大,会引起堆溢出,操作系统给每个进程分配的最大堆空间是4G,如果过大会导致堆溢出

2.5 explicit关键字

显示声明构造函数,与另一个关键字implicit, 意思是隐式声明相对应。

class CxString  // 没有使用explicit关键字的类声明, 即默认为隐式声明  
{  
public:  
   char *_pstr;  
   int _size;  
   CxString(const char *p)  
   {  
       int size = strlen(p);  
       _pstr = malloc(size + 1);    // 分配string的内存  
       strcpy(_pstr, p);            // 复制字符串  
       _size = strlen(_pstr);  
   }
}  
//在实际调用的过程中
CxString string2 = 10;    // 这样是OK的, 为CxString预分配10字节的大小的内存

上面的代码中, “CxString string2 = 10;” 这句为什么是可以的呢? 在C++中, 如果的构造函数只有一个参数时, 那么在编译的时候就会有一个缺省的转换操作:将该构造函数对应数据类型的数据转换为该类对象. 也就是说 “CxString string2 = 10;” 这段代码, 编译器自动将整型转换为CxString类对象, 实际上等同于下面的操作:

CxString string2(10);

通过关键字explicit取消了隐式转换
CxString string6 = ‘c’; // 这样是不行的, 其实调用的是CxString(int size), 且size等于’c’的ascii码。但是当构造函数的参数大于等于二时,是不会产生隐式转换的,所以explicit就无效了。
它存在的意义是,隐式转换常常带来程序逻辑的错误。

class A
{
public:
   A(int i = 5)
   {
       m_a = i;
    }
private:
   int m_a;
};
int main()
{
   A s;
   //我们会发现,我们没有重载'='运算符,但是去可以把内置的int类型赋值给了对象A.
   s = 10;
   //实际上,10被隐式转换成了下面的形式,所以才能这样.
   //s = A temp(10);
   system("pause");
   return 0;
}

explicit关键字详解

2.6 memset关键字

一般用于socket对一段内存空间进行初始化
例如,将一段char数组的每一位都置’\0’

char a[100];
memset(a, '/0', sizeof(a));

void *memset(void *s,int c,size_t n)
总的作用:将已开辟内存空间 s 的首 n 个字节的值设为值 c。

2.7 Struct和Class的区别

在c中struct是一种数据类型,所以struct中不能包含函数。
在c++中,struct的成员是public的,而class是private的。

2.8 Class C++

c++中的类只是一种数据类型,或是说是一种template,并不会占用系统内存。类的成员变量和普通的变量一样,会占一定大小的内存空间。

2.9 inline函数
  1. 一般只将那些短小的、频繁调用的函数声明为内联函数
  2. 对函数作 inline 声明只是程序员对编译器提出的一个建议,而不是强制性的,并非一经指定为 inline 编译器就必须这样做。编译器有自己的判断能力,它会根据具体情况决定是否这样做
  3. 宏展开仅仅是字符串的替换,不会进行任何计算或传值

比如宏定义

#define SQ(y) y*y
int main()
{
	SQ(n+1)
}

实际在宏展开的时候,会变成n+1*n+1,而不是(n+1)(n+1)
所以在实际宏定义的时候,要定义为

#define SQ(y) (y)*(y)

但是新问题又来了。

200/SQ(n+1)
实际计算时,会变成200/(n+1)*(n+1)

这个问题在编译的时候并不会被找到,所以在实际操作的过程中,宏定义一定要小心
可以用内敛函数进行替换

inline int SQ(int y){ return y*y; }

inline函数在编译期间会用它来替换函数调用处,编译完成后函数就不存在了。
内联函数虽然叫做函数,在定义和声明的语法上也和普通函数一样,但它已经失去了函数的本质。函数是一段可以重复使用的代码,它位于虚拟地址空间中的代码区,也占用可执行文件的体积,而内联函数的代码在编译后就被消除了,不存在于虚拟地址空间中,没法重复使用。
在多文件编程时,我建议将内联函数的定义直接放在头文件中,并且禁用内联函数的声明

2.10 宏定义和const的区别
2.11 碎片化

在堆频繁申请和删除内存空间时,会造成空间碎片化,这里比较好的解决方式是通过内存池的方式,当需要一块内存的时候,临时取出一块,然后在不需要后,再集中销毁。

2.12 NULL和nullptr

NULL在C++中就是0,这是因为在C++中void* 类型是不允许隐式转换成其他类型的,所以之前C++中用0来代表空指针,但是在重载整形的情况下,会出现上述的问题。所以,C++11加入了nullptr,可以保证在任何情况下都代表空指针,而不会出现上述的情况,因此,建议以后还是都用nullptr替代NULL吧,而NULL就当做0使用。

2.13 #include<>和include" "的区别
  1. #include< >引用类库路径中的头文件
  2. #include“ ”引用相对路径中的头文件
    它是会先在你项目的当前目录查找是否有对应头文件,如果没有,它还是会在对应的引用目录里面查找对应的头文件。例如,使用 #include “stdio.h” 如果在你项目目录里面,没有 stdio.h 这个头文件,它还是会定位到 C:\Keil\c51\INC\stdio.h 这个头文件的。

3. 如何深刻地理解ROS

首先要说明的一点是,ros本身是一个系统,这个系统的框架是一个通讯框架,而不同于原有系统框架,各个节点之间的是松耦合的。
ROS Master 通过RPC(Remote Procedure Call Protocol,远程过程调用)提供了登记列表和对其他计算图表的查找。没有控制器,节点将无法找到其他节点,交换消息或调用服务。

3.1 RPC(Remote Procedure Call Protocol)

——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。
远程过程调用(RPC)是一个协议,程序可以使用这个协议请求网络中另一台计算机上某程序的服务而不需知道网络细节。(过程调用有时也称作函数调用,或子例行程序调用。)RPC使用client/server模型。请求程序是client,而服务提供程序则为server。就像一般的本地过程调用一样,RPC是一个同步操作,直到远程过程结果返回请求程序才可以挂起。尽管如此,使用轻质进程或线程时,它们共享同一地址空间,是允许多个RPC并发执行的。如前所述RPC其实也是一种C/S的编程模式,有点类似C/SSocket编程模式,但要比它更高一层

3.2 ROS将每个工作进程看作一个节点

使用节点管理器进行统一管理,并提供一套相应的消息传递机制。在ROS中,所有的消息通信都必须使用节点管理器。ROS的特殊性主要体现在消息通信层,而不是更深的层次。点对点的连接和配置通过XMLRPC机制实现;节点间的数据流通过网络套接字实现,数据流在ROS中被称为消息,模块间的消息传递采用简单的、语言无关的接口定义描述。
ROS底层的通信是通过HTTP完成的,因此ROS内核本质上是一个HTTP服务器,它的地址一般是http://localhost:11311/,即本机的11311端口,当需要连接到另一台计算机上运行的ROS时,只要连上该机的11311端口即可。

3.3 机器人软件模块化

系统被分为控制系统和被控系统,控制系统是机器人控制软件或算法,被控系统是机器人硬件,为了实现软件的重用,可采用构件的抽象模型以解除控制系统与被控系统之间的紧耦合。
系统被划分为四种构件。

  1. 原子构件:原子构件适合于与硬件组件有直接联系的唯一构件。
  2. 复合构件:根据功能需求,将原子构件组合在一起形成复合构件。
  3. 算法构件:例如路径规划、卡尔曼滤波等。
  4. 应用构件:以上三种构件组合,来实现机器人系统的应用功能。
3.4 话题和服务的区别
条目话题服务
同步性异步同步
通信模型发布/订阅客户端/服务器
反馈机制
底层协议ROSTCP/ROSUDPROSTCP/ROSUDP
缓冲区
实时性
节点关系多对多一对多(一个Server)
使用场景弱逻辑处理,多数据传输强逻辑处理,少数据传输

简单来说,同步通信是一种比特同步通信技术,要求发收双方具有同频同相的同步时钟信号,只需在传送报文的最前面附加特定的同步字符,使发收双方建立同步,此后便在同步时钟的控制下逐位发送/接收。
相对于同步通信,异步通信在发送字符时,所发送的字符之间的时隙可以是任意的。但是接收端必须时刻做好接收的准备(如果接收端主机的电源都没有加上,那么发送端发送字符就没有意义,因为接收端根本无法接收)。发送端可以在任意时刻开始发送字符,因此必须在每一个字符的开始和结束的地方加上标志,即加上开始位和停止位,以便使接收端能够正确地将每一个字符接收下来。异步通信的好处是通信设备简单、便宜,但传输效率较低(因为开始位和停止位的开销所占比例较大)。

3.5 ROS和ROS 2的区别 ++++

3.5.1 ROS的缺点

  1. 多个机器人组成的集群; ROS的单master结构就蛋疼了。
  2. 小型嵌入式平台,甚至“裸奔”的微控制器;依赖于Ubuntu的ROS太重了。
  3. 实时系统:包括进程间和跨机器通信,ROS做不到(注意,ROS2.0系统本身不是一个实时性的系统,只是运用了实时性的通讯机制)。
  4. 非理想的网络;ROS延迟很大,甚至断网再连接时会挂。
  5. 生产制造等环境:ROS可靠性还不够。
  6. 为一些新特性,如部署的生命周期管理和静态配置,提供清晰的模式和支持工具。

3.5.2 ROS2相比ROS1的改进

  1. 实时性增强:数据必须在deadline之前完成更新。
  2. 持续性增强:ROS1尽管存在数据队列的概念,但是还有很大的局限,订阅者无法接收到加入网络> > 3 .之前的数据;​ DDS可以为ROS提供数据历史的服务,就算新加入的节点,也可以获取发布的所有历史数据。
  3. 可靠性增强:通过DDS配置可靠性原则,用户可以根据需求选择性能模式(BEST_EFFORT)或者稳定模式(RELIABLE)。
3.6 ROS中刷新环境变量 source setup.bash

电脑版本是Ubuntu16.04下执行命令:

echo $PATH
/opt/ros/kinetic/bin:/home/kai/bin:/home/kai/.local/bin:/opt/Qt5.8.0/Tools/QtCreator/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/usr/local/bin/:/snap/bin:/usr/local/bin/

PATH 环境变量的内容是由一堆目录组成的,比如当你执行roscore时,如果不刷新ros的环境变量,系统是无法识别这个指令的。
Linux 会依照 PATH 中包含的目录依次搜寻该命令的可执行文件,一旦找到,即正常执行;反之,则提示无法找到该命令。
注意,这里有个依次搜寻。那么会导致什么问题呢,举个例子,当你创建了两个相同名字的package,然后在两个package下起了同样名字的launch文件,然后终端启动roslaunch package foo.launch.系统会默认启动最后刷新环境变量的package.

4. 调试工具等测试

4.1 关于Linux下内存泄漏调试工具valgrind

Valgrind是一个GPL的软件,用于Linux(For x86, amd64 and ppc32)程序的内存调试和代码剖析。你可以在它的环境中运行你的程序来监视内存的使用情况,比如C 语言中的malloc和free或者 C++中的new和 delete。使用Valgrind的工具包,你可以自动的检测许多内存管理和线程的bug,避免花费太多的时间在bug寻找上,使得你的程序更加稳固。
参考网址 https://blog.csdn.net/andylauren/article/details/93189740

4.2 Valgrind 内存泄漏查找

最常用的格式为,

valgrind --tool=memcheck --leak-check=full ./console

这里举一个中控系统的例子,在devel下有可执行文件

Ros shutdown, proceeding to close the gui.
==6346== 
==6346== HEAP SUMMARY:
==6346==     in use at exit: 4,584,484 bytes in 40,116 blocks
==6346==   total heap usage: 631,614 allocs, 591,498 frees, >156,641,287 bytes allocated
==6346== 
==6346== LEAK SUMMARY:
==6346==    definitely lost: 21,952 bytes in 138 blocks
==6346==    indirectly lost: 84,678 bytes in 2,974 blocks
==6346==      possibly lost: 4,935 bytes in 47 blocks
==6346==    still reachable: 4,111,663 bytes in 35,233 blocks
==6346==                       of which reachable via heuristic:
==6346==                         length64           : 11,320 bytes in 166 blocks
==6346==                         newarray           : 6,584 bytes in 66 blocks
==6346==         suppressed: 0 bytes in 0 blocks
==6346== Rerun with --leak-check=full to see details of leaked memory
==6346== 
==6346== For counts of detected and suppressed errors, rerun with: -v
==6346== Use --track-origins=yes to see where uninitialised values >come from
==6346== ERROR SUMMARY: 1957 errors from 15 contexts >>(suppressed: 0 from 0)

可以清楚地看到,这个系统写的巨烂,还是我自己写的

5. C++11新特性总结

5.1 什么是右值引用

在C++11之前,左值、右值对于程序员来说,一直呈透明状态。不知道什么是左值、右值,并不影响写出正确的C++代码。引用的是左值和右值通常也 并不重要。
在C++11中,右值引用就是对一个右值进行引用的类型。事实上,由于右值通常不具有名字,我们也只能通过引用的方式找到它的存在。通常情况下,我们只能是从右值表达式获得其引用。比如:

T && a = ReturnRvalue();

在上面的例子中,ReturnRvalue函数返回的右值在表达式语句结束后,其生命也就终结了(通常我们也称其具有表达式生命期),而通过右值引 用的声明,该右值又“重获新生”,其生命期将与右值引用类型变量a的生命期一样。只要a还“活着”,该右值临时量将会一直“存活”下去。2
类的右值是一个临时对象,如果没有被绑定到引用,在表达式结束时就会被废弃。
例如在vector容器中,用push_back()将类实例保存下来,会造成不断构造和析构。
所以在设计系统时要注意Object的生命周期,编译器可能不会报错,但是在实际使用的时候会发生问题。
四种不同的对象生存方式(in stack、in heap、global、local static)
这里用大白话来解释,其实就是注意对象的作用域,什么时候对象会被构造,什么时候又会被析构。同时也需要注意内存泄漏。

  1. 第一种方法是在栈(stack)之中产生它:
  void MyFunc()
{
        CFoo foo; **// 在栈(stack)中产生foo 对象**
        ...
}
  1. 第二种方法是在堆(heap)之中产生它:
   void MyFunc()
{
      ...
     CFoo* pFoo = new CFoo(); // 在堆(heap)中产生对象
}

这里先解释一下堆栈stack和堆积heap的区别
堆、栈和堆栈的区别
在编程中,例如C/C++中,所有的方法调用都是通过栈来进行的,所有的局部变量,形式参数都是从栈中分配内存空间的。实际上也不是什么分配,只是从栈顶向上用就行,
就好像工厂中的传送带(conveyor belt)一样,Stack Pointer会自动指引你到放东西的位置,你所要做的只是把东西放下来就行.退出函数的时候,修改栈指针就可以把栈中的内容销毁这样的模式速度最快,当然要用来运行程序了.需要注意的是,在分配的时候,比如为一个即将要调用的程序模块分配数据区时,应事先知道这个数据区的大小,虽然分配是在程序运行时进行的,但是分配的大小多少是确定的,不变的,而这个"大小多少"是在编译时确定的,不是在运行时.
堆是应用程序在运行的时候请求操作系统分配给自己内存,由于从操作系统管理的内存分配,所以在分配和销毁时都要占用时间,因此用堆的效率非常低.但是堆的优点在于,编译器不必知道要从堆里分配多少存储空间,也不必知道存储的数据要在堆里停留多长的时间,因此,用堆保存数据时会得到更大的灵活性。事实上,面向对象的多态性,堆内存分配是必不可少的,因为多态变量所需的存储空间只有在运行时创建了对象之后才能确定.在C++中,要求创建一个对象时,只需用new命令编制相关的代码即可。执行这些代码时 会在堆里自动进行数据的保存.当然,为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间时会花掉更长的时间!这也正是导致效率低的原因,
array 保存在栈内存中,相比堆内存中的 vector,速度更快,但 array 长度无法更改,没有 vector 灵活。
我们常说堆栈堆栈,其实堆栈是两个不同的概念,最直观的理解,堆是由用户来控制的,我们可以使用 malloc 这种命令来在堆中申请内存,而栈是由操作系统控制的,在栈中存储的是这个进程的局部变量等,比如我们用 malloc 来申请一块内存,内存本身是在堆中开辟的,而指向这块内存的指针存储在栈中。

  1. 第三种方法是产生一个全域对象(同时也必然是个静态对象):
     CFoo foo; // 在任何函数范围之外做此动作
  1. 第四种方法是产生一个区域静态对象:
  void MyFunc()
 {
      static CFoo foo; // 在函数范围(scope)之内的一个静态对象
      ...
}
5.2 移动构造函数和拷贝构造函数

6. 数据结构与系统设计

6.1 问题的描述、抽象与建模
  1. 抽象(abstract)的概念是从众多事物中,抽取出共同的本质的特征,而舍弃其非本质的特征3
    例如,一个篮子里有各种各样的蔬菜水果,那么抽象就是提取出蔬菜、水果这种特征,而非本质特征则是形状或者颜色。说白了抽象就是提取特征,例如面部识别,是提取某些关键的特征点,从而识别。
    通过抽象来建立客观事物的描述模型。大白话就是通过抽象,把一个东西用数据来表达。
    抽象是面对问题的,根据问题的需求去进行不同的抽象,然后进行建模
  2. 建模,建模是为了理解事物而对其进行的一种抽象,是对事物的一种无歧义的书面描述,是研究系统的重要手段和前提。建模就是计算的依据,我利用什么模型去计算什么样的数据。
  3. 面向计算机的问题分析,计算机的局限性在哪里呢,就是正确的输入才能得到一个确定正确的输出,否则是一个概率的问题。
6.2 C++ 线性表
  1. 将数据依次存储在连续的整块物理空间中,这种存储结构称为顺序存储结构(简称顺序表)–>数组
    正常状态下,顺序表申请的存储容量要大于顺序表的长度。
  1. 数据分散的存储在物理空间中,通过一根线保存着它们之间的逻辑关系,这种存储结构称为链式存储结构 –>链表
    链表中存储的数据的物理地址是随用随分配的。
6.3 什么时候使用链表结构
  1. 一般在数据长度不确定的时候使用链表,会比数组Array节省内存,在使用vector时,如果已经分配的连续内存空间已经使用完,vector会重新分配新的更长的内存空间,如果push_back中为Object,会造成Object析构再构造。同时动作时间复杂度也不再是O(1),而无法控制。
  2. 在开发中控系统时,涉及动作流程,涉及下一个动作,回溯上一个动作,同时为了增加移植性,可以做到在某几步中间插入或者删除。(但是我还没做,笑)
  3. 在拿到一个链表的node后,是可以很容易对链表进行插入,或者删除操作的。
  4. 链表在存储数据时,每次只申请一个节点的空间,且空间的位置是随机的,所以会造成空间碎片化。
  5. 空间碎片,指的是某些容量很小(1KB 甚至更小)以致无法得到有效利用的物理空间。
6.4 选择集中存储还是选择分散存储?
  1. 首先集中存储需要在存储设备上申请一大块物理空间,分散存储是随机存储设备。对于需要大量检索的设备,需要集中存储,若后续需要对数据进行增加和删除,则选择分散存储。
  2. 对于需要访问某个元素的情况,数组可以通过下标直接访问元素,时间复杂度为O(1),而链表则需要遍历,最差的情况时间复杂度为O(n)
  3. 对于需要增加或者删除的情况,链表只需更改指针,时间复杂度为O(1),而数组则需要全部移动,时间复杂度为O(1)
6.5 如何衡量一个算法的好坏
  1. 准确性,一个算法的设计必须要彻底解决问题
  2. 健壮性,一个程序运行必须要在任何情况下不崩溃
  3. 对程序的运行时间,以及内存进行考量
6.6 时间复杂度比较

O(1)常数阶 < O(logn)对数阶 < O(n)线性阶 < O(n2)平方阶 < O(n3)(立方阶) < O(2n) (指数阶)

6.7 空间复杂度

对算法的空间复杂度影响最大的,往往是程序运行过程中所申请的临时存储空间

6.8 内存泄漏

内存泄漏也称作"存储渗漏",用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。(其实说白了就是该内存空间使用完毕之后未回收)即所谓内存泄漏。

6.9 开闭原则

开闭原则的含义是:当应用的需求改变时,在不修改软件实体的源代码或者二进制代码的前提下,可以扩展模块的功能,使其满足新的需求。

待解决问题。
ros1和ros2的区别,重点在ros master和dds上。
c++11的新特性
ros2的常见使用
关于程序封装的问题
多线程
socket通讯


  1. 如何通俗地解释什么是寄存器 ↩︎

  2. CSDN 左值、右值与右值引用 ↩︎

  3. 大学计算机-Mooc ↩︎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值