C++面试常见题目

2023-05-16

C++面试常见题目

    • c++编译过程
    • 自动类型推导auto和decltype
    • 重载、重写(覆盖)和隐藏的区别
    • C++构造函数和析构函数能调用虚函数吗
    • volatile关键词
    • 运算符重载格式
    • noexecpt
    • 函数连续出现两个括号
    • inline和define的作用和区别
    • 关于静态函数只能调用静态变量
    • this指针的调用
    • 友元 friend
    • C++ 公有继承、保护继承和私有继承的对比
    • C++如何避免内存泄露
    • 二进制、八进制、十进制、十六进制
    • new和malloc的区别
    • 内存字节对齐
    • C++ operator 简单使用
    • 指针和引用的区别
    • C++拷贝构造函数、构造函数和析构函数
    • 说一下什么是虚函数表,并解释为什么父类的析构函数要设为虚函数
    • explicit 关键字与隐式类型转换
    • extern 关键字
    • C++中拷贝赋值函数的形参能否进行值传递?
    • 函数指针
    • 面向对象三大特性
    • 大小端序的定义和代码判断
    • 几种常用的类型转换
    • 野指针和悬空指针
    • \__attribute__
    • ++i 和 i++
    • 请你来说一下智能指针shared_ptr的实现
    • const 成员函数
    • 以下四行代码的区别是什么? const char * arr = "123"; char * brr = "123"; const char crr[] = "123"; char drr[] = "123"
    • 面向对象和面向过程的区别
    • 为什么要定义虚的析构函数
    • 虚函数表
    • C++设计模式
      • 单例模式
    • atomic
      • 工厂模式
      • 观察者模式
    • map与set的实现(红黑树)
    • STL之map与unordered_map(红黑树VS哈希表)
    • 守护进程
    • 进程与线程的概念
    • 并行和并发
    • 进程与线程有什么区别
    • 本机进程间通信
    • OSI参考模型
    • TCP/IP网络模型
    • TCP协议为什么是可靠传输协议
    • tcp 三次握手与四次挥手的过程
    • TCP服务器的大概工作过程
    • 大顶堆小顶堆应用场景
    • 大端序和小端序
    • select,poll和epoll的区别
    • 程序的局部性原理
    • 库函数与系统调用
    • GDB调试

c++编译过程

  1. 预处理
    预处理过程主要处理那些源代码文件以“#”开始的预编译指令。比如“#include”、“#define”和条件预编译指令,如“#if”、“#ifdef”等。预处理时,将所有的“#define”删除,展开所有的宏定义,并且替换掉“#include”。
  2. 编译
    编译过程就是把预处理完的文件进行一系列的词法分析、语法分析、语义分析以及优化后产生相应的汇编代码文件.
     编译技巧:编译的作用是对源程序进行词法检查、语法检查和中间代码生成。编译时对文件中的全部内容进行检查,如果有语法错误,编译结束后会显示出所有的编译出错信息,开发人员可以根据错误提示修改程序。对于新写的一个保护多个文件的工程,一开始采用源文件分别编译,这样容易发现每个源文件的自身错误,限定了错误的范围,如果一开始就采用全部编译,多个源文件可能会产生许多错误,无形中增加了开发难度。如果每个源文件都通过了编译,再将所有文件进行编译。对源文件分别编译对于调试,纠错是一种很好的方法。
  3. 汇编
    汇编实际上指把汇编语言代码翻译成目标机器指令的过程。只是根据汇编指令和机器指令的对照表一一翻译,有时候我们也将预编译、编译和汇编统称为编译。
  4. 将目标文件链接
    链接程序的主要工作就是将有关的目标文件彼此相连接,如源文件产生的目标文件和库文件等,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。根据指定的库函数的不同,链接处理可分为两种:
    (1)静态链接:在这种链接方式下,函数的代码将从其所在地静态链接库中被拷贝到最终的可执行程序中。这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。静态链接库实际上是一个目标文件的集合,其中的每个文件含有库中的一个或者一组相关函数的代码。
    (2)动态链接:此种方式下,函数的代码被放到称作是动态链接库或共享对象的某个目标文件中。链接程序此时所作的只是在最终的可执行程序中记录下共享对象的名字以及其它少量的登记信息。在此可执行文件被执行时,动态链接库的全部内容将被映射到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。

自动类型推导auto和decltype

decltype的作用是“查询表达式的类型”,
推导出表达式类型:

int i = 4;
decltype(i) a; //推导结果为int。a的类型为int。

与using/typedef合用,用于定义类型。

using size_t = decltype(sizeof(0));//sizeof(a)的返回值为size_t类型
using ptrdiff_t = decltype((int*)0 - (int*)0);
using nullptr_t = decltype(nullptr);
vector<int >vec;
typedef decltype(vec.begin()) vectype;
for (vectype i = vec.begin; i != vec.end(); i++)
{
//...
}

auto 的作用是自动推导类型.

重载、重写(覆盖)和隐藏的区别

重载:
是指同一可访问区内被声明的几个具有不同参数列(参数的类型,个数,顺序不同)的同名函数,根据参数列表确定调用哪个函数,重载不关心函数返回类型。
隐藏:
是指派生类的函数屏蔽了与其同名的基类函数,注意只要同名函数,不管参数列表是否相同,基类函数都会被隐藏。
重写(覆盖):
是指派生类中存在重新定义的函数。其函数名,参数列表,返回值类型,所有都必须同基类中被重写的函数一致。只有函数体不同(花括号内),派生类调用时会调用派生类的重写函数,不会调用被重写函数。重写的基类中被重写的函数必须有virtual修饰。

重载和重写的区别:
(1)范围区别:重写和被重写的函数在不同的类中,重载和被重载的函数在同一类中。
(2)参数区别:重写与被重写的函数参数列表一定相同,重载和被重载的函数参数列表一定不同。
(3)virtual的区别:重写的基类必须要有virtual修饰,重载函数和被重载函数可以被virtual修饰,也可以没有。
隐藏和重写,重载的区别:
(1)与重载范围不同:隐藏函数和被隐藏函数在不同类中。
(2)参数的区别:隐藏函数和被隐藏函数参数列表可以相同,也可以不同,但函数名一定同;当参数不同时,无论基类中的函数是否被virtual修饰,基类函数都是被隐藏,而不是被重写。

C++构造函数和析构函数能调用虚函数吗

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

原因分析:

(1)不要在构造函数中调用虚函数的原因:因为父类对象会在子类之前进行构造,此时子类部分的数据成员还未初始化, 因此调用子类的虚函数是不安全的,故而C++不会进行动态联编。
(2)不要在析构函数中调用虚函数的原因:析构函数是用来销毁一个对象的,在销毁一个对象时,先调用子类的析构函数,然后再调用基类的析构函数。所以在调用基类的析构函数时,派生类对象的数据成员已经“销毁”,这个时再调用子类的虚函数已经没有意义了。

volatile关键词

volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。声明时语法:int volatile vInt; 当要求使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。

其实不只是“内嵌汇编操纵栈”这种方式属于编译无法识别的变量改变,另外更多的可能是多线程并发访问共享变量时,一个线程改变了变量的值,怎样让改变后的值对其它线程 visible。一般说来,volatile用在如下的几个地方:

  1. 中断服务程序中修改的供其它程序检测的变量需要加volatile;
  2. 多任务环境下各任务间共享的标志应该加volatile;
  3. 存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;
    https://www.cnblogs.com/yc_sunniwell/archive/2010/07/14/1777432.html

运算符重载格式

Point operator+(const Point &);

noexecpt

该关键字告诉编译器,函数中不会发生异常,这有利于编译器对程序做更多的优化。
如果在运行时,noexecpt函数向外抛出了异常(如果函数内部捕捉了异常并完成处理,这种情况不算抛出异常),程序会直接终止,调用std::terminate()函数,该函数内部会调用std::abort()终止程序。
常用异常处理
C++中的异常处理是在运行时而不是编译时检测的。为了实现运行时检测,编译器创建额外的代码,然而这会妨碍程序优化。
在实践中,一般两种异常抛出方式是常用的:
一个操作或者函数可能会抛出一个异常;
一个操作或者函数不可能抛出任何异常。

后面这一种方式中在以往的C++版本中常用throw()表示,在C++ 11中已经被noexcept代替。

什么时候该使用noexcept?

使用noexcept表明函数或操作不会发生异常,会给编译器更大的优化空间。然而,并不是加上noexcept就能提高效率,步子迈大了也容易扯着蛋。
以下情形鼓励使用noexcept:
移动构造函数(move constructor)
移动分配函数(move assignment)
析构函数(destructor)。这里提一句,在新版本的编译器中,析构函数是默认加上关键字noexcept的。
叶子函数(Leaf Function)。叶子函数是指在函数内部不分配栈空间,也不调用其它函数,也不存储非易失性寄存器,也不处理异常。
最后强调一句,在不是以上情况或者没把握的情况下,不要轻易使用noexception。

函数连续出现两个括号

func()() 第一个组括号执行func 第二组括号执行func的返回值。可以被执行的返回值的类型有:函数指针,函数对象(functor) 等,此外lambda表达式后面出现括号表示直接调用该表达式。

inline和define的作用和区别

一、为什么要用inline
函数调用时都会产生一些额外的开销,主要是系统栈的保护、代码的传递、系统栈的恢复以及参数传递等。对于那些函数体很小、执行时间很短但又频繁使用的函数,定义为内联函数提高函数调用的效率。内联函数不是在调用时发生转移,而是在编译时将函数体嵌入到每个内联函数调用处。这样就省去了参数传递、系统栈的保护与恢复等的时间开销。
注意:
1.内联函数以目标代码的增加为代价来换取时间的节省。
2.内联函数在编译时被替换。
3.内联函数一般不能含有循环语句和switch语句。
4.内联函数的定义必须出现在第一次被调用之前。
5.对内联函数不能进行异常接口说明。

如果违背了上述注意点中的任意一项,编译程序就会无视关键字inline的存在,像处理一般函数一样处理,不生成扩展代码。

二、#define
宏代码片段 由预处理器处理,进行简单的文本替换,没有任何编译过程.
简单来说,define是纯文本的替换,替换完成后进入编译。

inline是先将内联函数编译完成生成了函数体,直接插入被调用的地方,减少了压栈,跳转和返回的操作。

关于静态函数只能调用静态变量

this指针的调用

友元 friend

友元提供了一种 普通函数或者类成员函数 访问另一个类中的私有或保护成员 的机制。也就是说有两种形式的友元:

(1)友元函数:普通函数对一个访问某个类中的私有或保护成员。

(2)友元类:类A中的成员函数访问类B中的私有或保护成员

优点:提高了程序的运行效率。

缺点:破坏了类的封装性和数据的透明性。

总结: - 能访问私有成员 - 破坏封装性 - 友元关系不可传递 - 友元关系的单向性 - 友元声明的形式及数量不受限制

C++ 公有继承、保护继承和私有继承的对比

1.使用public继承时,派生类内部可以访问基类中public和protected成员,但是类外只能通过派生类的对象访问基类的public成员。
(1)基类的public成员在派生类中依然是public的。
(2)基类中的protected成员在派生类中依然是protected的。
(3)基类中的private成员在派生类中不可访问。
2.使用protected继承时,派生类内部可以访问基类中public和protected成员,并且类外也不能通过派生类的对象访问基类的成员(可以在派生类中添加公有成员函数接口间接访问基类中的public和protected成员)。
(1)基类的public成员在派生类中变为protected成员。
(2)基类的protected成员在派生类中依然是protected成员。
(3)基类中的private成员在派生类中不可访问。
3.使用private继承时,派生类内部可以访问基类中public和protected成员,并且类外也不能通过派生类的对象访问基类的成员(可以在派生类中添加公有成员函数接口间接访问基类中的public和protected成员)。
(1)基类的public成员在派生类中变成private成员。
(2)基类的protected成员在派生类中变成private成员。
(3)基类的private成员在派生类中不可访问。

C++如何避免内存泄露

1、不要手动管理内存,可以尝试在适用的情况下使用智能指针。

2、使用string而不是char*。string类在内部处理所有内存管理,而且它速度快且优化得很好。

3、除非要用旧的lib接口,否则不要使用原始指针。

4、在C++中避免内存泄漏的最好方法是尽可能少地在程序级别上进行new和delete调用–最好是没有。任何需要动态内存的东西都应该隐藏在一个RAII对象中,当它超出范围时释放内存。RAII在构造函数中分配内存并在析构函数中释放内存,这样当变量离开当前范围时,内存就可以被释放。

(注:RAII 资源获取即初始化,也就是说在构造函数中申请分配资源,在析构函数中释放资源)
5、使用了内存分配的函数,要记得使用其相应的函数释放掉内存。可以始终在new和delete之间编写代码,通过new关键字分配内存,通过delete关键字取消分配内存。

6、培养良好的编码习惯,在涉及内存的程序段中,检测内存是否发生泄漏。

二进制、八进制、十进制、十六进制

二进制:(前缀:0b/0B)(后缀:b/B)
八进制:(前缀:0)(后缀:o/O)
十进制:(前缀:无,可加+/-)(后缀d/D)
十六进制:(前缀:0x/0X)(后缀:h/H)

new和malloc的区别

  1. 属性不同. new/delete是C++关键字,需要编译器支持。malloc/free是库函数,需要头文件支持c
  2. 一个是在自由存储区开辟内存,一个是在堆上
    堆和自由存储区的区别
  3. 内存分配失败时的返回值. new内存分配失败时,会抛出bac_alloc异常,它不会返回NULL;malloc分配内存失败时返回NULL。
  4. 是否需要指定内存大小. 使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地指出所需内存的大小
  5. new中分配内存对于变量和数组不同,malloc分配内存则相同
  6. 是否调用构造函数/析构函数. new会先调用operator_ new函数,申请足够的内存(通常底层使用malloc实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator_ delete函数释放内存(通常底层使用free实现)。malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作。
  7. 已分配内存的扩充. malloc/free可以通过realloc函数扩充,new/free无法直观地处理
  8. 能否相互调用. operator_new /operator _delete的实现可以基于malloc/free,而malloc的实现不可以去调用new。

内存字节对齐

内存对齐规则
对于结构的各个成员,第一个成员位于偏移为0的位置,以后的每个数据成员的偏移量必须是 min(#pragma pack()指定的数,这个数据成员的自身长度)的倍数
在所有的数据成员完成各自对齐之后,结构或联合体本身也要进行对齐,对齐将按照 #pragam pack指定的数值和结构或者联合体最大数据成员长度中比较小的那个,也就是 min(#pragram pack() , 长度最长的数据成员)

C++ operator 简单使用

1、operator作类的转换函数

2、operator在类中重载运算符
重载与类型转换虽然使用的是同一个关键字,但它们在形式上还是有很大的差别的:
类型转换:operator <类型>()
运算符重载:<类型> operator <运算符>(<参数表>)
超链接

指针和引用的区别

1.指针和引用的定义和性质区别:
(1)指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已。
(2)可以有const指针,但是没有const引用;
(3)指针可以有多级,但是引用只能是一级(int **p;合法 而 int &&a是不合法的)
(4)指针的值可以为空,但是引用的值不能为NULL,并且引用在定义的时候必须初始化;
(5)指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了。
(6)"sizeof引用"得到的是所指向的变量(对象)的大小,而"sizeof指针"得到的是指针本身的大小;
(7)指针和引用的自增(++)运算意义不一样;

2.指针和引用作为函数参数进行传递时的区别。
将指针作为参数进行传递时,事实上也是值传递,只不过传递的是地址。当把指针作为参数进行传递时,也是将实参的一个拷贝传递给形参,即上面程序main函数中的p何test函数中使用的p不是同一个变量,存储2个变量p的单元也不相同(只是2个p指向同一个存储单元),那么在test函数中对p进行修改,并不会影响到main函数中的p的值。

在讲引用作为函数参数进行传递时,实质上传递的是实参本身,即传递进来的不是实参的一个拷贝,因此对形参的修改其实是对实参的修改,所以在用引用进行参数传递时,不仅节约时间,而且可以节约空间。

需要对齐的原因
平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常
硬件原因:经过内存对齐之后,CPU的内存访问速度大大提升。访问未对齐的内存,处理器要访问两次(数据先读高位,再读低位),访问对齐的内存,处理器只要访问一次,为了提高处理器读取数据的效率,我们使用内存对齐

C++拷贝构造函数、构造函数和析构函数

篇幅太长,该链接写的很好了,面试前一定要看一眼
C++中构造函数或析构函数定义为private

说一下什么是虚函数表,并解释为什么父类的析构函数要设为虚函数

虚函数表是一种实现C++多态的动态绑定技术,每一个包含虚函数的类(或者派生类的基类或者派生类本身有虚函数)都有虚表.
虚表是一个指针数组,指针数组中的元素对应一个虚函数的函数指针,普通函数的调用不需要经过虚表. 对于派生类,虚表中元素的地址会继承父类的基表,当继承的父类的虚函数被重写时,才会将指向虚函数的地址覆盖. 对于有虚表的类的每个对象,都会有一个虚表指针,来指向虚表的地址.
在定义一个基类指针指向派生类的情况下,由于指针类型是基类,因此只能指向基类类型的部分.但是对于有虚函数的情况,因为有虚表的存在,派生类的虚表指针类型和基类的相同,因为对象是派生类,虚表指针的地址指向的是派生类的虚表,因此在调用父类的虚函数时,其实指向的是派生类重载后的函数.
可以看到,通过使用这些虚函数表,即使使用的是基类的指针来调用函数,也可以达到正确调用运行中实际对象的虚函数。
我们把经过虚表调用虚函数的过程称为动态绑定,其表现出来的现象称为运行时多态。动态绑定区别于传统的函数调用,传统的函数调用我们称之为静态绑定,即函数的调用在编译阶段就可以确定下来了。
那么为什么父类的析构函数要设成虚函数呢?
一般来说,如果一个类要被另外一个类继承,而且用其指针指向其子类对象时,如题目中的A* d = new B();(假定A是基类,B是从A继承而来的派生类),那么其(A类)析构函数必须是虚的,否则在delete d时,B类的析构函数将不会被调用,因而会产生内存泄漏和异常.
析构函数要设成虚函数的原因

explicit 关键字与隐式类型转换

C++ 隐式类类型转换

extern 关键字

1.当它与"C"一起连用时,如: extern “C” void fun(int a, int b);告诉编译器在编译fun这个函数名时按着C的规则去翻译相应的函数名而不是C++的,C++的规则在翻译这个函数名时会把fun这个名字变得面目全非,可能是fun@aBc_int_int#%$也可能是别的(不同编译器不同),因为C++支持函数的重载。
2. extern可置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量或函数时,在其它模块中寻找其定义。
如果想声明一个变量而非定义它,就在变量名称前加关键字extern,而且不要显示地初始化变量。任何包含了显示初始化的声明即成为定义。我们能给由extern关键字标记的变量赋一个初始值,但是这么做也就抵消了extern的作用。extern语句如果包含初始值就不再是声明,而变成定义了。在函数体内部,如果试图初始化一个由extern关键字标记的变量,将引发错误。变量能且只能被定义一次,但是可以被多次声明。声明和定义的区别看起来也许微不足道,但实际上却非常重要。如果要在多个文件中使用同一个变量,就必须将声明和定义分离。此时,变量的定义必须出现在且只能出现在一个文件中,而其它用到该变量的文件必须对其进行声明,却绝对不能重复定义。

C++中拷贝赋值函数的形参能否进行值传递?

这个描述看着靠谱点
拷贝构造函数(不能进行值传递)
拷贝赋值函数(能进行值传递)

函数指针

获取函数指针:
函数的地址就是函数名,要将函数作为参数进行传递,必须传递函数名。
声明函数指针:
声明指针时,必须指定指针指向的数据类型,同样,声明指向函数的指针时,必须指定指针指向的函数类型,这意味着声明应当指定函数的返回类型以及函数的参数列表。
程序示例

面向对象三大特性

通过类创建一个对象的过程叫实例化,实例化后使用对象可以调用类成员函数和成员变量,其中类成员函数称为行为,类成员变量称为属性。类和对象的关系:类是对象的抽象,对象是类的实例
封装
把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
public,private,protected
继承
基类(父类)——> 派生类(子类)
多态

大小端序的定义和代码判断

一个16进制的地址,存放在内存中从低地址开始存储,如16进制的地址为0x1234,对于地址而言,从右往左是从低到高
大端
若16进制的高地址存放在内存的低地址,则为大端字节序,34存储在高位,12存储在低位
小端
若16进制的低地址存放在内存的低地址,则为小端字节序,12存储在高位,34存储在高位

代码判断

可以通过联合体来判断,联合体是同一块内存被联合体中的所有成员公用,如果后续成员对内存重新赋值,会覆盖内存中原有数据

几种常用的类型转换

  1. static_cast
    用于各种隐式转换,比如非const转const,void*转指针等, static_cast能用于多态向上转化,如果向下转能成功但是不安全,结果未知;
  2. const_cast
    用于将const变量转为非const
  3. dynamic_cast
    用于动态类型转换。只能用于含有虚函数的类,用于类层次间的向上和向下转化。只能转指针或引用。向下转化时,如果是非法的对于指针返回NULL,对于引用抛异常。要深入了解内部转换的原理。
    向上转换:指的是子类向基类的转换
    向下转换:指的是基类向子类的转换
    它通过判断在执行到该语句的时候变量的运行时类型和要转换的类型是否相同来判断是否能够进行向下转换。
    尽量少使用转型操作,尤其是dynamic_cast,耗时较高,会导致性能的下降,尽量使用其他方法替代。
  4. reinterpret_cast
    几乎什么都可以转,比如将int转指针,可能会出问题,尽量少用;

野指针和悬空指针

野指针:访问一个已删除或访问受限的内存区域的指针,野指针不能判断是否为NULL来避免。指针没有初始化,释放后没有置空,越界。
悬空指针:一个指针的指向对象已被删除。

_attribute_

attribute__可以设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute)。__attribute__前后都有两个下划线,并且后面会紧跟一对原括弧,括弧里面是相应的__attribute__参数

__attribute__语法格式为:attribute ( ( attribute-list ) )

若函数被设定为constructor属性,则该函数会在main()函数执行之前被自动的执行。类似的,若函数被设定为destructor属性,则该函数会在main()函数执行之后或者exit()被调用后被自动的执行。

++i 和 i++

++i实现

int&  int::operator++()
{
*this +=1return *this}

i++

    A  operator ++(int)      //后++
    {
        A t=*this;          //先保存一份变量
        ++(*this);          //调用前++
        return t;
     }
}

这样一眼就看出差别来啦,i++有一个赋值操作,而且要调用前++,返回是++之前的值, 所以效率要低一点.

请你来说一下智能指针shared_ptr的实现

const 成员函数

const 成员函数可以使用类中的所有成员变量,但是不能修改它们的值,这种措施主要还是为了保护数据而设置的。
函数开头的 const 用来修饰函数的返回值,表示返回值是 const 类型,也就是不能被修改,例如const char * getname()。
函数头部的结尾加上 const 表示常成员函数,这种函数只能读取成员变量的值,而不能修改成员变量的值,例如char * getname() const。

有点奇怪
如果给以“指针传递”方式的函数返回值加const修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const修饰的同类型指针。
任何不会修改数据成员的函数都应该声明为const类型。如果在编写const成员函数时,不慎修改了数据成员,或者调用了其它非const成员函数,编译器将指出错误,这无疑会提高程序的健壮性。定义形式如下

 int GetCount(void) const;

const 放在函数头的后面(看着有点别扭).

在这里插入代码片

以下四行代码的区别是什么? const char * arr = “123”; char * brr = “123”; const char crr[] = “123”; char drr[] = “123”

const char * arr = “123”;
//字符串123保存在常量区,const本来是修饰arr指向的值不能通过arr去修改,但是字符串“123”在常量区,本来就不能改变,所以加不加const效果都一样

char * brr = “123”;
//字符串123保存在常量区,这个arr指针指向的是同一个位置,同样不能通过brr去修改"123"的值

const char crr[] = “123”;
//这里123本来是在栈上的,但是编译器可能会做某些优化,将其放到常量区

char drr[] = “123”;
//字符串123保存在栈区,可以通过drr去修改

面向对象和面向过程的区别

实例

为什么要定义虚的析构函数

原因是因为多态的存在。因为这样子类的析构函数才能被正确的调用到,否则会发生一些错误。

实例

虚函数表

虚函数表剖析

C++设计模式

单例模式

参考
单例 Singleton 是设计模式的一种,其特点是只提供唯一一个类的实例,具有全局变量的特点,在任何位置都可以通过接口获取到那个唯一实例
应用场景:
在一些软件系统中,有一些特殊的类,必须保证他在系统中只能有一个实例,才能保证其逻辑的正确性,以及良好的效率.例如:一个设备管理器管理多个设备驱动;数据池,用来缓存数据的数据结构,多处需要对数据池的数据进行操作。
懒汉模式实现:

  1. 最简单的形式
    static Singleton* get_instance(){
        if(m_instance_ptr==nullptr){
              m_instance_ptr = new Singleton;
        }
        return m_instance_ptr;
    }

问题:线程不安全(多线程不能保证只创建一个对象)

2.加锁

      std::lock_guard<std::mutex> lk(m_mutex);
      if(m_instance_ptr==nullptr){
            if(m_instance_ptr == nullptr){
              m_instance_ptr = new Singleton;
            }
        }
        return m_instance_ptr;

问题:性能低
3.双检查锁

        if(m_instance_ptr==nullptr){
            std::lock_guard<std::mutex> lk(m_mutex);
            if(m_instance_ptr == nullptr){
              m_instance_ptr = new Singleton;
            }
        }
        return m_instance_ptr;

问题:内存读写reorder不安全,正常new的过程一般是三步,分配内存,调用构造器进行初始化,返回指针
但是由于编译器的优化问题,可能会先分配内存,然后返回指针,最后调用构造器.这会在多线程的情况下导致一个线程还没有构造完成,另一个线程就判断指针不为空,然后就直接return了一个未完全初始化的指针,造成错误.可以在new的语句前加volatile.(考虑跨平台兼容问题可以使用atomic设置内存屏障)
例如:

        if(m_instance_ptr==nullptr){
            std::lock_guard<std::mutex> lk(m_mutex);
            if(m_instance_ptr == nullptr){
              m_instance_ptr = new Singleton;
            }
        }
        return m_instance_ptr;

Meyers’ Singleton
懒汉式单例(magic static )——局部静态变量

public:
    ~Singleton(){
        std::cout<<"destructor called!"<<std::endl;
    }
    Singleton(const Singleton&)=delete;
    Singleton& operator=(const Singleton&)=delete;
    static Singleton& get_instance(){
        static Singleton instance;
        return instance;
    }
private:
    Singleton(){
        std::cout<<"constructor called!"<<std::endl;
    }
};

饿汉模式实现
// 饿汉模式的关键:初始化即实例化

static Singelton *single;
Singelton *Singelton::single = new Singelton;
    
   Singelton *Singelton::GetSingelton(){
    // 不再需要进行实例化
    //if(single == nullptr){
    //    single = new Singelton;
    //}
    return single;
} 

全局只有一个实例:static 特性,同时禁止用户自己声明并定义实例(把构造函数设为 private)
线程安全
禁止赋值和拷贝
用户通过接口获取实例:使用 static 类成员函数

atomic

工厂模式

建立创建对象的工厂
术语“工厂”表示一个负责创建其他类型对象的类。通常情况下,作为一个工厂的类有一个对象以及与它关联的多个方法。客户端使用某些参数调用此方法之后,工厂会据此创建所需类型的对象,然后将它们返回给客户端。
工厂具有下列优点:松耦合,即对象的创建可以独立于类的实现;客户端无需了解创建对象的类,但是照样可以使用它来创建对象。它只需要知道需要传递的接口、方法和参数,就能够创建 所需类型的对象了。这简化了客户端的实现;可以轻松地在工厂中添加其他类来创建其他类型的对象,而这无需更改客户端代码。最简单的情况下,客户端只需要传递一个参数就可以了;工厂还可以重用现有对象。但是,如果客户端直接创建对象的化,总是创建一个新对象。
工厂模式分为简单工厂模式,工厂方法模式和抽象工厂模式,它们都属于设计模式中的创建型模式。其主要功能都是帮助我们把对象的实例化部分抽取了出来,目的是降低系统中代码耦合度,并且增强了系统的扩展性。

简单工厂模式
简单工厂模式最大的优点在于实现对象的创建和对象的使用分离,将对象的创建交给专门的工厂类负责,但是其最大的缺点在于工厂类不够灵活,增加新的具体产品需要修改工厂类的判断逻辑代码,而且产品较多时,工厂方法代码逻辑将会非常复杂。
工厂方法模式
此模式中,通过定义一个抽象的核心工厂类,并定义创建产品对象的接口,创建具体产品实例的工作延迟到其工厂子类去完成。这样做的好处是核心类只关注工厂类的接口定义,而具体的产品实例交给具体的工厂子类去创建。当系统需要新增一个产品是,无需修改现有系统代码,只需要添加一个具体产品类和其对应的工厂子类,使系统的扩展性变得很好,符合面向对象编程的开闭原则。
抽象工厂模式
在工厂方法模式中,一个具体的工厂负责生产一类具体的产品,即一对一的关系,但是,如果需要一个具体的工厂生产多种产品对象,那么就需要用到抽象工厂模式了。

观察者模式

观察者模式:定义了一种一对多的依赖关系,让多个观察者对象同时监听某一主题对象,在主题对象的状态发生变化时,会通知所有的观察者。Observer模式提供给关联对象一种同步通信的手段,使某个对象与依赖它的其他对象之间保持状态同步。
讲解

角色职责

Subject(被观察者)
被观察的对象。当需要被观察的状态发生变化时,需要通知队列中所有观察者对象。Subject需要维持(添加,删除,通知)一个观察者对象的队列列表。
ConcreteSubject
被观察者的具体实现。包含一些基本的属性状态及其他操作。
Observer(观察者)
接口或抽象类。当Subject的状态发生变化时,Observer对象将通过一个callback函数得到通知。
ConcreteObserver
观察者的具体实现。得到通知后将完成一些具体的业务逻辑处理。

适用场景

  • 侦听事件驱动程序设计中的外部事件
  • 侦听/监视某个对象的状态变化
  • 发布者/订阅者(publisher/subscriber)模型中,当一个外部事件(新的产品,消息的出现等等)被触发时,通知邮件列表中的订阅者。

具体实现
主要分为以下几个步骤:

  1. 首先定义通知者的行为(维护接收者的队列,包括增加、删除、通知操作)
  2. 实现通知者:要定义一个类型为接收者指针的队列,然后实现添加操作、删除操作和通知操作(通知操作要遍历整个接收者队列,每个都要调用接收函数发送消息)
  3. 定义接收者实现消息接收的接口。
  4. 定义接收者的类,类中主要实现每个接收者接收到消息的行为。
class ZhangSan:public ITeacherListenner
{
public:
    void onTecherComming(int value)
    {
        stopCopyWork(value);
    }
    
    void stopCopyWork(int value)
    {
        cout<<"zhangsan stopCopyWork + "<<value<<endl;
    }
};

class LiSi:public ITeacherListenner
{
public:
    void onTecherComming(int value)
    {
        stopPlayGame(value);
    }
    void stopPlayGame(int value)
    {
        cout<<"lisi stopPlayGame + "<<value<<endl;
    }
};

int main()
{
   cout << "Hello World1-------------"<<endl;
   MonitorNotifier monitor;
   ZhangSan zs;
   LiSi ls;
   monitor.registerListenner(&zs);
   monitor.registerListenner(&ls);
   monitor.setValue(1);
    
   cout << "Hello World2-------------"<<endl;
   monitor.removeListenner(&ls);
   monitor.setValue(2);
    
   return 0;
}

map与set的实现(红黑树)

map和set底层是使用红黑树来实现的.(。vector封装了数组,list封装了链表,map和set封装了二叉树).
set和map都是关联式容器,它的特点是增加或删除元素对迭代器的影响很小,除了操作的节点,其他节点都没有什么影响。
set也是用来存储同一数据类型的数据,在set中每个元素都是唯一的,而且能够根据元素的值自动进行排序。set中元素的值不能直接被改变。c++ STL中标准关联容器set,multiset,map,multimap 内部采用的就是一种非常高效的平衡二叉树:红黑树(RB树),其统计性能要好于一般的二叉树,所以被选择为了关联容器的内部结构。

  1. map和set的插入删除效率要高于其他序列容器
  2. 每次insert之后,以前的iterator不会失效
  3. 当数据元素增多时,set的插入和搜索速度变化为log2

为何map和set的插入删除效率要比其他序列容器高?

简单来说,因为对于关联容器而言,不需要做内存拷贝和内存移动。map和set容器内的所有元素都是以节点方式来存储的,其节点结构和链表类似,指向父节点和子节点。

因此,在插入的时候只需要稍作变换,把节点的指针指向新的节点就可以了。删除的时候类似,稍作变换后吧指向删除节点的节点的指针指向其他的节点就可以了。
这其中,只涉及到指针的转换,而没有涉及到内存的移动。

参考:c++中set与map用法

STL之map与unordered_map(红黑树VS哈希表)

相同:两者都是键-值对的集合,关联容器的一种。两者中的元素都是pair,同时拥有实值和键值。两者都不允许有两个相同的键值(实值可以相同)。两个的外部接口调用基本一致。

不同:内部实现机理不同,即map内部实现了一个红黑树;unordered_map内部实现了一个哈希表。(两者的比较成为红黑树与哈希表的比较)。由于内部实现机理不同(底层实现)造成以下不同。

  1. map的有序性:红黑树(非严格平衡二叉树),该结构具有自动排序的功能,因此map内部的所有元素都是有序的。
  2. unordered_map的无序性:哈希表不会根据key值大小进行排序,存储时是根据key的hash值判断元素是否相同,因此unordered_map内部元素是无序的。
  3. map的运行效率:红黑树可以在O(log n)时间内做查找,插入和删除,这里的n是树中元素的数目。
  4. unordered_map的运行效率:哈希表的查找的时间复杂度可达到O(1)
  5. unordered_map内存占用比map高。

红黑树:
红黑树是一种二叉搜索树(个人见解:红黑树的一系列操作比如节点着色、左旋、右旋、颜色调整以及迭代操作都是为了一个目的,那就是维护一个近似平衡化的二叉搜索树,来维持一个近似为logn的查找效率),有以下特点

  1. 每个结点不是红色就是黑色
  2. 根结点为黑色
  3. 每个叶结点(空结点)是黑色的
  4. 每个红色结点的两个子结点都是黑色的
  5. 从任一节点到其每个叶子结点的所有路径都包含相同数目的黑色节点。
    一棵内部有n个结点的红黑树的高度至多为2∗logn(性质4)。这保证了红黑树任意操作的复杂度都是O(logn)。
    基本操作:左旋,右旋,重新着色
    目的:红黑树在插入,删除过程中可能会破坏原本的平衡条件导致不满足红黑树的性质,这时候一般情况下要通过左旋、右旋和重新着色这个三个操作来使红黑树重新满足平衡化条件。

哈希表:
定义:散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
哈希函数
直接寻址法(直接将键值作为地址值)、 数字分析法(事先知道关键字的分布,来对数字进行操作)、 平方取中法(地址根据键值的平方中间的几个数来确定)、 折叠法(将键值高位数与低位数相加)、随机数法(利用随机函数来生成地址)、除留余数法(取摸留余数)。但是任何一种方法都有可能产生不同键值对应与同一个哈希值的结果,称为冲突。
解决碰撞(冲突):
1.开放定址法
一旦发生了冲突。就去找下一个空的散列地址:
Hi=(H(key) + di) MOD m,i=1,2,…,k(k<=m-1),其中H(key)为散列函数,m为散列表长,di为增量序列
(1)线性探测 di=1,2,3,…,m-1;
(2)二次探测 di=1^2 , -1 ^2 ,22,-22,⑶2,…,±(k)2,(k<=m/2);
(3)伪随机探测 di=伪随机数序列。
2.再散列法
Hi=RHi(key),i=1,2,…,k RHi均是不同的散列函数,即在同义词产生地址冲突时计算另一个散列函数地址,直到冲突不再发生,这种方法不易产生“聚集”,但增加了计算时间。
3. 链地址法
如果遇到冲突,会在原地址新建一个空间,然后以链表结点的形式插入到该空间。
4.公共区溢出
为所有冲突的关键字建立一个公共的溢出区来存放,进行查找时,先与基本表的相应位置进行对比,如果相等,查找成功,如果不等,到溢出表中进行顺序查找。
查找性能分析:
4. 散列函数是否均匀(对于同一组关键字,产生冲突的可能性是相同的,可以不考虑其对平均查找长度的影响)
5. 处理冲突的方法(有的处理冲突的方法(线性探测处理)会产生堆积(不如二次探测法好),影响性能,链地址法不会产生堆积问题,平均查找性能更佳)
6. 散列表的装填因子 :α= 填入表中的元素个数 / 散列表的长度(α越大,填入最后一个关键字产生冲突的可能性就越大,平均查找长度就越长)

守护进程

在linux系统中,我们会发现在系统启动的时候有很多的进程就已经开始跑了,也称为服务,这也是我们所说的守护进程。
守护进程是脱离于终端并且在后台运行的进程,脱离终端是为了避免在执行的过程中的信息在终端上显示,并且进程也不会被任何终端所产生的终端信息所打断。
守护进程一般的生命周期是系统启动到系统停止运行,也可以通过杀死进程的方式来结束进程的生命周期。

进程与线程的概念

进程是对运行时程序的封装,是系统进行资源调度和分配的的基本单位,实现了操作系统的并发;
进程的四要素:
(1)有一段程序供其执行(不一定是一个进程所专有的),就像一场戏必须有自己的剧本。
(2)有自己的专用系统堆栈空间(私有财产)
(3)有进程控制块(task_struct)(“有身份证,PID”)
(4)有独立的存储空间。
缺少第四条的称为线程,如果完全没有用户空间称为内核线程,共享用户空间的称为用户线程。

线程是进程的子任务,是CPU调度和分派的基本单位,用于保证程序的实时性,实现进程内部的并发;线程是操作系统可识别的最小执行和调度单位。每个线程都独自占用一个虚拟处理器:独自的寄存器组,指令计数器和处理器状态。每个线程完成不同的任务,但是共享同一地址空间(也就是同样的动态内存,映射文件,目标代码等等),打开的文件队列和其他内核资源。

并行和并发

并发
在同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果。
并行
在同一时刻,有多条指令在多个处理器上同时执行。

计算密集任务和IO密集任务
特点是要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。
虽然可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以,要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数。

IO密集型任务
涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。
对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。常见的大部分任务都是IO密集型任务,比如Web应用。

进程与线程有什么区别

1.一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程依赖于进程而存在。

2.进程在执行过程中拥有独立的内存单元,而多个线程共享进程的内存。(资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。)

3.进程是资源分配的最小单位,线程是CPU调度的最小单位;

4.系统开销: 由于在创建或撤消进程时,系统都要为之分配或回收资源,如内存空间、I/o设备等。因此,操作系统所付出的开销将显著地大于在创建或撤消线程时的开销。类似地,在进行进程切换时,涉及到整个当前进程CPU环境的保存以及新被调度运行的进程的CPU环境的设置。而线程切换只须保存和设置少量寄存器的内容,并不涉及存储器管理方面的操作。可见,进程切换的开销也远大于线程切换的开销。

5.通信:由于同一进程中的多个线程具有相同的地址空间,致使它们之间的同步和通信的实现,也变得比较容易。进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。在有的系统中,线程的切换、同步和通信都无须操作系统内核的干预

6.进程编程调试简单可靠性高,但是创建销毁开销大;线程正相反,开销小,切换速度快,但是编程调试相对复杂。

7.进程间不会相互影响 ;线程一个线程挂掉将导致整个进程挂掉

8.进程适应于多核、多机分布,适用于计算密集型任务;线程适用于多核,IO密集型

PCB与进程分配资源
fork,vfork和clone()的区别

本机进程间通信

  1. 信号
  2. 管道(有名管道,无名管道)
  3. system V IPC (消息队列、共享内存、无名信号量)
    4)域套接字(字节流域套接字、数据包域套接字)

OSI参考模型

应用层:要能够产生流量的应用程序才能属于应用层,本地打开一个计算器,这个就不算应用程序。要有通信才行。
表示层:我们通过微信视频聊天,视频传输前先要压缩,到对方那边再解压缩还原,这样传输带宽就小很多,这个压缩和解压缩就处于表示层

在通信主机上完成的功能:应用层,表示层,会话层,传输层
在网络设备上实现的功能:网络层,数据链路层,物理层
在这里插入图片描述https://blog.csdn.net/taotongning/article/details/81390979

TCP/IP网络模型

TCP协议为什么是可靠传输协议

通过三点来保证
第一:三次握手建立连接
第二:有应答机制,也就是将数据发送给对方后,对方必须应答是否发送成功
第三:使用"滑动窗口"机制,根据网络的好坏,控制发送的分组数据的大小

tcp 三次握手与四次挥手的过程

三次握手建立连接的目的

  1. 提高通信的可靠性
  2. 记录对方的ip和端口,正式通信时会自动使用记录的ip和端口
    详解 TCP 连接的“ 三次握手 ”与“ 四次挥手 ”

TCP服务器的大概工作过程

  1. 服务器会使用专门的文件描述符来监听客户的三次握手,然后建立连接
  2. 一旦连接建立成功,服务器会分配一个专门的通信文件描述符,用于实现与连接客户的通信
    通信流程

大顶堆小顶堆应用场景

大端序和小端序

1、大端模式:高字节保存在内存的低地址
2、小端模式:高字节保存在内存的高地址
(自大的人眼高手低)

select,poll和epoll的区别

阻塞,非阻塞,同步,异步
Unix五种IO模型
[1] blocking IO - 阻塞IO
[2] nonblocking IO - 非阻塞IO
[3] IO multiplexing - IO多路复用
[4] signal driven IO - 信号驱动IO
[5] asynchronous IO - 异步IO

其中前面4种IO都可以归类为synchronous IO - 同步IO,而select、poll、epoll本质上也都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的。
https://www.jianshu.com/p/397449cadc9a

程序的局部性原理

时间局部性
良好时间局部性的程序中,被引用过一次的内存位置很可能在不远的将来再被多次引用
空间局部性
良好空间局部性的程序中,一个内存位置被引用,程序很可能在不远的将来引用其附近的一个内存位置
从存储结构看如何利用程序的局部性

库函数与系统调用

概念
一、系统调用
系统调用,我们可以理解是操作系统为用户提供的一系列操作的接口(API),这些接口提供了对系统硬件设备功能的操作。这么说可能会比较抽象,举个例子,我们最熟悉的 hello world 程序会在屏幕上打印出信息。程序中调用了 printf() 函数,而库函数 printf 本质上是调用了系统调用 write() 函数,实现了终端信息的打印功能。
二、库函数
库函数可以理解为是对系统调用的一层封装。系统调用作为内核提供给用户程序的接口,它的执行效率是比较高效而精简的,但有时我们需要对获取的信息进行更复杂的处理,或更人性化的需要,我们把这些处理过程封装成一个函数再提供给程序员,更方便于程序猿编码。
库函数有可能包含有一个系统调用,有可能有好几个系统调用,当然也有可能没有系统调用,比如有些操作不需要涉及内核的功能。

如何正确理解库函数与系统调用的区别与联系
现有跨平台技术就是通过库函数调用实现的,不使用系统函数调用。
跨平台技术实现原理

GDB调试

常用命令
主要包括:
运行gdb调试:gdb (-q) 程序名
list 查看源代码
r 运行程序
bt/where查看堆栈信息
f N进入标号为N的栈 使用p命令查看这个栈的临时变量
设置断点:b 最常见的有两种:一是设置程序运行到源代码的某一行,二是设置程序运行到某个函数。
查看断点:info b
在调试程序时,最常用的gdb命令是:n、s、p

n即next,单步执行,执行下一步的意思,遇到函数会调用函数。
s即step,也是单步执行,但是会进入函数内部,然后结合n命令来调试函数。
p即print,打印变量,最常用的命令。p可以打印普通变量、std::string字符串、指针、数组等。

gdb查看线程信息:info thread,可以查看线程编号和正在执行的函数
进入某个线程:t N,N是线程编号,如1、2、3…
查看所有线程的栈信息:thread apply all bt

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++面试常见题目 的相关文章

  • Java Optional使用

    文章目录 Optional一 Optional 简介二 创建 Optional 实例2 1 empty 方法2 2 of 方法2 3 ofNullable 方法 三 Optional的使用3 1 访问 Optional 对象的值3 1 1
  • 正则表达式:基础详解以及在Java中的使用

    文章目录 一 正则表达式1 1 正则表达式中的特殊字符1 2 正则表达式所支持的合法字符1 3 方括号表达式1 4 边界匹配符1 5 三种模式的数量表示符 二 应用2 1 String 类2 2 Pattern 类和 Matcher 类 一
  • Python学习:关键字global和nonlocal的用法说明

    一 global global关键字用来在函数或其他局部作用域中使用全局变量 1 1 如果局部要对全局变量修改 xff0c 而不使用global关键字 count 61 0 def global test count 43 61 1 pri
  • Python:flask框架下前后端的数据交互

    文章目录 前提 一 前端发送数据 xff0c 后端接受数据1 1 路由传参数 数据 1 2 表单提交 二 后端发送数据 xff0c 前端接受数据 前提 后端 xff1a python 的 flask 框架 前端 xff1a html css
  • Python关于None的报错:'NoneType' object is not iterable和cannot unpack non-iterable NoneType object

    文章目录 一 TypeError 39 NoneType 39 object is not iterable xff08 类型错误 xff1a 39 NoneType 39 对象不是可迭代的 xff09 二 TypeError cannot
  • Git:合并分支----git merge命令应用的三种情景

    文章目录 一 git merge 命令应用的三种情景1 1 快进 无冲突 1 2 非 快进 xff0c 修改不同文件 无冲突 1 3 非 快进 xff0c 修改相同文件 有冲突 一 git merge 命令应用的三种情景 1 1 快进 无冲
  • Git:远程分支----git fetch命令的使用

    git fetch 命令的使用 从远程主机克隆 Git 的 clone 命令会为你自动将远程主机命名为 origin xff0c 拉取它的所有数据 xff0c 创建一个指向它的 master 分支的指针 xff0c 并且在本地将其命名为 o
  • Git:移除文件----git rm命令的使用

    文章目录 一 git rm 命令使用1 1 rm 命令1 2 git rm 命令1 3 git rm f 命令1 4 git rm cached 命令 一 git rm 命令使用 Git 本地数据管理 xff0c 大概可以分为三个区 xff
  • 【OpenMv小车】OpenMv追小球的小车之pid调用

    pid py gt gt https github com wagnerc4 flight controller blob master pid py openmv 官网 xff1a http book openmv cc project
  • 【深入理解C++】函数模板作为成员函数

    文章目录 1 普通类的成员函数模板2 类模板的成员函数模板 1 普通类的成员函数模板 不管是普通类还是类模板 xff0c 它们的成员函数都可以是函数模板 xff0c 称为成员函数模板 xff0c 但不可以是虚函数 xff0c 否则编译器报错
  • QGroundControl开发之使用自定义mavlink

    工具 对QGC进行二次开发时 xff0c 常常会遇到想使用自定义mavlink的情况 xff0c 但不像APM那样编译命令会根据xml文件自动生成mavlink协议 QGC似乎不能自动生成mavlink协议 xff08 之前试过似乎不能自动
  • 字符串连接 (c语言)

    题目描述 将给定的字符串连接起来 书中的算法描述如下 xff1a 图 xff1a 字符串连接算法 输入描述 三对字符串 xff0c 每对字符串占一行 xff0c 用空格隔开 每个字符串只包含数字和英文字母大小写且长度不超过100 输出描述
  • STM32—UART中断收发 Day4

    软件 xff1a STM32CubeMX xff0c MDK ARM 硬件 xff1a 蓝桥杯物联网Lora开发板 xff0c 板载芯片STM32L071 一 STM32CubeMX配置 1 先在连接 xff08 Connectivity
  • 虚拟机出现command XXX is available in /bin/ls问题

    问题 xff1a 使用本地的shell命令时候 The command could not be located because 39 usr bin bin 39 is not included in the PATH environme
  • 全志lichee的pack命令

    全志lichee目录打包命令流程 pack 将打包命令传进去build sh脚本里面 查看buildsh里面的脚本命令 其实里面的脚本还是较为简单地的 xff0c 仅仅是作为一个过渡 xff0c 然后就跑进去buildroot script
  • Linux_kernel驱动之GPIO子系统

    前言 xff1a gpio子系统的内容在drivers gpio文件夹下 xff0c 主要文件有 xff1a devres c xff1a devres c是针对gpio api增加的devres机制的支持gpiolib c xff1a g
  • 转载:全志问题解决方法

    版权声明 xff1a 本文为博主原创文章 xff0c 遵循 CC 4 0 BY SA 版权协议 xff0c 转载请附上原文出处链接和本声明 本文链接 xff1a https blog csdn net yanzheng1113 articl
  • 从零开始学ESP32:(二) 开启ESP32WIFI -STA和AP模式共存

    从零开始学ESP32 xff1a 个人笔记记录 xff1a 芯片型号 ESP32 网络环境支持 LWIP IDF PY SDK ESP IDF v4 3 芯片功能 xff1a 支持STA AP网络共存模式 xff1a 工程 xff1a es
  • 从零开始学ESP32:(四)ESP32/freeRTOS 实现一个内存池操作

    零开始学ESP32 xff1a 个人笔记记录 xff1a 芯片型号 ESP32 网络环境支持 LWIP IDF PY SDK ESP IDF v4 3 芯片功能 xff1a freeRTOS系统 声明 xff1a 当前内存池参考 Linux

随机推荐