C++经典知识点汇总 (持续更新中)

 

收集整理一些常见的知识点,供大家参考,如有不合理的地方,欢迎大家指正。

目录

收集整理一些常见的面试题,供大家参考,如有不合理的地方,欢迎大家指正。

1.const修饰函数体时,就想改变某个参数,怎么办?

2.什么是智能指针?

3.map的实现?

4.进程的地址空间

5.C++的特性

6.C++多态的原理

7.析构函数为啥加virtual关键字

8.静态库与动态库

9.list和vector的区别

10.构造函数重载问题

11.面向对象与面向过程

12.C++的指针和引用的区别?

13.C++的局部变量、静态局部变量、全局变量、静态全局变量,分别存储在什么地方?

14.C++程序的存储区域?

15.static关键字的作用?

16.C++有没有内存回收机制?

17.内存泄漏 栈溢出机制

18.堆和栈的区别,栈上面存储的是什么?

19.C++继承

20.static、const、volatile、typeof关键字的用途描述

21.接口和虚函数的区别?

22.什么时候用虚函数什么时候用纯虚函数?

23.一个指针指向一个对象,将指针指为空,则对象内存会释放吗?

24.指针和引用(怎么引用一个指针??)

25.const的作用和用法

26.static关键字的用法(为什么类的静态成员函数内部只能使用静态变量?)

27.数组指针和指针数组

28.多态的实现和虚函数表

29.构造函数及析构函数里能不能抛异常?

30.默认构造函数会不会执行默认初始化?

31.long a[4], sizeof(a)?

32.内存泄露的原因?

33.什么是野指针?

34.map set 区别

35.c++11特性知道哪些?

36.重载,覆盖和隐藏是什么意思?

37.C++STL库哪些用的比较熟练?vector怎么扩容的?

38.在多线程模式下shared_ptr会出现什么问题?

39.struct 字节对齐,计算大小。

40.C++程序运行步骤?

41.构造函数为什么不能是虚函数?

42.char *a="abc" 放在哪?

43.动态链接过程

44.C++ 类只在栈中 或堆中实例化

45.C++不能被重载的运算符有哪些?

46.C++支持多继承吗?讲一下虚继承?

47.构造函数、静态成员函数、内联函数和析构函数分别可以是虚函数吗?

48.虚函数表是什么?

49.c++重载函数为什么不能用返回值来区别呢?

50.代码从源文件到可执行文件的过程,是不是所有的语言都是一样的处理流程?

51.UTF-8中中文字符的大小,英文和数字的呢?

52.宏和inline区别?

53.对强制类型转换有什么了解?

54.shared_ptr ,循环引用问题

55.全局变量优缺点,以及如何克服?

56.几个关于指针的概念

57.成员函数和友元函数关系,友元函数能访问类的方法吗?

58.new/delete,malloc/free,delete/delete[]关系与区别

59.深浅拷贝


1.const修饰函数体时,就想改变某个参数,怎么办?

答:加上mutable修饰符的数据成员,对于任何情况下通过任何手段都可修改,自然此时的const成员函数是可以修改它的。

扩展1:const函数的几点规则:

1.const对象只能访问const成员函数,而非const对象可以访问任意的成员函数,包括const成员函数.

2. const对象的成员是不可修改的,然而const对象通过指针维护的对象却是可以修改的.

3. const成员函数不可以修改对象的数据,不管对象是否具有const性质.它在编译时,以是否修改成员数据为依据,进行检查.

4. 然而加上mutable修饰符的数据成员,对于任何情况下通过任何手段都可修改,自然此时的const成员函数是可以修改它的。

扩展2:普通const变量

1.const修饰的量不是常量,仅仅是个只读量。在编译的时候全部替换const变量被赋予的值(这点和C语言的宏相似),在运行的时候该const变量可通过内存进行修改。

2.const只在编译期间保证常量被使用时的不变性,无法保证运行期间的行为。程序员直接修改常量会得到一个编译错误,但是使用间接指针修改内存,只要符合语法则不会得到任何错误和警告。因为编译器无法得知你是有意还是无意的修改,但是既然定义成const,那么程序员就不应当修改它,不然直接使用变量定义好了。

2.什么是智能指针?

为什么需要智能指针?

因为每个人都会犯错,特别是大型项目中,很容易忘记对象的释放申请的内存。智能指针利用类的作用域性质,当超出类的作用域时,自动析构,在析构函数中释放申请的资源,从而达到降低内存泄露的风险。

答:智能指针实际上是一个栈对象,在栈对象生命期即将结束时,智能指针通过析构函数释放有它管理的堆内存。STL中的智能指针有:auto_ptr、shared_ptr、weak_ptr、unique_ptr。

auto_ptr:生命周期结束时,销毁指向的内存空间;不能指向堆数组,只能指向堆对象(变量);一片堆空间只属于一个智能指针对象;多个智能指针对象不能指向同一片堆空间。

shared_ptr:带有引用计数机制,支持多个指针对象指向同一片内存。

3.map的实现?

答:map是使用红黑树来实现的,具有自动排序的功能。(如果让你手写map,直接GG吧)

扩展1:unordered_map内部实现了一个哈希表(也叫散列表,通过把关键码值映射到Hash表中一个位置来访问记录,查找的时间复杂度可达到O(1),其在海量数据处理中有着广泛应用)其元素的排列顺序是无序的。

4.进程的地址空间

答:就是从进程的视角看到的地址空间,是进程运行时所用到的虚拟地址的集合。

 

从下至上分别是代码段,数据段,堆栈段。

代码段:存储程序文本的,所以有时候也叫做文本段,指令指针中的指令就是从这里取得。

数据段:

        1.初始化数据段通常称为数据段,包含已初始化的全局变量和静态变量,可以进一步划分为只读区域和读写区域。(C中的char=“hello world”的全局字符串,以及main(例如全局)之外的int debug=1这样的C语句,将被存储在初始的读写区域中。而像const char字符串=“hello world”这样的全局C语句常量字符串文字“hello world”被存储在初始化的只读区域中,并在初始化的读写区域中存储字符指针变量字符串)

2.未初始化数据段:也称bbs段,存放未初始化的全局变量和静态变量。

       3.堆堆是动态内存分配通常发生的部分,由程序员自己分配的。堆区域由所有共享库和进程中动态加载的模块共享。

堆栈段:存放自动变量,以及每次调用函数时保存的信息。每当调用一个函数时,返回到的地址和关于调用者环境的某些信息的地址,比如一些机器寄存器,就会被保存在栈中。然后,新调用的函数在栈上分配空间,用于自动和临时变量。

5.C++的特性

答:封装、继承与多态

封装:将数据和对数据的操作封装在一起——即类。类只对外界开放接口(即有权访问的函数接口),而将接口的实现细节和该类的一些属性(变量)隐藏起来,达到数据的抽象性(使具有相同行为的不同数据可以抽象为同一个类)、隐藏性和封装性。

继承:子类继承父类的相关属性(即变量)和方法(即函数)。

多态:多态是通过父类的指针,调用了一个在父类中是virtual类型的函数,实现动态绑定机制。若想使用父类的指针调用子类的函数,需要在父类中将其声明为虚函数(virtual),且必须与子类中重写的函数参数列表及返回值也相同。(父类对象调用父类的函数,子类对象调用子类的重写函数)(多态是在运行中展现出来的)

6.C++多态的原理

多态:在父类的函数前加上virtual关键字,在子类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数,父用父的函数,子用子的函数。

多态的好处:1.程序需要修改或增加功能时,只需改动或增加较少的代码。

 

7.析构函数为啥加virtual关键字

答:只有当一个类被用来作为父类的时候,才会将其析构函数写成虚函数。写成虚函数的目的是当父类指针指向子类对象时,删除时会触发动态绑定,先调用子类的析构函数,再调用父类的析构函数,从而防止了内存泄露。

8.静态库与动态库

库:库是写好的现有的,成熟的,可以复用的代码。本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。

静态库:在链接阶段,将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中。因此对应的链接方式称为静态链接,所使用到的库成为静态库。

动态库:动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例。

为什么要用动态库:因为动态库在内存里只需要有一份该共享库的实例,规避了空间浪费问题,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新。

9.list和vector的区别

list:list底层是由双向链表实现的,因此内存空间不是连续的。

list特点:List查询效率较低,时间复杂度为O(n);插入和删除效率较高,只需要在插入删除的地方更改指针的指向即可,不用移动数据。

vector:是一个封装了动态大小数组的顺序容器,能够存放任意类型的数据。

vector特点:便于随机访问,时间复杂度为O(1),但因为内存空间是连续的,所以在进入插入和删除操作时,会造成内存块的拷贝,时间复杂度为O(n)。此外,当数组内存空间不足,会采取扩容,通过重新申请一块更大的内存空间进行内存拷贝。

10.构造函数重载问题

答:构造函数是可以重载的,且构造函数重载需要注意其参数列表不能相同。

构造函数可以在直接在类中定义重载构造函数,也可以在类中声明重载构造函数,在类外定义。构造函数没有返回值,如果用户没有定义构造函数,系统会默认的生成一个缺省的构造函数,该函数体是空的,没有参数,也不进行初始化操作。

11.面向对象与面向过程

面向对象:将构成问题事务分解成各个对象,以描述某个事物在整个解决问题的步骤中的行为。

优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护 。
缺点:性能比面向过程低。

面向过程: 是一种 以过程为中心 的编程思想。

优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、 Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
缺点:没有面向对象易维护、易复用、易扩展。

举例:https://www.cnblogs.com/fengbohello/p/3269903.html

12.C++的指针和引用的区别?

答:

1.非空区别:指针可以为空,引用不可以为空,在创建时必须被初始化。

2.可修改区别:指针是可以被修改的,而引用不可以被修改,一旦初始化,则无法改变,但是引用指定的对象其内容可以改变。

3.合法性区别:因为指针可能为空,所以在使用指针时,要检测其合法性,而引用则不需要检查。

13.C++的局部变量、静态局部变量、全局变量、静态全局变量,分别存储在什么地方?

答:

局部变量:

静态局部变量:(未初始化:BSS段、已初始化:DATA段)

全局变量:(未初始化:BSS段、已初始化:DATA段)

静态全局变量:(未初始化:BSS段、已初始化:DATA段)

字符串常量:文字常量区(也在数据段中)

扩展:

数据段:分为只读数据段rodata,有初始化数据段data,无初始化数据段bss。

全局变量在整个工程文件内都有效;

静态全局变量只在定义它的文件内有效;

静态局部变量只在定义它的函数内有效,只是程序仅分配一次内存,函数返回后,该变量不会消失;

局部变量在定义它的函数内有效,但是函数返回后失效。
全局变量和静态变量如果没有手工初始化,则由编译器初始化为0。局部变量的值不可知。

14.C++程序的存储区域?

答:

栈区:局部变量(系统自动分配)

堆区:new,malloc(手动分配类似于链表

数据段

1.未初始化数据段data:全局变量和静态变量

2.已初始化数据段bss:全局变量和静态变量

3.文字常量区:常量字符串

代码段:程序二进制代码

15.static关键字的作用?

答:

静态局部变量:修饰函数体内部的变量,生存期长于该函数。

静态全局变量:定义在函数体外,用于修饰全局变量,表示该变量只在本文件中可见。

静态成员变量:修饰class的数据成员,其生存期大于class的对象。静态数据成员是每个class有一份(类变量)。

静态成员函数:修饰class的成员函数,静态成员函数不可访问非静态成员函数和非静态数据成员。

静态函数:与静态全局变量类似。被修饰的函数不能被其它文件所用。

16.C++有没有内存回收机制?

答:C++没有内存回收机制。

C++内存回收:可以利用析构函数,智能指针等来解决内存泄露。

17.内存泄漏 栈溢出机制

答:

内存泄露:是指程序在申请内存后,无法释放已申请的内存空间,占用有用内存。

内存溢出:是指程序在申请内存时,没有足够的内存空间供其使用。

内存越界:向系统申请一块内存后,使用时却超出申请范围。比如一些操作内存的函数:sprintf、strcpy等。

缓冲区溢出:程序为了临时存取数据的需要,一般会分配一些内存空间称为缓冲区。如果向缓冲区中写入缓冲区无法容纳的数据,就会造成缓冲区以外的存储单元被改写,称为缓冲区溢出。

栈溢出:是缓冲区溢出的一种,分为上溢出和下溢出。上溢出是指栈满而又向其增加新的数据,导致数据溢出;下溢出是指空栈而又进行删除操作等,导致空间溢出。(解决栈溢出:用栈把递归转换成非递归,增大堆栈大小值)

18.堆和栈的区别,栈上面存储的是什么?

栈区(stack):由系统自动分配释放,存放函数的参数值、局部变量值等。

向低地址扩展的数据结构,一块连续的内存区域,栈顶的地址和栈的容量是系统预先规定好的,栈大小是2M,如果申请空间超过栈的剩余空间,将提示overflow。

堆区(heap):一般由程序员分配释放并指明大小,若程序员不释放,程序结束时可能由OS回收。

向高地址扩展,不连续的内存区域,大小受限于计算机系统的虚拟内存。

栈上存储:局部变量,函数参数,函数返回地址等。

19.C++继承

继承的意义:继承是代码复用的重要手段,方便了创建和维护应用程序,子类可以获得父类的所有功能,并且可在子类中重写已有功能或添加新功能。

继承:子类继承父类,则拥有父类的所有属性和行为,子类也是一种特殊的父类,可作为父类对象使用,也可以添加新方法和属性。

20.static、const、volatile、typeof关键字的用途描述

static:静态函数、静态变量、静态类成员

const:const变量,const指针、const函数

volatile:多线程共享变量

typeof:获取类型值

21.接口和虚函数的区别?

虚函数:在父类中用virtual声明成员函数为虚函数,就可以在派生类中重新定义此函数,为它赋予新的功能,用来实现多态。

接口:C++ 接口是使用抽象类来实现的,如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类。纯虚函数是通过在声明中使用 "= 0" 来指定的。

22.什么时候用虚函数什么时候用纯虚函数?

答:纯虚函数是为了实现一个接口,规范继承这个类的程序员必须实现这个函数,当想使用接口的时候,可以用纯虚函数。虚函数是为了允许用基类的指针按需求来调用子类的这个函数。

23.一个指针指向一个对象,将指针指为空,则对象内存会释放吗?

答:不会,指针赋值 只是改指向另一个地址。原地址内容并不会受任何影响。 不会改变,也不会释放。

24.指针和引用(怎么引用一个指针??)

答:

指针是一变量,存有其它变量的地址。

引用变量是是某个已存在变量的另一个名字。

引用一个指针:int v = 1 ;  int* p = &v ; int *& rp = p;

25.const的作用和用法

1.const 修饰普通类型的变量:例:const int a = 7; a被定义为一个只读量,不能再被赋值。

2.const 修饰指针变量:若const修饰指针指向的内容,则内容为不可变量,若const修饰指针,则指针为不可变量。若const修饰指针和指针指向的内容,则指针和指针指向的内容都为不可变量。

3.const参数传递和函数返回值

3.1:const修饰传递,一般这种情况不需要const修饰,因为函数会自动产生临时变量复制实参数。

例:void fun(const int a)

3.2:当const参数为指针时,可以防止指针被意外篡改。

例:void fun(int  * const a)

3.3:自定义类型的参数传递,需要临时对象复制参数,对于临时对象的构造,需要调用构造函数,比较浪费时间,因此我们采取const外加引用传递的方法。

3.4: const 修饰内置函数的返回值,修饰语不修饰返回值作用一样。

3.5:const 修饰自定义类型的作为返回值,此时返回的值不能作为左值使用,既不能被赋值,也不能被修改。

3.6:const 修饰返回的指针或者引用,是否返回一个指向const的指针,取决于我们想让用户干什么。

4.const 修饰类成员函数:

const 修饰类成员函数,其目的是防止成员函数修改被调用对象的值,如果我们不想修改一个调用对象的值,所有的成员函数都应当声明为const成员函数。注意:const关键字不能与static关键字同时使用,因为static关键字修饰静态成员函数,静态成员函数不含有this指针,即不能实例化,const成员函数必须具体到某一实例。

26.static关键字的用法(为什么类的静态成员函数内部只能使用静态变量?)

答:

1.static修饰内置类型变量为静态变量:static修饰的内置类型变量分为静态全局变量和静态局部变量,静态变量内存分配在 .data段,生成的符号为local类型的符号,在链接阶段进行符号解析时不做处理。静态变量只初始化一次,未初始化的静态变量会默认初始化为0。静态全局变量只在本文件可见,外部文件无法访问。而静态局部变量只在定义的作用域内可见,但他们的生存周期都是整个程序运行时期。

2.static修饰函数:static修饰的函数为静态函数,静态函数主要是起到函数的隐藏作用,static修饰的函数只允许在当前文件中使用,在其他文件中无法找到该函数的地址.

3.static修饰类成员变量:static修饰的数据成员属于类的组成部分,static修饰的数据成员不在栈上分配内存而在.data段分配内存,static修饰的数据成员不能通过调用构造函数来进行初始化,因此static修饰的数据成员必须在类外进行初始化且只会初始化一次。

4.static修饰类成员函数:static修饰的成员方法为静态成员方法,静态成员方法可以在类内或类外定义,但必须在类内声明;static成员方法没有this指针,所以不能直接引用非static数据成员或调用类的非static成员方法,只能调用类的static成员数据和static成员方法;static成员不是任何对象的组成,不依赖对象的调用所以static成员方法不能被声明为const,因为const只限定该类的对象;static成员方法不能同时被声明为虚函数。

27.数组指针和指针数组

数组指针(行指针):定义 int (*p)[n]。

()优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。

指针数组:定义 int *p[n];
[]优先级高,先与p结合成为一个数组,再由int*说明这是一个整型指针数组,它有n个指针类型的数组元素。

28.多态的实现和虚函数表

多态的实现:使用虚函数实现多态,虚函数父类可以实现,子类需要重写,他们都由关键字virtual修饰。

多态的条件

1. 必须是继承关系。

2.基类中必须包含虚函数,并且派生类中一定要对基类中的虚函数进行重写。

3.通过基类对象的指针或者引用调用虚函数。

注意事项:重写虚函数时一定要保证函数的返回值,参数列表,函数名称完全一致

虚函数表:是一个类的虚函数的地址表,用于解决继承和覆盖的问题。

虚函数表特性

1.拥有虚函数的类才有虚函数表

2.虚函数表属于类,然后类的所有对象通过虚函数表指针共享类的虚函数表

3.虚函数表的作用:当使用父类指针来操作子类对象时,虚函数表就像一个地图一样,指明了实际所应该调用的函数

4.c++编译器保证虚函数表的指针存在于对象实例中最前面的位置(为了保证在多层继承或者多重继承的情况下获得函数表的性能),这意味着我们可以通过对象实例的地址得到虚函数表,然后就可以遍历其中的虚函数指针,并且调用响应的虚函数。

29.构造函数及析构函数里能不能抛异常?

构造函数:

1. 构造函数中抛出异常,会导致析构函数不能被调用,但对象本身已申请到的内存资源会被系统释放(已申请到资源的内部成员变量会被系统依次逆序调用其析构函数)。

2. 因为析构函数不能被调用,所以可能会造成内存泄露或系统资源未被释放。

3. 构造函数中可以抛出异常,但必须保证在构造函数抛出异常之前,把系统资源释放掉,防止内存泄露。(如何保证???使用auto_ptr???)

析构函数:

 1. 不要在析构函数中抛出异常!虽然C++并不禁止析构函数抛出异常,但这样会导致程序过早结束或出现不明确的行为。

2. 如果某个操作可能会抛出异常,class应提供一个普通函数(而非析构函数),来执行该操作。目的是给客户一个处理错误的机会。

 3. 如果析构函数中异常非抛不可,那就用try catch来将异常吞下,但这样方法并不好,我们提倡有错早些报出来。

总结:

1. 构造函数中尽量不要抛出异常(可以),能避免的就避免,如果必须,要考虑不要内存泄露!

2. 不要在析构函数中抛出异常!

30.默认构造函数会不会执行默认初始化?

答:

对于全局对象:对象中的内置类型(如int,double,char,bool等)和复合类型(数组,指针)将被初始化为0,具有类类型的成员通过运行各自的默认构造函数来进行初始化。

对于局部对象:若没有初始化,其值是未定义的,具有类类型的成员通过运行各自的默认构造函数来进行初始化。

补充:具有类类型的成员通过运行各自的默认构造函数来进行初始化,如类类型string,自动执行其构造函数,初始化为空。

31.long a[4], sizeof(a)?

答:自己算吧,红色箭头为32位与64位的区别,其余的一样。

32.内存泄露的原因?

答:

1. 在类的构造函数和析构函数中没有匹配的调用new和delete函数。

2. 没有正确地清除嵌套的对象指针(如将已申请内存的对象指针赋值给void*指针,然后删除void*指针并不能释放资源)。

3. 在释放对象数组时在delete中没有使用方括号。

4. 指向对象的指针数组不等同于对象数组。

对象数组是指:数组中存放的是对象,只需要delete []p,即可调用对象数组中的每个对象的析构函数释放空间。

指向对象的指针数组是指:数组中存放的是指向对象的指针,不仅要释放每个对象的空间,还要释放每个指针的空间,delete []p只是释放了每个指针,但是并没有释放对象的空间,正确的做法,是通过一个循环,将每个对象释放了,然后再把指针释放了。

5.缺少拷贝构造函数。

两次释放相同的内存是一种错误的做法,同时可能会造成堆的奔溃。

按值传递会调用(拷贝)构造函数,引用传递不会调用。

在C++中,如果没有定义拷贝构造函数,那么编译器就会调用默认的拷贝构造函数,会逐个成员拷贝的方式来复制数据成员,如果是以逐个成员拷贝的方式来复制指针被定义为将一个变量的地址赋给另一个变量。这种隐式的指针复制结果就是两个对象拥有指向同一个动态分配的内存空间的指针。当释放第一个对象的时候,它的析构函数就会释放与该对象有关的动态分配的内存空间。而释放第二个对象的时候,它的析构函数会释放相同的内存,这样是错误的。

所以,如果一个类里面有指针成员变量,要么必须显示的写拷贝构造函数和重载赋值运算符,要么禁用拷贝构造函数和重载赋值运算符

 C++中构造函数,拷贝构造函数和赋值函数的区别和实现参见:http://www.cnblogs.com/liushui-sky/p/7728902.html

6.缺少重载赋值运算符

这种问题跟上述问题类似,也是逐个成员拷贝的方式复制对象,如果这个类的大小是可变的,那么结果就是造成内存泄露。

7.没有将基类的析构函数定义为虚函数

当基类指针指向子类对象时,如果基类的析构函数不是virtual,那么子类的析构函数将不会被调用,子类的资源没有正确是释放,因此造成内存泄露。

33.什么是野指针?

答:指向被释放的或者访问受限内存的指针。

原因:

  1. 指针变量没有被初始化(如果值不定,可以初始化为NULL)
  2. 指针被free或者delete后,没有置为NULL, free和delete只是把指针所指向的内存给释放掉,并没有把指针本身干掉,此时指针指向的是“垃圾”内存。释放后的指针应该被置为NULL.
  3. 指针操作超越了变量的作用范围,比如返回指向栈内存的指针就是野指针。

34.map set 区别

特性:

set是一种关联式容器,其特性如下:

  1. set以RBTree作为底层容器
  2. 所得元素的只有key没有value,value就是key
  3. 不允许出现键值重复
  4. 所有的元素都会被自动排序
  5. 不能通过迭代器来改变set的值,因为set的值就是键

区别:

map和set一样是关联式容器,它们的底层容器都是红黑树,区别就在于map的值不作为键,键和值是分开的。它的特性如下:

  1. map以RBTree作为底层容器
  2. 所有元素都是键+值存在
  3. 不允许键重复
  4. 所有元素是通过键进行自动排序的
  5. map的键是不能修改的,但是其键对应的值是可以修改的。

35.c++11特性知道哪些?

auto关键字:编译器可以根据初始值自动推导出类型。但是不能用于函数传参以及数组类型的推导

nullptr关键字:nullptr是一种特殊类型的字面值,它可以被转换成任意其它的指针类型;而NULL一
               般被宏定义为0,在遇到重载时可能会出现问题。

智能指针:C++11新增了std::shared_ptr、std::weak_ptr等类型的智能指针,用于解决内存管理的问题。

初始化列表:使用初始化列表来对类进行初始化。

右值引用:基于右值引用可以实现移动语义和完美转发,消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率

atomic原子操作用于多线程资源互斥操作。
新增STL容器array以及tuple。

36.重载,覆盖和隐藏是什么意思?

重载:

(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。

覆盖:

覆盖是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;

(4)基类函数必须有virtual 关键字。

隐藏:

“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual
关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual
关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。

(需要注意的是这一部分与覆盖最大的区别是函数有没有关键字virtual,有virtual的是覆盖,没有virtual的是隐藏)

(个人感觉隐藏的主要作用在于子类对象会优先调用子类函数,其次找不着子类函数时,才会去调用父类函数)

37.C++STL库哪些用的比较熟练?vector怎么扩容的?

答:stack 栈,queue 队列,priority_queue 优先级队列,map 容器,vector 动态数组。

 

38.在多线程模式下shared_ptr会出现什么问题?

答:

1.在多线程的情况下,要保证引用计数的加加操作是原子性的,就需要加锁,否则当引用计数为0,资源已释放,但是却还有一个对象指向该资源,也就是出现野指针问题,产生不安全的线程。

2.shared_ptr会产生循环引用问题。

39.struct 字节对齐,计算大小。

答:

1.去除static变量,因为static变量是存在数据段中的,在编译的时候已经分配好内存空间,所以对结构体的总内存大小不做任何贡献。

2.取结构体中最大的数据类型、最大的对齐模数、指定对齐模数(#pragma pack (2))三者中的最小值u作为单元格长度,将数据依次放入单元格中。

3.放入规则:

对于内置数据类型(char,int,long,double),其起始位置是数据自身大小的整数倍,所占空间为其大小;

对于数组成员,则将其拆分后按内置数据类型依次放入;

对于指针,也按内置数据类型方式放入,大小一般为4字节;

对于子联合体和子结构体,其放入位置为其内部最大对齐模数的整数倍,所占空间为其自身大小。

扩展:

联合体的大小:联合体大小要是所有成员变量类型大小的整数倍,同时要能容纳最大的成员变量。

结构体的大小:递归本回答方案。

40.C++程序运行步骤?

:程序代码是自然语言,需要翻译为可执行的机器语言。整个翻译过程可以分成两大步。

1)编译 :把文本形式的源代码翻译成机器语言,并形成目标文件。

2)链接 :把目标文件,操作系统的启动代码和库文件组织起来形成可执行程序。

编译三部曲

1.编译预处理:预处理又称为预编译,是做些代码文本替换工作。编译器执行预处理指令(以#开头,例如#include),这个过程会得到不包含#指令的.i文件。这个过程会拷贝#include 包含的文件代码,进行#define 宏定义的替换 , 处理条件编译指令 (#ifndef #ifdef #endif)等。

得到.i文件。

2.编译优化

通过预编译输出的.i文件中,只有常量:数字、字符串、变量的定义,以及c语言的关键字:main、if、else、for、while等。这阶段要做的工作主要是,通过语法分析和词法分析,确定所有指令是否符合规则,之后翻译成汇编代码。

将.i文件转化位.s文件。

3.汇编:汇编过程就是把汇编语言翻译成目标机器指令的过程,生成目标文件(.obj .o等)。目标文件中存放的也就是与源程序等效的目标的机器语言代码。

目标文件由段组成,通常至少有两个段:

代码段:包换主要程序的指令。该段是可读和可执行的,一般不可写

数据段:存放程序用到的全局变量或静态数据。可读、可写、可执行。

将.s文件转化成.o文件。

链接

由汇编程序生成的目标文件并不能立即就执行,还要通过链接过程。

原因:

1).某个源文件调用了另一个源文件中的函数或常量

2).在程序中调用了某个库文件中的函数

链接程序的主要工作就是将有关的目标文件连接起来。

这个过程将.o文件转化成可执行的文件。

总结:预编译处理(.c) -> 编译、优化程序(.s)->汇编程序(.obj、.o、.a、.ko) -> 链接程序(.exe、.elf、.axf等)

 

41.构造函数为什么不能是虚函数?

答:C++中如果创建一个对象,则需要知道对象的准确类型,因此构造函数不能为虚函数。虚函数采用一种虚调用的方法,虚调用是一种可以在只有部分信息的情况下工作的机制。

42.char *a="abc" 放在哪?

答:指针a存放在栈上,字符串常量“abc”存放在rodata数据段。

43.动态链接过程

答:

  1. 启动动态链接器本身
  2. 装载所有需要的共享对象
  3. 重定位和初始化

启动动态连接器本身

动态链接器本身也是一个共享对象。对于普通共享对象文件来说,它的重定位工作由动态链接器来完成;它也可以依赖于其他共享对象,其中的被依赖的共享对象由动态链接器负责链接和装载。可是对于动态链接器本身来说, 不可以依赖于其他任何共享对象;其次是动态链接器本身所需要的全局和静态变量的重定位工作由它本身完成。

动态链接器必须在启动时有一段非常精巧的代码可以完成这项艰巨的工作而同时又不能用到全局和静态变量。这种具有一定限制条件的启动代码往往被称为自举。

动态链接器入口地址即是自举代码的入口,当操作系统将进程控制权交给动态链接器时,动态链接器的自举代码即开始执行。

装载所有需要的共享对象

完成基本自举以后,动态链接器将可执行文件和链接器本身的符号表都合并到一个符号表当中,我们可以称它为全局符号表。

链接器从可执行文件中的.dynamic 段中获取所依赖的共享对象,并将这些共享对象的名字放入到一个装载集合中。当一个新的共享对象被装载进来的时候,它的符号表会被合并到全局符号表中,所以当所有的共享对象都被装载进来的时候,全局符号表里面将包含进程中所有的动态链接所需要的符号。

当多个共享对象中,存在同名的函数或变量时,一个共享对象里面的全局符号被另一个共享对象的同名全局符号覆盖的现象又被称为共享对象全局符号介入(Global Symbol Interpose)。

重定位和初始化

当上面的步骤完成之后,链接器开始重新遍历可执行文件和每个共享对象的重定位表,将它们的GOT/PLT中的每个需要重定位的位置进行修正。因为此时动态链接器已经拥有了进程的全局符号表,所以这个修正过程也显得比较容易。

重定位完成之后,如果某个共享对象有“.init”段,那么动态链接器会执行“.init”段中的代码,用以实现共享对象特有的初始化过程。

当完成了重定位和初始化之后,所有的准备工作就宣告完成了,所需要的共享对象也都已经装载并且链接完成了,这时候动态链接器就如释重负,将进程的控制权转交给程序的入口并且开始执行。

44.C++ 类只在栈中 或堆中实例化

答:一般情况下写一个类都是可以采用new在堆上分配空间,或直接采用 类名+对象名 的方式在栈上分配空间。但如果想让一个类只能在栈上或者堆上分配空间,又该怎么实现呢?

只在栈中:首先要禁用new运算符,所以我们只要对new操作符进行重载,并将它声明为private的,就能保证不能再使用new实例化对象,如:

class A{
private:
    void* operator new(size_t t){}
    void operator delete(void* ptr){}
public:
    A();
    ~A();
};

只在堆中:将构造或者析构函数私有化。

构造函数私有化:二阶构造模式,通过类外调用静态函数,在堆上面创建新对象。

using namespace std;
class SecondOrder
{
private:
    char* pchar;
    int val;

    SecondOrder()       //实现第一阶段的构造
    {
        val = 5;
    }

    bool construct()    //实现第二阶段的构造
    {
        pchar = new char;       
        if (NULL == pchar)
            return false;
        return true;
    }

public:
    static SecondOrder* instance()  //类的使用者要调用的接口,用于返回对象的指针
    {
        SecondOrder* ret = new SecondOrder();

        if (!(ret && ret->construct())) //两个new操作只要任务失败,ret都将为NULL
        {
            delete ret;
            ret = NULL;
        }
        return ret;
    }

    ~SecondOrder()
    {
        delete pchar;
        pchar = NULL;
    }

    void SetValue(char c, int i)
    {
        *pchar = c;
        val = i;
    }

    void PrintValue(void)
    {
        cout << "*pchar = " << *pchar << endl;
        cout << "val = " << val << endl;
    }
};

int main(void)
{
    SecondOrder *ps = SecondOrder::instance();
    if (ps != NULL)
    {
        ps->SetValue('6', 9);
        ps->PrintValue();
    }
    else
        cout << "ps = NULL" << endl;

    return 0;
}

析构函数私有化:

当析构函数被私有化之后,若在栈上实例化对象,编译器先会检查该对象的析构函数是否可用,如果不可用,则会报错。

在堆上释放对象时,若不调用delete就不会发现析构函数不可访问,在类中自定义一个公有函数用来销毁对象,该函数调用delete操作符。注意:采用这种方式后,类A不能用在继承体系当中。
若A为基类,那么其析构函数就是virtual的(保证内存完全释放),子类必须要能重写该析构函数,但子类根本无法访问他,这是其一。其二,即使子类能够重写析构函数,若B是继承自A的子类,且声明了一个B类的对象b,那么此时若析构B,基类A不能被正确释放,导致内存泄露。

class A{
public:
    A(){}
    void destroy(){delete this;}
private:
    ~A(){}

};

45.C++不能被重载的运算符有哪些?

答:6个。

点操作符:.

指向成员操作的指针操作符:->*,.*

作用域分辨符"::"

sizeof运算符

预处理符号:#

三目运算符"?:"

注意点:

重载不能改变该运算符用于内置类型时的函义、优先级、结合律、操作数个数。

.、.*运算符不能重载是为了保证访问成员的功能不能被改变,域运算符和sizeof运算符的运算对象是类型而不是变量或一般表达式,不具备重载的特征。

46.C++支持多继承吗?讲一下虚继承?

答:支持。

多继承:一个类继承多个直接基类称为多继承。

class 派生类名 : 访问控制 基类名1, 访问控制 基类名2, ...
{
    数据成员和成员函数声明
};

 多继承的派生类构造和访问:

多个基类的派生类构造函数可以用初始化列表调用基类构造函数来初始化数据成员;

执行顺序与单继承构造函数情况类似。多个直接基类构造函数执行顺序取决于定义派生类时指定的各个继承基类的顺序。

一个派生类对象拥有多个直接或间接基类的成员。不同名成员访问不会出现二义性。如果不同的基类有同名成员,派生类对象访问时应加以识别。

多继承问题:

1.“倒三角”问题——同名二义性

描述:2个父类派生一个子类,但两个父类中都有同名的成员函数。派生出的子类产生二义性问题,编译时会报。

解决:

1.利用作用域限定符(::),用来限定子类调用的是哪个父类的show()函数。

2.在类中定义同名成员,覆盖掉父类中的相关成员。

2.菱形问题——路径二义性

描述:有最基类A,有A的派生类B、C,又有D同时继承B、C,那么若A中有成员a,那么在派生类B,C中就存在a,又D继承了B,C,那么D中便同时存在B继承A的a和C继承A的a,那么当D的实例调用a的时候就不知道该调用B的a还是C的a,就导致了二义性。

解决:

1.使用作用域限定符,指明访问的是哪一个基类的成员。

2.在类中定义同名成员,覆盖掉父类中的相关成员。

3.虚继承、使用虚基类。

 

虚继承:为了解决多继承时的命名冲突和冗余数据问题,C++ 提出了虚继承,使得在派生类中只保留一份间接基类的成员,在继承方式前面加上 virtual 关键字就是虚继承。

虚继承的目的:目的是让某个类做出声明,承诺愿意共享它的基类。其中,这个被共享的基类就称为虚基类(Virtual Base Class),本例中的 A 就是一个虚基类。在这种机制下,不论虚基类在继承体系中出现了多少次,在派生类中都只包含一份虚基类的成员。

47.构造函数、静态成员函数、内联函数和析构函数分别可以是虚函数吗?

答:

1.静态成员函数不能是虚函数。

原因:静态成员函数属于类属于类,在内存中只有一份,即没有this指针;而虚函数必须根据指向哪一个对象来确定调用谁的虚函数,即虚函数要在有对象的基础上才可以。所以静态成员函数不可以定义为虚函数。

2.内联函数不能是虚函数。

原因:由于内联函数是直接展开代码,并不存在函数调用,即没有函数地址,那么就不能存在虚表中,所以内联函数不可定义为虚函数。

3.构造函数不能是虚函数。

原因:C++中如果创建一个对象,则需要知道对象的准确类型,因此构造函数不能为虚函数。

4.析构函数最好是虚函数。

原因:通过虚函数可以使基类指针指向派生类时,当析构时可以先析构派生类再析构基类,从而避免了内存泄露。

5.operator=可以但最好不要。

原因:虽然可以把operator=定义为虚函数(返回值不同,可以构成协变),但最好不要将operator=定义为虚函数,因为使用时容易引起混淆且没有意义。

注意点:

不要在构造函数和析构函数中调用虚函数。

原因:因为在构造函数中,对象还不完整,所以虚表指针可能还没初始化好,那么调用虚函数,就会发生未定义行为;
因为在析构函数中,会将对象某些成员清理,那么对象也不完整,同理,会发生未定义行为。

48.虚函数表是什么?

答:一个类的虚函数的地址表,用于解决继承和覆盖的问题。

特点:

1.拥有虚函数的类才有虚函数表。

2.虚函数表属于类,然后类的所有对象通过虚函数表指针共享类的虚函数表。

3.虚函数表的作用:当使用父类指针来操作子类对象时,虚函数表就像一个地图一样,指明了实际所应该调用的函数,实现多态。

4.c++编译器保证虚函数表的指针存在于对象实例中最前面的位置(为了保证在多层继承或者多重继承的情况下获得函数表的性能),这意味着我们可以通过对象实例的地址得到虚函数表,然后就可以遍历其中的虚函数指针,并且调用响应的虚函数。

49.c++重载函数为什么不能用返回值来区别呢?

答:不能。

原因:C++调用一个函数是可以忽略其返回值的,这种情况下编译器就无法根据返回值类型来确定调用哪一个函数。

     所以,重载不能用返回值类型来区别。

补充:重载: 同作用域下,同函数名,参数不同(包括类型、个数、顺序)。

50.代码从源文件到可执行文件的过程,是不是所有的语言都是一样的处理流程?

答:不是,java就和C不一样。

51.UTF-8中中文字符的大小,英文和数字的呢?

答:UTF-8 英文和数字占用一个byte, 欧洲语言的字符占用2个byte, 中文占用3个byte。

52.宏和inline区别?

答:区别:

 (1)内联函数在编译时展开,宏在预编译时展开;

  (2)内联函数直接嵌入到目标代码中,宏是简单的做文本替换;

  (3)内联函数有类型检测、语法判断等功能,而宏没有;

  (4)inline函数是函数,宏不是;

  (5)宏定义时要注意书写(参数要括起来)否则容易出现歧义,内联函数不会产生歧义;

扩展:

1.为什么要引入内联函数?

答:用它替代宏定义,消除宏定义的缺点。宏定义使用预处理器实现,做一些简单的字符替换因此不能进行参数有效性的检测。另外它的返回值不能被强制转换为可转换的合适类型,且C++中引入了类及类的访问控制,在涉及到类的保护成员和私有成员就不能用宏定义来操作。

2.inline相比宏定义有哪些优越处?

答:

  (1)inline函数代码是被放到符号表中,使用时像宏一样展开,没有调用的开销效率很高;

  (2)inline函数是真正的函数,所以要进行一系列的数据类型检查;

  (3)inline函数作为类的成员函数,可以使用类的保护成员及私有成员;

3.inline函数使用的场合

  (1)使用宏定义的地方都可以使用inline函数;

  (2)作为类成员接口函数来读写类的私有成员或者保护成员;

4.为什么不能把所有的函数写成inline函数

  内联函数以代码复杂为代价,它以省去函数调用的开销来提高执行效率。所以一方面如果内联函数体内代码执行时间相比函数调用开销较大没有太大的意义;另一方面每一处内联函数的调用都要复制代码,消耗更多的内存空间,因此以下情况不宜使用内联函数。

  (1)函数体内的代码比较长,将导致内存消耗代价;

  (2)函数体内有循环,函数执行时间要比函数调用开销大;

  另外类的构造与析构函数不要写成内联函数。

53.对强制类型转换有什么了解?

答:

C方式的强制类型转换的弊端有:

1.过于粗暴。

2.潜在的问题不易被发现。

3.不易在代码中定位。

新式类型转换以C++关键字的方式出现,其好处有:

1.编译器能够帮助检测潜在的问题。

2.非常方便的在代码中定位。

3.支撑动态类型识别(dynamic_cast)。

C++强制类型转换方式:

1.static_cast :用于基本类型间的转换,不能用于基本类型指针间的转换,也可用于有继承关系的类对象之间的转换和类指针之间的转换。

2.const_cast:用于去除变量的只读属性,前置转换的目标类型必须是指针或引用。

3.reinterpret_cast:用于指针间的类型转换,用于整数和指针类型间的强制类型转换。

4.dynamic_cast:用于有继承关系或交叉关系的类指针间的类型转换,具有类型检测的功能,需要虚函数的支持。

54.shared_ptr ,循环引用问题

答:观察下面这段代码:

class B; // 前置声明
class A {
public:
    shared_ptr<B> ptr;
};

class B {
public:
    shared_ptr<A> ptr;
};

int main()
{
    while(true) {
        shared_ptr<A> pa(new A());
        shared_ptr<B> pb(new B());
        pa -> ptr = pb;
        pb -> ptr = pa;
    }
    return 0;
}

这个程序中智能指针的引用情况如下图:

上图中,class A和class B的对象各自被两个智能指针管理,也就是A object和B object引用计数都为2,为什么是2?

分析class A对象的引用情况,该对象被main函数中的pa和class B对象中的ptr管理,因此A object引用计数是2,B object同理。

在这种情况下,在main函数中一个while循环结束的时候,pa和pb的析构函数被调用,但是class A对象和class B对象仍然被一个智能指针管理,A object和B object引用计数变成1,于是这两个对象的内存无法被释放,造成内存泄漏,如下图所示

解决方法:

1.把class A或者class B中的shared_ptr改成weak_ptr即可,由于weak_ptr不会增加shared_ptr的引用计数,所以A object和B object中有一个的引用计数为1,在pa和pb析构时,会正确地释放掉内存。

2. 当只剩下最后一个引用的时候需要手动打破循环引用释放对象。

3.当A的生存期超过B的生存期的时候,B改为使用一个普通指针指向A。

55.全局变量优缺点,以及如何克服?

答:

优点:全局可视。减少传递实参、形参带来的开销。

缺点:

(1)全局变量保存在静态存贮区,程序开始运行时为其分配内存,程序结束释放该内存。与局部变量的动态分配、动态释放相比,生存期比较长,因此过多的全局变量会占用较多的内存单元。
(2)全局变量破坏了函数的封装性能。前面的章节曾经讲过,函数象一个黑匣子,一般是通过函数参数和返回值进行输入输出,函数内部实现相对独立。但函数中如果使用了全局变量,那么函数体内的语句就可以绕过函数参数和返回值进行存取,这种情况破坏了函数的独立性,使函数对全局变量产生依赖。同时,也降低了该函数的可移植性。
(3)全局变量使函数的代码可读性降低。由于多个函数都可能使用全局变量,函数执行时全局变量的值可能随时发生变化,对于程序的查错和调试都非常不利。

56.几个关于指针的概念

1.悬垂指针:指向曾经存在的对象,但该对象已经不再存在了,此类指针称为垂悬指针。结果未定义,往往导致程序错误,而且难以检测。

解决方案:引入智能指针可以防止垂悬指针出现。一般是把指针封装到一个称之为智能指针类中,这个类中另外还封装了一个使用计数器,对指针的复制等操作将导致该计数器的值加1,对指针的delete操作则会减1,值为0时,指针为NULL。

2. 哑指针:哑指针指传统的C/C++指针,它只是一个指向,除此以外它不会有其他任何动作,所有的细节必须程序员来处理,比如指针初始化,释放等等

3.野指针:“野指针”不是NULL指针,是指向“垃圾”内存(不可用内存)的指针。人们一般不会错用NULL指针,因为用if语句很容易判断。但是“野指针”是很危险的,if无法判断一个指针是正常指针还是“野指针”。有个良好的编程习惯是避免“野指针”的唯一方法。

形成原因:

1.指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。

2.指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。别看free和delete的名字(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。通常会用语句if (p != NULL)进行防错处理。很遗憾,此时if语句起不到防错作用,因为即便p不是NULL指针,它也不指向合法的内存块。

3.指针操作超越了变量的作用范围。

4.空指针:空指针不指向任何实际的对象或者函数;NULL指针和零指针都是空指针。

5.零指针:指针值为0,零值指针,没有存储任何内存地址的指针;可以使任意一种指针类型,eg:void * ;int * ;double *。

57.成员函数和友元函数关系,友元函数能访问类的方法吗?

 

关系与区别:

友元函数和类的成员函数都可以访问类的私有成员变量或者是成员函数,但是他们调用的形式不同。
类的成员函数是属于类的,所以调用的时候是通过指针this调用的。
而类的友元函数不属于类,当然也不能有this指针了,也就是在友元函数中不能出现this指针。
同时友元函数是有关键字friend修饰。

友元函数:友元函数是在类中用关键字friend修饰的友元函数,友元函数可以是一个普通的函数,也可以是其他类的成员函数。虽然它不是本类的成员函数,但是在它的函数体中可以通过对象名访问类的私有和保护成员。

#include<iostream>
using namespace std;
class Time
{
public:
	Time(int, int, int);
	friend void display(Time &);//普通函数在类中的声明。
private:
	int hour;
	int minute;
	int second;
};
Time::Time(int h,int m,int s)
{
	hour = h;
	minute = m;
	second = s;
}
void display(Time &t)//普通函数在类中声明为友元
{
	cout << t.hour << ":" << t.minute << ":" << t.second << endl;
}
int main()
{
	Time p(13,42,32);
	display(p);
	return 0;
}

58.new/delete,malloc/free,delete/delete[]关系与区别

答:https://blog.csdn.net/hycxag/article/details/82995890

说的很好,我就不重复了。

59.深浅拷贝

答:https://blog.csdn.net/xu1105775448/article/details/80546950

60.宏定义

答:

不带参数的宏:#define 标识符 字符串  #define PI 3.1415926

带参数的宏:#define 宏名(参数表) 字符串  #define S(r) ((r)*(r))

61.红黑树与AVL树的区别

答:

一,AVL树

(1)简介

一般用平衡因子判断是否平衡并通过旋转来实现平衡,左右子树树高不超过1,和红黑树相比,AVL树是高度平衡的二叉树,平衡条件必须满足(所有节点的左右子树高度差不超过1)。不管我们是执行插入还是删除操作,只要不满足上面的条件,就要通过旋转来保持平衡,而的由于旋转比较耗时,由此我们可以知道AVL树适合用于插入与删除次数比较少,但查找多的情况

(2)局限性

由于维护这种高度平衡所付出的代价比从中获得的效率收益还大,故而实际的应用不多,更多的地方是用追求局部而不是非常严格整体平衡的红黑树。当然,如果应用场景中对插入删除不频繁,只是对查找要求较高,那么AVL还是较优于红黑树。

(3)应用

1,Windows NT内核中广泛存在;

二、红黑树

(1)简介

也是一种平衡二叉树,但每个节点有一个存储位表示节点的颜色,可以是红或黑。通过对任何一条从根到叶子的路径上各个节点着色的方式的限制,红黑树确保没有一条路径会比其它路径长出两倍,因此,红黑树是一种弱平衡二叉树(由于是弱平衡,可以看到,在相同的节点情况下,AVL树的高度<=红黑树),相对于要求严格的AVL树来说,它的旋转次数少,所以对于搜索,插入,删除操作较多的情况下,用红黑树。

(2)性质

                 如图1所示,每个节点非红即黑;

1. 每个节点非红即黑
2. 根节点是黑的;
3. 每个叶节点(叶节点即树尾端NULL指针或NULL节点)都是黑的;
4. 如图所示,如果一个节点是红的,那么它的两儿子都是黑的;
5. 对于任意节点而言,其到叶子点树NULL指针的每条路径都包含相同数目的黑节点;
6. 每条路径都包含相同的黑节点;

(3)应用

1. 广泛用于C ++的STL中,地图是用红黑树实现的;
2. Linux的的进程调度,用红黑树管理进程控制块,进程的虚拟内存空间都存储在一颗红黑树上,每个虚拟内存空间都对应红黑树的一个节点,左指针指向相邻的虚拟内存空间,右指针指向相邻的高地址虚拟内存空间;
3. IO多路复用的epoll采用红黑树组织管理sockfd,以支持快速的增删改查;
4. Nginx中用红黑树管理定时器,因为红黑树是有序的,可以很快的得到距离当前最小的定时器;
5. Java的TreeMap的实现;

62.为什么C++支持函数重载而C语言不支持呢?

答:C语言没办法支持重载,因为同名函数没办法区分。而C++是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载。

63.C++申请内存没有的情况下返回什么

答:

   1,malloc 函数申请失败时返回 NULL 值;

    2,new 关键字申请失败时(根据编译器的不同):

        a,返回 NULL 值;

           古代编译器兼容 C 中方式,返回 NULL 值;

        b,抛出 std::bad_alloc 异常;

           近代编译器不会返回 NULL 值,而是抛出一个标准库中的 std::bad_alloc 异常;

64.vector、list的使用场景

答:

Vector:顺序表

优点:和数组类似开辟一段连续的空间,并且支持随机访问,所以它的查找效率高其时间复杂度O(1)。
缺点:由于开辟一段连续的空间,所以插入删除会需要对数据进行移动比较麻烦,时间复杂度O(n),另外当空间不足时还需要进行扩容。

List:链表

优点:底层实现是循环双链表,当对大量数据进行插入删除时,其时间复杂度O(1)
缺点:底层没有连续的空间,只能通过指针来访问,所以查找数据需要遍历其时间复杂度O(n),没有提供[]操作符的重载。

应用场景

vector拥有一段连续的内存空间,因此支持随机访问,如果需要高效的随即访问,而不在乎插入和删除的效率,使用vector。

list拥有一段不连续的内存空间,如果需要高效的插入和删除,而不关心随机访问,则应使用list。

65.为什么map要用红黑树(而不用AVL树之类的)?

答:

(1)AVL以及红黑树是高度平衡的树数据结构。它们非常相似,真正的区别在于在任何添加/删除操作时完成的旋转操作次数。

(2)两种实现都缩放为a O(lg N),其中N是叶子的数量,但实际上AVL树在查找密集型任务上更快:利用更好的平衡,树遍历平均更短。另一方面,插入和删除方面,AVL树速度较慢:需要更高的旋转次数才能在修改时正确地重新平衡数据结构。
 

(3)在AVL树中,从根到任何叶子的最短路径和最长路径之间的差异最多为1。在红黑树中,差异可以是2倍。

(4)两个都给O(log n)查找,但平衡AVL树可能需要O(log n)旋转,而红黑树将需要最多两次旋转使其达到平衡(尽管可能需要检查O(log n)节点以确定旋转的位置)。旋转本身是O(1)操作,因为你只是移动指针。

66.动态库、静态库的区别,应用场景?

静态库:在链接步骤中,连接器将库文件取得所需的代码,复制到生成的可执行文件中,这种库叫做静态库,其特点是可执行文件中包含了库代码的一份完整拷贝;缺点就是被多次使用就会有多份冗余拷贝。即静态库中的指令全部被直接包含在最终生成的exe文件中。在vs中新建生成静态库的工程,编译生成成功后,只产生一个.lib文件。

动态库:动态库链接是一个包含可由多个程序同时使用的代码和数据的库,DLL不是可执行的文件,动态库提供一种方法,使进程可以调用不属于其可执行代码的函数。函数的可执行代码位于一个DLL中,该DLL中包含一个或者多个已经被编译,链接并使用它们的进程分开存储的函数,在vs中新建生成动态库的工程,编译成功后,产生一个.lib和一个.dll文件。

静态库的lib:该lib包含函数的代码本身(包括函数的索引,也包括实现),在编译 的时候将代码加入程序当中

动态库的lib:该lib包含了函数所在的DLL文件和文件中函数位置的信息,函数实现代码由运行的时候加载在进程空间中DLL提供,总之,lib是编译的时候用到的,如果完成源代码的编译,只需要lib,如果要使得动态库的程序运行起来,只需要dll。

应用场景应该是根据是否经常需要修改库函数来决定。经常修改的话用动态库,不经常的话用静态库。

67.自旋锁在多cpu与单cpu区别

答:单处理器环境不需要自旋锁,调用了自旋锁的函数,里面也不是自旋锁的实现,只不过是用了层壳而已。

单处理器来说,非抢占的话,自旋锁退化为 关开中断;

对于抢占来说,自旋锁变成  禁止/打开抢占+关开中断

68.malloc、realloc、calloc区别

答:参考:https://blog.csdn.net/weibo1230123/article/details/81503135

(1)malloc函数。其原型void *malloc(unsigned int num_bytes);
num_byte为要申请的空间大小,需要我们手动的去计算,如int *p = (int *)malloc(20*sizeof(int)),如果编译器默认int为4字节存储的话,那么计算结果是80Byte,一次申请一个80Byte的连续空间,并将空间基地址强制转换为int类型,赋值给指针p,此时申请的内存值是不确定的。

(2)calloc函数,其原型void *calloc(size_t n, size_t size);
其比malloc函数多一个参数,并不需要人为的计算空间的大小,比如如果他要申请20个int类型空间,会int *p = (int *)calloc(20, sizeof(int)),这样就省去了人为空间计算的麻烦。但这并不是他们之间最重要的区别,malloc申请后空间的值是随机的,并没有进行初始化,而calloc却在申请后,对空间逐一进行初始化,并设置值为0;

(3)realloc函数和上面两个有本质的区别,其原型void realloc(void *ptr, size_t new_Size)
用于对动态内存进行扩容(及已申请的动态空间不够使用,需要进行空间扩容操作),ptr为指向原来空间基址的指针, new_size为接下来需要扩充容量的大小。

69.extern的使用方法

答:参考:https://www.cnblogs.com/bytebee/p/8194569.html

extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。另外,extern也可用来进行链接指定。

70.vector和list区别?vector在begin的位置插入一个元素迭代器为什么会失效?

区别:

1)vector底层实现是数组;list是双向 链表。

2)vector支持随机访问,list不支持。

3)vector是顺序内存,list不是。

4)vector在中间节点进行插入删除会导致内存拷贝,list不会。

5)vector一次性分配好内存,不够时才进行2倍扩容;list每次插入新节点都会进行内存申请。

6)vector随机访问性能好,插入删除性能差;list随机访问性能差,插入删除性能好。

应用

vector拥有一段连续的内存空间,因此支持随机访问,如果需要高效的随即访问,而不在乎插入和删除的效率,使用vector。

list拥有一段不连续的内存空间,如果需要高效的插入和删除,而不关心随机访问,则应使用list。

vector迭代器失效情况
(1)执行erase方法时,指向删除节点及其之后的全部迭代器均失效;
(2)执行push_back方法时,end操作返回的迭代器失效;
(3)插入一个元素后,capacity返回值与没有插入元素之前相比有改变,则需要重新加载整个容器,此时begin和end操作返回的迭代器都会失效;(capacity是指在发生realloc前能允许的最大元素数,即预分配的内存空间)
(4)插入一个元素后,如果空间未重新分配,指向插入位置之前的元素的迭代器仍然有效。

71.map容器怎么查找元素?如果map的key是string类型查找匹配的速度很慢怎么办?

答:

Map是键-值对的集合,map中的所有元素都是pair,可以使用键作为下标来获取一个值。Map中所有元素都会根据元素的值自动被排序,同时拥有实值value和键值key,pair的第一元素被视为键值,第二元素被视为实值,同时map不允许两个元素有相同的键值。

第二问不会。

72.怎么让new不抛出异常而返回null?

答:

标准 C++ 亦提供了一个方法来抑制 new 抛出异常,而返回空指针:

int* p = new (std::nothrow) int; 

 

 

73.变长结构体如何使用

答:

使用方法:

typedef struct
{
    int a;
    char b[0];
}Empty;

Empty *p_array = (Empty *)malloc(sizeof(Empty) + 100); 

特点:变长结构体的内存是连续的(严谨的说是虚拟内存),而常规方法的不是,所以变长结构体只需释放一次空间,而常规方法需要释放两次。

74.内存泄漏如何检测

参考:https://blog.csdn.net/daaikuaichuan/article/details/80874436

75.mian函数前面有什么?

答:

main函数执行之前,主要就是初始化系统相关资源:

     1. 设置栈指针

     2. 初始化static静态和global全局变量,即data段的内容

     3. 将未初始化部分的全局变量赋初值:数值型short,int,long等为0,bool为FALSE,指针为NULL,等等,即.bss段的内容

     4. 全局对象初始化,在main之前调用构造函数

     5. 将main函数的参数,argc,argv等传递给main函数,然后才真正运行main函数

76.大小端了解吗?怎么判断?

答:

大端是高字节存放到内存的低地址

小端是高字节存放到内存的高地址

 

假如现有一32位int型数0x12345678,那么其MSB(Most Significant Byte,最高有效字节)为0x12,其LSB (Least Significant Byte,最低有效字节)为0x78,在CPU内存中有两种存放方式:(假设从地址0x4000开始存放)

判断方法:


int is_little_endian2(void)
{
	int a = 0x12345678;
	char b = *((char *)(&a));		// 指针方式其实就是共用体的本质
	if(0x78 == b)
        return 1;
    else if(0x12 == b)
	    return 0;
}

77.怎么判断是32位还是64位?

答:我觉得方法很多,我能想到的是通过sizeof(long)来判断,32位下是4字节,64位下是8字节。

78.

 

  • 10
    点赞
  • 80
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值