2018网易游戏开发面试题(经验)锦集

5 篇文章 0 订阅
5 篇文章 0 订阅

一、C++部分

1.1 malloc和new之前的区别以及底层实现

 

 相关文章:

https://blog.csdn.net/nie19940803/article/details/76358673

https://blog.csdn.net/seamanj/article/details/81019920

https://www.cnblogs.com/sinaxyz/archive/2012/08/20/2647631.html

 

1.2 什么是多态

C++多态方式:

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

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

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

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

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

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

相关文章:

https://www.cnblogs.com/Allen-rg/p/6927129.html

https://www.cnblogs.com/dormant/p/5223215.html

http://blog.jobbole.com/107432/

https://blog.csdn.net/haoel/article/details/1948051/

 

1.3 虚析构函数的作用

基类定义了虚函数,子类可以重写该函数,当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态地调用属于子类的该函数,且这样的函数调用是无法在编译器期间确认的,而是在运行期确认,也叫做迟绑定。

定义一个函数为虚函数,不代表函数为不被实现的函数。

定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。

定义一个函数为纯虚函数,才代表函数没有被实现。

定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。

(1)当派生类的对象从内存中撤销时,一般先调用派生类的析构函数释放该对象中的派生类部分,再调用基类的析构函数释放该对象中的基类部分,从而能够完整的释放该对象内存。

(2)但是,当用基类指针指向了一个派生类对象,即 base *bptr = new child;此时用delete bptr;来撤销bptr 指向的动态存储空间时,只会执行基类的析构函数来释放该堆内存中的基类部分,但是并不会执行派生类的析构函数来释放该堆内存中的派生类部分。此时,就会造成内存泄漏现象。

(3)为了避免此类现象发生,我们将基类的析构函数声明为虚析构函数,这样就解决了上述问题(即先调用派生类的析构函数释放该动态空间中的派生类部分,再调用基类的析构函数释放该动态空间中的基类部分,从而能够完整的释放该堆内存)。

(4)如果将基类的析构函数声明为虚析构函数,那么该基类的所有派生类的析构函数都自动成为虚析构函数。

相关文章:

https://www.cnblogs.com/milanleon/p/6479697.html

https://www.zhihu.com/question/23971699

 

1.4 虚函数的底层的实现

实现原理:虚函数表+虚表指针

关键字:虚函数底层实现机制;虚函数表;虚表指针

编译器处理虚函数的方法是:为每个类对象添加一个隐藏成员,隐藏成员中保存了一个指向函数地址数组的指针,称为虚表指针(vptr),这种数组成为虚函数表(virtual function table, vtbl),即,每个类使用一个虚函数表,每个类对象用一个虚表指针

举个例子:基类对象包含一个虚表指针,指向基类中所有虚函数的地址表。派生类对象也将包含一个虚表指针,指向派生类虚函数表。看下面两种情况:

  • 如果派生类重写了基类的虚方法,该派生类虚函数表将保存重写的虚函数的地址,而不是基类的虚函数地址。
  • 如果基类中的虚方法没有在派生类中重写,那么派生类将继承基类中的虚方法,而且派生类中虚函数表将保存基类中未被重写的虚函数的地址。注意,如果派生类中定义了新的虚方法,则该虚函数的地址也将被添加到派生类虚函数表中。

相关文章:

https://blog.csdn.net/wanghaobo920/article/details/7674631

https://blog.csdn.net/chenchong_219/article/details/41967321

https://www.cnblogs.com/Allen-rg/p/6927319.html

https://blog.csdn.net/m0_37316917/article/details/70143151

https://blog.csdn.net/f1033774377/article/details/80488867

 

1.5 数组和链表的区别

  • 数组静态分配内存,链表动态分配内存;
  • 数组在内存中连续,链表不连续;
  • 数组元素在栈区,链表元素在堆区;
  • 数组利用下标定位,时间复杂度为O(1),链表定位元素时间复杂度O(n);
  • 数组插入或删除元素的时间复杂度O(n),链表的时间复杂度O(1);

相关文章:

https://www.cnblogs.com/janneystory/p/5758958.html

https://blog.csdn.net/snow_wu/article/details/53172721

https://blog.csdn.net/hao10119/article/details/80412472

 

1.6 C++容器迭代器失效

vector迭代器的失效情况: 

1.当插入(push_back)一个元素后,end操作返回的迭代器肯定失效。

2.当插入(push_back)一个元素后,capacity返回值与没有插入元素之前相比有改变,则需要重新加载整个容器,此时begin和end操作返回的迭代器都会失效。 
3.当进行删除操作(erase,pop_back)后,指向删除点的迭代器全部失效;指向删除点后面的元素的迭代器也将全部失效。 
deque迭代器的失效情况: 
1.在deque容器首部或者尾部插入元素不会使得任何迭代器失效。 
2.在其首部或尾部删除元素则只会使指向被删除元素的迭代器失效。 
3.在deque容器的任何其他位置的插入和删除操作将使指向该容器元素的所有迭代器失效。 

List/set/map迭代器的失效情况: 

删除时,指向该删除节点的迭代器失效

list<int> intList; 
list<int>::iterator it = intList.begin(); 
while(it != intList.end()) 

    it = intList.erase(it); 
    …… 

相关文章:

https://www.cnblogs.com/blueoverflow/p/4923523.html

https://blog.csdn.net/lujiandong1/article/details/49872763

https://www.cnblogs.com/Commence/p/7526421.html

https://blog.csdn.net/mr_chenping/article/details/30548507

 

1.7 指针和引用的区别

为了进一步加深大家对指针和引用的区别,下面我从编译的角度来阐述它们之间的区别:

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

引用与指针的区别

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

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

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

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

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

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

参考答案:

(1)当引用被创建时,它必须被初始化。而指针则可以在任何时候被初始化。

(2)一旦一个引用被初始化为指向一个对象,它就不能被改变为对另一个对象的引用。而指针则可以在任何时候指向另一个对象。

(3)不可能有NULL引用。必须确保引用是和一块合法的存储单元关联。

进一步解析:

指针和引用都是C++中的基本语法成份,它们既有联系,也有不同之处。

它们都是地址的概念,其中指针指向一块内存,它的内容是所指内存的地址;而引用是某块内存的别名,具体来说,指针是一个变量的地址,引用是一个变量的别名。

但它们的不同之处也很明显,体现在以下方面:

  • 指针是一个实体,而引用仅是个别名;
  • 引用必须被初始化,指针不必;
  • 引用只能在定义时被初始化一次,之后不可变;指针可以改变所指的对象;
  • 可以有const指针,但是没有const引用;
  • 不存在指向空值的引用,但是存在指向空值的指针,即引用不能为空,指针可以为空;
  • “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小;
  • 指针和引用的自增(++)运算意义不一样;
  • 程序为指针变量分配内存区域,而引用不需要分配内存区域;
  • 指针可以有多级,但是引用只能是一级,例如int **p是合法的,而 int &&a;是不合法的;
  • 指针和引用作为函数参数进行传递时也不同。用指针传递参数,可以实现对实参进行改变的目的;在将引用作为函数参数进行传递时,实质上传递的是实参本身,而不是实参的一个拷贝,因此对形参的修改其实是对实参的修改。

相关文章:

https://www.cnblogs.com/x_wukong/p/5712345.html

https://blog.csdn.net/zhengqijun_/article/details/54980769

http://chuansong.me/n/2850726

 

1.8 C++中std::function模板和std::bind函数的使用

std::function

它是函数、函数对象、函数指针、和成员函数的包装器,可以容纳任何类型的函数对象,函数指针,引用函数,成员函数的指针。
以统一的方式处理函数、函数对象、函数指针、和成员函数。
允许保存和延迟执行函数。

std::bind绑定器

  • 将函数、成员函数和闭包转成function函数对象
  • 将多元(n>1)函数转成一元函数或者(n-1)元函数。

 

1、定义

bind(F f, T1 t1, T2 t2, ..., TN tN);

具体为:

bind(&要调用的函数,&对象, 要调用函数的参数1,要调用函数的参数2...,_1(bind函数的参数1),_2(bind函数的参数2)...)

注:如果bind的是一个非静态成员函数,第二个参数一定是一个该成员的一个指针,后面才是正常的参数。

2、bind使用形式

(1)bind(&f)()  假设f是一个全局函数,绑定全局函数并调用;

(2)bind (&A::f, A())()  假设A是一个构造函数为空的类,这个形式绑定了类的成员函数,故第二个参数需要传入一个成员(成员静态函数除外);

(3)bind (&A::f, _1)(new A()) 同上,效果是一样的,但是使用了占位符,使得没有固定的的对象,推荐。

注:使用的时候一定要注意指向的是没有this指针的函数(全局函数或静态成员函数),还是有this指针的函数。后面一种必须要用bind()函数,而且要多一个参数,因为静态成员函数与非静态成员函数的参 数表不一样,原型相同的非静态函数比静态成员函数多一个参数,即第一个参数this指针,指向所属的对象,任何非静态成员函数的第一个参数都是this指针。

核心理解绑定类的成员函数。

相关文章:

https://blog.csdn.net/cr2066/article/details/52145719

https://www.cnblogs.com/bencai/p/9124654.html

https://blog.csdn.net/u011602557/article/details/70162609

https://blog.csdn.net/hanzhenling/article/details/45483913

https://blog.csdn.net/yusiguyuan/article/details/37102283

 

1.9 Lambda表达式

声明Lambda表达式

Lambda表达式完整的声明格式如下:

[capture list] (params list) mutable exception-> return type { function body }

各项具体含义如下

  1. capture list:捕获外部变量列表
  2. params list:形参列表
  3. mutable指示符:用来说用是否可以修改捕获的变量
  4. exception:异常设定
  5. return type:返回类型
  6. function body:函数体

相关文章:

https://www.cnblogs.com/DswCnblog/p/5629165.html

https://www.cnblogs.com/langzou/p/5962033.html

 

1.10 C++仿函数(functor)

1 仿函数的概念

仿函数,又名函数对象,是一个定义了operator ()的对象。仿函数的主要功能代码在仿函数类的operator ()体内完成。仿函数的妙处:

(1) 仿函数比一般函数更灵巧,可以用有状态,对于仿函数可以同时拥有两个状态的不同实体。

(2) 每个仿函数都有其型别,通过传递不同型别的仿函数当作template参数给容器,可以构造出型别不同的容器。

(3) 执行速度上,仿函数通常比函数指针快。

很多stl算法有一个函数参数,例如remove_if,for_each等,这个函数可以是普通的全局函数,仿函数,类的成员函数(非static,static可以作为全局函数使用),类的成员函数比较特殊,需要使用适配器函数mem_fun/mem_fun_ref包装才可以。

相关文章:

https://blog.csdn.net/raito__/article/details/51612690

https://www.cnblogs.com/landy126/archive/2013/03/09/2951227.html

 

1.11 static_cast与dynamic_cast

用法:static_cast < type-id > ( exdivssion ) 
该运算符把exdivssion转换为type-id类型,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:
①用于类层次结构中基类和子类之间指针或引用的转换。
  进行上行转换(把子类的指针或引用转换成基类表示)是安全的;
  进行下行转换(把基类指针或引用转换成子类表示)时,由于没有动态类型检查,所以是不安全的。
②用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
③把空指针转换成目标类型的空指针。
④把任何类型的表达式转换成void类型。

注意:static_cast不能转换掉exdivssion的const、volitale、或者__unaligned属性。


3.2 dynamic_cast
用法:dynamic_cast < type-id > ( exdivssion )
该运算符把exdivssion转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void *;
如果type-id是类指针类型,那么exdivssion也必须是一个指针,如果type-id是一个引用,那么exdivssion也必须是一个引用。

dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。
在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;
在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。

一、static_cast关键字(编译时类型检查)

用法:static_cast < type-id > ( expression ),该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性,它主要有如下几种用法:

(1)用于基本数据类型之间的转换,如把int转换为char,把int转换成enum,但这种转换的安全性需要开发者自己保证(这可以理解为保证数据的精度,即程序员能不能保证自己想要的程序安全),如在把int转换为char时,如果char没有足够的比特位来存放int的值(int>127或int<-127时),那么static_cast所做的只是简单的截断,及简单地把int的低8位复制到char的8位中,并直接抛弃高位。

(2)把空指针转换成目标类型的空指针

(3)把任何类型的表达式类型转换成void类型

(4)用于类层次结构中父类和子类之间指针和引用的转换。

对于以上第(4)点,存在两种形式的转换,即上行转换(子类到父类)和下行转换(父类到子类)。对于static_cast,上行转换时安全的,而下行转换时不安全的,为什么呢?因为static_cast的转换时粗暴的,它仅根据类型转换语句中提供的信息(尖括号中的类型)来进行转换,这种转换方式对于上行转换,由于子类总是包含父类的所有数据成员和函数成员,因此从子类转换到父类的指针对象可以没有任何顾虑的访问其(指父类)的成员。而对于下行转换为什么不安全,是因为static_cast只是在编译时进行类型坚持,没有运行时的类型检查,具体原理在dynamic_cast中说明。

二、dynamic_cast关键字(运行时类型检查)

用法:同static_cast

dynamic_cast主要用于类层次结构中父类和子类之间指针和引用的转换,由于具有运行时类型检查,因此可以保证下行转换的安全性,何为安全性?即转换成功就返回转换后的正确类型指针,如果转换失败,则返回NULL,之所以说static_cast在下行转换时不安全,是因为即使转换失败,它也不返回NULL。

对于上行转换,dynamic_cast和static_cast是一样的。

对于下行转换,说到下行转换,有一点需要了解的是在C++中,一般是可以用父类指针指向一个子类对象,如parent* P1 = new Children(); 但这个指针只能访问父类定义的数据成员和函数,这是C++中的静态联翩,但一般不定义指向父类对象的子类类型指针,如Children* P1 = new parent;这种定义方法不符合生活习惯,在程序设计上也很麻烦。这就解释了也说明了,在上行转换中,static_cast和dynamic_cast效果是一样的,而且都比较安全,因为向上转换的对象一般是指向子类对象的子类类型指针;而在下行转换中,由于可以定义就不同了指向子类对象的父类类型指针,同时static_cast只在编译时进行类型检查,而dynamic_cast是运行时类型检查,则需要视情况而定。

相关文章:

https://blog.csdn.net/hahaha_val/article/details/79384723

https://blog.csdn.net/qq_26849233/article/details/62218385

https://www.cnblogs.com/rednodel/p/5800142.html

http://www.cnblogs.com/jerry19880126/archive/2012/08/14/2638192.html

 

1.12 STL标准库六大组件

STL提供六大组件,彼此可以组合套用:

  1. 容器(Containers):各种数据结构,如:vector、list、deque、set、map。用来存放数据。从实现的角度来看,STL容器是一种class template。
    1. https://www.cnblogs.com/geekpaul/tag/STL/
    2. https://blog.csdn.net/caojunhao123/article/details/11907857
  2. 算法(algorithms):各种常用算法,如:sort、search、copy、erase。从实现的角度来看,STL算法是一种 function template。
  3. 迭代器(iterators):容器与算法之间的胶合剂,是所谓的“泛型指针”。共有五种类型,以及其他衍生变化。从实现的角度来看,迭代器是一种将 operator*、operator->、operator++、operator- - 等指针相关操作进行重载的class template。所有STL容器都有自己专属的迭代器,只有容器本身才知道如何遍历自己的元素。原生指针(native pointer)也是一种迭代器。
    1. https://www.cnblogs.com/wxquare/p/4699429.html
    2. https://www.cnblogs.com/maluning/p/8570717.html
    3. https://www.cnblogs.com/bhlsheji/p/4842539.html

4、 仿函数(functors):行为类似函数,可作为算法的某种策略(policy)。从实现的角度来看,仿函数是一种重载了operator()的class或class template。一般的函数指针也可视为狭义的仿函数。

5、 配接器(adapters);一种用来修饰容器、仿函数、迭代器接口的东西。例如:STL提供的queue 和 stack,虽然看似容器,但其实只能算是一种容器配接器,因为它们的底部完全借助deque,所有操作都由底层的deque供应。改变 functors接口者,称为function adapter;改变 container 接口者,称为container adapter;改变iterator接口者,称为iterator adapter。

  1. https://blog.csdn.net/jakemiao/article/details/24815713
  2. https://www.cnblogs.com/yongpan/p/7966865.html

6、 配置器(allocators):负责空间配置与管理。从实现的角度来看,配置器是一个实现了动态空间配置、空间管理、空间释放的class template。

这六大组件的交互关系:container(容器) 通过 allocator(配置器) 取得数据储存空间,algorithm(算法)通过 iterator(迭代器)存取 container(容器) 内容,functor(仿函数) 可以协助 algorithm(算法) 完成不同的策略变化,adapter(配接器) 可以修饰或套接 functor(仿函数)。

 

二、cocos2dx部分

2.1 Cocos2d-x内存管理机制之release和autorelease

·  autorelease内部是成对的retain()和release();

·  retain对++m_uReference,release对--m_uReference,在retain和release中间++m_uAutoReleaseCount。

·  autorelease自动释放对象资源(自动回收池通常在游戏每一帧的结束时开始release清理),此时对应m_uReference为1,m_uAutoReleaseCount为1,release释放资源。

·  release释放资源,此时如果--m_uReference为0,就释放对象资源,且m_uAutoReleaseCount大于0,就再清自动回收池中占位信息。

相关文章:

https://www.cnblogs.com/steven66/p/5235790.html

https://blog.csdn.net/liaohongwei/article/details/21552785

https://blog.csdn.net/asforasiare/article/details/48667079

https://www.cnblogs.com/gongjiangzhixin/p/5391225.html

https://www.cnblogs.com/sniperHW/p/3789837.html

https://www.cnblogs.com/sniperHW/p/3789837.html

https://www.cnblogs.com/gongjiangzhixin/p/5391225.html

 

三、Python部分

3.1 Python中__get__、__getattr__、__getitem__、__getattribute__之间的差异与联系

相关文章:

https://blog.csdn.net/qianguozheng/article/details/50396776

https://blog.csdn.net/yiifaa/article/details/78068962

 

3.2 Python中 dict.items() dict.iteritems()区别

Python 文档解释:

  • dict.items(): Return a copy of the dictionary’s list of (key, value) pairs.
  • dict.iteritems(): Return an iterator over the dictionary’s (key, value) pairs.

dict.items()返回的是一个完整的列表,而dict.iteritems()返回的是一个生成器(迭代器)。

dict.items()返回列表list的所有列表项,形如这样的二元组list:[(key,value),(key,value),...],dict.iteritems()是generator, yield 2-tuple。相对来说,前者需要花费更多内存空间和时间,但访问某一项的时间较快(KEY)。后者花费很少的空间,通过next()不断取下一个值,但是将花费稍微多的时间来生成下一item。

相关文章:

https://blog.csdn.net/u012542422/article/details/52052877

 

3.3Python中range和xrange的区别

相关文章:

https://www.cnblogs.com/wswang/p/5501565.html

 

3.4 python中继承的作用以及多重继承的执行顺序

相关文章:

https://blog.csdn.net/aydfzmb/article/details/53287780

https://www.jianshu.com/p/71c14e73c9d9

 

3.5  @classmethod 和 @staticmethod的区别

相关文章:

https://www.jb51.net/article/126598.htm

 

3.6 Python迭代对象、迭代器、生成器

  • 容器是一系列元素的集合,str、list、set、dict、file、sockets对象都可以看作是容器,容器都可以被迭代(用在for,while等语句中),因此他们被称为可迭代对象。
  • 可迭代对象实现了__iter__方法,该方法返回一个迭代器对象。
  • 迭代器持有一个内部状态的字段,用于记录下次迭代返回值,它实现了__next__和__iter__方法,迭代器不会一次性把所有元素加载到内存,而是需要的时候才生成返回结果。
  • 生成器是一种特殊的迭代器,它的返回值不是通过return而是用yield。

相关文章:

https://blog.csdn.net/u014745194/article/details/70176117

https://foofish.net/iterators-vs-generators.html

https://www.jb51.net/article/73939.htm

 

 

 

 

【注】:本文主要是将相关的比较有含金量的文章进行筛选并进行归纳以飨大家,版权归各原作者所有。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值