C#/C++常见问题

34 篇文章 0 订阅

 什么是动态特性?

在绝大多数情况下, 程序的功能是在编译的时候就确定下来的, 我们称之为静态特性。 反之, 如果程序的功能是在运行时刻才能确定下来的, 则称之为动态特性

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/freeC/C++标准库函数,new/deleteC++运算符。他们都可以用于动态申请和释放内存。

对于内置类型数据而言,二者没有多大区别。

malloc申请内存的时候要制定分配内存的字节数,而且malloc不会做初始化

new申请的时候有默认的初始化,同时可以指定初始化;

对于类类型的对象而言,用malloc/free无法满足要求的。对象在创建的时候要自动执行构造函数,消亡之前要调用析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制之内,不能把执行构造函数和析构函数的任务强加给它,因此,C++还需要new/delete。而C++程序经常要调用C函数,因此还需要malloc/free来管理动态内存。所以,二者都是不可或缺的。

 

newmalloc的区别?

共同点: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.另外还有一个专门放常量的地方。程序结束释放

程序代码区,存放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题:内存的分配方式有几种?

【参考答案】

一、从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量。

二、在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

三、从堆上分配,亦称动态内存分配。程序在运行的时候用mallocnew申请任意多少的内存,程序员自己负责在何时用freedelete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。

 

内存分配方式

 在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关键字并且类型必须为委托类型。事件没有返回值。

 

ArrayArraylist之间的区别是什么?

Array数组是相同类型的集合。数组大小在它声明的时候就固定了。

ArrayList链表和数组相似,但它没有固定的大小。可以存储任意类型。

 

C#有哪几种集合的类型

 ArrayArrayList

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. }  

 

 待续。。。。。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

屠变恶龙之人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值