新的问题-代码

1.请简述拆箱和装箱

1.1装箱操作:
值类型隐式转换为object类型或由此值类型实现的任何接口类型的过程。
1).在堆中开辟内存空间。
2).将值类型的数据复制到堆中。
3).返回堆中新分配对象的地址。
1.2拆箱操作:
object类型显示转换为值类型或从接口类型到实现该接口值类型的过程。
1.判断给定类型是否是装箱时的类型。
2.返回已装箱实例中属于原值类型字段的地址。

2.请简述ArrayList和List<>的主要区别
ArrayList是非泛型列表(List<Object>),存储数据是把所有的数据都当成object类型数据,存在装箱问题,取出来使用的时候存在拆箱问题,装箱拆箱会使性能变差,而且存在数据安全问题,但是优点在于可以让值类型和引用类型相互转换。
List是泛型列表,在使用的时候才会去定义数据类型,泛型避免了拆装箱的问题,存入读取熟读较快,类型也更安全。

3.常用类型比较

3.1.Array(数组)

  3.1.1.数组存储在连续的内存上。插入元素慢

  3.1.2.数组的内容都是相同类型。

  3.1.3.数组可以直接通过下标访问。访问快

  3.1.4.声明指定长度,过长会浪费内存,过短会导致溢出风险

3.2.ArrayList:(本质上还是数组,不指定长度里面有个默认处理,默认增涨为原来的50%)

  3.2.1.解决数组创建时必须指定长度以及只能存放相同类型的缺点而推出的数据结构。

  3.2.2.ArrayList是System.Collections命名空间下,

  3.2.3.ArrayList不是类型安全的。因为把不同的类型都当做Object来做处理,很有可能会在使用ArrayList时发生类型不匹配的情况。

  3.2.4.数组存储值类型时并未发生装箱,但是ArrayList由于把所有类型都当做了Object,所以不可避免的当插入值类型时会发生装箱操作,在索引取值时会发生拆箱操作

  3.2.4需要处理的元素数量确定并且需要使用下标时可以考虑,不过建议使用List

3.3.Vector

  3.3.1.用数组方式存储数据(默认增长为原来一倍)

  3.3.2.Vector由于使用了synchronized方法(线程安全)所以性能上比ArrayList要差

3.4.List<T>

  3.4.1.是对数组的封装,当Item元素数量超过了Capacity的时,List对象会重新申请一块Capacity*2的内存空间,然后将当前元素以及待添加元素复制到新的内存空间中,因此频繁的添加数据消耗大,但查询速度快;它的内存区域是连续的,可以用角标来取值;

  3.4.2.即确保了类型安全。

  3.4.3.也取消了装箱和拆箱的操作。

  3.4.4.它融合了Array可以快速访问的优点以及ArrayList长度可以灵活变化的优点。

3.5.链表(C#只有默认的双向链表,单独链表需要自己去做)

   3.5.1.链表动态地进行存储分配,现用现申请,可以适应数据动态增减的情况,且可以方便地插入删除数据项。链表必须根据next指针找到下个元素 

  3.5.2.链表从堆中分配空间,自由度大但是申请管理比较麻烦,链表是物理上非连续的内存空间,对于访问数据,需要从头遍历整个链表直到找到要访问的数据,没有数组有效,但是在添加和删除数据方面,只要知道操作位置的指针,很方便实现增删

  3.5.3.实现

  

public class ListNode   // 结点类
{
     public ListNode(int NewValue)
     {
        Value=NewValue;
     }
     public ListNode Previous;      //前一个
     public ListNode Next;      //后一个
     public int Value;              //
}
//构造函数
public Clist()
{
    ListCountValue = 0;//初始化
    Head = null;
    Tail = null;
}
private ListNode Head;//头指针
private ListNode Tail;//尾指针
private ListNode Current;//当前指针
private int ListCountValue;//链表数据的个数

 

3.6.LinkedList<T>:

  3.6.1.长度不定,存储空间可能不连续,读取速度慢,插入,删除速度快

  3.6.2.链表在内存存储的排序上是不连续的。这是由于链表是通过上一个元素指向下一个元素来排列的,所以不能通过下标来访问

  3.6.3.添加元素和删除元素都要比数组要有优势。

  3.6.4.由于其在内存空间中不一定是连续排列,所以访问时候无法利用下标,而是必须从头结点开始,逐次遍历下一个节点直到寻找到目标。所以当需要快速访问对象时,数组无疑更有优势。

3.7 Queue<T>:

  3.7.1.先进先出”(FIFO—first in first out)的线性表。

  3.7.2.Enqueue和Dequeue这两个方法来实现对 Queue<T> 的存取。

  3.7.3.内存连续

3.8.Stack<T>:

  3.8.1.后进先出顺序(LIFO)的数据结构时,

  3.8.2.使用pop和push来操作。

  3.8.3.空间由操作系统自动分配和释放,空间有限

  3.8.4.内存连续

3.9.Heap

  3.9.1.间是手动申请和释放的,heap常用new关键字来分配。

  3.9.2.heap的空间是很大的自由区

  3.9.3.内存无序

3.10.Dictionary<K,T>:

  3.10.1.是一种变种的HashTable

  3.10.2.内部实现方式都是使用数组

  3.10.3.以空间换时间,非线程安全Dictionary不能直接序列化,其对象不能出现在检视面板

  3.10.4.在单条数据的读取上速度快

3.11.Hashtable

  3.11.1.内存是用key值根据hash算法算出来的

  3.11.2.内存区域是不连续的

  3.11.3.线程安全,适合多线程,无序;

3.12.HashMap

    内部维护了一个存储数据的Entry数组,HashMap采用链表解决冲突,每一个Entry本质上是一个单向链表。当准备添加一个key-value对时,首先通过hash(key)方法计算hash值,然后通过indexFor(hash,length)求该key-value对的存储位置,计算方法是先用hash&0x7FFFFFFF后,再对length取模,这就保证每一个key-value对都能存入HashMap中,当计算出的位置相同时,由于存入位置是一个链表,则把这个key-value对插入链表头。

      HashMap中key和value都允许为null。key为null的键值对永远都放在以table[0]为头结点的链表中。

4.C#中四种访问修饰符是哪些?各有什么区别?

1.属性修饰符 2.存取修饰符 3.类修饰符 4.成员修饰符。

属性修饰符:

Serializable:按值将对象封送到远程服务器。

STATread:是单线程套间的意思,是一种线程模型。

MATAThread:是多线程套间的意思,也是一种线程模型。

存取修饰符:

public:存取不受限制。

private:只有包含该成员的类可以存取。(会被继承,但是不能被子类使用)

internal:只有当前工程可以存取。

protected:只有包含该成员的类以及派生类可以存取。

类修饰符:

abstract:抽象类。指示一个类只能作为其它类的基类。

sealed:密封类。指示一个类不能被继承。理所当然,密封类不能同时又是抽象类,因为抽象总是希望被继承的。

成员修饰符:

abstract:指示该方法或属性没有实现。

sealed:密封方法。可以防止在派生类中对该方法的override(重载)。不是类的每个成员方法都可以作为密封方法密封方法,必须对基类的虚方法进行重载,提供具体的实现方法。所以,在方法的声明中,sealed修饰符总是和override修饰符同时使用。

delegate:委托。用来定义一个函数指针。C#中的事件驱动是基于delegate + event的。

const:指定该成员的值只读不允许修改。声明时候初始化,字段是编译时常数

event:声明一个事件。

extern:指示方法在外部实现。

override:重写。对由基类继承成员的新实现。

overload:是重载,也表示重写父类的函数,但是它只要函数名相同就行,其他的可以不同。

readonly:指示一个域只能在声明时以及相同类的内部被赋值。运行时候常熟

在声明或构造函数中初始化

static:指示一个成员属于类型本身,而不是属于特定的对象。即在定义后可不经实例化,就可使用。

virtual:指示一个方法或存取器的实现可以在继承类中被覆盖。

new:在派生类中隐藏指定的基类成员,从而实现重写的功能。 若要隐藏继承类的成员,请使用相同名称在派生类中声明该成员,并用 new 修饰符修饰它。

5.什么是强类型。

  为所有变量指定数据类型称为“强类型”。C#是强类型语言。

6.什么是托管代码。

  使用基于公共语言运行库的语言编译器开发的代码称为托管代码;托管代码具有许多优点,例如:跨语言集成、跨语言异常处理、增强的安全性、版本控制和部署支持、简化的组件交互模型、调试和分析服务等。

7.什么是CLR?

  CLR:公共语言运行库 Common Language Runtime。是一个运行时环境,它负责资源管理(内存分配和垃圾收集),并保证应用和底层操作系统之间必要的分离。

8.简述GC(垃圾回收)产生的原因,并描述如何避免

  GC回收堆上的内存

  避免:

    1.减少new产生对象的次数

    2.使用公用的对象(静态成员)

    3.将String换为StringBuilder

9.C#中,string str = null 与 string str = "",说明区别。

  string str = "" 初始化对象分配空间。
  string str = null 表示一个空引用,没有占用空间。

10.C# String类型比stringBuilder类型的优势是什么?

如果是处理字符串的话,用string中的方法每次都需要创建一个新的字符串对象并且分配新的内存地址,而stringBuilder是在原来的内存里对字符串进行修改,所以在字符串处理方面还是建议用stringBuilder这样比较节约内存。但是string 类的方法和功能仍然还是比stringBuilder类要强。

string类由于具有不可变性(即对一个string对象进行任何更改时,其实都是创建另外一个string类的对象),所以当需要频繁的对一个string类对象进行更改的时候,建议使用StringBuilder类,StringBuilder类的原理是首先在内存中开辟一定大小的内存空间,当对此StringBuilder类对象进行更改时,如果内存空间大小不够,会对此内存空间进行扩充,而不是重新创建一个对象,这样如果对一个字符串对象进行频繁操作的时候,不会造成过多的内存浪费,其实本质上并没有很大区别,都是用来存储和操作字符串的,唯一的区别就在于性能上。

String主要用于公共API,通用性好、用途广泛、读取性能高、占用内存小。

StringBuilder主要用于拼接String,修改性能好。

不过现在的编译器已经把 String 的 + 操作优化成 StringBuilder 了,所以一般用String就可以了

String是不可变的,所以天然线程同步。

StringBuilder可变,非线程同步。

11.反射的实现原理?

审查元数据并收集关于它的类型信息的能力。实现原理:在运行时根据程序集及其中的类型得到元数据。下面是实现步骤:

    1. 导入using System.Reflection;
    2. Assembly.Load(“程序集”)加载程序集,返回类型是一个Assembly
    3. 得到程序集中所有类的名称
      foreach (Type type in assembly.GetTypes())
      {
      string t = type.Name;
      }
    4. Type type = assembly.GetType(“程序集.类名”); //获取当前类的类型
    5. Activator.CreateInstance(type); 创建此类型实例
    6. MethodInfo mInfo = type.GetMethod(“方法名”); //获取当前方法
    7. m.Info.Invoke(null,方法参数);

12.C#的委托是什么?有何用处?

  1) 委托是一种引用方法的类型。
  2) 委托类似于 C++ 函数指针,但它是类型安全的。
  3) 委托允许将方法作为参数进行传递。
  4) 委托可用于定义回调方法。

  委托类似于一种安全的指针引用,在使用它时是当做类来看待而不是一个方法,相当于对一组方法的列表的引用。用处:使用委托使程序员可以将方法引用封装在委托对象内。然后可以将该委托对象传递给可调用所引用方法的代码,而不必在编译时知道将调用哪个方法。与C或C++中的函数指针不同,委托是面向对象,而且是类型安全的。

13.能用foreach遍历访问的对象需要实现接口或声明_方法的类型

IEnumerable;GetEnumerator

14.值类型和引用类型有何区别?

  14.1.速度上的区别

    值类型存取速度快,引用类型存取速度慢。

  14.2.用途上的区别

    值类型表示实际数据,引用类型表示指向存储在内存堆中的数据的指针或引用。

  14.3.来源上的区别

  值类型继承自System.ValueType,引用类型继承自System.Object

  14.1.位置上的区别

    值类型的数据存储在内存的栈中,引用类型的数据存储在内存的堆中,而内存单元中只存放堆中对象的地址。

  14.5.类型上的区别

    值类型的变量直接存放实际的数据,而引用类型的变量存放的则是数据的地址,即对象的引用。

15.ref参数和out参数是什么?有什么区别?

  ref和out参数的效果一样,都是通过关键字找到定义在主函数里面的变量的内存地址,并通过方法体内的语法改变它的大小。不同点就是输出参数必须对参数进行初始化。ref必须初始化,out 参数必须在函数里赋值。ref参数是引用,out参数为输出参数。

16.在类的构造函数前加上static会报什么错?为什么?

构造函数格式为 public+类名如果加上static会报错(静态构造函数不能有访问修饰符)
原因:静态构造函数不允许访问修饰符,也不接受任何参数;
无论创建多少类型的对象,静态构造函数只执行一次;
运行库创建类实例或者首次访问静态成员之前,运行库调用静态构造函数;
静态构造函数执行先于任何实例级别的构造函数;
显然也就无法使用this和base来调用构造函数。

17.构造器Constructor是否可被override(重写)?

  构造器Constructor不能被继承,因此不能重写override,但可以被重载Overloade。

18.在C#中using和new这两个关键字有什么意义。

using 关键字有两个主要用途:
1) 作为指令,用于为命名空间创建别名或导入其他命名空间中定义的类型。
2) 作为语句,用于定义一个范围,在此范围的末尾将释放对象。
new 关键字:新建实例或者隐藏父类方法

19.接口是否可继承接口? 抽象类是否可实现(implements)接口? 抽象类是否可继承实体类(concrete class)?

  接口可以继承接口。抽象类可以实现(implements)接口,抽象类是否可继承实体类,但前提是实体类必须有明确的构造函数。

20.什么是虚函数?什么是抽象函数?

1) 虚函数:没有实现的,可由子类继承并重写的函数。
2) 抽象函数:规定其非虚子类必须实现的函数,必须被重写。 

21.函数参数中的this

  • 这个叫做扩展方法,扩展方法能够向现有类型“添加”方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。扩展方法是一种特殊的静态方法,但可以像扩展类型上的实例方法一样进行调用,
  • 扩展方法可以写入最初没有提供该方法的类中。还可以把方法添加到实现某个接口的任何类中,这样多个类可以使用相同的实现代码。

22.class和struct的异同
相同点:
1) 语法类似。
不同点:
1) class是引用类型,继承自System.Object类; struct是值类型,继承自System.ValueType类,因此不具多态性。但是注意,System.ValueType是个引用类型。
2) 从职能观点来看,class表现为行为; 而struct常用于存储数据。
3) class支持继承,可以继承自类和接口; 而struct没有继承性,struct不能从class继承,也不能作为class的基类,但struct支持接口继承。
4) 实例化时,class要使用new关键字; 而struct可以不使用new关键字,struct在声明时就进行了初始化过程,所有的成员变量均默认为0或null。

如何选择结构还是类。
1) 堆栈的空间有限,对于大量的逻辑的对象,创建类要比创建结构好一些。
2) 结构表示如点、矩形和颜色这样的轻量对象。例如,如果声明一个含有 1000 个点对象的数组,则将为引用每个对象分配附加的内存。在此情况下,结构的成本较低。
3) 在表现抽象和多级别的对象层次时,类是最好的选择。
4) 大多数情况下该类型只是一些数据时,结构时最佳的选择

23.抽象类(abstract class)和接口(interface)的区别。
抽象类:
1) 抽象方法只作声明,而不包含实现,可以看成是没有实现体的虚方法。
2) 抽象类不能被实例化。
3) 抽象类可以但不是必须有抽象属性和抽象方法,但是一旦有了抽象方法,就一定要把这个类声明为抽象类。
4) 具体派生类必须覆盖基类的抽象方法。
5) 抽象派生类可以覆盖基类的抽象方法,也可以不覆盖。如果不覆盖,则其具体派生类必须覆盖它们。
接口:
1) 接口不能被实例化。
2) 接口只能包含方法声明。
3) 接口的成员包括方法、属性、索引器、事件。
4) 接口中不能包含常量、字段(域)、构造函数、析构函数、静态成员。
5) 接口中的所有成员默认为public,因此接口中不能有private修饰符。
6) 派生类必须实现接口的所有成员。
7) 一个类可以直接实现多个接口,接口之间用逗号隔开。
8) 一个接口可以有多个父接口,实现该接口的类必须实现所有父接口中的所有成员。
抽象类和接口的异同:
相同点:
1) 都可以被继承。
2) 都不能被实例化。
3) 都可以包含方法声明。
4) 派生类必须实现未实现的方法。
区别:
1) 抽象基类可以定义字段、属性、方法实现。接口只能定义属性、索引器、事件、和方法声明,不能包含字段。
2) 抽象类是一个不完整的类,需要进一步细化,而接口是一个行为规范。微软的自定义接口总是后带able字段,证明其是表述一类“我能做。。。”。
3) 接口可以被多重实现,抽象类只能被单一继承。
4) 抽象类更多的是定义在一系列紧密相关的类间,而接口大多数是关系疏松但都实现某一功能的类中。
5) 抽象类是从一系列相关对象中抽象出来的概念,因此反映的是事物的内部共性;接口是为了满足外部调用而定义的一个功能约定,因此反映的是事物的外部特性。
6) 接口基本上不具备继承的任何具体特点,它仅仅承诺了能够调用的方法。
7) 接口可以用于支持回调,而继承并不具备这个特点。
8) 抽象类实现的具体方法默认为虚的,但实现接口的类中的接口方法却默认为非虚的,当然您也可以声明为虚的。
9) 如果抽象类实现接口,则可以把接口中方法映射到抽象类中作为抽象方法而不必实现,而在抽象类的子类中实现接口中方法。

 

 

 

 

 

posted @ 2020-10-12 16:51  Elijah_j  阅读(55)  评论(0编辑  收藏  举报