c++面试基础题汇总

http://www.cnblogs.com/277223178dudu/p/10750434.html
1、栈上的分配内存快还是堆上快?
答:栈,原因如下:

①栈的分配有计算机底层驱动,算法简单,堆的分配需要C++库支持,算法比较复杂;

②栈的分配不会遇到清理内存碎片的情况,但堆的分配中可能会遇到未释放的内存碎片垃圾的清理问题;

2、Top K问题
答:Top k问题即:在大量数据(n>>100000)中查找前k个最大的数据。

思路:排序是不可取的,因为大量数据排序耗时太大,且空间复杂度也很大,一般利用数据结构的最小堆(最小堆即父节点的值小于等于孩子节点的数值)来处理;

具体做法:建立一个含有K个节点的最小堆,遍历海量数据分别与根节点比较,若小于根节点则舍弃,否则用新数值替换根节点数值,并进行最小堆的调整,那么最终得到的堆节点就是最大的k个数据。

时间复杂度=nlogK(堆调整时间复杂度为logK);

此题若用于热门搜索推荐,即所有搜索项若都在日志文件中,查找搜索次数最多的K项。那么在top K计算之前还去要去统计每一搜索项的个数,此时需要用到数据结构——hashtable;

3、 C++中的什么是多态性? 是如何实现的?
答:多态性是面向对象程序设计语言继数据抽象和继承之后的第三个基本特征。它是在运行时出现的多态性通过派生类和虚函数实现。基类和派生类中使用同样的函数名, 完成不同的操作具体实现相隔离的另一类接口,即把" w h a t"从"h o w"分离开来。多态性提高了代码的组织性和可读性,虚函数则根据类型的不同来进行不同的隔离。

4、i++是原子操作吗?
答:不是,i++分为三个阶段:①从内存读取到寄存器;②寄存器数值自增;③寄存器写回内存。

其每个阶段之间都可以被打断,故不是原子操作。

5、字典树如何优化?
答:字典树也是空间换时间的数据结构(哈希表是典型的空间换时间),一般优化方向就是:空间优化。

常用优化方法是:使用哈希表替换每个节点中指向孩子的指针数组,在建立字典树时根据需要向哈希表中添加指针,从而避免有些指针数组方式中的多余指针元素浪费空间。

6、 多态的作用?
答:主要是两个:
1)隐藏实现细节,使得代码能够模块化;扩展代码模块,实现代码重用;
2)接口重用,为了类在继承和派生的时候,保证使用家族中任一类的实例的某一属性时的正确调用。

7、智能指针的作用及实现
智能指针是一个类,用来存储指向动态分配对象的指针,负责自动释放动态分配的对象,防止堆内存泄漏。动态分配的资源,交给一个类对象去管理,当类对象声明周期结束时,自动调用析构函数释放资源。

智能指针的种类

    shared_ptr、unique_ptr、weak_ptr、auto_ptr 

(1) shared_ptr

    实现原理:采用引用计数器的方法,允许多个智能指针指向同一个对象,每当多一个指针指向该对象时,指向该对象的所有智能指针内部的引用计数加1,每当减少一个智能指针指向对象时,引用计数会减1,当计数为0的时候会自动的释放动态分配的资源。 

    1) 智能指针将一个计数器与类指向的对象相关联,引用计数器跟踪共有多少个类对象共享同一指针;

     2) 每次创建类的新对象时,初始化指针并将引用计数置为1;

     3) 当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;

     4) 对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;

     5) 调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。

(2) unique_ptr

   unique_ptr采用的是独享所有权语义,一个非空的unique_ptr总是拥有它所指向的资源。转移一个unique_ptr将会把所有权全部从源指针转移给目标指针,源指针被置空;所以unique_ptr不支持普通的拷贝和赋值操作,不能用在STL标准容器中;局部变量的返回值除外(因为编译器知道要返回的对象将要被销毁);如果你拷贝一个unique_ptr,那么拷贝结束后,这两个unique_ptr都会指向相同的资源,造成在结束时对同一内存指针多次释放而导致程序崩溃。

(3) weak_ptr

  weak_ptr:弱引用。 引用计数有一个问题就是互相引用形成环(环形引用),这样两个指针指向的内存都无法释放。需要使用weak_ptr打破环形引用。weak_ptr是一个弱引用,它是为了配合shared_ptr而引入的一种智能指针,它指向一个由shared_ptr管理的对象而不影响所指对象的生命周期,也就是说,它只引用,不计数。如果一块内存被shared_ptr和weak_ptr同时引用,当所有shared_ptr析构了之后,不管还有没有weak_ptr引用该内存,内存也会被释放。所以weak_ptr不保证它指向的内存一定是有效的,在使用之前使用函数lock()检查weak_ptr是否为空指针。

(4) auto_ptr

   auto_ptr不支持拷贝和赋值操作,不能用在STL标准容器中。STL容器中的元素经常要支持拷贝、赋值操作,在这过程中auto_ptr会传递所有权,auto_ptr采用的是独享所有权语义,一个非空的unique_ptr总是拥有它所指向的资源。转移一个auto_ptr将会把所有权全部从源指针转移给目标指针,源指针被置空。

智能指针代码实现: 用两个类来实现智能指针的功能,一个是引用计数类,另一个则是指针类。

8、常用数据类型对应字节数
可用如sizeof(char),sizeof(char*)等得出

  32位编译器:

  char :1个字节
  char*(即指针变量): 4个字节(32位的寻址空间是2^32, 即32个bit,也就是4个字节。同理64位编译器)
  short int : 2个字节
  int:  4个字节
  unsigned int : 4个字节
  float:  4个字节
  double:   8个字节
  long:   4个字节
  long long:  8个字节
  unsigned long:  4个字节

  64位编译器:

  char :1个字节
  char*(即指针变量): 8个字节
  short int : 2个字节
  int:  4个字节
  unsigned int : 4个字节
  float:  4个字节
  double:   8个字节
  long:   8个字节
  long long:  8个字节
  unsigned long:  8个字节

9、三次握手,四次挥手,中间的等待
第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。

第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

1)客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
2)服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
3)客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
4)服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
5)客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
6)服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。

10、多态,虚函数与虚函数表
C++ 支持静态联编的编译时多态,和动态联编的运行时多态。函数联编是指对一个函数的调用,是确定“函数引用的目标函数体”的过程,C++ 的动态联编通过虚函数(virtual method / virtual function)实现。虚函数存在于继承关系中,在基类声明虚函数,子类覆写虚函数,使我们能够通过表面使用基类的该函数,实际访问到子类覆写后的函数。

C++ 中虚函数是通过虚函数表(virtual table,v-table)来实现的。每个有虚函数的类有一个虚函数表,包括纯虚函数和派生类中隐式声明的虚函数。虚函数表的入口指针在对象最开始的位置。虚函数表只存储虚函数“函数指针”的地址,不存放普通函数或是构造函数指针的地址。

11、 线程安全(单例模式, 懒汉,饿汉)
单例大约有两种实现方法:懒汉与饿汉。
懒汉:故名思义,不到万不得已就不会去实例化类,也就是说在第一次用到类实例的时候才会去实例化,
饿汉:饿了肯定要饥不择食。所以在单例类定义的时候就进行实例化。

(1)饿汉

饿汉单例,即在最开始的时候,静态对象就已经创建完成;
设计方法是类中包含一个静态成员指针,该指针指向该类的一个对象,提供一个公有的静态成员方法,返回该对象指针;为了使得对象唯一,还需要将构造函数设为私有,

(2)懒汉

所谓懒汉模式,就是尽可能晚的创建这个对象的实例,即在单例类第一次被引用时将自己初始化;其实C++里很多地方都是类似这样的思想,比如晚绑定,写时拷贝技术等,就是尽量使资源的利用最大化,不要让空闲的人还占着有限的资源。

(3)懒汉的线程安全问题

如果此时多线程进行操作,简单点以两个线程为例,假设pthread_1刚判断完 intance 为NULL 为真,准备创建实例的时候,切换到了pthread_2, 此时pthread_2也判断intance为NULL为真,创建了一个实例,再切回pthread_1的时候继续创建一个实例返回,那么此时就不再满足单例模式的要求了, 既然这样,是因为多线程访问出的问题,那我们就来加把锁,使得线程同步;

单例模式的适用场景
(1)系统只需要一个实例对象,或者考虑到资源消耗的太大而只允许创建一个对象。
(2)客户调用类的单个实例只允许使用一个公共访问点,除了该访问点之外不允许通过其它方式访问该实例 (就是共有的静态方法)。

12、拷贝构造函数的参数为什么必须用引用?
答: 如果拷贝构造函数中的参数不是一个引用,即形如CClass(const CClass c_class),那么就相当于采用了传值的方式(pass-by-value),而传值的方式会调用该类的拷贝构造函数,从而造成无穷递归地调用拷贝构造函数。因此拷贝构造函数的参数必须是一个引用。

13、vector的动态增长?
当添加元素时,如果vector空间大小不足,则会以原大小的两倍另外配置一块较大的新空间,然后将原空间内容拷贝过来,在新空间的内容末尾添加元素,并释放原空间。vector的空间动态增加大小,并不是在原空间之后的相邻地址增加新空间,因为vector的空间是线性连续分配的,不能保证原空间之后有可供配置的空间。因此,对vector的任何操作,一旦引起空间的重新配置,指向原vector的所有迭代器就会失效。
14、memcpy内存重叠的解决及其实现?

内存重叠:拷贝的目的地址在源地址范围内。所谓内存重叠就是拷贝的目的地址和源地址有重叠。

在函数strcpy和函数memcpy都没有对内存重叠做处理的,使用这两个函数的时候只有程序员自己保证源地址和目标地址不重叠,或者使用memmove函数进行内存拷贝。

当源字节串和目标字节串重叠是,bcopy能够正确处理,但是memcpy的操作结果不得而知,这种情况必须改用ANSI C的memmove函数[网络编程]。故该函数实现过程中要考虑src 和dst是否有重叠的情况。
内存重叠:拷贝的目的地址在源地址范围内。所谓内存重叠就是拷贝的目的地址和源地址有重叠。
重叠从两方面考虑:
(1).dst部分数据是src的
(2).src部分数据是dst的

15、空的类是否占用内存?
答:空的类是会占用内存空间的,而且大小是1,原因是C++要求每个实例在内存中都有独一无二的地址。
(一)类内部的成员变量:
普通的变量:是要占用内存的,但是要注意对齐原则(这点和struct类型很相似)。static修饰的静态变量:不占用内容,原因是编译器将其放在全局变量区。

(二)类内部的成员函数:普通函数:不占用内存。虚函数:要占用4个字节,用来指定虚函数的虚拟函数表的入口地址。所以一个类的虚函数所占用的地址是不变的,和虚函数的个数是没有关系的。

16、堆排序
堆排序是指利用堆这种数据结构所设计的一种选择排序算法。堆是一种近似完全二叉树的结构(通常堆是通过一维数组来实现的),并满足性质:以最大堆(也叫大根堆、大顶堆)为例,其中父结点的值总是大于它的孩子节点。

我们可以很容易的定义堆排序的过程:

由输入的无序数组构造一个最大堆,作为初始的无序区
把堆顶元素(最大值)和堆尾元素互换
把堆(无序区)的尺寸缩小1,并调用heapify(A, 0)从新的堆顶元素开始进行堆调整
重复步骤2,直到堆的尺寸为1
17、内联函数INline和宏定义一起使用的区别。
解析:内联函数是在编译的时候已经做好将对应的函数代码替换嵌入到对应的位置,适用于代码较少的函数。 宏定义是简单的替换变量,如果定义的是有参数的函数形式,参数不做类型校验。

18、 C中static有什么作用
正确答案:
(1)隐藏。 当我们同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性,故使用static在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。
(2)static的第二个作用是保持变量内容的持久。存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量。
(3)static的第三个作用是默认初始化为0.其实全局变量也具备这一属性,因为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是0×00,某些时候这一特点可以减少程序员的工作量。

19、引用和指针的区别与联系
区别

指针是一个实体他在栈中有自己使用的空间,但是引用没有;
引用必须初始化,指针不用但是最好初始化
指针使用时必须加*,引用不用;
引用只能初始化一次是个专一的人,指针不是;
引用不用const去修饰,但是指针可以
指针和地址运用自增(++)不同,引用是值进行自增,而指针是地址进行自增;
联系

引用的内部使用指针实现的
引用是受了限制的指针

20、STL容器各自的优缺点
如果需要高效的随机存取,不在乎插入和删除的效率,使用vector;
如果需要大量的插入和删除元素,不关心随机存取的效率,使用list;
如果需要随机存取,并且关心两端数据的插入和删除效率,使用deque;
如果打算存储数据字典,并且要求方便地根据key找到value,一对一的情况使用map,一对多的情况使用multimap;
如果打算查找一个元素是否存在于某集合中,唯一存在的情况使用set,不唯一存在的情况使用multiset。

21、udp怎么保证能收到数据?
udp与tcp的区别
TCP(TransmissionControl Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。

     UDP是User Datagram Protocol,一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务。可靠性由上层应用实现,所以要实现udp可靠性传输,必须通过应用层来实现和控制。

TCP如何实现可靠性传输?

     确认机制、重传机制、滑动窗口。

22、重载和重写区别?
方法重写(overriding):

1、也叫子类的方法覆盖父类的方法,要求返回值、方法名和参数都相同。

2、子类抛出的异常不能超过父类相应方法抛出的异常。(子类异常不能超出父类异常)

3、子类方法的的访问级别不能低于父类相应方法的访问级别(子类访问级别不能低于父类访问级别)

方法重载(overloading):重载是在同一个类中的两个或两个以上的方法,拥有相同的方法名,但是参数却不相同,方法体也不相同,最常见的重载的例子就是类的构造函数,可以参考API帮助文档看看类的构造方法

23、stl的包括哪些模板?
向量(vector) 连续存储的元素
列表(list) 由节点组成的双向链表,每个结点包含着一个元素
双端队列(deque) 连续存储的指向不同元素的指针所组成的数组
适配器容器
栈(stack) 后进先出的值的排列
队列(queue) 先进先出的值的排列
优先队列(priority_queue) 元素的次序是由作用于所存储的值对上的某种谓词决定的的一种队列
关联式容器
集合(set) 由节点组成的红黑树,每个节点都包含着一个元素,节点之间以某种作用于元素对的谓词排列,没有两个不同的元素能够拥有相同的次序
多重集合(multiset) 允许存在两个次序相等的元素的集合
映射(map) 由{键,值}对组成的集合,以某种作用于键对上的谓词排列
多重映射(multimap) 允许键对有相等的次序的映射

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

深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝。

25、虚继承
在多重继承中,如果发生了如:类B继承类A,类C继承类A,类D同时继承了类B和类C。最终在类D中就有了两份类A的成员,这在程序中是不能容忍的。当然解决这个问题的方法就是利用虚继承。

虚继承是解决C++多重继承问题的一种手段,从不同途径继承来的同一基类,会在子类中存在多份拷贝。这将存在两个问题:其一,浪费存储空间;第二,存在二义性问题,通常可以将派生类对象的地址赋值给基类对象,实现的具体方式是,将基类指针指向继承类(继承类有基类的拷贝)中的基类对象的地址,但是多重继承可能存在一个基类的多份拷贝,这就出现了二义性。

虚继承可以解决多种继承前面提到的两个问题:

虚继承底层实现原理与编译器相关,一般通过虚基类指针和虚基类表实现,每个虚继承的子类都有一个虚基类指针(占用一个指针的存储空间,4字节)和虚基类表(不占用类对象的存储空间)(需要强调的是,虚基类依旧会在子类里面存在拷贝,只是仅仅最多存在一份而已,并不是不在子类里面了);当虚继承的子类被当做父类继承时,虚基类指针也会被继承。

实际上,vbptr指的是虚基类表指针(virtual base table pointer),该指针指向了一个虚基类表(virtual table),虚表中记录了虚基类与本类的偏移地址;通过偏移地址,这样就找到了虚基类成员,而虚继承也不用像普通多继承那样维持着公共基类(虚基类)的两份同样的拷贝,节省了存储空间。

26、为什么要把析构函数定义为虚函数?
new出来的是子类son的对象,采用一个父类father的指针来接收,故在析构的时候,编译器因为只知道这个指针是父类的,所以只将父类部分的内存析构了,而不会去析构子类的内存,就造成了内存泄露。基类析构函数定义为虚拟函数的时候,在子类的对象的首地址开始会有一块基类的虚函数表拷贝,在析构子类对象的时候会删除此虚函数表,此时会调用基类的析构函数,所以此时内存是安全的。

27、堆栈溢出的原因?
没有回收垃圾资源

栈溢出:

一般都是由越界访问导致的。例如局部变量数组越界访问或者函数内局部变量使用过多,超出了操作系统为该进程分配的栈的大小。

堆溢出:

由于堆是用户申请的,所以溢出的原因可能是程序员申请了资源但是忘记释放了。

28、为什么构造函数不能是虚函数?
构造函数不可以是虚函数的,这个很显然,毕竟虚函数都对应一个虚函数表,虚函数表是存在对象内存空间的,如果构造函数是虚的,就需要一个虚函数表来调用,但是类还没实例化没有内存空间就没有虚函数表,这根本就是个死循环。

29、介绍一下STL,详细说明STL如何实现vector。
STL是标准模版库,由容器算法迭代器组成。

STL有以下的一些优点:

(1)可以很方便的对一堆数据进行排序(调用sort());

(2)调试程序时更加安全和方便;

(3)stl是跨平台的,在linux下也能使用。

vector实质上就是一个动态数组,会根据数据的增加,动态的增加数组空间

30、简述#define #endif 和#ifndef的作用?
这三个命令一般是为了避免头文件被重复引用。

#ifndef CH_H //意思是如果没有引用ch.h

#define CH_H //引用ch.h

#endif //否则不需要引用

31、正确区分重载、重写和隐藏。
注意三个概念的适用范围:处在同一个类中的函数才会出现重载。处在父类和子类中的函数才会出现重写和隐藏。
重载:同一类中,函数名相同,但参数列表不同。
重写:父子类中,函数名相同,参数列表相同,且有virtual修饰。
隐藏:父子类中,函数名相同,参数列表相同,但没有virtual修饰;函数名相同,参数列表不同,无论有无virtual修饰都是隐藏
基类中:
(1) virtual void show(); //是虚函数
(2) void show(int); //不是虚函数
子类中:(3) void show(); //是虚函数
(4) void show(int); //不是虚函数
1,2构成重载,3,4构成重载,1,3构成重写,2,4构成隐藏。另外2,3也会构成隐藏,子类对象无法访问基类的void show(int)成员方法,但是由于子类中4的存在导致了子类对象也可以直接调用void show(int)函数,不过此时调用的函数不在是基类中定义的void show(int)函数2,而是子类中的与3重载的4号函数。

32、C++继承机制?
n类成员的访问控制方式

public:类本身、派生类和其它类均可访问;

protected:类本身和派生类均可访问,其它类不能访问;

private(默认):类本身可访问,派生类和其它类不能访问。

继承成员的访问控制规则

——由父类成员的访问控制方式和继承访问控制方式共同决定

private+public(protectd,private)=>不可访问

pubic(protected)+public=>public(protected)

public(protected)+protected=>protected

public(protected)+private(默认)=>private

33、类和对象的两个基本概念?
类的作用或概念:用来描述一组具有相似属性的东西的对象的一种数据结构。类中有数据成员的声明和定义,有成员函数的实现代码。对象就是类的实例化。计算机中想要使用类,只能进行实例化。

34、什么是内存泄露?
C++内存泄漏检测内存泄露是指程序中动态分配了内存,但是在程序结束时没有释放这部分内存,从而造成那一部分内存不可用的情况

有一些内存泄漏的检测工具,比如BoundsChecker。

静态内存泄漏通过工具或者仔细检查代码找到泄漏点。

动态的内存泄漏很难查,一般通过在代码中加断点跟踪和Run-Time内存检测工具来查找。

内存泄漏的检测可以分以下几个步骤:

(1)看代码new之后是否delete,就是申请了静态内存用完是否释放。看析构函数是否真的执行,如果没有真正执行,就需要动态释放对象;

(2)让程序长时间运行,看任务管理器对应程序内存是不是一直向上增加;

(3)使用常用内存泄漏检测工具来检测内存泄漏点。

35、头文件的作用是什么?
(1)头文件用于保存程序的声明。
(2)通过头文件可以来调用库函数。因为有些代码不能向用户公布,只要向用户提供头文件和二进制的库即可。用户只需要按照头文件中的接口声明来调用库功能,编译器会从库中提取相应的代码。
(3)如果某个接口被实现或被使用时,其方式与头文件中的声明不一致,编译器就会指出错误,这一简单的规则能大大减轻程序员调试、改错的负担。

36、函数模板与类模板有什么区别?
答:函数模板的实例化是由编译程序在处理函数调用时自动完成的,而类模板的实例化必须由程序员在程序中显式地指定。

函数模板是模板的一种,可以生成各种类型的函数实例:

template

Type min( Type a, Type b )

{

return a < b ? a : b;

}

参数一般分为类型参数和非类型参数:

类型参数代表了一种具体的类型

非类型参数代表了一个常量表达式

37、析构函数和虚函数的用法和作用?
析构函数是类成员函数,在类对象生命期结束的时候,由系统自动调用,释放在构造函数中分配的资源。

虚函数是为了实现多态。含有纯虚函数的类称为抽象类,不能实例化对象,主要用作接口类

Test(int j):pb(j),pa(pb+5)
{
}

~Test()
{
cout<<“释放堆区director内存空间1次”;
}

析构函数的特点:

  1. 函数名称固定:~类名( )
  2. 没有返回类型,没有参数
  3. 不可以重载,一般由系统自动的调用

38、new、delete;malloc、free关系
new和delete是一组,new用调用构造函数来实例化对象和调用析构函数释放对象申请的资源。

malloc和free是一对,malloc用来申请内存和释放内存,但是申请和释放的对象只能是内部数据类型。

区别:

malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。

maloc/free只能操作内部数据类型

39、delete与 delete []区别
都是用来调用析构函数的:

(1)delete只会调用一次析构函数,delete[]会调用每一个成员的析构函数。

(2)delete与new配套,delete []与new []配套,用new分配的内存用delete删除用new[]分配的内存用delete[]删除

40、继承优缺点
优点:

继承可以方便地改变父类的实现,可以实现多态,子类可以继承父类的方法和属性。

缺点:

破坏封装,子类和父类可能存在耦合。

子类不能改变父类的接口。

41、C和C++有什么不同?
(1)c是面向过程的,也就是说更偏向逻辑设计;c++是面向对象的,提供了类,偏向类的设计。

(2)c适合要求代码体积小的,效率高的场合,如比如嵌入式。

42、析构函数的调用次序,子类析构时要调用父类的析构函数吗?
析构函数调用的次序是:先派生类的析构后基类的析构,也就是说在基类的的析构调用的时候,派生类的信息已经全部销毁了定义一个对象时先调用基类的构造函数、然后调用派生类的构造函数;

43、什么是“野指针”?
野指针指向一个已删除的对象或无意义地址的指针。与空指针不同,野指针无法通过简单地判断是否为 NULL避免,而只能通过养成良好的编程习惯来尽力避免。造成的主要原因是:指针变量没有被初始化,或者指针p被free或者delete之后,没有置为NULL。

44、常量指针和指针常量的区别?
常量指针:是一个指向常量的指针。可以防止对指针误操作而修改该常量。
指针常量:是一个常量,且是一个指针。指针常量不能修改指针所指向的地址,一旦初始化,地址就固定了,不能对它进行移动操作。但是指针常量的内容是可以改变。

45、如果NULL定义成#define NULL ((char *)0) 难道不就可以向函数传入不加转换的NULL了吗?
不行。因为有的机器不同类型数据的指针有不同的内部表达。如果是字符指针的函数没有问题, 但对于其它类型的指针参数仍然有问题, 而合法的构造如FILE *fp = NULL;则会失败。
如果定义#define NULL ((void *)0)除了潜在地帮助错误程序运行以外, 这样的定义还可以发现错误使用NULL 的程序。无论如何, ANSI 函数原型确保大多数指针参数在传入函数时正确转换。

46、空指针到底是什么?
空指针表示“未分配” 或者“尚未指向任何地方” 的指针。
空指针在概念上不同于未初始化的指针。空指针可以确保不指向任何对象或函数; 而未初始化指针则可能指向任何地方。

47、C++文件编译与执行的四个阶段
第一阶段:预处理阶段。根据文件中的预处理指令来修改源文件的内容。如#include指令,作用是把头文件的内容添加到.cpp文件中。

第二阶段:编译阶段,将其翻译成等价的中间代码或汇编代码。

第三阶段:汇编阶段,把汇编语言翻译成目标机器指令。

第四阶段:是链接,例如,某个源文件中的函数可能引用了另一个源文件中定义的某个函数;在程序中可能调用了某个库文件中的函数。

48、什么是预编译?何时需要预编译?
预编译又称为预处理 , 是做些代码文本的替换工作。处理 # 开头的指令 , 比如拷贝 #include 包含的文件代码, #define 宏定义的替换 , 条件编译等。
c 编译系统在对程序进行通常的编译之前,先进行预处理。 c 提供的预处理功能主要有以下三 种: 1 )宏定义  2 )文件包含  3 )条件编译

49、内联函数与宏有什么区别
内联函数在编译时展开,宏在预编译时展开
在编译的时候内联函数可以直接被嵌入到目标代码中,而宏只是一个简单的文本替换
内联函数可以完成诸如类型检测、语句是否正确等编译功能,宏就不具备这样的功能
inline函数是函数,宏不是函数。

50、iostream与iostream.h的区别
#include <iostream.h>非标准输入输出流
#include 标准输入输出流
有“.h”的就是非标准的,C的标准库函数,无“.h”的,就要用到命令空间,是C++的。

51、堆与栈的区别
(1)一个是静态的,一个是动态的,堆是静态的,由用户申请和释放,栈是动态的,保存程序的局部变量

(2)申请后系统的响应不同
栈:只要栈的剩余空间大于申请空间,系统就为程序提供内存,否则将抛出栈溢出异常
堆:当系统收到程序申请时,先遍历操作系统中记录空闲内存地址的链表,寻找第一个大于所申请空间的堆结点,然后将该结点从空间结点链表中删除,并将该结点的空间分配给程序。
(3)申请大小限制的不同
栈:在windows下,栈的大小一般是2M,如果申请的空间超过栈的剩余空间时,将提示overflow。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

52、虚函数与构造函数,析构函数,成员函数的关系
为什么基类析构函数是虚函数?

编译器总是根据类型来调用类成员函数。但是一个派生类的指针可以安全地转化为一个基类的指针。这样删除一个基类的指针的时候,C++不管这个指针指向一个基类对象还是一个派生类的对象,调用的都是基类的析构函数而不是派生类的。如果你依赖于派生类的析构函数的代码来释放资源,而没有重载析构函数,那么会有资源泄漏。

为什么构造函数不能为虚函数

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

如果虚函数是有效的,那为什么不把所有函数设为虚函数?

不行。因为每个虚函数的对象都要维护一个虚函数表,因此在使用虚函数的时候都会产生一定的系统开销,这是没有必要的。

53、面向对象的三个基本特征,并简单叙述之?

  1. 封装:将客观事物抽象成类,每个类对自身的数据和方法。封装可以使得代码模块化,目的是为了代码重用
  2. 继承:子类继承父类的方法和属性,继承可以扩展已存在的代码,目的是为了代码重用
  3. 多态:允许将子类类型的指针赋值给父类类型的指针。

54、什么是“&(引用)”?申明和使用“引用”要注意哪些问题?
引用就是某个目标变量的“别名”。注意事项:(1)申明一个引用的时候,必须要对其进行初始化。(2)初始化后,该引用名不能再作为其他变量名的别名。

(3)引用本身不占存储单元,系统不给引用分配存储单元。(4)返回引用时,在内存中不产生被返回值的副本

(5)不能返回局部变量的引用。主要原因是局部变量会在函数返回后被销毁.

55、引用与多态的关系?
引用就是对象的别名。引用主要用作函数的形参。引用必须用与该引用同类型的对象初始化: 引用是除指针外另一个可以产生多态效果的手段。这意味着,一个基类的引用可以指向它的派生类实例。
int ival = 1024;int &refVal = ival;
const 对象的引用只能是const类型的:const int ival = 1024;const int &refVal = ival;
多态是通过虚函数实现的。

56、指针和引用有什么区别;为什么传引用比传指针安全?
如果我使用常量指针难道不行吗?
(1) 引用在创建的同时必须初始化,保证引用的对象是有效的,所以不存在NULL引用;而指针在定义的时候不必初始化,所以,指针则可以是NULL,可以在定义后面的任何地方重新赋值。
(2) 引用一旦被初始化为指向一个对象,它就不能被改变为另一个对象的引用;而指针在任何时候都可以改变为指向另一个对象.
(3) 引用的创建和销毁并不会调用类的拷贝构造函数
因为不存在空引用,并且引用一旦被初始化为指向一个对象,它就不能被改变为另一个对象的引用,所以比指针安全。
由于const 指针仍然存在空指针,并且有可能产生野指针,所以还是不安全

57、拷贝构造函数相关问题,深拷贝,浅拷贝,临时对象等。
深拷贝意味着拷贝了资源和指针,而浅拷贝只是拷贝了指针,没有拷贝资源
这样使得两个指针指向同一份资源,可能造成对同一份析构两次,程序崩溃。而且浪费时间,并且不安全。
临时对象的开销比局部对象小些。

58、面向对象如何实现数据隐藏
定义类来实现数据隐藏:

成员函数和属性的类型:

私有成员private

保护成员protected
公共成员public

59、C++是不是类型安全的?
不是。两个不同类型的指针之间可以强制转换.

60、const char*, char const*, char const的区别是什么?
把一个声明从右向左读,
读成指向
char * const cp;//cp是常指针,指向char类型的数据
const char * cp;//cp是char类型的指针,指向const char
char const * p;//C++里面没有const*的运算符,所以const属于前面的类型。

61、什么是模板和宏?模板怎么实现?模板有什么缺点和优点?模版特化的概念,为什么特化?
标准库大量采用了模板技术。比如容器。

模板是一个蓝图,它本身不是类或函数。编译器用模板产生指定的类或函数的特定类型版本。模版的形参分为类型形参和非类型形参类型形参就是表示类型的形参,跟在关键字typename后非类型形参用来表示常量表达式

62、空指针和悬垂指针的区别?
空指针是指被赋值为NULL的指针;delete指向动态分配对象的指针将会产生悬垂指针。

空指针可以被多次delete,而悬垂指针再次删除时程序会变得非常不稳定;

使用空指针和悬垂指针都是非法的,而且有可能造成程序崩溃,如果指针是空指针,尽管同样是崩溃,但和悬垂指针相比是一种可预料的崩溃。

(a)指针数组和数组指针,函数指针和指针函数相关概念

指针数组:用于存储指针的数组

int* a[4]

元素表示:*a[i]

数组指针:指向数组的指针

int (*a)[4]

元素表示:(*a)[i]

指针函数:函数返回类型是某一类型的指针,int *f(x,y);

指针函数与函数指针表示方法的不同。最简单的辨别方式就是看函数名前面的指针*号有没有被括号()包含,如果被包含就是函数指针,反之则是指针函数。

函数指针:是指向函数的指针变量,即本质是一个指针变量。

int (f) (int x); / 声明一个函数指针 */ 类型说明符 (*指针的变量名)(参数)

f=func; /* 将func函数的首地址赋给指针f */

指向函数的指针包含了函数的地址

指针的指针:
例如:char ** cp;
如果有三个星号,那就是指针的指针的指针,依次类推。
指针的指针需要用到指针的地址。
char c=‘A’;
char *p=&c;
char **cp=&p;
通过指针的指针,不仅可以访问它指向的指针,还可以访问它指向的指针所指向的数据:
char *p1=*cp;
char c1=**cp;
指向指针数组的指针:
char *Names[]={ Bill,Sam,0};
char **nm=Names;
while(*nm!=0) printf(%s\n,*nm++);
先用字符型指针数组Names的地址来初始化指针nm。每次printf()的调用都首先传递指针nm指向的字符型指针,然后对nm进行自增运算使其指向数组的下一个元素(还是指针)。

63、什么是智能指针?
当类中有指针成员时,一般有两种方式来管理指针成员:

(1)每个类对象都保留一份指针指向的对象的拷贝;

(2)使用智能指针,从而实现指针指向的对象的共享。实质是使用计数器与对象相关联,这样做可以保证对象正确的删除,避免垂悬指针。

每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;

对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数,并增加右操作数所指对象的引用计数;调用析构函数时,构造函数减少引用计数。

64、C++空类默认有哪些成员函数?
默认构造函数、析构函数、复制构造函数、赋值函数

65、哪一种成员变量可以在一个类的实例之间共享?
答:static静态成员变量

66、什么是多态?多态有什么作用?如何实现的?多态的缺点?
多态就是一个接口,多种方法。所以说,多态的目的则是为了实现接口重用。也就是说,不论传递过来的究竟是那个类的对象,函数都能够通过同一个接口调用到适应各自对象的实现方法。

C++的多态性是通过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(override),或重写。而重载则是允许有多个同名的函数,而这些函数的参数列表不同,允许参数个数不同,参数类型不同。编译器会根据函数列表的不同,而生成一些不同名称的预处理函数,来实现同名函数的重载。但这并没有体现多态性。多态与非多态的实质区别就是函数的地址是运行时确定还是编译时确定。如果函数的调用在编译器编译期间就可以确定函数的调用地址,并生产代码,是静态的。而如果函数调用的地址在运行时才确定,就是动态的。最常见的用法就是声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而实现不同的方法。如果没有使用虚函数的话,即没有利用C++多态性,则利用基类指针调用相应的函数的时候,将总被限制在基类函数本身,而无法调用到子类中被重写过的函数

67、虚函数表解析和内存布局
虚函数表

虚函数是通过一张虚函数表来实现的。就像一个地图一样,指明了实际所应该调用的函数的地址。

这里我们着重看一下这张虚函数表。C++的编译器保证了虚函数表的指针存在于对象实例中最前面的位置(为了性能)。因此我们可以通过对象实例的地址得到这张虚函数表,然后通过遍历其中函数指针,并调用相应的函数。

为什么可以由父类的指针调用子类的对象的虚函数:

Derive d;//Derive 是Base的子类

Base *b1 = &d;//这必须使用父类的指针???

b1->f(); //Derive::f()

68、公有继承、受保护继承、私有继承
1)公有继承时,派生类对象可以访问基类中的公有成员,派生类的成员函数可以访问基类中的公有和受保护成员;公有继承时基类受保护的成员,可以通过派生类对象访问但不能修改。

2)私有继承时,基类的成员只能被直接派生类的成员访问,无法再往下继承;

3)受保护继承时,基类的成员也只被直接派生类的成员访问,无法再往下继承。

69、有哪几种情况只能用构造函数初始化列表而不能用赋值初始化?
答:const成员,引用成员

70、C++如何阻止一个类被实例化?一般在什么时候将构造函数声明为private?
1)将类定义为抽象基类或者将构造函数声明为private;

2)不允许类外部创建类对象,只能在类内部创建对象

71、类使用static成员的优点,如何访问?
(1)static 成员的名字是在类的作用域中,因此可以避免与其他类的成员或全局对象名字冲突;

(2)可以实施封装。static 成员可以是私有成员,而全局对象不可以;

(3) static 成员是与特定类关联的,可清晰地显示程序员的意图。

72、static数据成员和static成员函数
(1)static数据成员:
static数据成员独立于该类的任意对象而存在;static数据成员(const static数据成员除外)在类定义体内声明,必须在类外进行初始化。不像普通数据成员,static成员不能在类的定义体中初始化,只能在定义时才初始化。 static数据成员定义放在cpp文件中,不能放在初始化列表中。Const static成员可就地初始化。

变量定义:用于为变量分配存储空间,还可为变量指定初始值。程序中,变量有且仅有一个定义。

变量声明:用于向程序表明变量的类型和名字。

(2)static成员函数:
在类的外部定义,Static成员函数没有this形参,它可以直接访问所属类的static成员,不能直接使用非static成员。因为static成员不是任何对象的组成部分,所以static成员函数不能被声明为const。同时,static成员函数也不能被声明为虚函数。

73、C++的内部连接和外部连接
编译单元:当编译cpp文件时,预处理器首先递归包含头文件,形成一个编译单元。这个编译单元会被编译成为一个与cpp文件名同名的目标文件(.o或是.obj)。连接程序把不同编译单元中产生的符号联系起来,构成一个可执行程序。

内部连接:如果一个名称对于它的编译单元来说是局部的,并且在连接时不会与其它编译单元中的同样的名称相冲突,那么这个名称有内部连接:

a)所有的声明

b)名字空间(包括全局名字空间)中的静态自由函数、静态友元函数、静态变量的定义

c)enum定义

d)inline函数定义(包括自由函数和非自由函数)

e)类的定义

f)名字空间中const常量定义

g)union的定义

外部连接:在一个多文件程序中,如果一个名称在连接时可以和其它编译单元交互,那么这个名称就有外部连接。

以下情况有外部连接:

a)类非inline函数总有外部连接。包括类成员函数和类静态成员函数

b)类静态成员变量总有外部连接。

c)名字空间(包括全局名字空间)中非静态自由函数、非静态友元函数及非静态变量

74、变量的分类,全局变量和局部变量有什么区别?实怎么实现的?操作系统和编译器是怎么知道的?static全局变量与普通的全局变量有什么区别?static局部变量和普通局部变量有什么区别?static函数与普通函数有什么区别?
1)变量可以分为:全局变量、局部变量、静态全局变量、静态局部变量
全局变量在整个工程文件内都有效;静态全局变量只在定义它的文件内有效局部变量在定义它的函数内有效,这个函数返回会后失效。
静态局部变量只在定义它的函数内有效,只是程序仅分配一次内存,函数返回后,该变量不会消失,直到程序运行结束后才释放;全局变量和静态变量如果没有手工初始化,则由编译器初始化为0。局部变量的值不可知。
静态全局变量是定义存储类型为静态型的外部变量,其作用域是从定义点到程序结束,所不同的是存储类型决定了存储地点,静态型变量是存放在内存的数据区中的,它们在程序开始运行前就分配了固定的字节,在程序运行过程中被分配的字节大小是不改变的.只有程序运行结束后,才释放所占用的内存.
变量的作用域:
形参变量只在被调用期间才分配内存单元,调用结束立即释放。 这一点表明形参变量只有在函数内才是有效的, 离开该函数就不能再使用了。局部变量也称为内部变量。其作用域仅限于函数内, 离开该函数后再使用这种变量是非法的。
全局变量也称为外部变量,它不属于哪一个函数,它属于一个源程序文件。其作用域是整个源程序。在函数中使用全局变量,一般应作全局变量说明。 只有在函数内经过说明的全局变量才能使用。全局变量的说明符为extern。 但在一个函数之前定义的全局变量,在该函数内使用可不再加以说明。
对于全局变量还有以下几点说明:
外部变量可加强函数模块之间的数据联系, 但是又使函数要依赖这些变量,因而使得函数的独立性降低。从模块化程序设计的观点来看这是不利的, 因此在不必要时尽量不要使用全局变量。
在同一源文件中,允许全局变量和局部变量同名。在局部变量的作用域内,全局变量不起作用。
变量的存储方式可分为“静态存储”和“动态存储”两种。
静态存储变量通常是在变量定义时就分定存储单元并一直保持不变, 直至整个程序结束。动态存储变量是在程序执行过程中,使用它时才分配存储单元, 使用完毕立即释放。 如果一个函数被多次调用,则反复地分配、 释放形参变量的存储单元。从以上分析可知, 静态存储变量是一直存在的, 而动态存储变量则时而存在时而消失。我们又把这种由于变量存储方式不同而产生的特性称变量的生存期。 生存期表示了变量存在的时间。 生存期和作用域是从时间和空间这两个不同的角度来描述变量的特性,这两者既有联系,又有区别。 一个变量究竟属于哪一种存储方式, 并不能仅从其作用域来判断,还应有明确的存储类型说明。
从作用域看:
全局变量具有全局作用域。全局变量只需在一个源文件中定义,就可以作用于所有的源文件。当然,其他不包含全局变量定义的源文件需要用extern 关键字再次声明这个全局变量。
静态局部变量具有局部作用域,它只被初始化一次,自从第一次被初始化直到程序运行结束都一直存在,它和全局变量的区别在于全局变量对所有的函数都是可见的,而静态局部变量只对定义自己的函数体始终可见。
局部变量也只有局部作用域,它是自动对象(auto),它在程序运行期间不是一直存在,而是只在函数执行期间存在,函数的一次调用执行结束后,变量被撤销,其所占用的内存也被收回。
静态全局变量也具有全局作用域,它与全局变量的区别在于如果程序包含多个文件的话,它作用于定义它的文件里,不能作用到其它文件里,即被static关键字修饰过的变量具有文件作用域。这样即使两个不同的源文件都定义了相同名字的静态全局变量,它们也是不同的变量。
从分配内存空间看:
全局变量,静态局部变量,静态全局变量都在静态存储区分配空间,而局部变量在栈里分配空间。
全局变量本身就是静态存储方式,静态全局变量当然也是静态存储方式。这两者在存储方式上并无不同。这两者的区别虽在于非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。
1)、静态变量会被放在程序的静态数据存储区(全局可见)中,这样可以在下一次调用的时候还可以保持原来的赋值。这一点是它与堆栈变量和堆变量的区别。
2)、变量用static告知编译器,自己仅仅在变量的作用范围内可见。这一点是它与全局变量的区别。

程序的局部变量存在于(堆栈)中,全局变量存在于(静态区 )中,动态申请数据存在于( 堆)中。

75、应用程序在运行时的内存包括代码区和数据区,其中数据区又包括哪些部分?
对于一个进程的内存空间而言,可以在逻辑上分成 3个部份:代码区,静态数据区和动态数据区。

动态数据区一般就是“堆栈”。 栈是一种线性结构,堆是一种链式结构。进程的每个线程都有私有的“栈”。

全局变量和静态变量分配在静态数据区,本地变量分配在动态数据区,即堆栈中。程序通过堆栈的基地址和偏移量来访问本地变量。

76、C++里面是不是所有的动作都是main()引起的?如果不是,请举例。
比如全局变量的初始化,就不是由main函数引起的:

class A{};

A a; //a的构造函数限执行

int main() {}

77、异常框架
异常存在于程序的正常功能之外,并要求程序立即处理。 C++ 的异常处理包括: 1. throw 表达式,错误检测部分使用这种表达式来说明遇到了不可处理的错误。2. try 块,错误处理部分使用它来处理异常。try 语句块以 try 关键字开 始,并以一个或多个 catch 子句结束。在 try 块中执行的代码所抛出 (throw)的异常,通常会被其中一个 catch 子句处理。3. 由标准库定义的一组异常类,用来在 throw 和相应的 catch 之间传递有关的错误信息。 throw 表达式:if (!item1.same_isbn(item2))throw runtime_error(“Data must refer to same ISBN”);
try 块:try {program-statements} catch (exception-specifier) {handler-statements} catch (exception-specifier) {handler-statements}
函数在寻找处理代码的过程中退出在复杂的系统中,程序的执行路径也许在遇到抛出异常的代码之前,就已经经过了多个 try 块。抛出一个异常时,首先要搜索 的是抛出异常的函数。如果没有找到匹配的 catch,则终止这个函数的执行,并在调用这个函数的函数中寻找相配的 catch。如果仍然没有找到相应的处理代码,该函数同样要终止,搜索调用它的函数。直到找到适当类型的 catch 为止。

78、死锁及其预防和处理方法
死锁的规范定义如下:如果一个进程在等待只能由该进程停止才能引发的事件,那么该进程就是死锁的。

(1)产生死锁的原因
因为系统资源不足。
进程运行推进的顺序不合适。
资源分配不当等。

(2)产生死锁的四个必要条件
互斥条件:每个资源要么已经分配给了一个进程,要么就是可用的。
占有和等待条件:已经得到了某个资源的进程可以再请求新的资源。
不可抢占条件:已经分配给一个进程的资源不能强制性地被抢占,只能被占有它的进程显式地释放;
环路等待条件:死锁发生时,系统中一定有两个或者两个以上的进程组成的一条环路,该环路中的每个进程都在等待着下一个进程所占有的资源。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。

(3)处理死锁的四种策略:
鸵鸟策略(忽略死锁);
检测死锁并恢复;
仔细对资源进行分配,动态地避免死锁;
通过破坏引起死锁的四个必要条件之一,防止死锁的产生。
(4)死锁避免
死锁避免的主要算法是基于一个安全状态 的概念。在任何时刻,如果没有死锁发生,并且即使所有进程忽然请求对资源的最大请求,也仍然存在某种调度次序能够使得每一个进程运行完毕,则称该状态是安全的。从安全状态出发,系统能够保证所有进程都能完成,而从不安全状态出发,就没有这样的保证。银行家算法 :判断对请求的满足是否会进入不安全状态,如果是,就拒绝请求,如果满足请求后系统仍然是安全的,就予以分配。不安全状态不一定引起死锁,因为客户不一定需要其最大贷款额度。

79、ceph bluestore和filestore的区别,filestore为什么使用日志,以及分布式存储中,日志的作用
ceph后端支持多种存储引擎,以插件化的形式来进行管理使用,目前支持filestore,kvstore,memstore以及bluestore,目前默认使用的是filestore,但是目前bluestore也可以上生产。

1)Firestore存在的问题是:

在写数据前需要先写journal,会有一倍的写放大;
若是另外配备SSD盘给journal使用又增加额外的成本;
filestore一开始只是对于SATA/SAS这一类机械盘进行设计的,没有专门针对SSD这一类的Flash介质盘做考虑。
2)而Bluestore的优势在于:

减少写放大;
针对FLASH介质盘做优化;
直接管理裸盘,进一步减少文件系统部分的开销。
但是在机械盘场景Bluestore与firestore在性能上并没有太大的优势,bluestore的优势在于flash介质盘。

80、进程和线程的区别?进程和程序的区别?
进程是资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例。程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。线程是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位,一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量。线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。同样多线程也可以实现并发操作,每个请求分配一个线程来处理。
进程和程序的区别
进程是动态的,而程序是静态的。

进程有一定的生命期,而程序是指令的集合,本身无“运动”的含义。没有建立进程的程序不能作为1个独立单位得到操作系统的认可。1个程序可以对应多个进程,但1个进程只能对应1个程序。进程和程序的关系犹如演出和剧本的关系。

81、静态连接与动态链接的区别
静态链接
所谓静态链接就是在编译链接时直接将需要的执行代码拷贝到调用处,优点就是在程序发布的时候就不需要依赖库,也就是不再需要带着库一块发布,程序可以独立执行,但是体积可能会相对大一些。

动态链接

所谓动态链接就是在编译的时候不直接拷贝可执行代码,而是通过记录一系列符号和参数,在程序运行或加载时将这些信息传递给操作系统,操作系统负责将需要的动态库加载到内存中,然后程序在运行到指定的代码时,去共享执行内存中已经加载的动态库可执行代码,最终达到运行时连接的目的。优点是多个程序可以共享同一段代码,而不需要在磁盘上存储多个拷贝,缺点是由于是运行时加载,可能会影响程序的前期执行性能。

82、虚函数
简单地说,那些被virtual关键字修饰的成员函数,就是虚函数。虚函数的作用,用专业术语来解释就是实现多态性(Polymorphism),多态性是将接口与实现进行分离;用形象的语言来解释就是实现以共同的方法,但因个体差异,而采用不同的策略

83、构造函数中调用虚函数能发生多态吗?
Vptr指针初始化的过程:
1.对象在创建的时,由编译器对VPTR指针进行初始化
2.只有当对象的构造完全结束后VPTR的指向才最终确定
3.父类对象的VPTR指向父类虚函数表
4.子类对象的VPTR指向子类虚函数表

当定义一个子类对象的时候比较麻烦,因为构造子类对象的时候会首先调用父类的构造函数然后再调用子类的构造函数。当调用父类的构造函数的时候,此时会创建Vptr指针(也可以认为Vptr指针是属于父类的成员,所以在子类中重写虚函数的时候virtual关键字可以省略,因为编译器会识别父类有虚函数,然后就会生成Vptr指针变量),该指针会指向父类的虚函数表;然后再调用子类的构造函数,此时Vptr又被赋值指向子类的虚函数表。
(执行父类的构造函数的时候Vptr指针指向的是父类的虚函数表,所以只能执行父类的虚函数)
上面的过程是Vptr指针初始化的过程。
这是因为这个原因,在构造函数中调用虚函数不能实现多态。

84、是否可将类的每个成员函数声明为虚函数?
1.静态成员函数不能定义为虚函数
1.因为静态成员函数没有this指针,并且静态成员函数可以通过类名来访问。
2.又因为虚函数是放在对象的虚表里面的,同一个类中的所有对象虽然共用同一张虚表,但是类名无法找到虚表。

2.内联函数不能定义为虚函数
因为内联函数没有地址,而虚表里面存放的就是虚函数的地址。

3.构造函数不能定义为虚函数
1.因为虚函数是存放在对象的虚表里面,如果将构造函数定义为虚函数,则构造函数也必须存放在虚表里面,但是此时对象都还没有创建也就没有所谓的虚表。

85、父类指针和子类指针步长的问题?
C++中父类指针可以指向子类对象,很多时候这的确提供了方便之门。担当遇到对象数组时,就要慎重考虑了。指针运算是按指针所指向数据类型的长度进行计算的,对于子类对象数组,当使用父类指针指向子类对象时,指针的步长依然是父类对象所占的长度,指针移动后,所指的位置不一定就是下一个子类对象数组元素的起始地址。只要是指针,就要符合指针的运算方式,不管你是不是类指针;如果子类在继承了父类之后,没有增加属性,那么此时他们的步长一致,如果子类增加了属性那么子类的步长将大于父类。

父类指针和子类指针的步长

1) 铁律1:指针也只一种数据类型,C++类对象的指针p++/–,仍然可用。

2) 指针运算是按照指针所指的类型进行的。

p++《=》p=p+1 //p = (unsigned int)basep + sizeof(*p) 步长。

3) 结论:父类p++与子类p++步长不同;不要混搭,不要用父类指针++方式操作数组。

猜你喜欢

转载自blog.csdn.net/kevin_lp/article/details/89476736