什么是动态特性?
在绝大多数情况下, 程序的功能是在编译的时候就确定下来的, 我们称之为静态特性。 反之, 如果程序的功能是在运行时刻才能确定下来的, 则称之为动态特性。
C++中, 虚函数,抽象基类, 动态绑定和多态构成了出色的动态特性。
多态,虚函数,纯虚函数
多态:是对于不同对象接收相同消息时产生不同的动作。C++的多态性具体体现 在运行和编译两个方面:在程序运行时的多态性通过继承和虚函数来实现;
在程序编译时多态性体现在函数和运算符的重载上;
好处:多态性提高了代码的组织性和可读性,虚函数则根据类型的不同来进行不同的隔离。
虚函数:在基类中冠以关键字 virtual 的成员函数。 它提供了一种接口界面。允许在派生类中对基类的虚函数重新定义(重写基类)。
纯虚函数的作用:在基类中为其派生类保留一个函数的名字,以便派生类根据需要对它进行定义。作为接口而存在。纯虚函数不具备函数的功能,一般不能直接被调用。
从基类继承来的纯虚函数,在派生类中仍是虚函数。如果一个类中至少有一个纯虚函数,那么这个类被称为抽象类(abstract class)。
抽象类中不仅包括纯虚函数,也可包括虚函数。抽象类必须用作派生其他类的基类,而不能用于直接创建对象实例。但仍可使用指向抽象类的指针支持运行时多态性。
重载 重写 的区别?
常考的题目。从定义上来说:
重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。
重写:是指子类重新定义父类虚函数的方法。
从实现原理上来说:
重载:派生类重新定义基类虚函数的做法叫做覆盖;
编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。如,有两个同名函数:function func(p:integer):integer;和function func(p:string):integer;。那么编译器做过修饰后的函数名称可能是这样的:int_func、str_func。对于这两个函数的调用,在编译器间就已经确定了,是静态的。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关!
重写:和多态真正相关。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚绑定)。
继承层次中,为什么基类析构函数是虚函数?
答:编译器总是根据类型来调用类成员函数。但是一个派生类的指针可以安全地转化为一个基类的指针。这样删除一个基类的指针的时候,C++不管这个指针指向一个基类对象还是一个派生类的对象,调用的都是基类的析构函数而不是派生类的。如果你依赖于派生类的析构函数的代码来释放资源,而没有重载析构函数,那么会有资源泄漏。
为什么构造函数不能为虚函数?
答:虚函数采用一种虚调用的方法。需调用是一种可以在只有部分信息的情况下工作的机制。如果创建一个对象,则需要知道对象的准确类型,因此构造函数不能为虚函数。
如果虚函数是有效的,那为什么不把所有函数设为虚函数?
答:不行。首先,虚函数是有代价的,由于每个虚函数的对象都要维护一个虚函数表,因此在使用虚函数的时候都会产生一定的系统开销,这是没有必要的。
什么是多态?多态有什么作用?
答:是对于不同对象接收相同消息时产生不同的动作。多态就是将基类类型的指针(或引用)指向派生类型的对象。
允许将子类类型的指针赋值给父类类型的指针
多态通过虚函数机制实现。
多态的作用是接口重用。
什么是虚指针?
答:虚指针或虚函数指针是虚函数的实现细节。带有虚函数的每一个对象都有一个虚指针指向该类的虚函数表。
解释下封装、继承和多态?
答:
一、封装:
封装就是将数据或函数等集合在一个个的单元中(我们称之为类)。
封装的意义在于保护或者防止代码(数据)被我们无意中破坏。
二、继承:
继承主要实现重用代码,节省开发时间。
子类可以继承父类的一些东西。
三、多态
多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。
什么是指针?谈谈你对指针的理解?
答:指针是一个变量,该变量专门存放内存地址;
指针变量的类型取决于其指向的数据类型,
指针变量的特点是它可以访问所指向的内存。
什么是“引用”?申明和使用“引用”要注意哪些问题?
答:引用就是某个目标变量的“别名”(alias),对引用的操作与对变量直接操作效果完全相同。申明一个引用的时候,切记要对其进行初始化。引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,不能再把该引用名作为其他变量名的别名。声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。不能建立数组的引用。
引用与指针有什么区别?
【参考答案】
1) 引用必须被初始化,指针不必。
2) 引用初始化以后不能被改变,指针可以改变所指的对象。
3) 不存在指向空值的引用,但是存在指向空值的指针。
【单例模式】
单例模式可以保证某个类的对象只有一个。只有一个实例,易于外界访问,并节约系统资源。
如果希望在程序中某个类的对象只能存在一个,单例模式是最好的解决方案。
在游戏中使用单例的例子:比如音乐管理类,游戏主页的背景音乐
C++中有malloc/free,为什么还有new/delete?
答:malloc/free是C/C++标准库函数,new/delete是C++运算符。他们都可以用于动态申请和释放内存。
对于内置类型数据而言,二者没有多大区别。
malloc申请内存的时候要制定分配内存的字节数,而且malloc不会做初始化;
new申请的时候有默认的初始化,同时可以指定初始化;
对于类类型的对象而言,用malloc/free无法满足要求的。对象在创建的时候要自动执行构造函数,消亡之前要调用析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制之内,不能把执行构造函数和析构函数的任务强加给它,因此,C++还需要new/delete。而C++程序经常要调用C函数,因此还需要malloc/free来管理动态内存。所以,二者都是不可或缺的。
new和malloc的区别?
共同点:malloc/free是标准库函数,new/delete是运算符。都可以用于申请动态内存和释放内存。
1、 new自动计算需要分配的空间,而malloc需要手工计算字节数。
new出来的指针是直接带类型信息的,而malloc返回的都是void指针。
2、 new是类型安全的,而malloc不是,比如:
int* p = new float[2]; // 编译时指出错误
int* p = malloc(2*sizeof(float)); // 编译时无法指出错误
new operator 由两步构成,分别是 operator new 和 construct
3、operator new对应于malloc,但operator new可以重载,可以自定义内存分配策略,甚至不做内存分配,甚至分配到非内存设备上。而malloc无能为力
4、new将调用constructor,而malloc不能;delete将调用destructor,而free不能。
5、malloc/free要库文件支持,new/delete则不要。
说下你对内存的了解?(内存分配的方式)
答:
1.栈 - 由编译器自动分配释放
2.堆 - 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收
3.全局区(静态区),全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。- 程序结束释放
4.另外还有一个专门放常量的地方。- 程序结束释放
5 程序代码区,存放2进制代码。
在函数体中定义的变量通常是在栈上,用malloc, calloc, realloc等分配内存的函数分配得到的就是在堆上。在所有函数体外定义的是全局量,加了static修饰符后不管在哪里都存放在全局区(静态区),在所有函数体外定义的static变量表示在该文件中有效,不能extern到别的文件用,在函数体内定义的static表示只在该函数体内有效。另外,函数中的"adgfdf"这样的字符串存放在常量区。
问14:什么是内存泄漏?面对内存泄漏和指针越界,你有哪些方法?你通常采用哪些方法来避免和减少这类错误?
答:用动态存储分配函数分配的空间,在使用完毕后未释放,结果导致一直占据该内存单元即为内存泄露。(new/delete)
使用的时候要记得指针的长度。
malloc的时候得确定在那里free.
对指针赋值的时候应该注意被赋值指针需要不需要释放.
动态分配内存的指针最好不要再次赋值.
内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。
内存泄露 memory leak,是指程序在申请内存后,未释放或无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
内存泄露 最终会导致内存溢出!
第28题:内存的分配方式有几种?
【参考答案】
一、从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量。
二、在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
三、从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
内存分配方式
在C++中,内存分成5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。
栈:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。效率很高,但是分配的内存容量有限。
堆:就是那些由 new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个 delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。
程序代码区:就存放二进制。是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。
全局/静态存储区:全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。
常量存储区:这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改。
堆和栈究竟有什么区别
好了,我们回到我们的主题:堆和栈究竟有什么区别?
主要的区别由以下几点:
(1). 管理方式不同
(2). 空间大小不同
(3). 能否产生碎片不同
(4). 生长方向不同
(5). 分配方式不同
(6). 分配效率不同
管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;
对于堆来说,释放工作由程序员控制,容易产生memory leak。
空间大小:一般来讲在32位系统下,堆内存可以达到4G的空间
但是对于栈来讲,一般都是有一定的空间大小的, 在VC6下面默认的栈空间大小是1M
碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,
使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是
如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出
生长方向:对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;
对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。
分配方式:堆都是动态分配的,没有静态分配的堆。动态分配是由编译器进行释放,无需手工实现。
栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,动态由alloca函数
分配效率:栈是机器系统提供的数据结构,计算机会在底层分配专门的寄存器存放栈的地址,压栈出
栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的。显
然,堆的效率比栈要低得多。
从这里我们可以看到,堆和栈相比,由于大量new/delete的使用,容易造成大量的内存碎片;由于没有专门的系统支持,效率很低;由于可能引发用户态和核心态的切换,内存的申请,代价变得更加昂贵。所以栈在程序中是应用最广泛的,就算是函数的调用也利用栈去完成,函数调用过程中的参数,返回地址,EBP和局部变量都采用栈的方式存放。所以,我们推荐大家尽量用栈,而不是用堆。
虽然栈有如此众多的好处,但是由于和堆相比不是那么灵活,有时候分配大量的内存空间,还是用堆好一些。
无论是堆还是栈,都要防止越界现象的发生(除非你是故意使其越界),
//==================================================
StringBuilder 和 String 的区别?
答:
String 在进行运算时(如赋值、拼接等)会产生一个新的实例,而 StringBuilder 则不会。所以在大量字符串拼接或频繁对某一字符串进行操作时最好使用 StringBuilder,不要使用 String
另外,对于 String 我们不得不多说几句:
1.它是引用类型,在堆上分配内存
2.运算时会产生一个新的实例
3.String 对象一旦生成不可改变(Immutable)
3.定义相等运算符(== 和 !=)是为了比较 String 对象(而不是引用)的值
C#的值类型包括:结构体(数值类型,bool型,用户定义的结构体),枚举,可空类型。
C#的引用类型包括:数组,用户定义的类、接口、委托,object,字符串。
什么是反射?
答:
反射,Reflection,通过它我们可以在运行时获得各种信息,如程序集、模块、类型、字段、属性、方法和事件
通过对类型动态实例化后,还可以对其执行操作。简单来说就是用string可以在runtime为所欲为的东西,实际上就是一个.net framework内建的万能工厂
一般用于插件式框架程序和设计模式的实现,当然反射是一种手段可以充分发挥其能量来完成你想做的任何事情(前面好象见过一位高人用反射调用一个官方类库中未说明的函数。。。)
为何需要装箱?(为何要将值类型转为引用类型?)
一种最普通的场景是,调用一个含类型为Object的参数的方法,该Object可支持任意为型,以便通用。当你需要将一个值类型(如Int32)传入时,需要装箱。
另一种用法是,一个非泛型的容器,同样是为了保证通用,而将元素类型定义为Object。于是,要将值类型数据加入容器时,需要装箱。
C#支持多重继承吗?
不支持,它不可能。支持多级继承。一个类可以继承自多个接口
结构和类之间有什么区别?
结构是值类型,类是引用类型。
结构不能有构造函数和析构函数。
类可以同时有构造函数和析构函数。
结构不支持继承,而类支持继承。
C#中的委托是什么?事件是不是一种委托?
答 : 委托可以把一个方法作为参数代入另一个方法。委托主要用于定义回调方法。
委托可以理解为指向一个函数的引用。
是,是一种特殊的委托
什么是多播委托?
每个委托对象保持对一个单独方法的引用。但是,一个委托对象保持对多个方法的引用并调用它们是可能的。这样的委托对象成为多播委托或者组合委托。
什么是事件?
事件是使一个类或对象能够提供通知的成员。事件声明像域声明一样,除了声明包含event关键字并且类型必须为委托类型。事件没有返回值。
Array和Arraylist之间的区别是什么?
Array数组是相同类型的集合。数组大小在它声明的时候就固定了。
ArrayList链表和数组相似,但它没有固定的大小。可以存储任意类型。
C#有哪几种集合的类型
Array、ArrayList、
List: 和ArrayList类(两者具有类似的功能)区别,记住List类在大多数情况下执行得更好并且是类型安全的。如果对List类的类型T使用引用类型,则两个类的行为是完全相同的。
但是如果对类型T使用值类型,则需要考虑实现和装箱问题,要更慢。
Hashtable:表示键/值对的集合。支持任何类型的键值类型
Dictionary:表示键和值的集合,但是相比有两点:第一支持泛型,第二Hashtable的元素属于Object类型,需要装箱拆箱操作。
HashSet:设计用来做高性能集运算的,例如对两个集合求交集、并集、差集等。
集合中包含一组不重复出现且无特性顺序的元素,HashSet拒绝接受重复的对象。
什么是泛型
我们在编写程序时,经常遇到两个模块的功能非常相似,只是一个是处理int数据,另一个是处理string数据,或者其他自定义的数据类型,但我们没有办法,只能分别写多个方法处理每个数据类型,因为方法的参数类型不同。有没有一种办法,在方法中传入通用的数据类型,这样不就可以合并代码了吗?泛型的出现就是专门解决这个问题的。读完本篇文章,你会对泛型有更深的了解。
使用泛型类型可以最大限度地重用代码、保护类型的安全以及提高性能。
泛型最常见的用途是创建集合类。
特性是什么?
特性(Attribute)可应用于类、字段、属性、方法以及程序集或模块,用来给它们提供一些附加的声明性信息。特性可用于运行时,也可用于设计时。你可以把它理解为对类、字段、属性、方法等进行的批注或解说。 在编译有特性的程序时编译器会读取这个特性,然后根据特性去进行编译
1)功能性信息:如[Serializable]加在类前表示该类可被串行化。
[Serializable]
public class HumanProperty
2)提示性信息:如[Obsolete]表示该方法已经过时,提醒程序员使用新的替代函数。
[Obsolete("Use another method : Average(), instead!", true)]
public virtual void CalcAverage(int speed)
3)限定性信息:如[Conditional("DEBUG")]表示下面的方法只有在调试模式下才有效。
[Conditional("DEBUG")]
public void UnitTest()
4)描述性信息:如[Description]对所指对象进行详细描述。
C#和C++的区别是什么?
C#不支持#include语句。它只用using语句。
C#中,类定义在最后不使用分号。
C#不支持多重继承。
数据类型的显示转换在C#中比C++中安全很多。
C#中switch也可用于字符串值。
命令行参数数组的行为在C#中和C++中不一样。
dynamic_cast转换引用失败时,是什么情况。
转指针失败时,返回NULL。
转引用失败时,抛出异常std::bad_cast。
设计模式的了解程度,观察者模式的应用场景,与 Reactor模式的区别。
常用的设计模式有:单例模式,工厂模式,观察者模式,策略模式。
观察者模式的基本思想:向目标subject注册自己感兴趣的事件,当目标subject发
生某事件时,则主动通知观察者。
观察者模式与单个事件源关联,Reactor与多个事件源关联。
单链表的操作
1. /*单链表的逆置*/
2. void Reverse_LinkList(LinkList &L)
3. {
4. Lnode *p, *q;
5. p = L->next; //P指向链表第一个元素
6. L->next = NULL; //断开头结点与链表
7. while(p != NULL)
8. {
9. q = p;
10. p = p->next;
11. q->next = L->next; //相当于前插法构建新的链表,和原来的相反
12. L->next = q;
13. }
14. }
15.
16. /*打印单链表*/
17. void Print_LinkList(LinkList &L)
18. {
19. Lnode* p;
20. p = L->next; //L是头指针,p指向第一个节点,开始打印
21. while(p != NULL)
22. {
23. printf("%d\n", p->data);
24. p = p->next;
25. }
26. }
待续。。。。。