精华内容
下载资源
问答
  • 2021-04-21 10:46:15

    我发现呀,这大家对面试题的需求还是很大的,这里总结了上千道知识点,能换您一个收藏吗

    C++

     

    引用和指针的区别?

    指针是一个实体,需要分配内存空间。引用只是变量的别名,不需要分配内存空间。

    引用在定义的时候必须进行初始化,并且不能够改变。指针在定义的时候不一定要初始化,并且指向的空间可变。(注:不能有引用的值不能为NULL)

    有多级指针,但是没有多级引用,只能有一级引用。

    指针和引用的自增运算结果不一样。(指针是指向下一个空间,引用时引用的变量值加1)

    sizeof 引用得到的是所指向的变量(对象)的大小,而sizeof 指针得到的是指针本身的大小。

    引用访问一个变量是直接访问,而指针访问一个变量是间接访问。

    使用指针前最好做类型检查,防止野指针的出现;

    引用底层是通过指针实现的;

    作为参数时也不同,传指针的实质是传值,传递的值是指针的地址;传引用的实质是传地址,传递的是变量的地址。

    从汇编层去解释一下引用

    x的地址为ebp-4,b的地址为ebp-8,因为栈内的变量内存是从高往低进行分配的。所以b的地址比x的低。lea eax,[ebp-4]  这条语句将x的地址ebp-4放入eax寄存器mov dword ptr [ebp-8],eax 这条语句将eax的值放入b的地址ebp-8中上面两条汇编的作用即:将x的地址存入变量b中,这不和将某个变量的地址存入指针变量是一样的吗?所以从汇编层次来看,的确引用是通过指针来实现的。

    C++中的指针参数传递和引用参数传递

    指针参数传递本质上是值传递,它所传递的是一个地址值。值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,会在栈中开辟内存空间以存放由主调函数传递进来的实参值,从而形成了实参的一个副本(替身)。值传递的特点是,被调函数对形式参数的任何操作都是作为局部变量进行的,不会影响主调函数的实参变量的值(形参指针变了,实参指针不会变)。

    引用参数传递过程中,被调函数的形式参数也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参(本体)的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量(根据别名找到主调函数中的本体)。因此,被调函数对形参的任何操作都会影响主调函数中的实参变量。

    引用传递和指针传递是不同的,虽然他们都是在被调函数栈空间上的一个局部变量,但是任何对于引用参数的处理都会通过一个间接寻址的方式操作到主调函数中的相关变量。而对于指针传递的参数,如果改变被调函数中的指针地址,它将应用不到主调函数的相关变量。如果想通过指针参数传递来改变主调函数中的相关变量(地址),那就得使用指向指针的指针或者指针引用。

    从编译的角度来讲,程序在编译时分别将指针和引用添加到符号表上,符号表中记录的是变量名及变量所对应地址。指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值(与实参名字不同,地址相同)。符号表生成之后就不会再改,因此指针可以改变其指向的对象(指针变量中的值可以改),而引用对象则不能修改。

    形参与实参的区别?

    形参变量只有在被调用时才分配内存单元,在调用结束时, 即刻释放所分配的内存单元。因此,形参只有在函数内部有效。 函数调用结束返回主调函数后则不能再使用该形参变量。

    实参可以是常量、变量、表达式、函数等, 无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值, 以便把这些值传送给形参。 因此应预先用赋值,输入等办法使实参获得确定值,会产生一个临时变量。

    实参和形参在数量上,类型上,顺序上应严格一致, 否则会发生“类型不匹配”的错误。

    函数调用中发生的数据传送是单向的。 即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参。 因此在函数调用过程中,形参的值发生改变,而实参中的值不会变化。

    当形参和实参不是指针类型时,在该函数运行时,形参和实参是不同的变量,他们在内存中位于不同的位置,形参将实参的内容复制一份,在该函数运行结束的时候形参被释放,而实参内容不会改变。

    值传递:有一个形参向函数所属的栈拷贝数据的过程,如果值传递的对象是类对象    或是大的结构体对象,将耗费一定的时间和空间。(传值)

    指针传递:同样有一个形参向函数所属的栈拷贝数据的过程,但拷贝的数据是一个固定为4字节的地址。(传值,传递的是地址值)

    引用传递:同样有上述的数据拷贝过程,但其是针对地址的,相当于为该数据所在的地址起了一个别名。(传地址)

    效率上讲,指针传递和引用传递比值传递效率高。一般主张使用引用传递,代码逻辑上更加紧凑、清晰。

    static的用法和作用?

    1.先来介绍它的第一条也是最重要的一条:隐藏。(static函数,static变量均可)

    当同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。

    2.static的第二个作用是保持变量内容的持久。(static变量中的记忆功能和全局生存期)存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围,说到底static还是用来隐藏的。

    3.static的第三个作用是默认初始化为0(static变量)

    其实全局变量也具备这一属性,因为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是0x00,某些时候这一特点可以减少程序员的工作量。

    4.static的第四个作用:C++中的类成员声明static

    函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;  

    在模块内的static全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;   

    在模块内的static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内;   

    在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;   

    在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。

    类内:

    static类对象必须要在类外进行初始化,static修饰的变量先于对象存在,所以static修饰的变量要在类外初始化;

    由于static修饰的类成员属于类,不属于对象,因此static类成员函数是没有this指针的,this指针是指向本对象的指针。正因为没有this指针,所以static类成员函数不能访问非static的类成员,只能访问 static修饰的类成员;

    static成员函数不能被virtual修饰,static成员不属于任何对象或实例,所以加上virtual没有任何实际意义;静态成员函数没有this指针,虚函数的实现是为每一个对象分配一个vptr指针,而vptr是通过this指针调用的,所以不能为virtual;虚函数的调用关系,this->vptr->ctable->virtual function

    静态变量什么时候初始化

    初始化只有一次,但是可以多次赋值,在主程序之前,编译器已经为其分配好了内存。

    静态局部变量和全局变量一样,数据都存放在全局区域,所以在主程序之前,编译器已经为其分配好了内存,但在C和C++中静态局部变量的初始化节点又有点不太一样。在C中,初始化发生在代码执行之前,编译阶段分配好内存之后,就会进行初始化,所以我们看到在C语言中无法使用变量对静态局部变量进行初始化,在程序运行结束,变量所处的全局内存会被全部回收。

    而在C++中,初始化时在执行相关代码时才会进行初始化,主要是由于C++引入对象后,要进行初始化必须执行相应构造函数和析构函数,在构造函数或析构函数中经常会需要进行某些程序中需要进行的特定操作,并非简单地分配内存。所以C++标准定为全局或静态对象是有首次用到时才会进行构造,并通过atexit()来管理。在程序结束,按照构造顺序反方向进行逐个析构。所以在C++中是可以使用变量对静态局部变量进行初始化的。

     const?

    阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;   

    对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const;   

    在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;   

    对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成员变量,类的常对象只能访问类的常成员函数;   

    对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为“左值”。

    const成员函数可以访问非const对象的非const数据成员、const数据成员,也可以访问const对象内的所有数据成员;

    非const成员函数可以访问非const对象的非const数据成员、const数据成员,但不可以访问const对象的任意数据成员;

    一个没有明确声明为const的成员函数被看作是将要修改对象中数据成员的函数,而且编译器不允许它为一个const对象所调用。因此const对象只能调用const成员函数。

    const类型变量可以通过类型转换符const_cast将const类型转换为非const类型;

    const类型变量必须定义的时候进行初始化,因此也导致如果类的成员变量有const类型的变量,那么该变量必须在类的初始化列表中进行初始化;

    对于函数值传递的情况,因为参数传递是通过复制实参创建一个临时变量传递进函数的,函数内只能改变临时变量,但无法改变实参。则这个时候无论加不加const对实参不会产生任何影响。但是在引用或指针传递函数调用中,因为传进去的是一个引用或指针,这样函数内部可以改变引用或指针所指向的变量,这时const 才是实实在在地保护了实参所指向的变量。因为在编译阶段编译器对调用函数的选择是根据实参进行的,所以,只有引用传递和指针传递可以用是否加const来重载。一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开来。

    const成员函数的理解和应用?

    const Stock & Stock::topval (②const Stock & s) ③const

    ①处const:确保返回的Stock对象在以后的使用中不能被修改

    ②处const:确保此方法不修改传递的参数 S

    ③处const:保证此方法不修改调用它的对象,const对象只能调用const成员函数,不能调用非const函数

    指针和const的用法

    当const修饰指针时,由于const的位置不同,它的修饰对象会有所不同。

    int *const p2中const修饰p2的值,所以理解为p2的值不可以改变,即p2只能指向固定的一个变量地址,但可以通过*p2读写这个变量的值。顶层指针表示指针本身是一个常量

    int const *p1或者const int *p1两种情况中const修饰*p1,所以理解为*p1的值不可以改变,即不可以给*p1赋值改变p1指向变量的值,但可以通过给p赋值不同的地址改变这个指针指向。底层指针表示指针所指向的变量是一个常量。

    int const *const p;

    mutable

    如果需要在const成员方法中修改一个成员变量的值,那么需要将这个成员变量修饰为mutable。即用mutable修饰的成员变量不受const成员方法的限制;

    可以认为mutable的变量是类的辅助状态,但是只是起到类的一些方面表述的功能,修改他的内容我们可以认为对象的状态本身并没有改变的。实际上由于const_cast的存在,这个概念很多时候用处不是很到了。

    extern用法?

    extern修饰变量的声明

    如果文件a.c需要引用b.c中变量int v,就可以在a.c中声明extern int v,然后就可以引用变量v。

    extern修饰函数的声明

    如果文件a.c需要引用b.c中的函数,比如在b.c中原型是int fun(int mu),那么就可以在a.c中声明extern int fun(int mu),然后就能使用fun来做任何事情。就像变量的声明一样,extern int fun(int mu)可以放在a.c中任何地方,而不一定非要放在a.c的文件作用域的范围中。

    extern修饰符可用于指示C或者C++函数的调用规范。

    比如在C++中调用C库函数,就需要在C++程序中用extern “C”声明要引用的函数。这是给链接器用的,告诉链接器在链接的时候用C函数规范来链接。主要原因是C++和C程序编译完成后在目标代码中命名规则不同。

    int转字符串字符串转int?strcat,strcpy,strncpy,memset,memcpy的内部实现?

    c++11标准增加了全局函数std::to_string

    可以使用std::stoi/stol/stoll等等函数

    strcpy拥有返回值,有时候函数原本不需要返回值,但为了增加灵活性如支持链式表达,

    深拷贝与浅拷贝?

    浅复制 —-只是拷贝了基本类型的数据,而引用类型数据,复制后也是会发生引用,我们把这种拷贝叫做“(浅复制)浅拷贝”,换句话说,浅复制仅仅是指向被复制的内存地址,如果原地址中对象被改变了,那么浅复制出来的对象也会相应改变。

    深复制 —-在计算机中开辟了一块新的内存地址用于存放复制的对象。

    在某些状况下,类内成员变量需要动态开辟堆内存,如果实行位拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。这时,如果B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。这就出现了问题:当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。

    C++模板是什么,底层怎么实现的?

    编译器并不是把函数模板处理成能够处理任意类的函数;编译器从函数模板通过具体类型产生不同的函数;编译器会对函数模板进行两次编译:在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。

    这是因为函数模板要被实例化后才能成为真正的函数,在使用函数模板的源文件中包含函数模板的头文件,如果该头文件中只有声明,没有定义,那编译器无法实例化该模板,最终导致链接错误。

    C语言struct和C++struct区别

    C语言中:struct是用户自定义数据类型(UDT);C++中struct是抽象数据类型(ADT),支持成员函数的定义,(C++中的struct能继承,能实现多态)。

    C中struct是没有权限的设置的,且struct中只能是一些变量的集合体,可以封装数据却不可以隐藏数据,而且成员不可以是函数。

    C++中,struct的成员默认访问说明符为public(为了与C兼容),class中的默认访问限定符为private,struct增加了访问权限,且可以和类一样有成员函数。

    struct作为类的一种特例是用来自定义数据结构的。一个结构标记声明后,在C中必须在结构标记前加上struct,才能做结构类型名

    虚函数可以声明为inline吗?

    虚函数用于实现运行时的多态,或者称为晚绑定或动态绑定。而内联函数用于提高效率。内联函数的原理是,在编译期间,对调用内联函数的地方的代码替换成函数代码。内联函数对于程序中需要频繁使用和调用的小函数非常有用。

    虚函数要求在运行时进行类型确定,而内敛函数要求在编译期完成相关的函数替换;

    类成员初始化方式?构造函数的执行顺序 ?为什么用成员初始化列表会快一些?

    赋值初始化,通过在函数体内进行赋值初始化;列表初始化,在冒号后使用初始化列表进行初始化。

    这两种方式的主要区别在于:

    对于在函数体中初始化,是在所有的数据成员被分配内存空间后才进行的。

    列表初始化是给数据成员分配内存空间时就进行初始化,就是说分配一个数据成员只要冒号后有此数据成员的赋值表达式(此表达式必须是括号赋值表达式),那么分配了内存空间后在进入函数体之前给数据成员赋值,就是说初始化这个数据成员此时函数体还未执行。 

    一个派生类构造函数的执行顺序如下:

    虚拟基类的构造函数(多个虚拟基类则按照继承的顺序执行构造函数)。

    基类的构造函数(多个普通基类也按照继承的顺序执行构造函数)。

    类类型的成员对象的构造函数(按照初始化顺序)

    派生类自己的构造函数。

    方法一是在构造函数当中做赋值的操作,而方法二是做纯粹的初始化操作。我们都知道,C++的赋值操作是会产生临时对象的。临时对象的出现会降低程序的效率。

    成员列表初始化?

    必须使用成员初始化的四种情况

    当初始化一个引用成员时;

    当初始化一个常量成员时;

    当调用一个基类的构造函数,而它拥有一组参数时;

    当调用一个成员类的构造函数,而它拥有一组参数时;

    成员初始化列表做了什么

    编译器会一一操作初始化列表,以适当的顺序在构造函数之内安插初始化操作,并且在任何显示用户代码之前;

    list中的项目顺序是由类中的成员声明顺序决定的,不是由初始化列表的顺序决定的;

    构造函数为什么不能为虚函数?析构函数为什么要虚函数?

    1. 从存储空间角度,虚函数相应一个指向vtable虚函数表的指针,这大家都知道,但是这个指向vtable的指针事实上是存储在对象的内存空间的。问题出来了,假设构造函数是虚的,就须要通过 vtable来调用,但是对象还没有实例化,也就是内存空间还没有,怎么找vtable呢?所以构造函数不能是虚函数。

    2. 从使用角度,虚函数主要用于在信息不全的情况下,能使重载的函数得到相应的调用。构造函数本身就是要初始化实例,那使用虚函数也没有实际意义呀。所以构造函数没有必要是虚函数。虚函数的作用在于通过父类的指针或者引用来调用它的时候可以变成调用子类的那个成员函数。而构造函数是在创建对象时自己主动调用的,不可能通过父类的指针或者引用去调用,因此也就规定构造函数不能是虚函数。

    3. 构造函数不须要是虚函数,也不同意是虚函数,由于创建一个对象时我们总是要明白指定对象的类型,虽然我们可能通过实验室的基类的指针或引用去訪问它但析构却不一定,我们往往通过基类的指针来销毁对象。这时候假设析构函数不是虚函数,就不能正确识别对象类型从而不能正确调用析构函数。

    4. 从实现上看,vbtl在构造函数调用后才建立,因而构造函数不可能成为虚函数从实际含义上看,在调用构造函数时还不能确定对象的真实类型(由于子类会调父类的构造函数);并且构造函数的作用是提供初始化,在对象生命期仅仅运行一次,不是对象的动态行为,也没有必要成为虚函数。

    5. 当一个构造函数被调用时,它做的首要的事情之中的一个是初始化它的VPTR。因此,它仅仅能知道它是“当前”类的,而全然忽视这个对象后面是否还有继承者。当编译器为这个构造函数产生代码时,它是为这个类的构造函数产生代码——既不是为基类,也不是为它的派生类(由于类不知道谁继承它)。所以它使用的VPTR必须是对于这个类的VTABLE。并且,仅仅要它是最后的构造函数调用,那么在这个对象的生命期内,VPTR将保持被初始化为指向这个VTABLE, 但假设接着另一个更晚派生的构造函数被调用,这个构造函数又将设置VPTR指向它的 VTABLE,等.直到最后的构造函数结束。VPTR的状态是由被最后调用的构造函数确定的。这就是为什么构造函数调用是从基类到更加派生类顺序的还有一个理由。可是,当这一系列构造函数调用正发生时,每一个构造函数都已经设置VPTR指向它自己的VTABLE。假设函数调用使用虚机制,它将仅仅产生通过它自己的VTABLE的调用,而不是最后的VTABLE(全部构造函数被调用后才会有最后的VTABLE)。

    因为构造函数本来就是为了明确初始化对象成员才产生的,然而virtual function主要是为了再不完全了解细节的情况下也能正确处理对象。另外,virtual函数是在不同类型的对象产生不同的动作,现在对象还没有产生,如何使用virtual函数来完成你想完成的动作。

    直接的讲,C++中基类采用virtual虚析构函数是为了防止内存泄漏。具体地说,如果派生类中申请了内存空间,并在其析构函数中对这些内存空间进行释放。假设基类中采用的是非虚析构函数,当删除基类指针指向的派生类对象时就不会触发动态绑定,因而只会调用基类的析构函数,而不会调用派生类的析构函数。那么在这种情况下,派生类中申请的空间就得不到释放从而产生内存泄漏。所以,为了防止这种情况的发生,C++中基类的析构函数应采用virtual虚析构函数。

    析构函数的作用,如何起作用?

    构造函数只是起初始化值的作用,但实例化一个对象的时候,可以通过实例去传递参数,从主函数传递到其他的函数里面,这样就使其他的函数里面有值了。规则,只要你一实例化对象,系统自动回调用一个构造函数,就是你不写,编译器也自动调用一次。

    析构函数与构造函数的作用相反,用于撤销对象的一些特殊任务处理,可以是释放对象分配的内存空间;特点:析构函数与构造函数同名,但该函数前面加~。 析构函数没有参数,也没有返回值,而且不能重载,在一个类中只能有一个析构函数。 当撤销对象时,编译器也会自动调用析构函数。 每一个类必须有一个析构函数,用户可以自定义析构函数,也可以是编译器自动生成默认的析构函数。一般析构函数定义为类的公有成员。

    构造函数和析构函数可以调用虚函数吗,为什么

    在C++中,提倡不在构造函数和析构函数中调用虚函数;

    构造函数和析构函数调用虚函数时都不使用动态联编,如果在构造函数或析构函数中调用虚函数,则运行的是为构造函数或析构函数自身类型定义的版本;

    因为父类对象会在子类之前进行构造,此时子类部分的数据成员还未初始化,因此调用子类的虚函数时不安全的,故而C++不会进行动态联编;

    析构函数是用来销毁一个对象的,在销毁一个对象时,先调用子类的析构函数,然后再调用基类的析构函数。所以在调用基类的析构函数时,派生类对象的数据成员已经销毁,这个时候再调用子类的虚函数没有任何意义。

    构造函数的执行顺序?析构函数的执行顺序?构造函数内部干了啥?拷贝构造干了啥?

    构造函数顺序

    基类构造函数。如果有多个基类,则构造函数的调用顺序是某类在类派生表中出现的顺序,而不是它们在成员初始化表中的顺序。

    成员类对象构造函数。如果有多个成员类对象则构造函数的调用顺序是对象在类中被声明的顺序,而不是它们出现在成员初始化表中的顺序。

    派生类构造函数。

    析构函数顺序

    调用派生类的析构函数;

    调用成员类对象的析构函数;

    调用基类的析构函数。

    虚析构函数的作用,父类的析构函数是否要设置为虚函数?

    C++中基类采用virtual虚析构函数是为了防止内存泄漏。具体地说,如果派生类中申请了内存空间,并在其析构函数中对这些内存空间进行释放。假设基类中采用的是非虚析构函数,当删除基类指针指向的派生类对象时就不会触发动态绑定,因而只会调用基类的析构函数,而不会调用派生类的析构函数。那么在这种情况下,派生类中申请的空间就得不到释放从而产生内存泄漏。所以,为了防止这种情况的发生,C++中基类的析构函数应采用virtual虚析构函数。

    纯虚析构函数一定得定义,因为每一个派生类析构函数会被编译器加以扩张,以静态调用的方式调用其每一个虚基类以及上一层基类的析构函数。因此,缺乏任何一个基类析构函数的定义,就会导致链接失败。因此,最好不要把虚析构函数定义为纯虚析构函数。

    构造函数析构函数可以调用虚函数吗?

    在构造函数和析构函数中最好不要调用虚函数;

    构造函数或者析构函数调用虚函数并不会发挥虚函数动态绑定的特性,跟普通函数没区别;

    即使构造函数或者析构函数如果能成功调用虚函数, 程序的运行结果也是不可控的。

    构造函数析构函数可否抛出异常

     C++只会析构已经完成的对象,对象只有在其构造函数执行完毕才算是完全构造妥当。在构造函数中发生异常,控制权转出构造函数之外。因此,在对象b的构造函数中发生异常,对象b的析构函数不会被调用。因此会造成内存泄漏。

    用auto_ptr对象来取代指针类成员,便对构造函数做了强化,免除了抛出异常时发生资源泄漏的危机,不再需要在析构函数中手动释放资源;

    如果控制权基于异常的因素离开析构函数,而此时正有另一个异常处于作用状态,C++会调用terminate函数让程序结束;

    如果异常从析构函数抛出,而且没有在当地进行捕捉,那个析构函数便是执行不全的。如果析构函数执行不全,就是没有完成他应该执行的每一件事情。

    类如何实现只能静态分配和只能动态分配

    前者是把new、delete运算符重载为private属性。后者是把构造、析构函数设为protected属性,再用子类来动态创建

    建立类的对象有两种方式:

    静态建立,静态建立一个类对象,就是由编译器为对象在栈空间中分配内存;

    动态建立,A *p = new A();动态建立一个类对象,就是使用new运算符为对象在堆空间中分配内存。这个过程分为两步,第一步执行operator new()函数,在堆中搜索一块内存并进行分配;第二步调用类构造函数构造对象;

    只有使用new运算符,对象才会被建立在堆上,因此只要限制new运算符就可以实现类对象只能建立在栈上。可以将new运算符设为私有。

    如果想将某个类用作基类,为什么该类必须定义而非声明?

    派生类中包含并且可以使用它从基类继承而来的成员,为了使用这些成员,派生类必须知道他们是什么。

    什么情况会自动生成默认构造函数?

    带有默认构造函数的类成员对象,如果一个类没有任何构造函数,但它含有一个成员对象,而后者有默认构造函数,那么编译器就为该类合成出一个默认构造函数。不过这个合成操作只有在构造函数真正被需要的时候才会发生;如果一个类A含有多个成员类对象的话,那么类A的每一个构造函数必须调用每一个成员对象的默认构造函数而且必须按照类对象在类A中的声明顺序进行;

    带有默认构造函数的基类,如果一个没有任务构造函数的派生类派生自一个带有默认构造函数基类,那么该派生类会合成一个构造函数调用上一层基类的默认构造函数;

    带有一个虚函数的类

    带有一个虚基类的类

    合成的默认构造函数中,只有基类子对象和成员类对象会被初始化。所有其他的非静态数据成员都不会被初始化。

    什么是类的继承?

    类与类之间的关系

    has-A包含关系,用以描述一个类由多个部件类构成,实现has-A关系用类的成员属性表示,即一个类的成员属性是另一个已经定义好的类;

    use-A,一个类使用另一个类,通过类之间的成员函数相互联系,定义友元或者通过传递参数的方式来实现;

    is-A,继承关系,关系具有传递性;

    继承的相关概念

    所谓的继承就是一个类继承了另一个类的属性和方法,这个新的类包含了上一个类的属性和方法,被称为子类或者派生类,被继承的类称为父类或者基类;

    继承的特点

    子类拥有父类的所有属性和方法,子类可以拥有父类没有的属性和方法,子类对象可以当做父类对象使用;

    继承中的访问控制

    public、protected、private

    继承中的构造和析构函数

    继承中的兼容性原则

    什么是组合?

    一个类里面的数据成员是另一个类的对象,即内嵌其他类的对象作为自己的成员;创建组合类的对象:首先创建各个内嵌对象,难点在于构造函数的设计。创建对象时既要对基本类型的成员进行初始化,又要对内嵌对象进行初始化。

    创建组合类对象,构造函数的执行顺序:先调用内嵌对象的构造函数,然后按照内嵌对象成员在组合类中的定义顺序,与组合类构造函数的初始化列表顺序无关。然后执行组合类构造函数的函数体,析构函数调用顺序相反。

     

    抽象基类为什么不能创建对象?

    抽象类是一种特殊的类,它是为了抽象和设计的目的为建立的,它处于继承层次结构的较上层。

    (1)抽象类的定义:
       称带有纯虚函数的类为抽象类。

    (2)抽象类的作用:
       抽象类的主要作用是将有关的操作作为结果接口组织在一个继承层次结构中,由它来为派生类提供一个公共的根,派生类将具体实现在其基类中作为接口的操作。所以派生类实际上刻画了一组子类的操作接口的通用语义,这些语义也传给子类,子类可以具体实现这些语义,也可以再将这些语义传给自己的子类。

    (3)使用抽象类时注意:
       抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类中没有重新定义纯虚函数,而只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体的类。

    抽象类是不能定义对象的。一个纯虚函数不需要(但是可以)被定义。

    纯虚函数定义


     纯虚函数是一种特殊的虚函数,它的一般格式如下:
      class <类名>
      {
      virtual <类型><函数名>(<参数表>)=0;
      …
      };
      在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。
      纯虚函数可以让类先具有一个操作名称,而没有操作内容,让派生类在继承时再去具体地给出定义。凡是含有纯虚函数的类叫做抽象类。这种类不能声明对象,只是作为基类为派生类服务。除非在派生类中完全实现基类中所有的的纯虚函数,否则,派生类也变成了抽象类,不能实例化对象。

    纯虚函数引入原因


      1、为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。
      2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔 雀等子类,但动物本身生成对象明显不合常理。
      为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;)。若要使派生类为非抽象类,则编译器要求在派生类中,必须对纯虚函数予以重载以实现多态性。同时含有纯虚函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。
    例如,绘画程序中,shape作为一个基类可以派生出圆形、矩形、正方形、梯形等, 如果我要求面积总和的话,那么会可以使用一个 shape * 的数组,只要依次调用派生类的area()函数了。如果不用接口就没法定义成数组,因为既可以是circle ,也可以是square ,而且以后还可能加上rectangle,等等.

    相似概念


    1、多态性

    指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。C++支持两种多态性:编译时多态性,运行时多态性。
      a.编译时多态性:通过重载函数实现
      b.运行时多态性:通过虚函数实现。
    2、虚函数
      虚函数是在基类中被声明为virtual,并在派生类中重新定义的成员函数,可实现成员函数的动态重载。
    3、抽象类
      包含纯虚函数的类称为抽象类。由于抽象类包含了没有定义的纯虚函数,所以不能定义抽象类的对象。

     

    类什么时候会析构?

    1. 对象生命周期结束,被销毁时;
    2. delete指向对象的指针时,或delete指向对象的基类类型指针,而其基类虚构函数是虚函数时;
    3. 对象i是对象o的成员,o的析构函数被调用时,对象i的析构函数也被调用。

     

    为什么友元函数必须在类内部声明?

    因为编译器必须能够读取这个结构的声明以理解这个数据类型的大、行为等方面的所有规则。有一条规则在任何关系中都很重要,那就是谁可以访问我的私有部分。

    介绍一下C++里面的多态?

    (1)静态多态(重载,模板)

    是在编译的时候,就确定调用函数的类型。

    (2)动态多态(覆盖,虚函数实现)

    在运行的时候,才确定调用的是哪个函数,动态绑定。运行基类指针指向派生类的对象,并调用派生类的函数。

    虚函数实现原理:虚函数表和虚函数指针。

    纯虚函数: virtual int fun() = 0;

    函数的运行版本由实参决定,在运行时选择函数的版本,所以动态绑定又称为运行时绑定。

    当编译器遇到一个模板定义时,它并不生成代码。只有当实例化出模板的一个特定版本时,编译器才会生成代码。

    用C语言实现C++的继承

    #include <iostream>
    using namespace std;
    
    //C++中的继承与多态
    struct A
    {
        virtual void fun()    //C++中的多态:通过虚函数实现
        {
            cout<<"A:fun()"<<endl;
        }
    
        int a;
    };
    struct B:public A         //C++中的继承:B类公有继承A类
    {
        virtual void fun()    //C++中的多态:通过虚函数实现(子类的关键字virtual可加可不加)
        {
            cout<<"B:fun()"<<endl;
        }
    
        int b;
    };
    
    //C语言模拟C++的继承与多态
    
    typedef void (*FUN)();      //定义一个函数指针来实现对成员函数的继承
    
    struct _A       //父类
    {
        FUN _fun;   //由于C语言中结构体不能包含函数,故只能用函数指针在外面实现
    
        int _a;
    };
    
    struct _B         //子类
    {
        _A _a_;     //在子类中定义一个基类的对象即可实现对父类的继承
        int _b;
    };
    
    void _fA()       //父类的同名函数
    {
        printf("_A:_fun()\n");
    }
    void _fB()       //子类的同名函数
    {
        printf("_B:_fun()\n");
    }
    
    
    void Test()
    {
        //测试C++中的继承与多态
        A a;    //定义一个父类对象a
        B b;    //定义一个子类对象b
    
        A* p1 = &a;   //定义一个父类指针指向父类的对象
        p1->fun();    //调用父类的同名函数
        p1 = &b;      //让父类指针指向子类的对象
        p1->fun();    //调用子类的同名函数
    
    
        //C语言模拟继承与多态的测试
        _A _a;    //定义一个父类对象_a
        _B _b;    //定义一个子类对象_b
        _a._fun = _fA;        //父类的对象调用父类的同名函数
        _b._a_._fun = _fB;    //子类的对象调用子类的同名函数
    
        _A* p2 = &_a;   //定义一个父类指针指向父类的对象
        p2->_fun();     //调用父类的同名函数
        p2 = (_A*)&_b;  //让父类指针指向子类的对象,由于类型不匹配所以要进行强转
        p2->_fun();     //调用子类的同名函数
    }
    

     

     

    继承机制中对象之间如何转换?指针和引用之间如何转换?

    • 向上类型转换

    将派生类指针或引用转换为基类的指针或引用被称为向上类型转换,向上类型转换会自动进行,而且向上类型转换是安全的。

    • 向下类型转换

    将基类指针或引用转换为派生类指针或引用被称为向下类型转换,向下类型转换不会自动进行,因为一个基类对应几个派生类,所以向下类型转换时不知道对应哪个派生类,所以在向下类型转换时必须加动态类型识别技术。RTTI技术,用dynamic_cast进行向下类型转换。

    组合与继承优缺点?

    一:继承

    继承是Is a 的关系,比如说Student继承Person,则说明Student is a Person。继承的优点是子类可以重写父类的方法来方便地实现对父类的扩展。

    继承的缺点有以下几点:

    ①:父类的内部细节对子类是可见的。

    ②:子类从父类继承的方法在编译时就确定下来了,所以无法在运行期间改变从父类继承的方法的行为。

    ③:如果对父类的方法做了修改的话(比如增加了一个参数),则子类的方法必须做出相应的修改。所以说子类与父类是一种高耦合,违背了面向对象思想。

    二:组合

    组合也就是设计类的时候把要组合的类的对象加入到该类中作为自己的成员变量。

    组合的优点:

    ①:当前对象只能通过所包含的那个对象去调用其方法,所以所包含的对象的内部细节对当前对象时不可见的。

    ②:当前对象与包含的对象是一个低耦合关系,如果修改包含对象的类中代码不需要修改当前对象类的代码。

    ③:当前对象可以在运行时动态的绑定所包含的对象。可以通过set方法给所包含对象赋值。

    组合的缺点:①:容易产生过多的对象。②:为了能组合多个对象,必须仔细对接口进行定义。

    左值右值

    1. 在C++11中所有的值必属于左值、右值两者之一,右值又可以细分为纯右值、将亡值。在C++11中可以取地址的、有名字的就是左值,反之,不能取地址的、没有名字的就是右值(将亡值或纯右值)。举个例子,int a = b+c, a 就是左值,其有变量名为a,通过&a可以获取该变量的地址;表达式b+c、函数int func()的返回值是右值,在其被赋值给某一变量前,我们不能通过变量名找到它,&(b+c)这样的操作则不会通过编译。
    2. C++11对C++98中的右值进行了扩充。在C++11中右值又分为纯右值(prvalue,Pure Rvalue)和将亡值(xvalue,eXpiring Value)。其中纯右值的概念等同于我们在C++98标准中右值的概念,指的是临时变量和不跟对象关联的字面量值;将亡值则是C++11新增的跟右值引用相关的表达式,这样表达式通常是将要被移动的对象(移为他用),比如返回右值引用T&&的函数返回值、std::move的返回值,或者转换为T&&的类型转换函数的返回值。将亡值可以理解为通过“盗取”其他变量内存空间的方式获取到的值。在确保其他变量不再被使用、或即将被销毁时,通过“盗取”的方式可以避免内存空间的释放和分配,能够延长变量值的生命期。
    3. 左值引用就是对一个左值进行引用的类型。右值引用就是对一个右值进行引用的类型,事实上,由于右值通常不具有名字,我们也只能通过引用的方式找到它的存在。右值引用和左值引用都是属于引用类型。无论是声明一个左值引用还是右值引用,都必须立即进行初始化。而其原因可以理解为是引用类型本身自己并不拥有所绑定对象的内存,只是该对象的一个别名。左值引用是具名变量值的别名,而右值引用则是不具名(匿名)变量的别名。左值引用通常也不能绑定到右值,但常量左值引用是个“万能”的引用类型。它可以接受非常量左值、常量左值、右值对其进行初始化。不过常量左值所引用的右值在它的“余生”中只能是只读的。相对地,非常量左值只能接受非常量左值对其进行初始化。
    4. 右值值引用通常不能绑定到任何的左值,要想绑定一个左值到右值引用,通常需要std::move()将左值强制转换为右值。

     

    移动构造函数

    1. 我们用对象a初始化对象b,后对象a我们就不在使用了,但是对象a的空间还在呀(在析构之前),既然拷贝构造函数,实际上就是把a对象的内容复制一份到b中,那么为什么我们不能直接使用a的空间呢?这样就避免了新的空间的分配,大大降低了构造的成本。这就是移动构造函数设计的初衷;
    2. 拷贝构造函数中,对于指针,我们一定要采用深层复制,而移动构造函数中,对于指针,我们采用浅层复制。浅层复制之所以危险,是因为两个指针共同指向一片内存空间,若第一个指针将其释放,另一个指针的指向就不合法了。所以我们只要避免第一个指针释放空间就可以了。避免的方法就是将第一个指针(比如a->value)置为NULL,这样在调用析构函数的时候,由于有判断是否为NULL的语句,所以析构a的时候并不会回收a->value指向的空间;
    3. 移动构造函数的参数和拷贝构造函数不同,拷贝构造函数的参数是一个左值引用,但是移动构造函数的初值是一个右值引用。意味着,移动构造函数的参数是一个右值或者将亡值的引用。也就是说,只用用一个右值,或者将亡值初始化另一个对象的时候,才会调用移动构造函数。而那个move语句,就是将一个左值变成一个将亡值。

     

    C语言的编译链接过程?

    源代码-->预处理-->编译-->优化-->汇编-->链接-->可执行文件

    • 预处理

    读取c源程序,对其中的伪指令(以#开头的指令)和特殊符号进行处理。包括宏定义替换、条件编译指令、头文件包含指令、特殊符号。 预编译程序所完成的基本上是对源程序的“替代”工作。经过此种替代,生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件。.i预处理后的c文件,.ii预处理后的C++文件。

    • 编译阶段

    编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。.s文件

    • 汇编过程

    汇编过程实际上指把汇编语言代码翻译成目标机器指令的过程。对于被翻译系统处理的每一个C语言源程序,都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。.o目标文件

    • 链接阶段

    链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够诶操作系统装入执行的统一整体。

    vector与list的区别与应用?怎么找某vector或者list的倒数第二个元素

    1. vector数据结构
      vector和数组类似,拥有一段连续的内存空间,并且起始地址不变。因此能高效的进行随机存取,时间复杂度为o(1);但因为内存空间是连续的,所以在进行插入和删除操作时,会造成内存块的拷贝,时间复杂度为o(n)。另外,当数组中内存空间不够时,会重新申请一块内存空间并进行内存拷贝。连续存储结构:vector是可以实现动态增长的对象数组,支持对数组高效率的访问和在数组尾端的删除和插入操作,在中间和头部删除和插入相对不易,需要挪动大量的数据。它与数组最大的区别就是vector不需程序员自己去考虑容量问题,库里面本身已经实现了容量的动态增长,而数组需要程序员手动写入扩容函数进形扩容。
    2. list数据结构
      list是由双向链表实现的,因此内存空间是不连续的。只能通过指针访问数据,所以list的随机存取非常没有效率,时间复杂度为o(n);但由于链表的特点,能高效地进行插入和删除。非连续存储结构:list是一个双链表结构,支持对链表的双向遍历。每个节点包括三个信息:元素本身,指向前一个元素的节点(prev)和指向下一个元素的节点(next)。因此list可以高效率的对数据元素任意位置进行访问和插入删除等操作。由于涉及对额外指针的维护,所以开销比较大。

    区别:

    vector的随机访问效率高,但在插入和删除时(不包括尾部)需要挪动数据,不易操作。list的访问要遍历整个链表,它的随机访问效率低。但对数据的插入和删除操作等都比较方便,改变指针的指向即可。list是单向的,vector是双向的。vector中的迭代器在使用后就失效了,而list的迭代器在使用之后还可以继续使用。 

    int mySize = vec.size();vec.at(mySize -2);

    list不提供随机访问,所以不能用下标直接访问到某个位置的元素,要访问list里的元素只能遍历,不过你要是只需要访问list的最后N个元素的话,可以用反向迭代器来遍历:

    ​​​​​​​STL vector的实现,删除其中的元素,迭代器如何变化?为什么是两倍扩容?释放空间?

    size()函数返回的是已用空间大小,capacity()返回的是总空间大小,capacity()-size()则是剩余的可用空间大小。当size()和capacity()相等,说明vector目前的空间已被用完,如果再添加新元素,则会引起vector空间的动态增长。

    由于动态增长会引起重新分配内存空间、拷贝原空间、释放原空间,这些过程会降低程序效率。因此,可以使用reserve(n)预先分配一块较大的指定大小的内存空间,这样当指定大小的内存空间未使用完时,是不会重新分配内存空间的,这样便提升了效率。只有当n>capacity()时,调用reserve(n)才会改变vector容量。

     resize()成员函数只改变元素的数目,不改变vector的容量。

    1. 空的vector对象,size()和capacity()都为0

    2. 当空间大小不足时,新分配的空间大小为原空间大小的2倍。

    3. 使用reserve()预先分配一块内存后,在空间未满的情况下,不会引起重新分配,从而提升了效率。

    4. 当reserve()分配的空间比原空间小时,是不会引起重新分配的。

    5. resize()函数只改变容器的元素数目,未改变容器大小。

    6. 用reserve(size_type)只是扩大capacity值,这些内存空间可能还是“野”的,如果此时使用“[ ]”来访问,则可能会越界。而resize(size_type new_size)会真正使容器具有new_size个对象。

     

     

    1. 不同的编译器,vector有不同的扩容大小。在vs下是1.5倍,在GCC下是2倍;
    2. 空间和时间的权衡。简单来说, 空间分配的多,平摊时间复杂度低,但浪费空间也多。
    3. 使用k=2增长因子的问题在于,每次扩展的新尺寸必然刚好大于之前分配的总和,也就是说,之前分配的内存空间不可能被使用。这样对内存不友好。最好把增长因子设为(1,2)
    4. 对比可以发现采用采用成倍方式扩容,可以保证常数的时间复杂度,而增加指定大小的容量只能达到O(n)的时间复杂度,因此,使用成倍的方式扩容。

    如何释放空间:

    由于vector的内存占用空间只增不减,比如你首先分配了10,000个字节,然后erase掉后面9,999个,留下一个有效元素,但是内存占用仍为10,000个。所有内存空间是在vector析构时候才能被系统回收。empty()用来检测容器是否为空的,clear()可以清空所有元素。但是即使clear(),vector所占用的内存空间依然如故,无法保证内存的回收。

    如果需要空间动态缩小,可以考虑使用deque。如果vector,可以用swap()来帮助你释放内存。

    vector(Vec).swap(Vec);
    将Vec的内存空洞清除;
    vector().swap(Vec);
    清空Vec的内存;

     

    容器内部删除一个元素​​​​​​​

    顺序容器​​​​​​​

    erase迭代器不仅使所指向被删除的迭代器失效,而且使被删元素之后的所有迭代器失效(list除外),所以不能使用erase(it++)的方式,但是erase的返回值是下一个有效迭代器;

    It = c.erase(it);

    关联容器

    erase迭代器只是被删除元素的迭代器失效,但是返回值是void,所以要采用erase(it++)的方式删除迭代器;

    c.erase(it++)

    ​​​​​​​STL迭代器如何实现

    1. 迭代器是一种抽象的设计理念,通过迭代器可以在不了解容器内部原理的情况下遍历容器,除此之外,STL中迭代器一个最重要的作用就是作为容器与STL算法的粘合剂。
    2. 迭代器的作用就是提供一个遍历容器内部所有元素的接口,因此迭代器内部必须保存一个与容器相关联的指针,然后重载各种运算操作来遍历,其中最重要的是*运算符与->运算符,以及++、--等可能需要重载的运算符重载。这和C++中的智能指针很像,智能指针也是将一个指针封装,然后通过引用计数或是其他方法完成自动释放内存的功能。
    3. 最常用的迭代器的相应型别有五种:value type、difference type、pointer、reference、iterator catagoly;

    ​​​​​​​set与hash_set的区别

    1. set底层是以RB-Tree实现,hash_set底层是以hash_table实现的;
    2. RB-Tree有自动排序功能,而hash_table不具有自动排序功能;
    3. set和hash_set元素的键值就是实值;
    4. hash_table有一些无法处理的型别;

    ​​​​​​​hashmap与map的区别

    1. 底层实现不同;
    2. map具有自动排序的功能,hash_map不具有自动排序的功能;
    3. hashtable有一些无法处理的型别;

    map、set是怎么实现的,红黑树是怎么能够同时实现这两种容器? 为什么使用红黑树?

    1. 他们的底层都是以红黑树的结构实现,因此插入删除等操作都在O(logn)时间内完成,因此可以完成高效的插入删除;
    2. 在这里我们定义了一个模版参数,如果它是key那么它就是set,如果它是map,那么它就是map;底层是红黑树,实现map的红黑树的节点数据类型是key+value,而实现set的节点数据类型是value
    3. 因为map和set要求是自动排序的,红黑树能够实现这一功能,而且时间复杂度比较低。
    4.  

    如何在共享内存上使用stl标准库?

    1. 想像一下把STL容器,例如map, vector, list等等,放入共享内存中,IPC一旦有了这些强大的通用数据结构做辅助,无疑进程间通信的能力一下子强大了很多。我们没必要再为共享内存设计其他额外的数据结构,另外,STL的高度可扩展性将为IPC所驱使。STL容器被良好的封装,默认情况下有它们自己的内存管理方案。当一个元素被插入到一个STL列表(list)中时,列表容器自动为其分配内存,保存数据。考虑到要将STL容器放到共享内存中,而容器却自己在堆上分配内存。一个最笨拙的办法是在堆上构造STL容器,然后把容器复制到共享内存,并且确保所有容器的内部分配的内存指向共享内存中的相应区域,这基本是个不可能完成的任务。

     

    1. 假设进程A在共享内存中放入了数个容器,进程B如何找到这些容器呢?一个方法就是进程A把容器放在共享内存中的确定地址上(fixed offsets),则进程B可以从该已知地址上获取容器。另外一个改进点的办法是,进程A先在共享内存某块确定地址上放置一个map容器,然后进程A再创建其他容器,然后给其取个名字和地址一并保存到这个map容器里。进程B知道如何获取该保存了地址映射的map容器,然后同样再根据名字取得其他容器的地址。

    map插入方式有几种?

    1. 用insert函数插入pair数据,

    mapStudent.insert(pair<int, string>(1, "student_one"));  

    1. 用insert函数插入value_type数据

    mapStudent.insert(map<int, string>::value_type (1, "student_one"));

    1. 在insert函数中使用make_pair()函数

    mapStudent.insert(make_pair(1, "student_one"));  

    1. 用数组方式插入数据

    mapStudent[1] = "student_one";  

    ​​​​​​​STL中unordered_map(hash_map)和map的区别,hash_map如何解决冲突以及扩容

    1. unordered_map和map类似,都是存储的key-value的值,可以通过key快速索引到value。不同的是unordered_map不会根据key的大小进行排序,
    2. 存储时是根据key的hash值判断元素是否相同,即unordered_map内部元素是无序的,而map中的元素是按照二叉搜索树存储,进行中序遍历会得到有序遍历。
    3. 所以使用时map的key需要定义operator<。而unordered_map需要定义hash_value函数并且重载operator==。但是很多系统内置的数据类型都自带这些,
    4. 那么如果是自定义类型,那么就需要自己重载operator<或者hash_value()了。
    5. 如果需要内部元素自动排序,使用map,不需要排序使用unordered_map
    6. unordered_map的底层实现是hash_table;
    7. hash_map底层使用的是hash_table,而hash_table使用的开链法进行冲突避免,所有hash_map采用开链法进行冲突解决。
    8. 什么时候扩容:当向容器添加元素的时候,会判断当前容器的元素个数,如果大于等于阈值---即当前数组的长度乘以加载因子的值的时候,就要自动扩容啦。
    9. 扩容(resize)就是重新计算容量,向HashMap对象里不停的添加元素,而HashMap对象内部的数组无法装载更多的元素时,对象就需要扩大数组的长度,以便能装入更多的元素。

    vector越界访问下标,map越界访问下标?vector删除元素时会不会释放空间?

    1. 通过下标访问vector中的元素时不会做边界检查,即便下标越界。也就是说,下标与first迭代器相加的结果超过了finish迭代器的位置,程序也不会报错,而是返回这个地址中存储的值。如果想在访问vector中的元素时首先进行边界检查,可以使用vector中的at函数。通过使用at函数不但可以通过下标访问vector中的元素,而且在at函数内部会对下标进行边界检查。
    2. map的下标运算符[]的作用是:将key作为下标去执行查找,并返回相应的值;如果不存在这个key,就将一个具有该key和value的某人值插入这个map。
    3. erase()函数,只能删除内容,不能改变容量大小; erase成员函数,它删除了itVect迭代器指向的元素,并且返回要被删除的itVect之后的迭代器,迭代器相当于一个智能指针;clear()函数,只能清空内容,不能改变容量大小;如果要想在删除内容的同时释放内存,那么你可以选择deque容器。

    map[]与find的区别?

    1. map的下标运算符[]的作用是:将关键码作为下标去执行查找,并返回对应的值;如果不存在这个关键码,就将一个具有该关键码和值类型的默认值的项插入这个map。
    2. map的find函数:用关键码执行查找,找到了返回该位置的迭代器;如果不存在这个关键码,就返回尾迭代器。

    STL中list与queue之间的区别

    1. list不再能够像vector一样以普通指针作为迭代器,因为其节点不保证在存储空间中连续存在;
    2. list插入操作和结合才做都不会造成原有的list迭代器失效;
    3. list不仅是一个双向链表,而且还是一个环状双向链表,所以它只需要一个指针;
    4. list不像vector那样有可能在空间不足时做重新配置、数据移动的操作,所以插入前的所有迭代器在插入操作之后都仍然有效;
    5. deque是一种双向开口的连续线性空间,所谓双向开口,意思是可以在头尾两端分别做元素的插入和删除操作;可以在头尾两端分别做元素的插入和删除操作;
    6. deque和vector最大的差异,一在于deque允许常数时间内对起头端进行元素的插入或移除操作,二在于deque没有所谓容量概念,因为它是动态地以分段连续空间组合而成,随时可以增加一段新的空间并链接起来,deque没有所谓的空间保留功能。

    STL中的allocator,deallocator

    1. 第一级配置器直接使用malloc()、free()和relloc(),第二级配置器视情况采用不同的策略:当配置区块超过128bytes时,视之为足够大,便调用第一级配置器;当配置器区块小于128bytes时,为了降低额外负担,使用复杂的内存池整理方式,而不再用一级配置器;
    2. 第二级配置器主动将任何小额区块的内存需求量上调至8的倍数,并维护16个free-list,各自管理大小为8~128bytes的小额区块;
    3. 空间配置函数allocate(),首先判断区块大小,大于128就直接调用第一级配置器,小于128时就检查对应的free-list。如果free-list之内有可用区块,就直接拿来用,如果没有可用区块,就将区块大小调整至8的倍数,然后调用refill(),为free-list重新分配空间;
    4. 空间释放函数deallocate(),该函数首先判断区块大小,大于128bytes时,直接调用一级配置器,小于128bytes就找到对应的free-list然后释放内存。
    5.  

    ​​​​​​​STL中hash_map扩容发生什么? 

    1. hash table表格内的元素称为桶(bucket),而由桶所链接的元素称为节点(node),其中存入桶元素的容器为stl本身很重要的一种序列式容器——vector容器。之所以选择vector为存放桶元素的基础容器,主要是因为vector容器本身具有动态扩容能力,无需人工干预。
    2. 向前操作:首先尝试从目前所指的节点出发,前进一个位置(节点),由于节点被安置于list内,所以利用节点的next指针即可轻易完成前进操作,如果目前正巧是list的尾端,就跳至下一个bucket身上,那正是指向下一个list的头部节点。

     

    ​​​​​​​map如何创建?

    1.vector      底层数据结构为数组 ,支持快速随机访问

    2.list            底层数据结构为双向链表,支持快速增删

    3.deque       底层数据结构为一个中央控制器和多个缓冲区,详细见STL源码剖析P146,支持首尾(中间不能)快速增删,也支持随机访问

    deque是一个双端队列(double-ended queue),也是在堆中保存内容的.它的保存形式如下:

    [堆1] --> [堆2] -->[堆3] --> ...

    每个堆保存好几个元素,然后堆和堆之间有指针指向,看起来像是list和vector的结合品.

    4.stack        底层一般用list或deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时

    5.queue     底层一般用list或deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时(stack和queue其实是适配器,而不叫容器,因为是对容器的再封装)

    6.priority_queue     的底层数据结构一般为vector为底层容器,堆heap为处理规则来管理底层容器实现

    7.set                   底层数据结构为红黑树,有序,不重复

    8.multiset         底层数据结构为红黑树,有序,可重复 

    9.map                底层数据结构为红黑树,有序,不重复

    10.multimap    底层数据结构为红黑树,有序,可重复

    11.hash_set     底层数据结构为hash表,无序,不重复

    12.hash_multiset 底层数据结构为hash表,无序,可重复 

    13.hash_map    底层数据结构为hash表,无序,不重复

    14.hash_multimap 底层数据结构为hash表,无序,可重复 

     

    ​​​​​​​vector的增加删除都是怎么做的?为什么是1.5倍?

    1. 新增元素:vector通过一个连续的数组存放元素,如果集合已满,在新增数据的时候,就要分配一块更大的内存,将原来的数据复制过来,释放之前的内存,在插入新增的元素;
    2. 对vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器就都失效了 ;
    3. 初始时刻vector的capacity为0,塞入第一个元素后capacity增加为1;
    4. 不同的编译器实现的扩容方式不一样,VS2015中以1.5倍扩容,GCC以2倍扩容。

     

    对比可以发现采用采用成倍方式扩容,可以保证常数的时间复杂度,而增加指定大小的容量只能达到O(n)的时间复杂度,因此,使用成倍的方式扩容。

    1. 考虑可能产生的堆空间浪费,成倍增长倍数不能太大,使用较为广泛的扩容方式有两种,以2二倍的方式扩容,或者以1.5倍的方式扩容。
    2. 以2倍的方式扩容,导致下一次申请的内存必然大于之前分配内存的总和,导致之前分配的内存不能再被使用,所以最好倍增长因子设置为(1,2)之间: 
    3. 向量容器vector的成员函数pop_back()可以删除最后一个元素.
    4. 而函数erase()可以删除由一个iterator指出的元素,也可以删除一个指定范围的元素。
    5. 还可以采用通用算法remove()来删除vector容器中的元素.
    6. 不同的是:采用remove一般情况下不会改变容器的大小,而pop_back()与erase()等成员函数会改变容器的大小。

    什么是函数指针?

    函数指针指向的是特殊的数据类型,函数的类型是由其返回的数据类型和其参数列表共同决定的,而函数的名称则不是其类型的一部分。

    一个具体函数的名字,如果后面不跟调用符号(即括号),则该名字就是该函数的指针(注意:大部分情况下,可以这么认为,但这种说法并不很严格)。

    函数指针的声明方法

    int (*pf)(const int&, const int&); (1)

    上面的pf就是一个函数指针,指向所有返回类型为int,并带有两个const int&参数的函数。注意*pf两边的括号是必须的,否则上面的定义就变成了:

    int *pf(const int&, const int&); (2)

    而这声明了一个函数pf,其返回类型为int *, 带有两个const int&参数。

    为什么有函数指针

    函数与数据项相似,函数也有地址。我们希望在同一个函数中通过使用相同的形参在不同的时间使用产生不同的效果。

    1. 一个函数名就是一个指针,它指向函数的代码。一个函数地址是该函数的进入点,也就是调用函数的地址。函数的调用可以通过函数名,也可以通过指向函数的指针来调用。函数指针还允许将函数作为变元传递给其他函数;
    2. 两种方法赋值:

    指针名 = 函数名;  指针名 = &函数名

    ​​​​​​​说说你对c和c++的看法,c和c++的区别?

    1. 第一点就应该想到C是面向过程的语言,而C++是面向对象的语言,一般简历上第一条都是熟悉C/C++基本语法,了解C++面向对象思想,那么,请问什么是面向对象?
    2. C和C++动态管理内存的方法不一样,C是使用malloc/free函数,而C++除此之外还有new/delete关键字;(关于malooc/free与new/delete的不同又可以说一大堆,最后的扩展_1部分列出十大区别);
    3. 接下来就不得不谈到C中的struct和C++的类,C++的类是C所没有的,但是C中的struct是可以在C++中正常使用的,并且C++对struct进行了进一步的扩展,使struct在C++中可以和class一样当做类使用,而唯一和class不同的地方在于struct的成员默认访问修饰符是public,而class默认的是private;
    4. C++支持函数重载,而C不支持函数重载,而C++支持重载的依仗就在于C++的名字修饰与C不同,例如在C++中函数int fun(int ,int)经过名字修饰之后变为 _fun_int_int ,而C是 
      _fun,一般是这样的,所以C++才会支持不同的参数调用不同的函数;
    5. C++中有引用,而C没有;这样就不得不提一下引用和指针的区别(文后扩展_2);
    6. 当然还有C++全部变量的默认链接属性是外链接,而C是内连接;
    7. C 中用const修饰的变量不可以用在定义数组时的大小,但是C++用const修饰的变量可以(如果不进行&,解引用的操作的话,是存放在符号表的,不开辟内存);
    8. 当然还有局部变量的声明规则不同,多态,C++特有输入输出流之类的,很多,下面就不再列出来了; “`

    c/c++的内存分配,详细说一下栈、堆、静态存储区?

    1、栈区(stack)—  由编译器自动分配释放,存放函数的参数值,局部变量的值等

    其操作方式类似于数据结构中的栈。  
    2、堆区(heap) —  一般由程序员分配释放,若程序员不释放,程序结束时可能由OS(操作系统)回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。  
    3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。  
    4、文字常量区  —常量字符串就是放在这里的。程序结束后由系统释放。
    5、程序代码区    —存放函数体的二进制代码。  

    ​​​​​​​堆与栈的区别?

    1. 管理方式对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory leak。 
    2. 空间大小一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的,例如,在VC6下面,默认的栈空间大小是1M(好像是,记不清楚了)。当然,我们可以修改: 打开工程,依次操作菜单如下:Project->Setting->Link,在Category 中选中Output,然后在Reserve中设定堆栈的最大值和commit。 注意:reserve最小值为4Byte;commit是保留在虚拟内存的页文件里面,它设置的较大会使栈开辟较大的值,可能增加内存的开销和启动时间。 
    3. 碎片问题对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出,详细的可以参考数据结构,这里我们就不再一一讨论了。 
    4. 生长方向对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。 
    5. 分配方式堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,它的动态分配是由编译器进行释放,无需我们手工实现。 
    6. 分配效率栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。

    ​​​​​​​野指针是什么?如何检测内存泄漏?

    1. 野指针:指向内存被释放的内存或者没有访问权限的内存的指针。
    2. “野指针”的成因主要有3种:
    • 指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。例如
      char *p = NULL;
      char *str = new char(100);
    • 指针p被free或者delete之后,没有置为NULL;
    • 指针操作超越了变量的作用范围。

    如何避免野指针:

    • 对指针进行初始化

    ①将指针初始化为NULL。

    char *   p  = NULL;

    ②用malloc分配内存

    char * p = (char * )malloc(sizeof(char));

    ③用已有合法的可访问的内存地址对指针初始化

    char num[ 30] = {0};

    char *p = num;

    • 指针用完后释放内存,将指针赋NULL。

    delete(p);

    p = NULL;

    ​​​​​​​悬空指针和野指针有什么区别?

    1. 野指针:野指针指,访问一个已删除或访问受限的内存区域的指针,野指针不能判断是否为NULL来避免。指针没有初始化,释放后没有置空,越界
    2. 悬空指针:一个指针的指向对象已被删除,那么就成了悬空指针。野指针是那些未初始化的指针。
        1. 内存泄漏
    3. 内存泄漏

    内存泄漏是指由于疏忽或错误造成了程序未能释放掉不再使用的内存的情况。内存泄漏并非指内存在物理上消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制;

    • 后果

    只发生一次小的内存泄漏可能不被注意,但泄漏大量内存的程序将会出现各种证照:性能下降到内存逐渐用完,导致另一个程序失败;

    • 如何排除

    使用工具软件BoundsChecker,BoundsChecker是一个运行时错误检测工具,它主要定位程序运行时期发生的各种错误;

    调试运行DEBUG版程序,运用以下技术:CRT(C run-time libraries)、运行时函数调用堆栈、内存泄漏时提示的内存分配序号(集成开发环境OUTPUT窗口),综合分析内存泄漏的原因,排除内存泄漏。

    • 解决方法

    智能指针。

    检查、定位内存泄漏

    检查方法:在main函数最后面一行,加上一句_CrtDumpMemoryLeaks()。调试程序,自然关闭程序让其退出,查看输出:

    输出这样的格式{453}normal block at 0x02432CA8,868 bytes long

    被{}包围的453就是我们需要的内存泄漏定位值,868 bytes long就是说这个地方有868比特内存没有释放。

    定位代码位置

    在main函数第一行加上_CrtSetBreakAlloc(453);意思就是在申请453这块内存的位置中断。然后调试程序,程序中断了,查看调用堆栈。加上头文件#include <crtdbg.h>

    ​​​​​​​new和malloc的区别?

    1. new/delete是C++关键字,需要编译器支持。malloc/free是库函数,需要头文件支持;
    2. 使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地指出所需内存的尺寸。
    3. new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。
    4. new内存分配失败时,会抛出bac_alloc异常。malloc分配内存失败时返回NULL。
    5. new会先调用operator new函数,申请足够的内存(通常底层使用malloc实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator delete函数释放内存(通常底层使用free实现)。malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作。

    ​​​​​​​delete p;与delete[]p,allocator

    1. 动态数组管理new一个数组时,[]中必须是一个整数,但是不一定是常量整数,普通数组必须是一个常量整数;
    2. new动态数组返回的并不是数组类型,而是一个元素类型的指针;
    3. delete[]时,数组中的元素按逆序的顺序进行销毁;
    4. new在内存分配上面有一些局限性,new的机制是将内存分配和对象构造组合在一起,同样的,delete也是将对象析构和内存释放组合在一起的。allocator将这两部分分开进行,allocator申请一部分内存,不进行初始化对象,只有当需要的时候才进行初始化操作。

    ​​​​​​​new和delete的实现原理, delete是如何知道释放内存的大小的额?

    • new简单类型直接调用operator new分配内存;而对于复杂结构,先调用operator new分配内存,然后在分配的内存上调用构造函数;对于简单类型,new[]计算好大小后调用operator new;对于复杂数据结构,new[]先调用operator new[]分配内存,然后在p的前四个字节写入数组大小n,然后调用n次构造函数,针对复杂类型,new[]会额外存储数组大小;
    1. new表达式调用一个名为operator  new(operator new[])函数,分配一块足够大的、原始的、未命名的内存空间;
    2. 编译器运行相应的构造函数以构造这些对象,并为其传入初始值;
    3. 对象被分配了空间并构造完成,返回一个指向该对象的指针。
    • delete简单数据类型默认只是调用free函数;复杂数据类型先调用析构函数再调用operator delete;针对简单类型,delete和delete[]等同。假设指针p指向new[]分配的内存。因为要4字节存储数组大小,实际分配的内存地址为[p-4],系统记录的也是这个地址。delete[]实际释放的就是p-4指向的内存。而delete会直接释放p指向的内存,这个内存根本没有被系统记录,所以会崩溃。
    • 需要在 new [] 一个对象数组时,需要保存数组的维度,C++ 的做法是在分配数组空间时多分配了 4 个字节的大小,专门保存数组的大小,在 delete [] 时就可以取出这个保存的数,就知道了需要调用析构函数多少次了。

    ​​​​​​​malloc申请的存储空间能用delete释放吗

    不能,malloc /free主要为了兼容C,new和delete 完全可以取代malloc /free的。malloc /free的操作对象都是必须明确大小的。而且不能用在动态类上。new 和delete会自动进行类型检查和大小,malloc/free不能执行构造函数与析构函数,所以动态对象它是不行的。当然从理论上说使用malloc申请的内存是可以通过delete释放的。不过一般不这样写的。而且也不能保证每个C++的运行时都能正常。

    ​​​​​​​malloc与free的实现原理?

    1. 在标准C库中,提供了malloc/free函数分配释放内存,这两个函数底层是由brk、mmap、,munmap这些系统调用实现的;
    2. brk是将数据段(.data)的最高地址指针_edata往高地址推,mmap是在进程的虚拟地址空间中(堆和栈中间,称为文件映射区域的地方)找一块空闲的虚拟内存。这两种方式分配的都是虚拟内存,没有分配物理内存。在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系;
    3. malloc小于128k的内存,使用brk分配内存,将_edata往高地址推;malloc大于128k的内存,使用mmap分配内存,在堆和栈之间找一块空闲内存分配;brk分配的内存需要等到高地址内存释放以后才能释放,而mmap分配的内存可以单独释放。当最高地址空间的空闲内存超过128K(可由M_TRIM_THRESHOLD选项调节)时,执行内存紧缩操作(trim)。在上一个步骤free的时候,发现最高地址空闲内存超过128K,于是内存紧缩。
    4. malloc是从堆里面申请内存,也就是说函数返回的指针是指向堆里面的一块内存。操作系统中有一个记录空闲内存地址的链表。当操作系统收到程序的申请时,就会遍历该链表,然后就寻找第一个空间大于所申请空间的堆结点,然后就将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。

    ​​​​​​​malloc、realloc、calloc的区别

    • malloc函数

    void* malloc(unsigned int num_size);

    int *p = malloc(20*sizeof(int));申请20个int类型的空间;

    • calloc函数

    void* calloc(size_t n,size_t size);

    int *p = calloc(20, sizeof(int));

    省去了人为空间计算;malloc申请的空间的值是随机初始化的,calloc申请的空间的值是初始化为0的;

    • realloc函数

    void realloc(void *p, size_t new_size);

    给动态分配的空间分配额外的空间,用于扩充容量。

    ​​​​​​​__stdcall和__cdecl的区别?

    • __stdcall

    __stdcall是函数恢复堆栈,只有在函数代码的结尾出现一次恢复堆栈的代码;在编译时就规定了参数个数,无法实现不定个数的参数调用;

    • __cdecl

    __cdecl是调用者恢复堆栈,假设有100个函数调用函数a,那么内存中就有100端恢复堆栈的代码;可以不定参数个数;每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用__stacall函数大。

    ​​​​​​​使用智能指针管理内存资源,RAII

    1. RAII全称是“Resource Acquisition is Initialization”,直译过来是“资源获取即初始化”,也就是说在构造函数中申请分配资源,在析构函数中释放资源。因为C++的语言机制保证了,当一个对象创建的时候,自动调用构造函数,当对象超出作用域的时候会自动调用析构函数。所以,在RAII的指导下,我们应该使用类来管理资源,将资源和对象的生命周期绑定。
    2. 智能指针(std::shared_ptr和std::unique_ptr)即RAII最具代表的实现,使用智能指针,可以实现自动的内存管理,再也不需要担心忘记delete造成的内存泄漏。毫不夸张的来讲,有了智能指针,代码中几乎不需要再出现delete了。

    手写实现智能指针类

    1. 智能指针是一个数据类型,一般用模板实现,模拟指针行为的同时还提供自动垃圾回收机制。它会自动记录SmartPointer<T*>对象的引用计数,一旦T类型对象的引用计数为0,就释放该对象。除了指针对象外,我们还需要一个引用计数的指针设定对象的值,并将引用计数计为1,需要一个构造函数。新增对象还需要一个构造函数,析构函数负责引用计数减少和释放内存。通过覆写赋值运算符,才能将一个旧的智能指针赋值给另一个指针,同时旧的引用计数减1,新的引用计数加1
    2. 一个构造函数、拷贝构造函数、复制构造函数、析构函数、移走函数;

    ​​​​​​​内存对齐?位域?

    1、 分配内存的顺序是按照声明的顺序。

    2、 每个变量相对于起始位置的偏移量必须是该变量类型大小的整数倍,不是整数倍空出内存,直到偏移量是整数倍为止。

    3、 最后整个结构体的大小必须是里面变量类型最大值的整数倍。

     

    添加了#pragma pack(n)后规则就变成了下面这样:

    1、 偏移量要是n和当前变量大小中较小值的整数倍

    2、 整体大小要是n和最大变量大小中较小值的整数倍

    3、 n值必须为1,2,4,8…,为其他值时就按照默认的分配规则

    ​​​​​​​结构体变量比较是否相等

    1. 重载了 “==” 操作符
    struct foo {
    
        int a;
    
        int b;
    
        bool operator==(const foo& rhs) // 操作运算符重载
    
        {
    
            return( a == rhs.a) && (b == rhs.b);
    
        }
    
    };
    1. 元素的话,一个个比;
    2. 指针直接比较,如果保存的是同一个实例地址,则(p1==p2)为真;
    更多相关内容
  • 对于各种类型的IC的功能特性,或许会清楚得更,但对于IC的封装,不知道了解了多少?本文将介绍一些日常常用IC的封装原理及功能特性,通过了解各种类型IC的封装,电子工程师在设计电子电路原理时,可以准确地选择IC...

    作为一名电子工程师,日常工作基本上都会接触上很多各种类型的IC,比如逻辑芯片、存储芯片、MCU或者FPGA等;对于各种类型的IC的功能特性,或许会清楚得更多,但对于IC的封装,不知道了解了多少?本文将介绍一些日常常用IC的封装原理及功能特性,通过了解各种类型IC的封装,电子工程师在设计电子电路原理时,可以准确地选择IC,而对于工厂批量生产烧录,更可以快速地找到对应IC封装的烧录座型号。

    一、DIP双列直插式封装

    DIP是指采用双列直插形式封装的集成电路芯片,绝大多数中小规模集成电路(IC)均采用这种封装形式,其引脚数一般不超过100个。采用DIP封装的IC有两排引脚,需要插入到具有DIP结构的芯片插座上。当然,也可以直接插在有相同焊孔数和几何排列的电路板上进行焊接。DIP封装的芯片在从芯片插座上插拔时应特别小心,以免损坏引脚。

    DIP封装具有以下特点:

    1. 适合在PCB(印刷电路板)上穿孔焊接,操作方便。

    2. 芯片面积与封装面积之间的比值较大,故体积也较大。

    DIP是最普及的插装型封装,应用范围包括标准逻辑IC,存储器和微机电路等!

    af99e568593f4162baef052b4e0c3d2c.png

    图1 DIP封装图

    二、QFP/ PFP类型封装

    QFP/PFP封装的芯片引脚之间距离很小,管脚很细,一般大规模或超大型集成电路都采用这种封装形式。用这种形式封装的芯片必须采用SMD(表面安装设备技术)将芯片与主板焊接起来。采用SMD安装的芯片不必在主板上打孔,一般在主板表面上有设计好的相应管脚的焊点。将芯片各脚对准相应的焊点,即可实现与主板的焊接。

    QFP/PFP封装具有以下特点:

    1. 适用于SMD表面安装技术在PCB电路板上安装布线;

    2. 成本低廉,适用于中低功耗,适合高频使用;

    3. 操作方便,可靠性高;

    4. 芯片面积与封装面积之间的比值较小;

    5. 成熟的封转类型,可采用传统的加工方法。

    目前QFP/PFP封装应用非常广泛,很多MCU 厂家的A芯片都采用了该封装。

    54d7aec96e58e5a2380d54943e6c441f.png

    图2 QFP封装图

    三、BGA类型封装

    随着集成电路技术的发展,对集成电路的封装要求更加严格。这是因为封装技术关系到产品的功能性,当IC的频率超过100MHZ时,传统封装方式可能会产生所谓的“CrossTalk”现象,而且当IC的管脚数大于208 Pin时,传统的封装方式有其困难度。因此,除使用QFP封装方式外,现今大多数的高脚数芯片皆转为使用BGA(BALL Grid Array PACKAGE)封装技术。

    BGA封装具有以下特点:

    1. I/O引脚数虽然增多,但引脚之间的距离远大于QFP封装方式,提高了成品率;

    2. BGA的阵列焊球与基板的接触面大、短,有利于散热;

    3. BGA阵列焊球的引脚很短,缩短了信号的传输路径,减小了引线电感、电阻;信号传输延迟小,适应频率大大提高,因而可改善电路的性能;

    4. 组装可用共面焊接,可靠性大大提高;

    5. BGA适用于MCM封装,能够实现MCM的高密度、高性能。

    512f91b3575fa8c021bbb3769f0ac1d7.png

    图3 BGA封装图

    四、SO类型封装

    SO类型封装包含有:SOP(小外形封装)、TOSP(薄小外形封装)、SSOP (缩小型SOP)、VSOP(甚小外形封装)、SOIC(小外形集成电路封装)等类似于QFP形式的封装,只是只有两边有管脚的芯片封装形式,该类型的封装是表面贴装型封装之一,引脚从封装两侧引出呈“ L” 字形。

    该类型的封装的典型特点就是在封装芯片的周围做出很多引脚,封装操作方便,可靠性比较高,是目前的主流封装方式之一,目前比较常见的是应用于一些存储器类型的IC。

    6183fb462bab37e061f113753b382e8e.png

    图4 SOP封装图

    五、QFN封装类型

    QFN是一种无引线四方扁平封装,是具有外设终端垫以及一个用于机械和热量完整性暴露的芯片垫的无铅封装。

    该封装可为正方形或长方形。封装四侧配置有电极触点,由于无引脚,贴装占有面积比QFP 小,高度 比QFP 低。

    QFN封装的特点:

    1. 表面贴装封装,无引脚设计;

    2. 无引脚焊盘设计占有更小的PCB面积;

    3. 组件非常薄(<1mm),可满足对空间有严格要求的应用;

    4. 非常低的阻抗、自感,可满足高速或者微波的应用;

    5. 具有优异的热性能,主要是因为底部有大面积散热焊盘;

    6. 重量轻,适合便携式应用。

    QFN封装的小外形特点,可用于笔记本电脑、数码相机、个人数字助理(PDA)、移动电话和MP3等便携式消费电子产品。从市场的角度而言,QFN封装越来越多地受到用户的关注,考虑到成本、体积各方面的因素,QFN封装将会是未来几年的一个增长点,发展前景极为乐观。

    f8735fc82b0af0ed362359357e8e5ee2.png

    图5 BGA封装图

    六、PLCC封装类型

    PLCC是一种带引线的塑料的芯片封装载体.表面贴装型的封装形式,引脚从封装的四个侧面引出,呈“丁”字形,外形尺寸比 DIP封装小得多。PLCC封装适合用SMT表面安装技术在PCB上安装布线,具有外形尺寸小、可靠性高的优点。

    PLCC为特殊引脚芯片封装,它是贴片封装的一种,这种封装的引脚在芯片底部向内弯曲,因此在芯片的俯视图中是看不见芯片引脚的。这种芯片的焊接采用回流焊工艺,需要专用的焊接设备,在调试时要取下芯片也很麻烦,现在已经很少用了。

    e57440261dbfcfedfe89c81100e702bb.png

    图6 PLCC封装图

    展开全文
  • 关系数据库与非关系数据库一、数据库概述1、关系数据库2、非关系数据库二、数据库区别1、数据存储方式不同2、扩展方式不同3、对事务性的支持不同三、非关系数据库产生背景四、Redis简介1、Redis 优点五、...

    一、数据库概述

    1、关系型数据库

    关系型数据库是一个结构化的数据库,创建在关系模型(二维表格模型)基础上,一般面向于记录。

    SQL 语句(标准数据查询语言)就是一种基于关系型数据库的语言,用于执行对关系型数据库中数据的检索和操作。

    主流的关系型数据库包括 Oracle、MySQL、SQL Server、Microsoft Access、DB2 等。

    2、非关系型数据库

    NoSQL(NoSQL = Not Only SQL ),意思是“不仅仅是 SQL”,是非关系型数据库的总称。

    除了主流的关系型数据库外的数据库,都认为是非关系型。

    主流的 NoSQL 数据库有 Redis、MongBD、Hbase、CouhDB 等。

    二、数据库区别

    1、数据存储方式不同

    关系型和非关系型数据库的主要差异是数据存储的方式。关系型数据天然就是表格式的,因此存储在数据表的行和列中。数据表可以彼此关联协作存储,也很容易提取数据。

    与其相反,非关系型数据不适合存储在数据表的行和列中,而是大块组合在一起。非关系型数据通常存储在数据集中,就像文档、键值对或者图结构。你的数据及其特性是选择数据存储和提取方式的首要影响因素。

    2、扩展方式不同

    SQL和NoSQL数据库最大的差别可能是在扩展方式上,要支持日益增长的需求当然要扩展。

    为了支持更多并发量,SQL数据库是纵向扩展,也就是说提高处理能力,使用速度更快速的计算机,这样处理相同的数据集就更快了。因为数据存储在关系表中,操作的性能瓶颈可能涉及很多个表,这都需要通过提高计算机性能来客服。虽然SQL数据库有很大扩展空间,但最终肯定会达到纵向扩展的上限。
    而NoSQL数据库是横向扩展的。因为非关系型数据存储天然就是分布式的,NoSQL数据库的扩展可以通过给资源池添加更多普通的数据库服务器(节点)来分担负载。

    3、对事务性的支持不同

    如果数据操作需要高事务性或者复杂数据查询需要控制执行计划,那么传统的SQL数据库从性能和稳定性方面考虑是你的最佳选择。SQL数据库支持对事务原子性细粒度控制,并且易于回滚事务。

    虽然NoSQL数据库也可以使用事务操作,但稳定性方面没法和关系型数据库比较,所以它们真正闪亮的价值是在操作的扩展性和大数据量处理方面。

    三、非关系型数据库产生背景

    可用于应对 Web2.0 纯动态网站类型的三高问题。

    • (1)High performance——对数据库高并发读写需求
    • (2)Huge Storage——对海量数据高效存储与访问需求
    • (3)High Scalability && High Availability——对数据库高可扩展性与高可用性需求

    关系型数据库和非关系型数据库都有各自的特点与应用场景,两者的紧密结合将会给Web2.0的数据库发展带来新的思路。让关系数据库关注在关系上,非关系型数据库关注在存储上。例如,在读写分离的MySQL数据库环境中,可以把经常访问的数据存储在非关系型数据库中,提升访问速度。

    总结:
    关系型数据库:

    • 实例–>数据库–>表(table)–>记录行(row)、数据字段(column)

    非关系型数据库:

    • 实例–>数据库–>集合(collection)–>键值对(key-value)
    • 非关系型数据库不需要手动建数据库和集合(表)。

    四、Redis简介

    Redis 是一个开源的、使用 C 语言编写的 NoSQL 数据库。 Redis
    基于内存运行并支持持久化,采用key-value(键值对)的存储形式,是目前分布式架构中不可或缺的一环。

    Redis服务器程序是单进程模型,也就是在一台服务器上可以同时启动多个Redis进程,Redis的实际处理速度则是完全依靠于主进程的执行效率。若在服务器上只运行一个Redis进程,当多个客户端同时访问时,服务器的处理能力是会有一定程度的下降;若在同一台服务器上开启多个Redis进程,Redis在提高并发处理能力的同时会给服务器的CPU造成很大压力。即:在实际生产环境中,需要根据实际的需求来决定开启多少个Redis进程。若对高并发要求更高一些,可能会考虑在同一台服务器上开启多个进程。若 CPU 资源比较紧张,采用单进程即可。

    1、Redis 优点

    • (1)具有极高的数据读写速度:数据读取的速度最高可达到 110000 次/s,数据写入速度最高可达到 81000 次/s。
    • (2)支持丰富的数据类型:支持 key-value、Strings、Lists、Hashes、Sets 及 Ordered Sets
      等数据类型操作。
    • (3)支持数据的持久化:可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
    • (4)原子性:Redis 所有操作都是原子性的。
    • (5)支持数据备份:即 master-salve 模式的数据备份。

    Redis作为基于内存运行的数据库,缓存是其最常应用的场景之一。除此之外,Redis常见应用场景还包括获取最新N个数据的操作、排行榜类应用、计数器应用、存储关系、实时分析系统、日志记录。

    五、Redis 安装部署

    systemctl stop firewalld
    setenforce 0
    
    yum install -y gcc gcc-c++ make
    
    tar zxvf redis-5.0.7.tar.gz -C /opt/
    
    cd /opt/redis-5.0.7/
    make PREFIX=/usr/local/redis install
    

    在这里插入图片描述

    cd /opt/redis-5.0.7/utils
    ./install_server.sh
    
    慢慢回车
    Please select the redis executable path []
    手动输入
    /usr/local/redis/bin/redis-server
    

    在这里插入图片描述

    Selected config:
    Port           : 6379								#默认侦听端口为6379
    Config file    : /etc/redis/6379.conf				#配置文件路径
    Log file       : /var/log/redis_6379.log			#日志文件路径
    Data dir       : /var/lib/redis/6379				#数据文件路径
    Executable     : /usr/local/redis/bin/redis-server	#可执行文件路径
    Cli Executable : /usr/local/bin/redis-cli			#客户端命令工具
    
    ln -s /usr/local/redis/bin/* /usr/local/bin/
    
    /etc/init.d/redis_6379 stop				#停止
    /etc/init.d/redis_6379 start			#启动
    /etc/init.d/redis_6379 restart			#重启
    /etc/init.d/redis_6379 status			#状态
    

    在这里插入图片描述
    修改配置 /etc/redis/6379.conf 参数

    vim /etc/redis/6379.conf
    
    70行,添加 监听的主机地址
    bind 127.0.0.1 192.168.90.10				
    
    93行,Redis默认的监听端口
    port 6379									
    
    137行,启用守护进程
    daemonize yes							
    
    159行,指定 PID 文件
    pidfile /var/run/redis_6379.pid				
    
    167行,日志级别
    loglevel notice								
    
    172行,指定日志文件
    logfile /var/log/redis_6379.log				
    
    /etc/init.d/redis_6379 restart
    

    在这里插入图片描述

    六、Redis 命令工具

    redis-server		用于启动 Redis 的工具
    redis-benchmark		用于检测 Redis 在本机的运行效率
    redis-check-aof		修复 AOF 持久化文件
    redis-check-rdb		修复 RDB 持久化文件
    redis-cli	 		Redis命令行工具
    

    (1)redis-cli 命令行工具

    语法:redis-cli -h host -p port -a password
    
    -h	指定远程主机
    -p	指定 Redis 服务的端口号
    -a	指定密码,未设置数据库密码可以省略-a 选项
    

    若不添加任何选项表示,则使用 127.0.0.1:6379 连接本机上的 Redis 数据库

    redis-cli -h 192.168.90.10 -p 6379
    此时无密码,不需要-a 直接登陆
    

    在这里插入图片描述

    (2)redis-benchmark 测试工具

    redis-benchmark 是官方自带的 Redis 性能测试工具,可以有效的测试 Redis 服务的性能。

    基本的测试语法:redis-benchmark [选项] [选项值]
    -h	指定服务器主机名。
    -p	指定服务器端口。
    -s	指定服务器 socket
    -c	指定并发连接数。
    -n	指定请求数。
    -d	以字节的形式指定 SET/GET 值的数据大小。
    -k	1=keep alive 0=reconnect 。
    -r	SET/GET/INCR 使用随机 key, SADD 使用随机值。
    -P	通过管道传输请求。
    -q	强制退出 redis。仅显示 query/sec 值。
    –csv	以 CSV 格式输出。
    -l	生成循环,永久执行测试。
    -t	仅运行以逗号分隔的测试命令列表。
    -I	Idle 模式。仅打开 N 个 idle 连接并等待。
    

    向 IP 地址为 192.168.90.10、端口为 6379 的 Redis 服务器发送 100 个并发连接与 100000 个请求测试性能

    redis-benchmark -h 192.168.90.10 -p 6379 -c 100 -n 100000
    

    在这里插入图片描述
    测试存取大小为 100 字节的数据包的性能

    redis-benchmark -h 192.168.90.10 -p 6379 -q -d 100
    

    在这里插入图片描述
    测试本机上 Redis 服务在进行 set 与 lpush 操作时的性能

    redis-benchmark -t set,lpush -n 100000 -q
    

    在这里插入图片描述

    (3)Redis 数据库常用命令

    set		存放数据,命令格式为 set key value
    get		获取数据,命令格式为 get key
    keys 	命令可以取符合规则的键值列表,通常情况可以结合*、?等选项来使用。
    exists 	命令可以判断键值是否存在。
    del 	命令可以删除当前数据库的指定 key。
    type 	命令可以获取 key 对应的 value 值类型。
    
    例:
    keys * 查询所有建和键值
    keys a* 查询a开头的所有键和键值
    keys a?? 查询a开头后面跟两位数的键和键值
    

    在这里插入图片描述

    例:
    exists teacher
    exists qqqqqqq
    

    在这里插入图片描述

    rename 命令是对已有 key 进行重命名。(覆盖)
    命令格式:rename 源key 目标key
    
    renamenx 命令的作用是对已有 key 进行重命名,并检测新名是否存在,如果目标 key 存在则不进行重命名。(不覆盖)
    命令格式:renamenx 源key 目标key
    
    dbsize 命令的作用是查看当前数据库中 key 的数目。
    
    使用config set requirepass password命令设置密码
    使用config get requirepass命令查看密码(一旦设置密码,必须先验证通过密码,否则所有操作不可用)
    

    使用rename命令进行重命名时,无论目标key是否存在都进行重命名,且源key的值会覆盖目标key的值。在实际使用过程中,建议先用
    exists 命令查看目标 key 是否存在,然后再决定是否执行 rename 命令,以避免覆盖重要数据。

    (4)Redis 多数据库常用命令

    Redis 支持多数据库,Redis 默认情况下包含 16 个数据库,数据库名称是用数字 0-15 来依次命名的。

    多数据库相互独立,互不干扰。

    多数据库间切换

    命令格式:select 序号
    使用 redis-cli 连接 Redis 数据库后,默认使用的是序号为 0 的数据库。
    

    在这里插入图片描述
    多数据库间移动数据

    格式:move 键值 序号
    
    例:
    keys *
    move a1 5
    select
    keys *
    

    在这里插入图片描述

    清除数据库内数据
    FLUSHDB :清空当前数据库数据
    FLUSHALL :清空所有数据库的数据,慎用!
    

    七、Redis 高可用

    在web服务器中,高可用是指服务器可以正常访问的时间,衡量的标准是在多长时间内可以提供正常服务(99.9%、99.99%、99.999%等等)。

    但是在Redis语境中,高可用的含义似乎要宽泛一些,除了保证提供正常服务(如主从分离、快速容灾技术),还需要考虑数据容量的扩展、数据安全不会丢失等。

    在Redis中,实现高可用的技术主要包括持久化、主从复制、哨兵和集群,下面分别说明它们的作用,以及解决了什么样的问题。

    • 持久化:持久化是最简单的高可用方法(有时甚至不被归为高可用的手段),主要作用是数据备份,即将数据存储在硬盘,保证数据不会因进程退出而丢失。
    • 主从复制:主从复制是高可用Redis的基础,哨兵和集群都是在主从复制基础上实现高可用的。主从复制主要实现了数据的多机备份,以及对于读操作的负载均衡和简单的故障恢复。缺陷:故障恢复无法自动化;写操作无法负载均衡;存储能力受到单机的限制。
    • 哨兵:在主从复制的基础上,哨兵实现了自动化的故障恢复。缺陷:写操作无法负载均衡;存储能力受到单机的限制。
    • 集群:通过集群,Redis解决了写操作无法负载均衡,以及存储能力受到单机限制的问题,实现了较为完善的高可用方案。

    八、Redis 持久化

    持久化的功能:Redis是内存数据库,数据都是存储在内存中,为了避免服务器断电等原因导致Redis进程异常退出后数据的永久丢失,需要定期将Redis中的数据以某种形式(数据或命令)从内存保存到硬盘;当下次Redis重启时,利用持久化文件实现数据恢复。除此之外,为了进行灾难备份,可以将持久化文件拷贝到一个远程位置。

    九、Redis 提供两种方式进行持久化

    由于AOF持久化的实时性更好,即当进程意外退出时丢失的数据更少,因此AOF是目前主流的持久化方式,不过RDB持久化仍然有其用武之地。

    RDB 持久化:
    原理是将 Reids在内存中的数据库记录定时保存到磁盘上。
    AOF 持久化(append only file):
    原理是将 Reids 的操作日志以追加的方式写入文件,类似于MySQL的binlog。

    RDB持久化

    是指在指定的时间间隔内将内存中当前进程中的数据生成快照保存到硬盘(因此也称作快照持久化),用二进制压缩存储,保存的文件后缀是rdb;当Redis重新启动时,可以读取快照文件恢复数据。

    触发条件
    RDB持久化的触发分为手动触发和自动触发两种。

    (1)手动触发

    save命令和bgsave命令都可以生成RDB文件。
    save命令会阻塞Redis服务器进程,直到RDB文件创建完毕为止,在Redis服务器阻塞期间,服务器不能处理任何命令请求。
    而bgsave命令会创建一个子进程,由子进程来负责创建RDB文件,父进程(即Redis主进程)则继续处理请求。

    bgsave命令执行过程中,只有fork子进程时会阻塞服务器,而对于save命令,整个过程都会阻塞服务器,因此save已基本被废弃,线上环境要杜绝save的使用。

    (2)自动触发

    在自动触发RDB持久化时,Redis也会选择bgsave而不是save来进行持久化。

    save m n
    

    自动触发最常见的情况是在配置文件中通过save m n,指定当m秒内发生n次变化时,会触发bgsave。

    vim /etc/redis/6379.conf
    
    ==219行==以下三个save条件满足任意一个时,都会引起bgsave的调用
    
    save 900 1 :当时间到900秒时,如果redis数据发生了至少1次变化,则执行bgsave
    save 300 10 :当时间到300秒时,如果redis数据发生了至少10次变化,则执行bgsave
    save 60 10000 :当时间到60秒时,如果redis数据发生了至少10000次变化,则执行bgsave
    
    ==254行==指定RDB文件名
    
    dbfilename dump.rdb
    
    ==264行==指定RDB文件和AOF文件所在目录
    
    dir /var/lib/redis/6379
    
    ==242行==是否开启RDB文件压缩
    
    rdbcompression yes
    

    其他自动触发机制

    除了save m n 以外,还有一些其他情况会触发bgsave:

    • 在主从复制场景下,如果从节点执行全量复制操作,则主节点会执行bgsave命令,并将rdb文件发送给从节点。
    • 执行shutdown命令时,自动执行rdb持久化。

    (3)执行流程

    • Redis父进程首先判断:当前是否在执行save,或bgsave/bgrewriteaof的子进程,如果在执行则bgsave命令直接返回。
      bgsave/bgrewriteaof的子进程不能同时执行,主要是基于性能方面的考虑:两个并发的子进程同时执行大量的磁盘写操作,可能引起严重的性能问题。
    • 父进程执行fork操作创建子进程,这个过程中父进程是阻塞的,Redis不能执行来自客户端的任何命令
    • 父进程fork后,bgsave命令返回”Background saving started”信息并不再阻塞父进程,并可以响应其他命令
    • 子进程创建RDB文件,根据父进程内存快照生成临时快照文件,完成后对原有文件进行原子替换
    • 子进程发送信号给父进程表示完成,父进程更新统计信息

    (4)启动时加载

    RDB文件的载入工作是在服务器启动时自动执行的,并没有专门的命令。但是由于AOF的优先级更高,因此当AOF开启时,Redis会优先载入 AOF文件来恢复数据;只有当AOF关闭时,才会在Redis服务器启动时检测RDB文件,并自动载入。服务器载入RDB文件期间处于阻塞状态,直到载入完成为止。
    Redis载入RDB文件时,会对RDB文件进行校验,如果文件损坏,则日志中会打印错误,Redis启动失败。

    AOF 持久化

    RDB持久化是将进程数据写入文件,而AOF持久化,则是将Redis执行的每次写、删除命令记录到单独的日志文件中,查询操作不会记录; 当Redis重启时再次执行AOF文件中的命令来恢复数据。
    与RDB相比,AOF的实时性更好,因此已成为主流的持久化方案。

    (1)开启AOF

    Redis服务器默认开启RDB,关闭AOF;要开启AOF,需要在配置文件中配置:

    vim /etc/redis/6379.conf
    
    700行修改,开启AOF
    
    appendonly yes
    
    704行指定AOF文件名称
    
    appendfilename "appendonly.aof"
    
    796行是否忽略最后一条可能存在问题的指令
    
    aof-load-truncated yes
    
    /etc/init.d/redis_6379 restart
    

    (2)执行流程

    由于需要记录Redis的每条写命令,因此AOF不需要触发,下面介绍AOF的执行流程。

    AOF的执行流程包括:

    • 命令追加(append):将Redis的写命令追加到缓冲区aof_buf;
    • 文件写入(write)和文件同步(sync):根据不同的同步策略将aof_buf中的内容同步到硬盘;
    • 文件重写(rewrite):定期重写AOF文件,达到压缩的目的。

    (3)命令追加(append)

    Redis先将写命令追加到缓冲区,而不是直接写入文件,主要是为了避免每次有写命令都直接写入硬盘,导致硬盘IO成为Redis负载的瓶颈。
    命令追加的格式是Redis命令请求的协议格式,它是一种纯文本格式,具有兼容性好、可读性强、容易处理、操作简单避免二次开销等优点。在AOF文件中,除了用于指定数据库的select命令(如select 0为选中0号数据库)是由Redis添加的,其他都是客户端发送来的写命令。

    (4)文件写入(write)和文件同步(sync)

    Redis提供了多种AOF缓存区的同步文件策略,策略涉及到操作系统的write函数和fsync函数

    说明如下:

    为了提高文件写入效率,在现代操作系统中,当用户调用write函数将数据写入文件时,操作系统通常会将数据暂存到一个内存缓冲区里,当缓冲区被填满或超过了指定时限后,才真正将缓冲区的数据写入到硬盘里。这样的操作虽然提高了效率,但也带来了安全问题:如果计算机停机,内存缓冲区中的数据会丢失;因此系统同时提供了fsync、fdatasync等同步函数,可以强制操作系统立刻将缓冲区中的数据写入到硬盘里,从而确保数据的安全性。

    AOF缓存区的同步文件策略存在三种同步方式,它们分别是:

    vim /etc/redis/6379.conf
    

    ● appendfsync always: 命令写入aof_buf后立即调用系统fsync操作同步到AOF文件,fsync完成后线程返回。这种情况下,每次有写命令都要同步到AOF文件,硬盘IO成为性能瓶颈,Redis只能支持大约几百TPS写入,严重降低了Redis的性能;即便是使用固态硬盘(SSD),每秒大约也只能处理几万个命令,而且会大大降低SSD的寿命。

    ● appendfsync no: 命令写入aof_buf后调用系统write操作,不对AOF文件做fsync同步;同步由操作系统负责,通常同步周期为30秒。这种情况下,文件同步的时间不可控,且缓冲区中堆积的数据会很多,数据安全性无法保证。

    ● appendfsync everysec: 命令写入aof_buf后调用系统write操作,write完成后线程返回;fsync同步文件操作由专门的线程每秒调用一次。everysec是前述两种策略的折中,是性能和数据安全性的平衡,因此是Redis的默认配置,也是我们推荐的配置。

    (5)文件重写(rewrite)

    随着时间流逝,Redis服务器执行的写命令越来越多,AOF文件也会越来越大;过大的AOF文件不仅会影响服务器的正常运行,也会导致数据恢复需要的时间过长。

    文件重写是指定期重写AOF文件,减小AOF文件的体积。需要注意的是,AOF重写是把Redis进程内的数据转化为写命令,同步到新的AOF文件;不会对旧的AOF文件进行任何读取、写入操作!

    关于文件重写需要注意的另一点是:对于AOF持久化来说,文件重写虽然是强烈推荐的,但并不是必须的;即使没有文件重写,数据也可以被持久化并在Redis启动的时候导入;因此在一些实现中,会关闭自动的文件重写,然后通过定时任务在每天的某一时刻定时执行。

    文件重写之所以能够压缩AOF文件,原因在于:

    • 过期的数据不再写入文件
    • 无效的命令不再写入文件:如有些数据被重复设值(set mykey v1, set mykey v2)、有些数据被删除了(sadd
      myset v1, del myset)等。
    • 多条命令可以合并为一个:如sadd myset v1, sadd myset v2, sadd myset v3可以合并为sadd
      myset v1 v2 v3。

    通过上述内容可以看出,由于重写后AOF执行的命令减少了,文件重写既可以减少文件占用的空间,也可以加快恢复速度。

    文件重写的触发,分为手动触发和自动触发:

    • 手动触发:直接调用bgrewriteaof命令,该命令的执行与bgsave有些类似:都是fork子进程进行具体的工作,且都只有在fork时阻塞。
    • 自动触发:通过设置auto-aof-rewrite-min-size选项和auto-aof-rewrite-percentage选项来自动执行BGREWRITEAOF。
      只有当auto-aof-rewrite-min-size和auto-aof-rewrite-percentage两个选项同时满足时,才会自动触发AOF重写,即bgrewriteaof操作。
    vim /etc/redis/6379.conf
    

    ● auto-aof-rewrite-percentage 100 :当前AOF文件大小(即aof_current_size)是上次日志重写时AOF文件大小(aof_base_size)两倍时,发生BGREWRITEAOF操作
    ● auto-aof-rewrite-min-size 64mb :当前AOF文件执行BGREWRITEAOF命令的最小值,避免刚开始启动Reids时由于文件尺寸较小导致频繁的BGREWRITEAOF

    关于文件重写的流程,有两点需要特别注意:(1)重写由父进程fork子进程进行;(2)重写期间Redis执行的写命令,需要追加到新的AOF文件中,为此Redis引入了aof_rewrite_buf缓存。

    文件重写的流程如下

    (1)Redis父进程首先判断当前是否存在正在执行bgsave/bgrewriteaof的子进程,如果存在则bgrewriteaof命令直接返回,如果存在 bgsave命令则等bgsave执行完成后再执行。
    (2)父进程执行fork操作创建子进程,这个过程中父进程是阻塞的。
    (3.1)父进程fork后,bgrewriteaof命令返回”Background append only file rewrite started”信息并不再阻塞父进程, 并可以响应其他命令。Redis的所有写命令依然写入AOF缓冲区,并根据appendfsync策略同步到硬盘,保证原有AOF机制的正确。
    (3.2)由于fork操作使用写时复制技术,子进程只能共享fork操作时的内存数据。由于父进程依然在响应命令,因此Redis使用AOF重写缓冲区(aof_rewrite_buf)保存这部分数据,防止新AOF文件生成期间丢失这部分数据。也就是说,bgrewriteaof执行期间,Redis的写命令同时追加到aof_buf和aof_rewirte_buf两个缓冲区。
    (4)子进程根据内存快照,按照命令合并规则写入到新的AOF文件。
    (5.1)子进程写完新的AOF文件后,向父进程发信号,父进程更新统计信息,具体可以通过info persistence查看。
    (5.2)父进程把AOF重写缓冲区的数据写入到新的AOF文件,这样就保证了新AOF文件所保存的数据库状态和服务器当前状态一致。
    (5.3)使用新的AOF文件替换老文件,完成AOF重写。
    

    (6)启动时加载

    当AOF开启时,Redis启动时会优先载入AOF文件来恢复数据;只有当AOF关闭时,才会载入RDB文件恢复数据。
    当AOF开启,但AOF文件不存在时,即使RDB文件存在也不会加载。
    Redis载入AOF文件时,会对AOF文件进行校验,如果文件损坏,则日志中会打印错误,Redis启动失败。但如果是AOF文件结尾不完整(机器突然宕机等容易导致文件尾部不完整),且aof-load-truncated参数开启,则日志中会输出警告,Redis忽略掉AOF文件的尾部,启动成功。aof-load-truncated参数默认是开启的。

    RDB和AOF的优缺点

    ●RDB持久化

    优点:RDB文件紧凑,体积小,网络传输快,适合全量复制;恢复速度比AOF快很多。当然,与AOF相比,RDB最重要的优点之一是对性能的影响相对较小。

    缺点:RDB文件的致命缺点在于其数据快照的持久化方式决定了必然做不到实时持久化,而在数据越来越重要的今天,数据的大量丢失很多时候是无法接受的,因此AOF持久化成为主流。此外,RDB文件需要满足特定格式,兼容性差(如老版本的Redis不兼容新版本的RDB文件)。
    对于RDB持久化,一方面是bgsave在进行fork操作时Redis主进程会阻塞,另一方面,子进程向硬盘写数据也会带来IO压力。

    ●AOF持久化

    与RDB持久化相对应,AOF的优点在于支持秒级持久化、兼容性好,缺点是文件大、恢复速度慢、对性能影响大。
    对于AOF持久化,向硬盘写数据的频率大大提高(everysec策略下为秒级),IO压力更大,甚至可能造成AOF追加阻塞问题。
    AOF文件的重写与RDB的bgsave类似,会有fork时的阻塞和子进程的IO压力问题。相对来说,由于AOF向硬盘中写数据的频率更高,因此对 Redis主进程性能的影响会更大。

    十、Redis 性能管理

    ----- 查看Redis内存使用 -----

    redis-cli -h 192.168.90.10 -p 6379
    192.168.90.10:6379> info memory
    

    ----- 内存碎片率 -----
    操系统分配的内存值used_memory_rss除以Redis使用的内存值used_memory计算得出

    内存碎片是由操作系统低效的分配/回收物理内存导致的(不连续的物理内存分配)

    跟踪内存碎片率对理解Redis实例的资源性能是非常重要的:
    
    ●内存碎片率稍大于1是合理的,这个值表示内存碎片率比较低
    ●内存碎片率超过1.5,说明Redis消耗了实际需要物理内存的150%,其中50%是内存碎片率。需要在redis-cli工具上输入shutdown save 命令,并重启 Redis 服务器。
    ●内存碎片率低于1的,说明Redis内存分配超出了物理内存,操作系统正在进行内存交换。需要增加可用物理内存或减少 Redis 内存占用。
    

    ----- 内存使用率 -----
    redis实例的内存使用率超过可用最大内存,操作系统将开始进行内存与swap空间交换。

    避免内存交换发生的方法:
    
    ●针对缓存数据大小选择安装 Redis 实例
    ●尽可能的使用Hash数据结构存储
    ●设置key的过期时间
    

    ----- 内回收key -----
    保证合理分配redis有限的内存资源。
    当达到设置的最大阀值时,需选择一种key的回收策略,默认情况下回收策略是禁止删除。
    配置文件中修改 maxmemory-policy 属性值:

    vim /etc/redis/6379.conf
    
    598取消注释
    
    maxmemory-policy noenviction
    
    ●volatile-lru	使用LRU算法从已设置过期时间的数据集合中淘汰数据
    ●volatile-ttl	从已设置过期时间的数据集合中挑选即将过期的数据淘汰
    ●volatile-random	从已设置过期时间的数据集合中随机挑选数据淘汰
    ●allkeys-lru	使用LRU算法从所有数据集合中淘汰数据
    ●allkeys-random	从数据集合中任意选择数据淘汰
    ●noenviction	禁止淘汰数据
    
    展开全文
  • ROS专题----导航功能包navigation基础汇总

    万次阅读 多人点赞 2017-03-02 09:59:01
    资料来源ROS官网:...但是,想要在任意机器人上使用导航功能包集可能有点复杂。使用导航功能包集的先决条件是,机器人必须运行ROS,有一个tf变换树,使用正确的ROS Message types发布传感器数据。而且

    资料来源ROS官网:http://wiki.ros.org/cn/navigation

    nav_comic.png

    概述

    • 概念层面上讲,导航功能包集是相当简单的。 它从里程计和传感器数据流获取信息,并将速度命令发送给移动基站(比如你的机器人)。但是,想要在任意机器人上使用导航功能包集可能有点复杂。使用导航功能包集的先决条件是,机器人必须运行ROS,有一个tf变换树,使用正确的ROS Message types发布传感器数据。而且,我们需要在高层为一个具有一定形状和动力学特点的机器人配置导航功能包集。本手册指导配置一个典型的导航功能包集。

    硬件需求

    • 虽然导航功能包集被设计成尽可能的通用,在使用时仍然有三个主要的硬件限制:
    1. 它是为差分驱动的完全约束的轮式机器人设计的。它假设移动基站受到理想的运动命令的控制并可实现预期的结果,命令的格式为:x速度分量,y速度分量,角速度(theta)分量。
    2. 它需要在移动基站上安装一个平面二维激光。这个激光用于构建地图和定位。
    3. 导航功能包集是为正方形的机器人开发的,所以方形或圆形的机器人将是性能最好的。 它也可以工作在任意形状和大小的机器人上,但是较大的矩形机器人将很难通过狭窄的空间,例如门道。

    ----配置机器人的TF----

    Transform Configuration(变换配置)

    许多ROS功能包,都要求利用tf软件库,以机器人识别的变换树的形式进行发布。抽象层面上,变换树其实就是一种“偏移”,代表了不同坐标系之间的变换和旋转。更具体点来说,设想一个简单的机器人,只有一个基本的移动机体和挂在机体上方的扫描仪。基于此,我们定义了两个坐标系:一个对应于机体中心点的坐标系,一个对应于扫描仪中心的坐标系。分别取名为“base_link”和“baser_laser”。关于坐标系的命名习惯,参考REP 105.

    此时,可以假设,我们已经从传感器获取了一些数据,以一种代表了物体到扫描仪中心点的距离的形式给出。换句话说,我们已经有了一些“base_laser”坐标系的数据。现在,我们期望通过这些数据,来帮助机体避开物理世界的障碍物。成功的关键是,我们需要一种方式,把传感器扫描的数据,从“base_laser”坐标系转换到“base_link”坐标系中去。本质上,就是定义一种两个坐标系的“关系”。

    http://wiki.ros.org/navigation/Tutorials/RobotSetup/TF?action=AttachFile&do=get&target=simple_robot.png

    为了定义这种关系,假设我们知道,传感器是挂在机体中心的前方10cm,高度20cm处。这就等于给了我们一种转换的偏移关系。具体来说,就是,从传感器到机体的坐标转换关系应该为(x:0.1m,y:0.0m, z:0.2m),相反的转换即是(x:-0.1m,y:0.0m,z:0.2m)。

    我们可以选择去自己管理这种变换关系,意味着需要自己去保存,以及在需要的时候调用。但是,这种做法的缺陷即是随着坐标转换关系数量的增加,而愈加麻烦。幸运的是,我们也没有必要这么干。相反,我们利用tf定义了这么一种转换关系,那么就让它来帮我们管理这种转换关系吧。

    利用tf来管理这种关系,我们需要把他们添加到转换树(transform tree)中。一方面来说,转换树中的每一个节点都对应着一类坐标系,节点之间的连线即是两个坐标相互转换的一种表示,一种从当前节点到子节点的转换表示。Tf利用树结构的方式,保证了两个坐标系之间的只存在单一的转换,同时假设节点之间的连线指向是从parent到child。

    http://wiki.ros.org/navigation/Tutorials/RobotSetup/TF?action=AttachFile&do=get&target=tf_robot.png

    基于我们简单的例子,我们需要创建两个节点,一个“base_link”,一个是“base_laser”。为了定义两者的关系,首先,我们需要决定谁是parent,谁是child。时刻记得,由于tf假设所有的转换都是从parent到child的,因此谁是parent是有差别的。我们选择“base_link”坐标系作为parent,其他的传感器等,都是作为“器件”被添加进robot的,对于“base_link”和“base_laser”他们来说,是最适合的。这就意味着转换关系的表达式应该是(x:0.1m,y0.0m,z:0.2m)。关系的建立,在收到“base_laser”的数据到“base_link”的转换过程,就可以是简单的调用tf库即可完成。我们的机器人,就可以利用这些信息,在“base_link”坐标系中,就可以推理出传感器扫描出的数据,并可安全的规划路径和避障等工作。

    Writing Code(代码编写)

    希望上面的例子,一定程度上可以帮助大家理解tf。现在,我们可以建立通过代码来实现转换树。对于这个例子来说,前提是熟悉ROS,所以如果不熟悉,先预习下ROS Documentation吧。

    假定,我们以上层来描述“base_laser”坐标系的点,来转换到"base_link"坐标系。首先,我们需要创建节点,来发布转换关系到ROS系统中。下一步,我们必须创建一个节点,来监听需要转换的数据,同时获取并转换。在某个目录创建一个源码包,同时命名“robot_setup_tf”。添加依赖包roscpp,tf,geometry_msgs

    $ cd %TOP_DIR_YOUR_CATKIN_WS%/src
    $ catkin_create_pkg robot_setup_tf roscpp tf geometry_msgs

    至此,你必须运行上面的命令,当然你必须有必要的权限(例如,~/ros目录下,你可能在之前的文档中操作过这个目录)

    Alternative in fuerte, groovy and hydro: there is a standard robot_setup_tf_tutorial package in the navigation_tutorialsstack. You may want to install by following (%YOUR_ROS_DISTRO% can be { fuertegroovy } etc.):

    $ sudo apt-get install ros-%YOUR_ROS_DISTRO%-navigation-tutorials

    Broadcasting a Transform(广播变换)

    至此,我们已经创建了package。我们需要创建对于的节点,来实现广播任务base_laser->base_link。在robot_setup_tf包中,用你最喜欢的编辑器打开,然后将下面的代码粘贴到src/tf_broadcaster.cpp文件中去。

    切换行号显示
       1 #include <ros/ros.h>
       2 #include <tf/transform_broadcaster.h>
       3 
       4 int main(int argc, char** argv){
       5   ros::init(argc, argv, "robot_tf_publisher");
       6   ros::NodeHandle n;
       7 
       8   ros::Rate r(100);
       9 
      10   tf::TransformBroadcaster broadcaster;
      11 
      12   while(n.ok()){
      13     broadcaster.sendTransform(
      14       tf::StampedTransform(
      15         tf::Transform(tf::Quaternion(0, 0, 0, 1), tf::Vector3(0.1, 0.0, 0.2)),
      16         ros::Time::now(),"base_link", "base_laser"));
      17     r.sleep();
      18   }
      19 }
    

    现在,让我们来针对上面的代码,作更细节的解释。

    Error: No code_block found Tf功能包提供了一种实现tf::TransformBroadcaster ,使任务发布变换更容易。为了调用TransformBroadcaster, 我们需要包含 tf/transform_broadcaster.h 头文件.

    Error: No code_block found 我们创建一个TransformBroadcaster对象,之后我们可以利用他来发送变换关系,即base_link→ base_laser。

    Error: No code_block found 这部分是关键部分。通过TransformBroadcaster来发送转换关系,需要附带5个参数。第1个参数,我们传递了旋转变换,在两个坐标系的发送的任意旋转,都必须通过调用btQuaternion.现在情况下,我们不想旋转,所以我们在调用btQauternion的时候,将pitch,roll,yaw的参数都置0.第2个参数,btVector3,任何变换过程都需要调用它。无论怎样,我们确实需要做一个变换,所以我们调用了btVector3,相应的传感器的x方向距离机体基准偏移10cm,z方向20cm。第3个参数,我们需要给定转换关系携带一个时间戳,我们标记为ros::Time::now()。第4个参数,我们需要传递parent节点的名字。第5个参数,传递的是child节点的名字。

    Using a Transform(调用变换)

    上面的代码,我们创建了一个节点来发布转换关系,baser_laser->base_link。现在,我们需要利用转换关系,将从传感器获取的数据转换到机体对应的数据,即是“base_laser”->到“base_link”坐标系的转换。下面的代码,后边会紧根更详细的解析。在robot_setup_if功能包中,在src目录下创建tf_listener.cpp,并将下面的代码粘贴到里面:

    切换行号显示
       1 #include <ros/ros.h>
       2 #include <geometry_msgs/PointStamped.h>
       3 #include <tf/transform_listener.h>
       4 
       5 void transformPoint(const tf::TransformListener& listener){
       6   //we'll create a point in the base_laser frame that we'd like to transform to the base_link frame
       7   geometry_msgs::PointStamped laser_point;
       8   laser_point.header.frame_id = "base_laser";
       9 
      10   //we'll just use the most recent transform available for our simple example
      11   laser_point.header.stamp = ros::Time();
      12 
      13   //just an arbitrary point in space
      14   laser_point.point.x = 1.0;
      15   laser_point.point.y = 0.2;
      16   laser_point.point.z = 0.0;
      17 
      18   try{
      19     geometry_msgs::PointStamped base_point;
      20     listener.transformPoint("base_link", laser_point, base_point);
      21 
      22     ROS_INFO("base_laser: (%.2f, %.2f. %.2f) -----> base_link: (%.2f, %.2f, %.2f) at time %.2f",
      23         laser_point.point.x, laser_point.point.y, laser_point.point.z,
      24         base_point.point.x, base_point.point.y, base_point.point.z, base_point.header.stamp.toSec());
      25   }
      26   catch(tf::TransformException& ex){
      27     ROS_ERROR("Received an exception trying to transform a point from \"base_laser\" to \"base_link\": %s", ex.what());
      28   }
      29 }
      30 
      31 int main(int argc, char** argv){
      32   ros::init(argc, argv, "robot_tf_listener");
      33   ros::NodeHandle n;
      34 
      35   tf::TransformListener listener(ros::Duration(10));
      36 
      37   //we'll transform a point once every second
      38   ros::Timer timer = n.createTimer(ros::Duration(1.0), boost::bind(&transformPoint, boost::ref(listener)));
      39 
      40   ros::spin();
      41 
      42 }
    

    Ok... now we'll walk through the important bits step by step.

    好的,现在让我们将上面代码的重点步步分解:

    Error: No code_block found 这里,我们包含tf/transform_listener.h头文件,是为了后边创建tf::TransformListener。一个TransformListener目标会自动的订阅ROS系统中的变换消息主题,同时管理所有的该通道上的变换数据。

    Error: No code_block found创建一个函数,参数为TransformListener,作用为将“base_laser”坐标系的点,变换到“base_link”坐标系中。这个函数将会以ros::Timer定义的周期,作为一个回调函数周期调用。目前周期是1s。

    Error: No code_block found 此处,我们创建一个虚拟点,作为geometry_msgs::PointStamped。消息名字最后的“Stamped”的意义是,它包含了一个头部,允许我们去把时间戳和消息的frame_id相关关联起来。我们将会设置laser_point的时间戳为ros::time(),即是允许我们请求TransformListener取得最新的变换数据。对于header里的frame_id,我们设置为“base_laser”,因为我们是创建的是扫描仪坐标系里的虚拟点。最后,我们将会设置具体的虚拟点,比如x:1.0,y:0.2,z:0.0

    Error: No code_block found 至此,我们已经有了从“base_laser”到“base_link”变换的点数据。进一步,我们通过TransformListener对象,调用transformPoint(),填充三个参数来进行数据变换。第1个参数,代表我们想要变换的目标坐标系的名字。第2个参数填充需要变换的原始坐标系的点对象,第3个参数填充,目标坐标系的点对象。所以,在函数调用后,base_point里存储的信息就是变换后的点坐标。

    Error: No code_block found 如果某些其他的原因,变换不可得(可能是tf_broadcaster 挂了),调用transformPoint()时,TransformListener调用可能会返回异常。为了体现代码的优雅性,我们将会截获异常并把异常信息呈现给用户。

    Building the Code代码构建

    至此,根据我们写的例子,接下来我们需要构建编译。打开CMakeList.txt,在文件末尾添加下面的几行:

    add_executable(tf_broadcaster src/tf_broadcaster.cpp)
    add_executable(tf_listener src/tf_listener.cpp)
    target_link_libraries(tf_broadcaster ${catkin_LIBRARIES})
    target_link_libraries(tf_listener ${catkin_LIBRARIES})

    Next, make sure to save the file and build the package.

    $ cd %TOP_DIR_YOUR_CATKIN_WS%
    $ catkin_make

    Running the Code(代码运行)

    好的,万事俱备只欠东风!让我恩试着实际运行下吧。这部分,你需要开三个终端窗口

    第一个窗口,运行core

    roscore

    第二个,运行 tf_broadcaster

    rosrun robot_setup_tf tf_broadcaster

    好的。现在,我们会在第三个窗口运行tf_listener,将从传感器坐标系获取的虚拟点,变换到机体坐标系。

    rosrun robot_setup_tf tf_listener

    如果一切顺利,应该会看到类似的结果。每次打印间隔1s。

    [ INFO] 1248138528.200823000: base_laser: (1.00, 0.20. 0.00) -----> base_link: (1.10, 0.20, 0.20) at time 1248138528.19
    [ INFO] 1248138529.200820000: base_laser: (1.00, 0.20. 0.00) -----> base_link: (1.10, 0.20, 0.20) at time 1248138529.19
    [ INFO] 1248138530.200821000: base_laser: (1.00, 0.20. 0.00) -----> base_link: (1.10, 0.20, 0.20) at time 1248138530.19
    [ INFO] 1248138531.200835000: base_laser: (1.00, 0.20. 0.00) -----> base_link: (1.10, 0.20, 0.20) at time 1248138531.19
    [ INFO] 1248138532.200849000: base_laser: (1.00, 0.20. 0.00) -----> base_link: (1.10, 0.20, 0.20) at time 1248138532.19

    祝贺你,你已经成功的编写了一个针对平面传感器的坐标变换。下一步就是替换PointStamped,来使用真正的传感器进行操作。幸运的是,已经有相关的指导文档了here

    ----在机器人上配置并使用导航综合功能包----

    机器人配置

    attachment:overview_tf.png假定我们已经以特定方式配置机器人,导航功能包集将使其可以运动。上图概述了这种配置方式。白色的部分是必须且已实现的组件,灰色的部分是可选且已实现的组件,蓝色的部分是必须为每一个机器人平台创建的组件。以下章节将介绍使用导航功能包集的先决条件以及如何满足不同平台。

    ROS

    导航功能包集假定机器人使用ROS系统。请查阅ROS documentation以了解如何在你的机器人上安装ROS。

    TF变换配置(其他变换)

    导航功能包集需要机器人不断使用tf发布有关坐标系之间的关系的信息。详细的配置教程请查阅:Tf配置.

    传感器信息(sensor source)

    导航功能包集使用来自传感器的信息避开现实环境中的障碍物,它假定这些传感器在ROS上不断发布sensor_msgs/LaserScan消息或者sensor_msgs/PointCloud消息。有关在ROS上发布这些消息的教程,请查阅在ROS上发布传感器数据流。此外,一些已经有了ROS上的驱动的传感器亦满足这一教程。一下是部分ROS支持的传感器以及相关驱动链接:

    里程信息(odometry source)

    导航功能包集需要使用tfnav_msgs/Odometry消息发布的里程信息。这里有一篇发布里程信息的教程:在ROS上发布里程信息. 以下列出部分支持里程计的平台以及可用的驱动:

    基座控制器(base controller)

    导航功能包集假定它可以通过话题"cmd_vel"发布geometry_msgs/Twist类型的消息,这个消息基于机器人的基座坐标系,它传递的是运动命令。这意味着必须有一个节点订阅"cmd_vel"话题, 将该话题上的速度命令(vx, vy, vtheta转换为电机命令(cmd_vel.linear.x, cmd_vel.linear.y, cmd_vel.angular.z)发送给移动基座。以下列出部分支持基座控制器的平台以及可用的驱动:

    地图 (map_server)

    导航功能包集的配置并不需要有一张地图以供操作,但基于本教程的目的,我们假定你有一张地图。请查阅教程创建一张地图了解在你的系统环境下创建一张地图的细节。

    导航功能包集配置

    本节介绍如何配置导航功能包集。假设上述所有需要的环境都已满足。特别的,这意味着,机器人必须使用tf发布坐标帧,并从所有的传感器接收 sensor_msgs/LaserScan 或者 sensor_msgs/PointCloud 消息用于导航,同时需要使用 tf 和nav_msgs/Odometry 消息发布导航消息,消息会以命令的形式下发给机器人底座。如果所需要的配置你都没有,请参见机器人配置

    创建一个软件包

    首先,我们创建一个软件包,用来保存我们所有的配置文件和启动文件。这个软件包需要包含所有用于实现 机器人配置小节所述以来,就如其以依赖导航功能包集高级接口 move_base 软件包一样。因此, 为你的软件包选好位置,执行以下命令:

    catkin_create_pkg my_robot_name_2dnav move_base my_tf_configuration_dep my_odom_configuration_dep my_sensor_configuration_dep

    这个指令会创建一个包含运行导航功能包集所需依赖的软件包。

    创建机器人启动配置文件

    现在,我们有了一个存放所有配置文件和启动文件的工作空间,我们会创建一个roslaunch文件来启动所有的硬件以及发布机器人所需的tf。打开你喜欢的编辑器,粘贴一下内容到my_robot_configuration.launch。你可以自由的将 "my_robot" 改成你的机器人的名字。以后,我们会对launch文件做相似的更改,确保你阅读了本节其余内容。

    <launch>
      <node pkg="sensor_node_pkg" type="sensor_node_type" name="sensor_node_name" output="screen">
        <param name="sensor_param" value="param_value" />
      </node>
    
      <node pkg="odom_node_pkg" type="odom_node_type" name="odom_node" output="screen">
        <param name="odom_param" value="param_value" />
      </node>
    
      <node pkg="transform_configuration_pkg" type="transform_configuration_type" name="transform_configuration_name" output="screen">
        <param name="transform_configuration_param" value="param_value" />
      </node>
    </launch>

    好了,现在我们有了一个launch文件模板,但是,我们需要根据自己的机器人去完善它。以下章节,我们会逐步的改变它。

      <node pkg="sensor_node_pkg" type="sensor_node_type" name="sensor_node_name" output="screen">
        <param name="sensor_param" value="param_value" />
      </node>

    这里,我们会启动机器人运行导航功能包所需的所有传感器。用你的传感器对应的ROS驱动包替换"sensor_node_pkg",用你的传感器类型替换"sensor_node_type"(通常与节点名一致),用你的传感器节点名替换"sensor_node_name","sensor_param"包含所有必需的参数。注意,如果你有多个传感器,在这里一起启动它们。

      <node pkg="odom_node_pkg" type="odom_node_type" name="odom_node" output="screen">
        <param name="odom_param" value="param_value" />
      </node>

    这里,我们启动基座(底盘)的里程计。同样,替换相应的pkg, type, name,并根据实际指定相关参数。

      <node pkg="transform_configuration_pkg" type="transform_configuration_type" name="transform_configuration_name" output="screen">
        <param name="transform_configuration_param" value="param_value" />
      </node>

    这里,我们启动相应的tf变换。同样,替换相应的pkg, type, name,并根据实际指定相关参数。

    配置代价地图 (local_costmap) & (global_costmap)

    导航功能包集需要两个代价地图来保存世界中的障碍物信息。一张代价地图用于 规划,在整个环境中创建长期的规划,另一个用于局部规划与避障。有一些参数两个地图都需要,而有一些则各不相同。因此,对于代价地图,有三个配置项: common配置项, global配置项和local配置项。

    注意: 接下来的内容只是代价地图的基本配置项。想要查看完整的配置,参看costmap_2d文档.

    共同配置(local_costmap) & (global_costmap)

    导航功能包集使用代价地图存储障碍物信息。为了使这个过程更合理,我们需要指出要监听的传感器的话题,以更新数据。我们创建一个名为costmap_common_params.yaml的文件,内容如下:

    obstacle_range: 2.5
    raytrace_range: 3.0
    footprint: [[x0, y0], [x1, y1], ... [xn, yn]]
    #robot_radius: ir_of_robot
    inflation_radius: 0.55
    
    observation_sources: laser_scan_sensor point_cloud_sensor
    
    laser_scan_sensor: {sensor_frame: frame_name, data_type: LaserScan, topic: topic_name, marking: true, clearing: true}
    
    point_cloud_sensor: {sensor_frame: frame_name, data_type: PointCloud, topic: topic_name, marking: true, clearing: true}

    好,现在我们分解以上代码。

    obstacle_range: 2.5
    raytrace_range: 3.0

    这些参数设置放入代价地图的障碍信息的阈值。 “obstacle_range”参数决定了引入障碍物到代价地图的传感器读书的最大范围。 在这里,我们把它设定为2.5米,这意味着机器人只会更新以其底盘为中心半径2.5米内的障碍信息。 “raytrace_range”参数确定的空白区域内光线追踪的范围。 设置为3.0米意味着机器人将试图根据传感器读数清除其前面3.0米远的空间。

    footprint: [[x0, y0], [x1, y1], ... [xn, yn]]
    #robot_radius: ir_of_robot
    inflation_radius: 0.55

    这里我们设置机器人的footprint或机器人半径(如果是圆形的)。 指定的footprint时,机器人的中心被认为是在(0.0,0.0),顺时针和逆时针规范都支持。 我们还将设置代价地图膨胀半径。膨胀半径应该设置为障碍物产生代价的最大距离。 例如,膨胀半径设定在0.55米意味着机器人所有路径与障碍物保持0.55米或更的远离(具有同样的成本)。

    observation_sources: laser_scan_sensor point_cloud_sensor

    “observation_sources”参数定义了一系列传递空间信息给代价地图的传感器。每个传感器定义在下一行。

    laser_scan_sensor: {sensor_frame: frame_name, data_type: LaserScan, topic: topic_name, marking: true, clearing: true}

    这一行设置“observation_sources”中提到的传感器。这个例子定义了 laser_scan_sensor。 “frame_name”参数应设置为传感器坐标帧的名称,“data_type”参数应设置为LaserScan或PointCloud,这取决于主题使用的消息,“topic_name”应该设置为发布传感器数据的主题的名称。 “marking”和“clearing”参数确定传感器是否用于向代价地图添加障碍物信息,或从代价地图清除障碍信息,或两者都有。

    全局配置(global_costmap)

    下面我们将创建一个存储特定的全局代价地图配置选项的文件。新建一个文件:global_costmap_params.yaml并粘贴以下内容:

    global_costmap:
      global_frame: /map
      robot_base_frame: base_link
      update_frequency: 5.0
      static_map: true

    “global_frame”参数定义了代价地图运行所在的坐标帧。在这种情况下,我们会选择/map frame。 “robot_base_frame”参数定义了代价地图参考的的机器地毯的坐标帧。“update_frequency”参数决定了代价地图更新的频率。 “static_map”参数决定代价地图是否根据map_server提供的地图初始化。如果你不使用现有的地图,设为false。

    本地配置(local_costmap)

    下面我们将创建一个存储特定的本地代价地图配置选项的文件。新建一个文件:localal_costmap_params.yaml并粘贴以下内容:

    local_costmap:
      global_frame: odom
      robot_base_frame: base_link
      update_frequency: 5.0
      publish_frequency: 2.0
      static_map: false
      rolling_window: true
      width: 6.0
      height: 6.0
      resolution: 0.05

    “global_frame”,“robot_base_frame”,“update_frequency”,“static_map”参数与全局配置意义相同。“publish_frequency”参数决定了代价地图发布可视化信息的频率。将“rolling_window”参数设置为true,意味着随着机器人在限时世界里移动,代价地图会保持以机器人为中心。“width”、“height”,“resolution”参数分别设置代价地图的宽度(米、)高度(米)和分辨率(米/单元)。 注意,这里的分辨率和你的静态地图的分辨率可能不同,但我们通常把他们设成一样的。

    完整的配置选项

    这里是用于启动和运行的最简单的配置,更多的细节请参阅costmap_2d 文档.

    === Base Local Planner 配置=== Base_local_planner负责根据高层规划计算速度命令并发送给机器人基座。 我们需要根据我们的机器人规格配置一些选项使其正常启动与运行。新建一个名为base_local_planner_params.yaml的文件,内容如下:

    注意: 本节只涵盖TrajectoryPlanner的基本配置选项。 文档的全部选项,请参阅base_local_planner 文档.

    TrajectoryPlannerROS:
      max_vel_x: 0.45
      min_vel_x: 0.1
      max_vel_theta: 1.0
      min_in_place_vel_theta: 0.4
    
      acc_lim_theta: 3.2
      acc_lim_x: 2.5
      acc_lim_y: 2.5
    
      holonomic_robot: true

    上面的第一部分参数定义机器人的速度限制。 第二部分定义了机器人的加速度的限制。

    为导航功能包创建一个Launch启动文件

    现在我们已经有了所有的配置文件,我么需要在一个启动文件中一起启动他们,创建一个名为move_base.launch的文件,内容如下:

    <launch>
      <master auto="start"/>
    
      <!-- Run the map server -->
      <node name="map_server" pkg="map_server" type="map_server" args="$(find my_map_package)/my_map.pgm my_map_resolution"/>
    
      <!--- Run AMCL -->
      <include file="$(find amcl)/examples/amcl_omni.launch" />
    
      <node pkg="move_base" type="move_base" respawn="false" name="move_base" output="screen">
        <rosparam file="$(find my_robot_name_2dnav)/costmap_common_params.yaml" command="load" ns="global_costmap" />
        <rosparam file="$(find my_robot_name_2dnav)/costmap_common_params.yaml" command="load" ns="local_costmap" />
        <rosparam file="$(find my_robot_name_2dnav)/local_costmap_params.yaml" command="load" />
        <rosparam file="$(find my_robot_name_2dnav)/global_costmap_params.yaml" command="load" />
        <rosparam file="$(find my_robot_name_2dnav)/base_local_planner_params.yaml" command="load" />
      </node>
    </launch>

    唯一需要修改的地方是更改地图服务器使指向你的已有的地图,并且,如果你有一台差分驱动的机器人,将"amcl_omni.launch"改为"amcl_diff.launch"。对于如何创建一张地图,请查阅 创建一张地图.

    AMCL 配置(amcl)

    AMCL有许多配置选项将影响定位的性能。 有关AMCL的更多信息请参阅amcl文档.

    运行导航功能包集

    现在我们配置结束,我们可以运行导航功能包了。为此我们需要在机器人上启动两个终端人。 在一个终端上,我们将启动 my_robot_configuration.launch 文件,在另一个终端上我们将启动我们刚刚创建的move_base.launch

    终端1:

    roslaunch my_robot_configuration.launch

    终端2:

    roslaunch move_base.launch

    祝贺你,导航功能包集现在应该运行了。关于如何通过图形化界面给导航功能包集发送一个目标信息,请参阅rviz和导航教程。 如果你想使用代码给导航功能包集发送导航目标,请参阅发送简单导航目标教程。

    故障排除

    关于运行导航功能包集时遇到的常见问题,请参阅导航功能包集故障排除页面。

    #keywords 移动平台配置,机器人配置,设置机器人,getting started with mobile robot

    ----在ROS上发布传感器数据----

    在ROS上发布传感器数据流

    在ROS上正确地发布从传感器获取的数据对导航功能包集的安全运行很重要。 如果导航功能包集无法从机器人的传感器接收到任何信息,那么它就会盲目行事,最有可能的是发生碰撞。 有许多传感器可用于为导航功能包集提供信息:激光、摄像头、声纳、红外线、碰撞传感器等等。然而,目前导航功能包集只接受使用sensor_msgs/LaserScansensor_msgs/PointCloud消息类型发布的传感器数据。下面的教程将提供典型的设置和使用这两种类型的消息的例子。 相关: TF配置

    ROS消息头

    消息类型 sensor_msgs/LaserScan和 sensor_msgs/PointCloud跟其他的消息一样,包括tf帧和与时间相关的信息。为了标准化发送这些信息,消息类型Header被用于所有此类消息的一个字段。

    类型Header包括是哪个字段。字段seq对应一个标识符,随着消息被发布,它会自动增加。字段stamp存储与数据相关联的时间信息。以激光扫描为例,stamp可能对应每次扫描开始的时间。字段frame_id存储与数据相关联的tf帧信息。以激光扫描为例,它将是激光数据所在帧。

    #Standard metadata for higher-level flow data types
    #sequence ID: consecutively increasing ID 
    uint32 seq
    #Two-integer timestamp that is expressed as:
    # * stamp.secs: seconds (stamp_secs) since epoch
    # * stamp.nsecs: nanoseconds since stamp_secs
    # time-handling sugar is provided by the client library
    time stamp
    #Frame this data is associated with
    # 0: no frame
    # 1: global frame
    string frame_id

    在ROS上发布LaserScans

    LaserScan消息

    对于机器人的激光扫描仪,ROS提供了一个特殊的消息类型LaserScan来存储激光信息,它位于包sensor_msgsLaserScan消息方便代码来处理任何激光,只要从扫描仪获取的数据可以格式化为这种类型的消息。我们谈论如何生成和发布这些消息之前,让我们来看看消息本身的规范:

    #
    # Laser scans angles are measured counter clockwise, with 0 facing forward
    # (along the x-axis) of the device frame
    #
    
    Header header
    float32 angle_min        # start angle of the scan [rad]
    float32 angle_max        # end angle of the scan [rad]
    float32 angle_increment  # angular distance between measurements [rad]
    float32 time_increment   # time between measurements [seconds]
    float32 scan_time        # time between scans [seconds]
    float32 range_min        # minimum range value [m]
    float32 range_max        # maximum range value [m]
    float32[] ranges         # range data [m] (Note: values < range_min or > range_max should be discarded)
    float32[] intensities    # intensity data [device-specific units]

    正如所期望的,上面的名字/注释明确表述了消息里的各个字段。为了更具体的说明,我们来写一个简单的激光数据发布器来展示他们是如何工作的。

    编写代码发布一个LaserScan消息

    在ROS上发布一个LaserScan消息是相当简单的。我们先提供下面的示例代码,然后将代码分解逐行。

    切换行号显示
       1 #include <ros/ros.h>
       2 #include <sensor_msgs/LaserScan.h>
       3 
       4 int main(int argc, char** argv){
       5   ros::init(argc, argv, "laser_scan_publisher");
       6 
       7   ros::NodeHandle n;
       8   ros::Publisher scan_pub = n.advertise<sensor_msgs::LaserScan>("scan", 50);
       9 
      10   unsigned int num_readings = 100;
      11   double laser_frequency = 40;
      12   double ranges[num_readings];
      13   double intensities[num_readings];
      14 
      15   int count = 0;
      16   ros::Rate r(1.0);
      17   while(n.ok()){
      18     //generate some fake data for our laser scan
      19     for(unsigned int i = 0; i < num_readings; ++i){
      20       ranges[i] = count;
      21       intensities[i] = 100 + count;
      22     }
      23     ros::Time scan_time = ros::Time::now();
      24 
      25     //populate the LaserScan message
      26     sensor_msgs::LaserScan scan;
      27     scan.header.stamp = scan_time;
      28     scan.header.frame_id = "laser_frame";
      29     scan.angle_min = -1.57;
      30     scan.angle_max = 1.57;
      31     scan.angle_increment = 3.14 / num_readings;
      32     scan.time_increment = (1 / laser_frequency) / (num_readings);
      33     scan.range_min = 0.0;
      34     scan.range_max = 100.0;
      35 
      36     scan.ranges.resize(num_readings);
      37     scan.intensities.resize(num_readings);
      38     for(unsigned int i = 0; i < num_readings; ++i){
      39       scan.ranges[i] = ranges[i];
      40       scan.intensities[i] = intensities[i];
      41     }
      42 
      43     scan_pub.publish(scan);
      44     ++count;
      45     r.sleep();
      46   }
      47 }
    

    现在我们将上面的代码一步一步分解。

    切换行号显示
       2 #include <sensor_msgs/LaserScan.h>
       3 
    

    这里,我们include我们想要发布的sensor_msgs/LaserScan消息。

    切换行号显示
       8   ros::Publisher scan_pub = n.advertise<sensor_msgs::LaserScan>("scan", 50);
    

    段代码创建了一个ros::Publisher用于使用ROS发送 LaserScan 消息。

    切换行号显示
      10   unsigned int num_readings = 100;
      11   double laser_frequency = 40;
      12   double ranges[num_readings];
      13   double intensities[num_readings];
    

    里我们创建一组存储虚拟数据的变量,用来模拟激光扫描(其中填充所扫描到的障碍物信息)。实际的程序应从他们的激光驱动程序获取这些数据。

    切换行号显示
      18     //generate some fake data for our laser scan
      19     for(unsigned int i = 0; i < num_readings; ++i){
      20       ranges[i] = count;
      21       intensities[i] = 100 + count;
      22     }
      23     ros::Time scan_time = ros::Time::now();
    

    填充激光数据,填充值每秒加1.

    切换行号显示
      25     //populate the LaserScan message
      26     sensor_msgs::LaserScan scan;
      27     scan.header.stamp = scan_time;
      28     scan.header.frame_id = "laser_frame";
      29     scan.angle_min = -1.57;
      30     scan.angle_max = 1.57;
      31     scan.angle_increment = 3.14 / num_readings;
      32     scan.time_increment = (1 / laser_frequency) / (num_readings);
      33     scan.range_min = 0.0;
      34     scan.range_max = 100.0;
      35 
      36     scan.ranges.resize(num_readings);
      37     scan.intensities.resize(num_readings);
      38     for(unsigned int i = 0; i < num_readings; ++i){
      39       scan.ranges[i] = ranges[i];
      40       scan.intensities[i] = intensities[i];
      41     }
    

    创建一个 scan_msgs::LaserScan 消息,并使用我们预先生成的数据填充,准备发布它。

    切换行号显示
      43     scan_pub.publish(scan);
    

    在ROS上发布这个消息。

    在ROS上发布点云 PointClouds

    点云消息

    为了存储与分享世界中的一系列点, ROS 提供了 sensor_msgs/PointCloud 消息。正如前文所述,这个消息支持将三维空间中的点的数组以及任何保存在一个信道中的相关数据。 例如,一条带有"intensity"信道的 PointCloud 可以保持点云数据中每一个点的强度。接下来我们使用ROS发布一个 PointCloud 来探索这个过程。

    #This message holds a collection of 3d points, plus optional additional information about each point.
    #Each Point32 should be interpreted as a 3d point in the frame given in the header
    
    Header header
    geometry_msgs/Point32[] points  #Array of 3d points
    ChannelFloat32[] channels       #Each channel should have the same number of elements as points array, and the data in each channel should correspond 1:1 with each point

    编写代码发布 PointCloud 消息

    使用ROS发布 PointCloud 相当简单.接下来,我们给出一个完整的例子,并详细的讨论他的每一个细节.

    切换行号显示
       1 #include <ros/ros.h>
       2 #include <sensor_msgs/PointCloud.h>
       3 
       4 int main(int argc, char** argv){
       5   ros::init(argc, argv, "point_cloud_publisher");
       6 
       7   ros::NodeHandle n;
       8   ros::Publisher cloud_pub = n.advertise<sensor_msgs::PointCloud>("cloud", 50);
       9 
      10   unsigned int num_points = 100;
      11 
      12   int count = 0;
      13   ros::Rate r(1.0);
      14   while(n.ok()){
      15     sensor_msgs::PointCloud cloud;
      16     cloud.header.stamp = ros::Time::now();
      17     cloud.header.frame_id = "sensor_frame";
      18 
      19     cloud.points.resize(num_points);
      20 
      21     //we'll also add an intensity channel to the cloud
      22     cloud.channels.resize(1);
      23     cloud.channels[0].name = "intensities";
      24     cloud.channels[0].values.resize(num_points);
      25 
      26     //generate some fake data for our point cloud
      27     for(unsigned int i = 0; i < num_points; ++i){
      28       cloud.points[i].x = 1 + count;
      29       cloud.points[i].y = 2 + count;
      30       cloud.points[i].z = 3 + count;
      31       cloud.channels[0].values[i] = 100 + count;
      32     }
      33 
      34     cloud_pub.publish(cloud);
      35     ++count;
      36     r.sleep();
      37   }
      38 }
    

    下来,我们一句一句来看.

    切换行号显示
       2 #include <sensor_msgs/PointCloud.h>
       3 
    

    包含 sensor_msgs/PointCloud 消息头文件.

    切换行号显示
       8   ros::Publisher cloud_pub = n.advertise<sensor_msgs::PointCloud>("cloud", 50);
    

    创建我们用来发布Creat PointCloud 消息的 ros::Publisher .

    切换行号显示
      15     sensor_msgs::PointCloud cloud;
      16     cloud.header.stamp = ros::Time::now();
      17     cloud.header.frame_id = "sensor_frame";
    

    填充 PointCloud 消息的头:frame 和 timestamp.

    切换行号显示
      19     cloud.points.resize(num_points);
    

    设置点云的数量.

    切换行号显示
      21     //we'll also add an intensity channel to the cloud
      22     cloud.channels.resize(1);
      23     cloud.channels[0].name = "intensities";
      24     cloud.channels[0].values.resize(num_points);
    

    增加信道 "intensity" 并设置其大小,使与点云数量相匹配.

    切换行号显示
      26     //generate some fake data for our point cloud
      27     for(unsigned int i = 0; i < num_points; ++i){
      28       cloud.points[i].x = 1 + count;
      29       cloud.points[i].y = 2 + count;
      30       cloud.points[i].z = 3 + count;
      31       cloud.channels[0].values[i] = 100 + count;
      32     }
    

    使用虚拟数据填充 PointCloud 消息.同时,使用虚拟数据填充 intensity 信道.

    切换行号显示
      34     cloud_pub.publish(cloud);
    

    使用 ROS 发布 PointCloud 消息.

    ----附----

    概述

    导航堆栈在概念级别上是相当简单的。它接收来自里程计和传感器流的信息,并输出速度命令以发送到移动基站。但是,在任意机器人上使用导航堆栈有点复杂。作为导航堆栈使用的先决条件,机器人必须运行ROS,具有tf变换树,并使用正确的ROS消息类型发布传感器数据此外,导航堆栈需要被配置为机器人的形状和动力学在高水平执行。为了帮助这个过程,本手册旨在作为典型的导航堆栈设置和配置的指南。


    硬件要求

    虽然导航堆栈设计为尽可能通用,但有三个主要硬件要求限制其使用:

    1. 它仅适用于差速驱动和完整轮式机器人。它假定移动基站通过发送期望的速度命令来控制,以便以下列形式实现:x速度,y速度,θ速度。
    2. 它需要安装在移动基座上某处的平面激光器。该激光器用于地图构建和本地化。
    3. 导航堆栈是在方形机器人上开发的,因此其性能将是最接近正方形或圆形的机器人。它在任意形状和尺寸的机器人上工作,但是在狭窄空间(如门口)中的大型矩形机器人可能有困难。

    文档

    以下文档假定熟悉机器人操作系统。关于ROS的文档可以在这里找到:ROS文档

    报告错误

    例子

    使用导航堆栈的机器人列表

    教程

    1. 在模拟中导航

      这个pagge描述真棒模拟

    2. 用真正的机器人导航

      本页描述了真实机器人的导航

    3. 为TurtleBot设置导航堆栈

      提供了机器人导航配置的第一瞥,参考其他更全面的教程。

    4. 为TurtleBot设置导航堆栈

      提供了机器人导航配置的第一瞥,参考其他更全面的教程。

    5. 赫斯基移动基地演示

      使用基本move_base设置运行Husky ,没有映射或本地化。

    6. 探索周边地区并制作地图

      从机器人的视觉探索真实的环境,并保存地图。

    7. 使用已知地图导航

      在已知区域中使用先前保存的地图来淹没

    8. 赫斯基AMCL演示

      使用move_base设置运行Husky ,使用amcl进行本地化。

    9. 赫斯基Gmapping演示

      使用move_base设置运行Husky ,使用gmapping映射和本地化(SLAM)。

    10. 赫斯基边境勘探演示

      使用move_base设置运行Husky ,使用frontier_exploration进行勘探规划,gmapping用于映射和本地化(SLAM)。

    11. 探索周边地区并制作地图

      从机器人的视野探索环境,并保存地图。

    12. 使用已知地图导航

      在已知区域中使用先前保存的地图来淹没。

    13. SLAM地图大厦与TurtleBot

      如何使用gmapping生成地图

    14. 使用TurtleBot自动导航已知地图

      本教程介绍如何使用TurtleBot与以前已知的地图。

    15. SLAM地图大厦与TurtleBot

      如何使用gmapping生成地图

    16. 使用TurtleBot自动导航已知地图

      本教程介绍如何使用TurtleBot与以前已知的地图。

    17. 使用tf设置您的机器人

      本教程提供了设置您的机器人开始使用tf的指南。

    18. Evarobot的导航在凉亭的

      如何使用以前已知的地图导航在凉亭的evarobot。

    19. 基本导航调整指南

      本指南旨在给出一些关于如何调整机器人上ROS导航堆栈的标准建议本指南并不全面,但应该给出一些洞察过程。我也鼓励人们,确保他们已经阅读ROS导航教程之前,这篇文章,因为它提供了一个很好的概述设置导航堆栈在机器人wheras本指南只是提供建议的过程。

    20. 编写全局路径规划器作为ROS中的插件

      在本教程中,我将介绍在ROS中编写和使用全局路径规划器的步骤。首先要知道的是,为ROS添加一个新的全局路径规划器,新的路径规划器必须遵守nav_core包中定义nav_core :: BaseGlobalPlanner C ++接口一旦编写了全局路径规划器,它必须作为插件添加到ROS中,以便它可以被move_base包使用。在本教程中,我将提供从编写路径规划器类开始直到将其部署为插件的所有步骤。我将使用Turtlebot作为机器人的一个例子来部署新的路径规划器。有关如何将真实GA计划程序集成为ROS插件的教程,请参阅在ROS中添加遗传算法全局路径规划器作为插件

    21. 无题

      没有说明

    22. 无题

      没有说明

    23. 机器人上的导航堆栈的设置和配置

      本教程提供了如何获取在机器人上运行的导航堆栈的分步说明。涵盖的主题包括:使用tf发送转换,发布里程计信息,通过ROS从激光器发布传感器数据,以及基本导航堆栈配置。

    24. Gazebo'da Evarobot Navigasyonu

      ÇıkartılmışharitaüzerindenGazebo'da otonom Evarobot navigasyonu。

    25. Bilinen Bir Haritada Otonom Evarobot Navigasyonu

      Dahaöncedençıkartılmışharitada otonom robot navigasyonu。

    26. 在导航堆栈中使用rviz

      本教程提供了使用rviz与导航堆栈初始化本地化系统,发送目标到机器人,以及查看导航堆栈通过ROS发布的许多可视化的指南。

    27. 通过ROS发布Odometry信息

      本教程提供了一个为导航堆栈发布里程测量信息的示例。它包括通过ROS发布nav_msgs / Odometry消息,以及通过tf从“odom”坐标框架到“base_link”坐标框架的转换。

    28. 在ROS上发布传感器流

      本教程提供了通过ROS 发送两种类型的传感器流(sensor_msgs / LaserScan消息和sensor_msgs / PointCloud消息)的示例

    29. 将目标发送到导航堆栈

      导航堆栈用于将移动基座从一个位置驱动到另一个位置,同时安全地避开障碍物。通常,机器人被赋予使用诸如rviz的预先存在的工具结合地图移动到目标位置的任务。例如,为了告诉机器人去特定的办公室,用户可以在地图中点击办公室的位置,并且机器人将试图去那里。然而,能够发送机器人目标以使用代码移动到特定位置也是重要的,很像rviz在引擎盖下。例如,用于插入机器人的代码可以首先检测插座,然后告诉机器人驱动到距离墙壁一英尺的位置,然后尝试使用臂将插头插入插座。本教程的目标是提供一个从用户代码发送导航堆栈简单目标的示例。

    30. 安装

      安装和编译此软件包的说明

    31. Evarobot探索

      如何使用frontier_exploration自动生成SLAM映射与Evarobot

    32. 使用Evarobot自动导航已知地图

      如何自主导航Evarobot与已知地图。



    机器人使用ROS

    下面机器人的硬件平台使用,或者可以与ROS软件一起使用。为了你的机器人添加到该页面,您可以按照该指令

    门户

    内嵌图片:0x-Alpha-Tracked_288.jpg
    0X阿尔法NEX由机器人
    内嵌图片:0x-Delta-4WD_340.jpg
    0X三角洲NEX机器人
    210士丹利创新V3赛格威
    220士丹利创新V3赛格威
    内嵌图片:innok-heros-223-thumb.jpg
    223 Innok英雄
    内嵌图片:innok-heros-224-thumb.jpg
    224 Innok英雄
    420全士丹利创新V3赛格威
    440LE士丹利创新V3赛格威
    440SE士丹利创新V3赛格威
    内嵌图片:innok-heros-444-thumb.jpg
    444 Innok英雄
    ABB机器人(ROS-实业)
    擅长MobileRobots公司先锋系列(P3DX,P3AT,...)
    擅长MobileRobots公司先锋LX
    擅长MobileRobots公司的Seekur家庭(的Seekur,小的Seekur)
    淖艾尔帕兰
    快板手SIMLAB
    朋友
    内嵌图片:AR10.jpg
    AR10机器人手
    AscTec旋翼
    内嵌图片:x-terrabot.jpg
    阿西莫夫机器人的X Terrabot
    内嵌图片:aubo_robot/aubo_robotics.jpg
    AUBO机器人
    巴雷特手
    内嵌图片:BIG-i.jpg
    BIG-I
    BipedRobin
    内嵌图片:crazyflize20_small.png
    Bitcraze Crazyflie
    内嵌图片:bluerov-small.jpg
    蓝机器人BlueROV
    的ClearPath机器人灰熊
    的ClearPath机器人赫斯基
    的ClearPath机器人豺狼
    的ClearPath机器人翠鸟
    内嵌图片:Ridgeback+Icon+-+Small.png
    的ClearPath机器人背脊犬
    的ClearPath机器人疣猪
    内嵌图片:hamster_img.ong
    Cogniteam仓鼠
    司空见惯的机器人操作平台
    司空见惯的机器人移动器
    司空见惯的机器人SRA服务机器人手臂
    CoroWare Corobot
    Cyton伽马
    Dataspeed ADAS发展汽车
    Dataspeed移动基地
    电装VS060
    机器人捷豹博士
    Eddiebot
    易诺华机器人彩扩
    厄尔 - 脑
    尔勒脑2
    厄尔 - 直升机
    厄尔 - 直升机Ubuntu的核心特别版
    厄尔 - HexaCopter
    厄尔平面
    厄尔 - 罗孚
    厄尔 - 蜘蛛
    内嵌图片:evarobot50.png
    evarobot
    发那科机器人(ROS-实业)
    费斯托说教Robotino
    取机器人:取
    取机器人:货运
    弗劳恩霍夫IPA护理-O-BOT 3
    弗劳恩霍夫IPA护理-O-BOT 4
    吕(开放源码的类人型机器人)
    Gosti爵士
    内嵌图片:GoThere.png
    去那里!机器人
    内嵌图片:hansagv.png
    韩寒的机器人
    内嵌图片:icart_mini.png
    I-车迷你
    Ingeniarius ForteRC
    内嵌图片:innok-heros-thumb.jpg
    Innok英雄
    英特尔爱迪生
    iRobot公司的Roomba
    川田NEXTAGE /浩
    JACI版本
    版本图标
    Kobuki
    乐高NXT
    劣质煤
    内嵌图片:mecanumbot-small.jpg
    Mecanumbot
    梅林miabotPro
    内嵌图片:milvus_atr.png
    鸢属ATR机器人
    内嵌图片:milvus_mrp2.png
    鸢属机器人MRP2
    内嵌图片:milvus_robin.png
    鸢属机器人罗宾
    内嵌图片:motoman/robots_icon.jpg
    莫托曼,安川(ROS-实业)
    NAV2
    内嵌图片:navio2.png
    Navio2
    Neobotix MP-500
    Neobotix和珍贵-500
    Neobotix和珍贵-700
    开放式机组机器人
    奥托博克速度感应手
    PAL机器人PMB-2
    PAL机器人REEM-C
    PAL机器人蒂亚戈
    RazBot
    REEM
    Robonaut 2
    RoboSavvy自平衡平台
    内嵌图片:Armadillo.jpg
    RoboTiCan犰狳
    RoboTiCan科莫多
    RoboTiCan Lizi
    内嵌图片:robotis_manipulator_h.png
    ROBOTIS手-H
    内嵌图片:robotis_thormang3.png
    ROBOTIS Thormang3
    内嵌图片:robotnik_agvs_small.png
    Robotnik AGVS
    内嵌图片:CROM_WIKI_ROS.png
    Robotnik CROM
    内嵌图片:robotnik_guardian_small.png
    Robotnik GUARDIAN
    内嵌图片:robotnik_rb1_small.png
    Robotnik RB-1
    内嵌图片:RB-1_BASE_WIKI_ROS.png
    Robotnik RB-1 BASE
    内嵌图片:robotnik_rbcar_small.png
    工人RBCAR
    内嵌图片:robotnik_summit_xl_small.png
    Robotnik SUMMIT XL
    内嵌图片:robotnik_summit_x_small.png
    Robotnik SUMMIT-X
    内嵌图片:Roch.png
    罗奇
    ROS-工业
    内嵌图片:ros2bot.jpg
    Ros2Bot
    影手
    内嵌图片:tally.jpg
    Simbe机器人理货
    软银辣椒
    内嵌图片:Spark.png
    火花
    郁金香
    TurtleBot
    内嵌图片:universal_robot/robots_icon.jpg
    通用机器人(ROS-实业)
    而且不稳定
    WheeledRobin
    Willow Garage的PR2
    Xaxxon魔环总理
    内嵌图片:xbot.png
    Xbot


    src="http://player.vimeo.com/video/146183080" width="480" height="270" frameborder="0" allowfullscreen="" style="box-sizing: border-box;">

    还有的ROS.org博客了一系列 利用ROS机器人

    完整列表

    移动机械臂

    自定义移动机械臂

    移动机器人

    定制的移动机器人

    机械手

    自动驾驶汽车

    社交机器人

    人形

    无人机

    水下机器人

    UWVs

    其他


    ----End----

    展开全文
  • 单火线电原理

    万次阅读 2019-06-10 14:10:25
    但是在实际的安装出现了问题,老式的开关方便父母,但是装上之后,我们的智能模块的供电问题是一个大问题,查阅了很资料 一些朋友不理解什么是单火线,也很奇怪一根火线怎么取得电压和电流。下面简要介绍。   ...
  • node-red教程 7dashboard简介与输入仪表板控件的使用

    万次阅读 多人点赞 2018-06-21 10:33:48
    前端技术主要负责界面呈现,与用户交互等等,很炫酷的特效都是前端呈现的。Node-red技术有这样的“基因”,界面当然不会差劲。它只需要一个控件就可以实现一个页面。由于本书讲述的应用与页面关系不大,就不讲HTTP...
  • 相关问题已知intx=43,y=0;charch='A';则表达式(x>y&&ch下面程序的功能是 : 计算 1 ~10 之间的奇数之和与偶数之和,请填空。 #include main() { int a,b,c,i; a=c=0; for(i=0;i<=10;i+...
  • Android 8.0 功能和 API

    千次阅读 2017-09-25 12:50:10
    Android 8.0 为用户和开发者引入多种新功能。本文重点介绍面向开发者的新功能
  • 游戏中成功的定义非常 – 使玩家具有独特功能,每种功能都可用来满足一类基本的个性化需求,并有助于满足其他玩家的需求; 功能提供模式: 预先定义 – “角色分类”(游戏加入很特性使每种职业有各自的特色,并在...
  • 这种工具可用来存储和处理大型矩阵,比Python自身的嵌套列表(nested list structure)结构要高效的(该结构也可以用来表示矩阵(matrix))。据说NumPy将Python相当于变成一种免费的更强大的MATLAB系统。 ...
  • 如何写好一篇技术文档?

    万次阅读 多人点赞 2022-01-26 13:14:32
    如何写好一篇技术文档 周智 2022/1/20 参加工作时间久一点的工程师应该有这样一个体会:自己平时代码写得再再好,可一旦要用文档去描述或者表达某一个事情或者问题时,都感觉非常困难,无从下手,不知道自己该...
  • Java 11中的新功能和API详解系列1

    千次阅读 2018-09-27 17:33:08
    Java 11中的新功能和API详解系列1 2018.9.27 版权声明:本文为博主chszs的原创文章,未经博主允许不得转载。 JDK 11在语言语法方面有一个小改动,增加了相当数量的新API,以及运行单文件应用程序而无需使用编译器...
  • 摘要:逐次逼近寄存器(SAR)模数转换器(ADC)占据着大部分的中等至高分辨率ADC市场。SAR ADC的采样速率最高可达5Msps,分辨率为8位至18位。SAR架构允许高性能、低功耗ADC采用小尺寸封装,适合对尺寸要求严格的系统。...
  • 一篇文章讲清python开发必懂的8种数据结构

    千次阅读 多人点赞 2021-08-05 09:08:57
    数据结构决定了如何收集数据、我们可以实现的功能以及数据之间的关系。 数据结构几乎用于计算机科学和编程的所有领域,从操作系统到前端开发,再到机器学习。 数据结构有助于: 管理和利用大型数据集 从数据库中快速...
  • I级标准钢卷尺

    千次阅读 2020-12-31 10:25:52
    例:计量室检测钢卷尺、量油。也可直接用于大型设备安装及精密工程测量。 BZ标准钢卷尺该产品主要是用于对普通钢卷尺量值传递的工作标准器,也可以用于大尺寸、长距离精密测量之用。标准钢卷尺分为5m、10m、20m、...
  • 下表总结了 Bootstrap 网格系统如何跨个设备工作: 超小设备手机() 小型设备平板电脑(≥768px) 中型设备台式电脑(≥992px) 大型设备台式电脑(≥1200px) 网格行为 一直是水平的 以折叠开始,断点以上是...
  • 此时confidence字段就非常重要了,先看JavaCV源码中对confidence的解释,如下图红框所示,我的理解是:与lable值相关联的置信度,或者说这张脸是郭富城的可能性: 如果理解为可能性,那么问题来了,这是个double的...
  • 点击上方“3D视觉工坊”,选择“星标”干货第一时间送达作者丨ChaucerG来源丨集智书童交通标志检测对于无人驾驶系统来说是一项具有挑战性的任务,尤其是尺度目标检测和检测的实时性问题。在...
  • 然而,一旦客户端需要执行DNS lookup时,等待时间将会决于域名服务器的有效响应的速度。 虽然所有的ISP的DNS服务器都能缓存域名和IP地址映射表,但如果缓存的DNS记录过期了而需要更新,则可能需要通过遍历个...
  • ROS 学习篇(八)机器人的导航功能--navigation

    万次阅读 多人点赞 2018-11-04 21:32:04
    ROS 学习篇(八)机器人人的导航功能--navigation 1. 概述 ROS的二维导航功能包,简单来说,就是根据输入的里程计等传感器的信息流和机器人的全局位置,通过导航算法,计算得出安全可靠的机器人速度控制指令。...
  • 直播难:个人认为要想把直播从零开始做出来,绝对是牛逼中的牛逼,大牛中的大牛,因为直播中运用到的技术难点非常之,视频/音频处理,图形处理,视频/音频压缩,CDN分发,即时通讯等技术,每一个技术都够你学几年...
  • 逐次逼近ADC

    千次阅读 2016-06-07 16:53:00
    理解逐次逼近寄存器ADC:与其它类型ADC的架构对比 摘要:逐次逼近寄存器(SAR)模数转换器(ADC)占据着大部分的中等至高分辨率ADC市场。SAR ADC的采样速率最高可达5Msps,分辨率为8位至18位。SAR架构...
  • 工业级高精度电磁流量计解决方案

    万次阅读 2021-10-02 13:51:16
    系统性能决于模拟输入模块,该模块需要用到高阻抗、低噪声、高 CMRR输入放大器和低噪声、高分辨率Σ-∆ADC。后续发展趋势表明需要用到速度更快的ADC。 学习来源:ADI智库 链接:...
  • OLED 技术需要电流控制驱动方法 OLED 具有与标准发光二极管 (LED) 相当类似的电气特性,亮度均决于 LED 电流。若要开启和关闭 OLED 并控制 OLED 电流,需要使用薄膜晶体管 (TFT)的控制电路。 OLED为自发光材料,...
  • 智能智慧停车场管理系统解决方案

    万次阅读 多人点赞 2019-09-25 10:22:48
    智慧停车场系统采用纯车牌自动识别技术、视频停车诱导技术及移动互联网APP停车应用技术,同时支持多种缴费场景和支付方式的全新智能化停车场系统。 系统能准确识别进出车辆的车牌号码,并以车辆的车牌号码作为...
  • 常见的有环氧灌封胶有:阻燃、导热、低粘度、耐高温等。 优点:对硬质材料粘接力好,具有优秀的耐高温性能和电气绝缘能力,操作简单,固化前后都非常稳定,对多种金属底材和多孔底材都有优秀的附着力。 缺点...