1.什么是多态?为什么用多态?有什么好处?多态在什么地方用?

什么是多态?

概念:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,这就是多态性。简单的说:就是用基类的引用指向子类的对象。

为什么要用多态呢?

原因:我们知道,封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块(类);它们的目的都是为了——代码重用。而多态除了代码的复用性外,还可以解决项目中紧偶合的问题,提高程序的可扩展性.。耦合度讲的是模块模块之间,代码代码之间的关联度,通过对系统的分析把他分解成一个一个子模块,子模块提供稳定的接口,达到降低系统耦合度的的目的,模块模块之间尽量使用模块接口访问,而不是随意引用其他模块的成员变量。

多态有什么好处?

有两个好处:

  1. 应用程序不必为每一个派生类编写功能调用,只需要对抽象基类进行处理即可。大大提高程序的可复用性。//继承
  2. 派生类的功能可以被基类的方法或引用变量所调用,这叫向后兼容,可以提高可扩充性和可维护性。 //多态的真正作用,

2.C++中虚函数的动态绑定是怎么实现的?

在运行时,动态绑定的调用过程是这样的,首先,基类指针被赋值为派生类对象的地址,那么就可以找到指向这个类的虚函数的隐含指针,然后通过该虚函数的名字就可以在这个虚函数表中找到对应的虚函数的地址。然后进行调用就可以了。

3.多继承的子类有几个虚函数表

在多继承情况下,有多少个基类就有多少个虚函数表指针

4.虚函数可以定义为static变量吗?

不可以

static function 是静态决议(编译的时候就绑定了)
而virtual function 是动态决议的(运行时才绑定)
静态成员函数,可以不通过对象来调用,没有隐藏的this指针。
virtual函数一定要通过对象来调用,有隐藏的this指针。

如果定义为虚函数,那么它就是动态绑定的,也就是在派生类中可以被覆盖的,这与静态成员函数的定义本身就是相矛盾的。

5.什么是虚函数表?虚函数表中的内容是什么?

虚表是属于类的,而不是属于某个具体的对象,一个类只需要一个虚表即可 。同一个类的所有对象都使用同一个虚表。为了指定对象的虚表,对象内部包含一个虚表的指针,来指向自己所使用的虚表。为了让每个包含虚表的类的对象都拥有一个虚表指针,编译器在类中添加了一个指针,*__vptr,用来指向虚表。这样,当类的对象在创建时便拥有了这个指针,且这个指针的值会自动被设置为指向类的虚表。

虚表中有两部分内容:1、虚函数的地址;2、虚函数。

6.虚表是在编译时候产生的还是在运行的时候产生的?

虚拟函数表是在编译期就建立了,各个虚拟函数这时被组织成了一个虚拟函数的入口地址的数组.而对象的隐藏成员–虚拟函数表指针是在运行期–也就是构造函数被调用时进行初始化的,这是实现多态的关键。

7.虚函数表的原理?

虚函数按照其声明顺序放于表中。
父类的虚函数在子类的虚函数前面。

有虚函数覆盖时(派生类中定义基类中已有的同名函数):

覆盖的f()函数被放到了虚表中原来父类虚函数的位置。
没有被覆盖的函数依旧保持原样。

所以在实际调用发生时,是覆盖之后的f()被调用了。这就实现了多态。
https://www.cnblogs.com/Allen-rg/p/6927319.html

8.动态多态和静态多态的区别?

静态多态

优点:
由于静多态是在编译期完成的,因此效率较高,编译器也可以进行优化;
有很强的适配性和松耦合性,比如可以通过偏特化、全特化来处理特殊类型;
最重要一点是静态多态通过模板编程为C++带来了泛型设计的概念,比如强大的STL库。

缺点:
由于是模板来实现静态多态,因此模板的不足也就是静多态的劣势,比如调试困难、编译耗时、代码膨胀、编译器支持的兼容性不能够处理异质对象集合

动态多态

优点:
OO设计,对是客观世界的直觉认识;
实现与接口分离,可复用
处理同一继承体系下异质对象集合的强大威力

缺点:
运行期绑定,导致一定程度的运行时开销;
编译器无法对虚函数进行优化
笨重的类继承体系,对接口的修改影响整个类层次;

不同点:
本质不同,早晚绑定。静态多态在编译期决定,由模板具现完成,而动态多态在运行期决定,由继承、虚函数实现;
动态多态中接口是显式的,以函数签名为中心,多态通过虚函数在运行期实现,静态多台中接口是隐式的,以有效表达式为中心,多态通过模板具现在编译期完成

相同点:
都能够实现多态性,静态多态/编译期多态、动态多态/运行期多态;
都能够使接口和实现相分离,一个是模板定义接口,类型参数定义实现,一个是基类虚函数定义接口,继承类负责实现;

9.引用和指针的区别?

指针-对于一个类型T,T* 就是指向T的指针类型,也即一个T*类型的变量能够保存一个T对象的地址,而类型T是可以加一些限定词的,如const、volatile等等。
引用-引用是一个对象的别名,主要用于函数参数和返回值类型,符号X&表示X类型的引用。

不同点:

本质: 指针指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名,引用不改变指向。

引用不可以为空,但指针可以为空。定义一个引用的时候,必须初始化。因此使用指针之前必须做判空操作,而引用就不必。
引用不可以改变指向,对一个对象"至死不渝";但是指针可以改变指向,而指向其它对象。
引用的大小是所指向的变量的大小,因为引用只是一个别名而已;指针是指针本身的大小,4个字节(32位)。
const int* p-> 指向常量的指针,int * const p->本身是常量的指针.后者需要在定义的时候初始化。对于引用来讲int const & p=i;和 const int &p=i;没什么区别,都是指指向的对象是常量。
引用和指针的++自增运算符意义不同,指针的++表示的地址的变化,一般是向下4个字节的大小(一个只针的大小),引用的++就是对应元素的++操作。
指针传递和引用传递
指针传递参数本质上是值传递的方式,它所传递的是一个地址值。值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,即在栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。
引用传递过程中,被调函数的形式参数也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。

10.list, vector, map, set 区别与用法比较?

List封装了链表,Vector封装了数组, list和vector得最主要的区别在于vector使用连续内存存储的,他支持[]运算符,而list是以链表形式实现的,不支持[]。

Vector对于随机访问的速度很快,但是对于插入尤其是在头部插入元素速度很慢,在尾部插入速度很快。List对于随机访问速度慢得多,因为可能要遍历整个链表才能做到,但是对于插入就快的多了,不需要拷贝和移动数据,只需要改变指针的指向就可以了。另外对于新添加的元素,Vector有一套算法,而List可以任意加入。
Map,Set属于标准关联容器,使用了非常高效的平衡检索二叉树:红黑树,他的插入删除效率比其他序列容器高是因为不需要做内存拷贝和内存移动,而直接替换指向节点的指针即可。
Set和Vector的区别在于Set不包含重复的数据。Set和Map的区别在于Set只含有Key,而Map有一个Key和Key所对应的Value两个元素。
Map和Hash_Map的区别是Hash_Map使用了Hash算法来加快查找过程,但是需要更多的内存来存放这些Hash桶元素,因此可以算得上是采用空间来换取时间策略。

1 vector

向量 相当于一个数组
在内存中分配一块连续的内存空间进行存储。支持不指定vector大小的存储。STL内部实现时,首先分配一个非常大的内存空间预备进行存储,即capacituy()函数返回的大小,当超过此分配的空间时再整体重新放分配一块内存存储,这给人以vector可以不指定vector即一个连续内存的大小的感觉。通常此默认的内存分配能完成大部分情况下的存储。

优点:(1) 不指定一块内存大小的数组的连续存储,即可以像数组一样操作,但可以对此数组
进行动态操作。通常体现在push_back() pop_back()
(2) 随机访问方便,即支持[ ]操作符和vector.at()
(3) 节省空间。
缺点:(1) 在内部进行插入删除操作效率低。
(2) 只能在vector的最后进行push和pop,不能在vector的头进行push和pop。
(3) 当动态添加的数据超过vector默认分配的大小时要进行整体的重新分配、拷贝与释

2 list—双向链表

每一个结点都包括一个信息快Info、一个前驱指针Pre、一个后驱指针Post。可以不分配必须的内存大小方便的进行添加和删除操作。使用的是非连续的内存空间进行存储。
优点:(1) 不使用连续内存完成动态操作。
(2) 在内部方便的进行插入和删除操作
(3) 可在两端进行push、pop
缺点:(1) 不能进行内部的随机访问,即不支持[ ]操作符和vector.at()
(2) 相对于verctor占用内存多

3 deque
双端队列 double-end queue
deque是在功能上合并了vector和list。
优点:(1) 随机访问方便,即支持[ ]操作符和vector.at()
(2) 在内部方便的进行插入和删除操作
(3) 可在两端进行push、pop
缺点:(1) 占用内存多

使用区别:
1 如果你需要高效的随即存取,而不在乎插入和删除的效率,使用vector
2 如果你需要大量的插入和删除,而不关心随即存取,则应使用list
3 如果你需要随即存取,而且关心两端数据的插入和删除,则应使用deque

11.什么情况下会用到RB树?RB树比BST、AVL树的优势在哪?

如果你的应用中,搜索的次数远远大于插入和删除,那么选择AVL,如果搜索,插入删除次数几乎差不多,应该选择RB。

红黑树的查询性能略微逊色于AVL树,因为他比avl树会稍微不平衡最多一层,也就是说红黑树的查询性能只比相同内容的avl树最多多一次比较,但是,红黑树在插入和删除上完爆avl树,avl树每次插入删除会进行大量的平衡度计算,而红黑树为了维持红黑性质所做的红黑变换和旋转的开销,相较于avl树为了维持平衡的开销要小得多
红黑树是牺牲了严格的高度平衡的优越条件为 代价红黑树能够以O(logn)的时间复杂度进行搜索、插入、删除操作。此外,由于它的设计,任何不平衡都会在三次旋转之内解决。红黑树的算法时间复杂度和AVL相同,但统计性能比AVL树更高.

12.红黑树和哈希表,他们各适用于什么情况?

权衡三个因素: 查找速度, 数据量, 内存使用,可扩展性。
  总体来说,hash查找速度会比map快,而且查找速度基本和数据量大小无关,属于常数级别;而map的查找速度是log(n)级别。并不一定常数就比log(n) 小,hash还有hash函数的耗时,明白了吧,如果你考虑效率,特别是在元素达到一定数量级时,考虑考虑hash。但若你对内存使用特别严格, 希望程序尽可能少消耗内存,那么一定要小心,hash可能会让你陷入尴尬,特别是当你的hash对象特别多时,你就更无法控制了,而且 hash的构造速度较慢。

红黑树并不适应所有应用树的领域。如果数据基本上是静态的,那么让他们待在他们能够插入,并且不影响平衡的地方会具有更好的性能。如果数据完全是静态的,例如,做一个哈希表,性能可能会更好一些。

在实际的系统中,例如,需要使用动态规则的防火墙系统,使用红黑树而不是散列表被实践证明具有更好的伸缩性。Linux内核在管理vm_area_struct时就是采用了红黑树来维护内存块的。

13.构造函数与析构函数是什么?区别是什么?

构造函数:每个类都分别定义了它的对象被初始化的方式,类通过一个或几个特殊的成员函数来控制其对象的初始化过程,这些函数叫做构造函数。构造函数的任务是初始化对象的数据成员,构造函数最重要的作用是创建对象本身。

1.函数名和类名必须一样,没有返回值。

2.当没有显式的定义构造函数时,系统会自己生成默认的构造函数。

3.构造函数可以重载。

析构函数:析构函数初始化对象的非static数据成员,还可能做一些其他工作;析构函数释放对象使用的资源,并销毁对象非static数据成员。由于析构函数没有参数,所以它不能被重载。

无论何时一个对象被销毁,就会自动调用其析构函数(以下是销毁的部分):

1.变量在离开其作用域时被销毁。

2.当一个对象被销毁式,其成员被销毁。

3.容器(无论是标准库容器还是数组)被销毁时,其元素被销毁。

4.对于动态分配的对象,当对指向它的指针应用delete运算符时被销毁。

5.对于临时对象,当创建它的完整表达式结束时被销毁。

区别:

1.构造函数可以有参数,所以可以被重载。析构函数没有参数,所以不可以被重载。

2.在构造函数中,成员初始化实在函数体执行之前完成的,且按照它们在类中出现的顺序进行初始化。在一个析构函数中,首先执行函数体,然后销毁成员,成员按初始化顺序的逆序销毁。

3.在一个构造函数中,不存在类似构造函数中初始化列表的东西来控制成员如何销毁,析构部分是隐式的。成员销毁时发生什么完全依赖于成员的类型。销毁类类型的成员需要执行成员自己的析构函数。内置类型没有析构函数,因此销毁内置类型成员什么也不需要做。

14.当子类继承父类,由创建子类的实例到释放时,构造函数和析构函数的调用顺序?

当创建子类实例时,先调用父类的构造函数,再调用子类的构造函数。当要释放子类的对象时,先调用子类的析构函数,再调用父类的析构函数来销毁对象。

15.构造函数可不可以是虚函数?为什么?

不可以。虚函数对应一个vtable,这个vtable是存储在对象的内存空间的。如果构造函数是虚的,就需要通过vtable来调用,但是对象还没有实例化,也就是内存空间还没有,无法找到vtable,所以构造函数不能是虚函数。

16.析构函数可以是虚函数吗?为什么?

析构函数可以是虚函数。当一个基类有继承的子类时,基类的析构函数就必须是虚函数。如果不是虚函数的话会造成只调用基类的析构函数,从而导致子类中的内存泄漏。

17.什么是野指针?什么情况下会出现野指针?

野指针指向了一块随机内存空间,不受程序控制。如指针指向已经被删除的对象或者指向一块没有访问权限的内存空间,之后如果对其再解引用的话,就会出现问题。

1、指针定义时未被初始化:指针在被定义的时候,如果程序不对其进行初始化的话,它会指向随机区域,因为任何指针变量(除了static修饰的指针变量)在被定义的时候是不会被置空的,它的默认值是随机的。

2、指针被释放时没有被置空:我们在用malloc开辟内存空间时,要检查返回值是否为空,如果为空,则开辟失败;如果不为空,则指针指向的是开辟的内存空间的首地址。指针指向的内存空间在用free()或者delete(注意delete只是一个操作符,而free()是一个函数)释放后,如果程序员没有对其置空或者其他的赋值操作,就会使其成为一个野指针。

3、指针操作超越变量作用域:不要返回指向栈内存的指针或引用,因为栈内存在函数结束的时候会被释放,示例(转自高质量C++):

野指针的危害:

野指针的问题在于,指针指向的内存已经无效了,而指针没有被置空,解引用一个非空的无效指针是一个未被定义的行为,也就是说不一定导致段错误,野指针很难定位到是哪里出现的问题,在哪里这个指针就失效了,不好查找出错的原因。所以调试起来会很麻烦,有时候会需要很长的时间。

规避方法

初始化指针时将其置为NULL,之后再对其进行操作。

释放指针时将其置为NULL,最好在编写代码时将free()函数封装一下,在调用free()后就将指针置为NULL。

要想彻底地避免野指针,最好的办法就是养成一个良好的编程习惯。

18.什么是拷贝构造函数?怎么理解深拷贝和浅拷贝?

拷贝构造函数是一种特殊的构造函数,函数的名称必须和类名称一致。当用一个已初始化过了的自定义类类型对象去初始化另一个新构造的对象的时候,拷贝构造函数就会被自动调用。也就是说,当类的对象需要拷贝时,拷贝构造函数将会被调用。以下情况都会调用拷贝构造函数:
一个对象以值传递的方式传入函数体
一个对象以值传递的方式从函数返回
一个对象需要通过另外一个对象进行初始化。

如果在类中没有显式地声明一个拷贝构造函数,那么,编译器将会自动生成一个默认的拷贝构造函数,该构造函数完成对象之间的位拷贝(浅拷贝)。

浅拷贝资源后在释放资源的时候会产生资源归属不清的情况导致程序运行出错。在销毁对象时,两个对象的析构函数将对同一个内存空间释放两次,这就是错误出现的原因。我们需要的不是两个p有相同的值,而是两个p指向的空间有相同的值,解决办法就是使用“深拷贝”。

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

当出现类的等号赋值时,会调用拷贝函数,在未定义显示拷贝构造函数的情况下,系统会调用默认的拷贝函数——即浅拷贝,它能够完成成员的一一复制。当数据成员中没有指针时,浅拷贝是可行的。但当数据成员中有指针时,如果采用简单的浅拷贝,则两类中的两个指针将指向同一个地址,当对象快结束时,会调用两次析构函数,而导致指针悬挂现象。所以,这时,必须采用深拷贝。

深拷贝与浅拷贝的区别就在于深拷贝会在堆内存中另外申请空间来储存数据,从而也就解决了指针悬挂的问题。简而言之,当数据成员中有指针时,必须要用深拷贝。

https://www.cnblogs.com/wuchanming/p/4050969.html

19.介绍一下STL map容器?

map容器有四种,每一种都是由类模板定义的。所有类型的map容器保存的都是键值对的元素。map容器的元素是pair<const K, T>类型的对象,这种对象封装了一个T类型的对象和一个与其关联的K类型的键。pair元素中的键是const,因为修改键会扰乱容器中元素的顺序。每种map容器的模板都有不同的特性:

1、map容器:map的底层是由红黑树实现的,红黑树的每一个节点都代表着map的一个元素。该数据结构具有自动排序的功能,因此map内部的元素都是有序的,元素在容器中的顺序是通过比较键值确定的。默认使用 less 对象比较。

2、multimap容器:与map容器类似,区别只在于multimap容器可以保存键值相同的元素。

3、unordered_map容器:该容器的底层是由哈希(又名散列)函数组织实现的。元素的顺序并不是由键值决定的,而是由键值的哈希值确定的,哈希值是由哈希函数生成的一个整数。利用哈希函数,将关键字的哈希值都放在一个桶(bucket)里面,具有相同哈希值的放到同一个桶。unordered_map内部元素的存储是无序的,也不允许有重复键值的元素,相当于java中的HashMap。

4、unordered_multimap容器:也可以通过键值生成的哈希值来确定对象的位置,但是它允许有重复的元素。

map和multimap容器的模板都定义在map头文件中,unordered_map和unordered_multimap容器的模板都定义在unordered_map头文件中中。

multi前缀表明键值不必唯一,但是如果没有这个前缀,键值必须唯一。
unordered前缀表明容器中元素的位置是通过其键值所产生的哈希值来决定的,而不是通过比较键值决定的,即容器中的元素是无序的。如果没有这个前缀,则容器中元素是由比较键值决定的,即有序。
在这里插入图片描述

map容器的底层是由红黑树(一种非严格意义上的平衡二叉树)实现的,元素检索的时间复杂度是O(logN),相比于顺序检索的线性时间复杂度还是很快的。、

20.hash表内部是怎么实现的?怎么处理冲突?如果是字符串又怎么处理冲突?

哈希表hashtable(key,value) 就是把Key通过一个固定的算法函数既所谓的哈希函数转换成一个整型数字,然后就将该数字对数组长度进行取余,取余结果就当作数组的下标,将value存储在以该数字为下标的数组空间里。

hashmap 是以数组为基础。首先将关键字通过哈希函数,获得存储位置,即数组的下标。当两个关键字,计算出的数组下标相同时,需要使用链表。即数组中每一个元素都可以看作是链表。

处理冲突的方法:

开放定址法 再哈希法 链地址法 建立公共溢出区

链地址法处理冲突:将产生冲突的值以链表的形式连起来

好处:不会产生堆积,适合无法确定表长的情况,但是会增加空间消耗(指针需要空间)

方法:采用结构体数组的方式,首先将结构体内每一个元素赋值 -1 NULL,然后采用插入的方式产生链表,同一个函数值得元素在一条链表上

如果是字符串的处理冲突方法:可以使用BKDRHash法。

由一个字符串(比如:ad)得到其哈希值,为了减少碰撞,应该使该字符串中每个字符都参与哈希值计算,使其符合雪崩效应,也就是说即使改变字符串中的一个字节,也会对最终的哈希值造成较大的影响。我们直接想到的办法就是让字符串中的每个字符相加,得到其和SUM,让SUM作为哈希值。

21.什么是C#中的反射?

反射是一种机制 , 通过这种机制我们可以知道一个未知类型的类型信息 . 比如 , 有一个对象 a, 这个对象不是我们定义的,也许是通过网络捕捉到的,也许是使用泛型定义的,但我们想知道这个对象的类型信息,想知道这个对象有哪些方法或者属性什么的.甚至我们想进一步调用这个对象的方法.关键是现在我们只知道它是一个对象,不知道它的类型,自然不会知道它有哪些方法等信息.这时我们该怎么办?反射机制就是解决这么一个问题的.通过反射机制我们可以知道未知类型对象的类型信息.
  再比如,我们有一个 dll 文件,我们想调用里面的类.现在假设这个 dll 文件的类的定义,数量等不是固定的,是经常变化的.也许某一天你要在这个 dll 里面增加一个类定义.也许你觉得这没什么问题,现在关键是我们在另一个程序集里面要调用这个 dll ,这是我们的程序必须能够适应这个 dll 的变化,也就是说即使改变了 dll 文件的定义也不需要改变我们的程序集.这时候我们就会使用一个未知 dll .我们该怎么办?同样,反射机制帮助了我们,我们可以通过反射来实现.

说白了,反射就是能知道我们未知类型的类型信息这么一个东西.

22.void*的作用

用void* 定义一个void类型的指针,它不指向任何类型的数据,意思是,void指针“指向空类型”或“不指向确定的类型”,而不要理解为void指针能指向“任何的类型”数据。简而言之:void只提供一个地址,没有指向。
void
指针不指向任何数据类型,它属于一种未确定类型的过渡型数据,因此如果要访问实际存在的数据,必须将void指针强转成为指定一个确定的数据类型的数据,如int、string等。void指针只支持几种有限的操作:与另一个指针进行比较;向函数传递void指针或从函数返回void指针;给另一个void指针赋值。不允许使用void指针操作它所指向的对象,例如,不允许对void指针进行解引用。不允许对void指针进行算术操作。

23.一个vector能存多个不同类型的元素吗?

可以。先将要存的多个不同类型的数据向上转型为obj类型,之后即可存放进vector中,但是这样做有个很大的缺点:就是很难取出。因为取出时需要知道每一个对应的类型,再向下转型才可以。

24.Struct和Union的区别

1.在存储多个成员信息时,编译器会自动给struct第个成员分配存储空间,struct 可以存储多个成员信息,而Union每个成员会用同一个存储空间,只能存储最后一个成员的信息。

2.都是由多个不同的数据类型成员组成,但在任何同一时刻,Union只存放了一个被先选中的成员,而结构体的所有成员都存在。

3.对于Union的不同成员赋值,将会对其他成员重写,原来成员的值就不存在了,而对于struct 的不同成员赋值 是互不影响的。

25.可以在头文件定义static变量吗?这意味着什么?

可以,在头文件中定义static变量时,每当其他模块#include一次,则会申请一次内存,相当于生成了多个局部变量,会造成空间资源的浪费。如果在源文件内定义static,在其他模块引用则需要加上extern关键字。

26.虚函数可以定义成static吗?

不可以,简单的说就是static没有this指针。静态函数都是在编译器绑定的,而虚函数是运行期动态绑定。

27.面对过程和面对对象语言的区别是什么?

面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。

28.如何解决UDP的丢包现象?

模拟tcp三次握手协议,通过使用Timer定时器监视发送请求后接受数据的时间,如果一段时间内没有接受到数据包则判定丢包,并重新发送本次请求。

29.重载new操作符的意义?

1.可以帮助检查错误,通过重载操作符可以跟踪所有的内存泄漏。
2.不需要修改原有的代码,只需要重载运算符即可。

30.系统自带的默认析构函数有什么用?

当用户没有显式定义析构函数时, 编译器同样会为对象生成一个默认的析构函数, 但默认生成的析构函数只能释放类的普通数据成员所占用的空间, 无法释放通过 new 或 malloc 进行申请的空间, 因此有时我们需要自己显式的定义析构函数对这些申请的空间进行释放, 避免造成内存泄露。

31.vector的push_back底层原理?

首先它是一种浅拷贝。
如果没有备用空间,在备用空间先申请一块原来内存2倍的内存,之后调用拷贝构造函数把数据赋值给vector的finish迭代器指向的内存。
如果有备用空间,在已有内存上调用拷贝构造函数赋值给finish迭代器指向的内存。

push_back源码:

template <class T, class Alloc = alloc>
void vector::push_back(const T& x)
{
	//如果还有备用空间,则在备用空间起始处构造一个元素,并以vector最后一个值为其初值
	if (finish != end_of_storage)construct(++finish, x);
	//如果没有备用空间...
	else insert_aux(finish, x);
}

32.C++中二维数组是如何存放的?

按行存储,计算机在cpu与内存之间有一个CPU cache。它的内存很小但是交换速度却很快。访问数组元素时,CPU不会每次只从内存中读取一个元素,而是读取一个区域的元素。当访问第一个元素时,CPU也会读取它的相邻元素,因此无论是按行访问还是按列访问,CPU访问主存的数量都相同。随着数组元素越来越多,CPU缓存一次只能读取数组不到一行的数据,因此按列访问会慢得多。

33.一个类中有多个构造函数,执行的时候会怎么样?

尽管在一个类中可以包含多个构造函数,但是对于每一个对象来说,建立对象时只执行其中一个构造函数,并非每个构造函数都被执行。

34.C#中for和foreach哪个更快,为什么?

for更快,因为foreach的本质是调用IEnumerator迭代器使用GetEnumerator()方法申请一个迭代器逐个遍历,并且在MoveNext()为空时还会GC,这就给了编译器很大的压力。

35.C++中空类所占内存的大小,有普通函数的类呢?有虚函数的类呢?

空类的大小是1个字节,因为C++要求每个实例在内存中都必须有一个独一无二的地址,空类在实例化的时候编译器会给空类隐匿的加一个字节。普通函数不占用类的内存,类内是通过指针来访问成员函数。拥有虚函数的类,类内会存储一个虚指针,对于一个类,有且只有一个虚表,对应的也只有一个虚指针,一个虚指针占4个字节大小。所以无论一个类有几个虚函数,类的大小都是4字节。

36.i++和++i哪个执行效率更高,为什么?

++i的效率更高,因为表达式执行的是i加1前的副本,所以先必须保存副本。从代码内部可以看到多了一个保存临时对象的操作。

37.new和malloc创建的变量或对象存放在虚拟内存还是物理内存上?

虚拟内存上,物理内存不足以存放你所要求的那么大的容量,
所以在没有操作的时候,
数据可能部分的会在虚拟内存中,
当需要使用的时候才从虚拟内存中调入物理内存

38.哪些排序算法是稳定的?

基数排序、冒泡排序、直接插入排序、折半插入排序、归并排序是稳定的排序算法。

39.shadergraph中加和乘有什么区别?

加法适合于融合效果,越乘越黑
乘法适合于发光效果,越加越亮

40.深度测试有办法提前吗?为什么要提前?

使用Early-Z技术可以提前,防止过度绘制,因为先进行深度测试,可以让着色器只对将来会出现在屏幕上的像素进行绘制,而不必对不会出现在屏幕上的像素绘制,这样会节省大量资源。

41.Early-Z可以和AlphaTest一起使用吗?为什么不能?

一旦进行了手动写入深度值、开启alpha test或者丢弃像素等操作,那么gpu就会关闭early-z直到下次clear z-buffer后才会重新开启,之所以gpu会选择关闭early-z是因为上述那些操作可能会在片元阶段与late-z阶段之间修改深度缓存中的深度值,导致提前的early-z的结果并不正确。

42.说一下各个算法的最优最差平均时间复杂度以及空间复杂度,是否稳定?

在这里插入图片描述

43.说一下C++中三种继承方式?

在这里插入图片描述

44.说一下页表的概念

页表是一种特殊的数据结构,放在系统空间的页表区,存放逻辑页与物理页帧的对应关系。在虚拟内存中通过页表中页号到页帧号的映射关系来找到物理内存。

45.说一下向量点乘和叉乘的几何意义?

点乘:又叫数量积,是一个向量和它在另一个向量上投影的长度的乘积,是标量。点乘反映了两个向量的相似度,两个向量越相似他们的点乘就越大。一般用于计算两个向量之间的夹角
叉乘:又叫向量积,两个向量的叉乘的结果是法向量。一般通过两个向量的叉乘获得法向量来构建一个xyz一坐标系。

46.什么是栈帧?栈帧的数据结构是什么?

栈帧也叫过程活动记录,是编译器用来实现过程/函数调用的一种数据结构。栈帧又是记录在栈上面的,一个栈上有N个栈帧的实体,换句话说也就是栈帧将栈分割成了N个记录块,但是这些记录块大小不是固定的。所以栈帧的本质就是栈。

47.两个线程同时对同个内存变量调用了i++ 100次,结束后i值可能的变化

2-200

首先,i++的操作并不是单独的一条指令,他实际在内部是由三条指令构成的

1.首先,他把i的值从CPU寄存器中拿出来
2.然后,把寄存器中的值+1
3.最后,把寄存器写回到内存中

结果为2的操作:

1.线程1执行第一次i++的前半部分操作,把i从寄存器中取出来,这时i为0,i+1,这时寄存器CPU1中寄存器值为1,内存中为0,因为没有放回去。
2.线程2执行第二次i++的前半部分操作,把i从寄存器中取出来,这时i为0,i+1,这时寄存器CPU2中寄存器值为1,内存中为0,因为没有放回去
3.线程1继续执行,直到第99次操作,并把值写到内存中,此时CPU1中寄存器的值为99,内存中为99
4.线程2继续执行第一次操作,将其值放回内存,此时CPU2中的寄存器值为1,内存中为1
5.线程1执行第100次i++,将内存中的值取回CPU1的寄存器,并执行加1,此时CPU1的寄存器中的值为2,内存中为1
6.线程2执行完所有操作,并将其放回内存,此时CPU2的寄存器值为100,内存中为100
7.线程1执行100次操作的最后一部分,将CPU1中的寄存器值放回内存,内存中值为2

结果为200的操作

1.线程1执行完第一次i++,把结果写回内存,此时内存中值为1.
2.线程2抢到CPU,执行第一次i++,把结果写回内存,此时内存中值为2.
3.两个线程交替执行,最终结果为200

48.重写和重载的区别

重载实现的是编译时的多态性,而重写实现的是运行时的多态性。

重载(overload):发生在同一个类中,方法名相同,参数不同,参数包括:参数的个数,参数类型,参数的类型顺序。

重写(override):发生在继承过程中方法名相同、参数相同、返回值类型相同。

49.内存的分配方式

1,从静态存储区域分配内存。程序编译的时候内存已经分配好了,并且在程序的整个运行期间都存在,例如全局变量。
2,在栈上创建。在执行函数时,函数内局部变量的存储单元可以在栈上创建,函数结束时这些存储单元自动被释放。处理器的指定集中有关于栈内存的分配运算,因此效率比较高,但是分配的内存容量有限。
3,在堆上分配内存,亦称动态内存分配,程序在运行的时候用malloc函数或new运算符申请任意大小的内存,程序员要用free函数或delete运算符释放内存。

50.7层模型与对应的协议

在这里插入图片描述
51.派生类的构造函数中调用虚函数执行的是哪个?
答:执行的是基类的虚函数。当一个类的构造函数执行时,第一件事就是建立虚指针,因为虚指针存放在一个类的前四个字节,而此派生类的构造函数会对当前被构造的对象的虚指针进行重写,此时派生类的构造函数还未执行完毕,虚指针还未重写,所以还指向基类的虚表。

51.什么时候要为传入引用参数加上const?

  1. 它告诉编译器,这个参数是一个常量,首先你在函数内部不能改变它;
  2. 其次,如果在函数内部需要多次引用这个值,CPU不必每次都重新读取,直接使用第一次读取的值(我想应该是存放在寄存器文件中的)。
  3. 如果在需要 const 引用时,将形参定 义为普通引用,则会导致const对象和需要类型转换的对象不能调用该函数,会增加函数的局限性

52.普通引用和const引用的区别

普通引用无法绑定一个变量,const可以绑定一个变量。
比如:
int & rm = 3;//错误
const int & rm = 3;//正确

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值