• dotNET面试(二)


    值类型与引用类型

    1.值类型和引用类型的区别?

    • 值类型包括简单类型、结构体类型和枚举类型,引用类型包括自定义类、数组、接口、委托等。
    • 赋值方式:将一个值类型变量赋给另一个值类型变量时,将复制包含的值。这与引用类型变量的赋值不同,引用类型变量的赋值只复制对象的引用(即内存地址,类似C++中的指针),而不复制对象本身。
    • 继承:值类型不可能派生出新的类型,所有的值类型均隐式派生自 System.ValueType。但与引用类型相同的是,结构也可以实现接口。
    • null:与引用类型不同,值类型不可能包含 null 值。然而,可空类型功能允许将 null 赋给值类型。
    • 每种值类型均有一个隐式的默认构造函数来初始化该类型的默认值,值类型初始会默认为0,引用类型默认为null。
    • 值类型存储在栈中,引用类型存储在托管堆中。

    2. 结构和类的区别?
    结构体是值类型,类是引用类型,主要区别如上述。其他的区别:
    结构不支持无参构造函数,不支持析构函数,并且不能有protected修饰
    结构常用于数据存储,类class多用于行为
    class需要用new关键字实例化对象,struct可以不使用new关键字
    class可以为抽象类,struct不支持抽象

    3. delegate是引用类型还是值类型?enum、int[]和string呢?
    enum枚举是值类型,其他都是引用类型。

    4. 堆和栈的区别?
    线程堆栈:简称栈 Stack
    托管堆: 简称堆 Heap
    值类型大多分配在栈上,引用类型都分配在堆上;
    栈由操作系统管理,栈上的变量在其作用域完成后就被释放,效率较高,但空间有限。堆受CLR的GC控制;栈是基于线程的,每个线程都有自己的线程栈,初始大小为1M。堆是基于进程的,一个进程分配一个堆,堆的大小由GC根据运行情况动态控制。

    5.“结构”对象可能分配在堆上吗?什么情况下会发生,有什么需要注意的吗?
    结构是值类型,有两种情况会分配在堆上面:
    结构作为class的一个字段或属性,会随class一起分配在堆上面;
    装箱后会在堆中存储,尽量避免值类型的装箱,值类型的拆箱和装箱都有性能损失

    6. 理解参数按值传递?以及按引用传递?
    按值传递:对于值类型传递它的值拷贝副本,而引用类型传递的是引用变量的内存地址,他们还是指向的同一个对象。
    按引用传递:通过关键字out和ref传递参数的内存地址,值类型和引用类型的效果是相同的。

    7. out和ref的区别与相同点?
    out和ref都指示编译器传递参数地址,在行为上是相同的;
    使用机制稍有不同,ref要求参数在使用之前要显式初始化,out要在方法内部初始化;
    out 和 ref不可以重载,就是不能定义Method(ref int a)和Method(out int a)这样的重载,从编译角度看,二者的实质是相同的,只是使用时有区别。

    8.有几种方法可以判定值类型和引用类型?
    简单来说,继承自System.ValueType的是值类型,反之是引用类型。

    9. C#支持哪几个预定义的值类型?支持哪些预定义的引用类型?
    值类型:整数、浮点数、字符、bool和decimal
    引用类型:Object,String

    10. 说说值类型和引用类型的生命周期?
    值类型在作用域结束后释放,引用类型由GC垃圾回收器回收。

    11.如果结构体中定义引用类型,对象在内存中是如何存储的?例如下面结构体中的class类 User对象是存储在栈上,还是堆上?

    public struct MyStruct
    {
    public int Index;
    public User User;
    }

    MyStruct存储在栈中,其字段User的实例存储在堆中,MyStruct.User字段存储指向User对象的内存地址。

     装箱与拆箱

    1.什么是拆箱和装箱?装箱就是值类型转换为引用类型,拆箱就是引用类型(被装箱的对象)转换为值类型。
    2.什么是箱子?就是引用类型对象。
    3.箱子放在哪里?托管堆上。
    4.装箱和拆箱有什么性能影响?装箱和拆箱都涉及到内存的分配和对象的创建,有较大的性能影响。
    5.如何避免隐身装箱?编码中,多使用泛型、显示装箱。
    6.箱子的基本结构?
    上面说了,箱子就是一个引用类型对象,因此它的结构,主要包含两部分:
    a.值类型字段值;
    b.引用类型的标准配置,引用对象的额外空间:TypeHandle和同步索引块。
    7.装箱的过程?

    1. 在堆中申请内存,内存大小为值类型的大小,再加上额外固定空间(引用类型的标配:TypeHandle和同步索引块);
    2. 将值类型的字段值(x=1023)拷贝新分配的内存中;
    3. 返回新引用对象的地址(给引用变量object o)

    8.拆箱的过程?

    1. 检查实例对象(object o)是否有效,如是否为null,其装箱的类型与拆箱的类型(int)是否一致,如检测不合法,抛出异常;
    2. 指针返回,就是获取装箱对象(object o)中值类型字段值的地址;
    3. 字段拷贝,把装箱对象(object o)中值类型字段值拷贝到栈上,意思就是创建一个新的值类型变量来存储拆箱后的值。

    string与字符串操作

    1.字符串是引用类型类型还是值类型?引用类型。
    2.在字符串连加处理中,最好采用什么方式,理由是什么?
    少量字符串连接,使用String.Concat,大量字符串使用StringBuilder,因为StringBuilder的性能更好,如果string的话会创建大量字符串对象。
    3.使用 StringBuilder时,需要注意些什么问题?
    少量字符串时,尽量不要用,StringBuilder本身是有一定性能开销的;
    大量字符串连接使用StringBuilder时,应该设置一个合适的容量。

    类与接口

    1. 所有类型都继承System.Object吗?
    基本上是的,所有值类型和引用类型都继承自System.Object,接口是一个特殊的类型,不继承自System.Object。
    2. 解释virtual、sealed、override和abstract的区别

    • virtual申明虚方法的关键字,说明该方法可以被重写;
    • sealed说明该类不可被继承;
    • override重写基类的方法;
    • abstract申明抽象类和抽象方法的关键字,抽象方法不提供实现,由子类实现,抽象类不可实例化。

    3. 接口和类有什么异同?
    不同点:
    1、接口不能直接实例化。
    2、接口只包含方法或属性的声明,不包含方法的实现。
    3、接口可以多继承,类只能单继承。
    4、类有分部类的概念,定义可在不同的源文件之间进行拆分,而接口没有(接口也可分部)。
    5、表达的含义不同,接口主要定义一种规范,统一调用方法,也就是规范类,约束类,类是方法功能的实现和集合。
    相同点:
    1、接口、类和结构都可以从多个接口继承。
    2、接口类似于抽象基类:继承接口的任何非抽象类型都必须实现接口的所有成员。
    3、接口和类都可以包含事件、索引器、方法和属性。
    4. 抽象类和接口有什么区别?
    1、继承:接口支持多继承;抽象类不能实现多继承。
    2、表达的概念:接口用于规范,更强调契约,抽象类用于共性,强调父子。抽象类是一类事物的高度聚合,那么对于继承抽象类的子类来说,对于抽象类来说,属于"Is A"的关系;而接口是定义行为规范,强调“Can Do”的关系,因此对于实现接口的子类来说,是"行为需要按照接口来完成"。
    3、方法实现:对抽象类中的方法,即可以给出实现部分,也可以不给出;而接口的方法(抽象规则)都不能给出实现部分,接口中方法不能加修饰符。
    4、子类重写:继承类对于两者所涉及方法的实现是不同的。继承类对于抽象类所定义的抽象方法,可以不用重写,也就是说,可以延用抽象类的方法;而对于接口类所定义的方法或者属性来说,在继承类中必须重写,给出相应的方法和属性实现。
    5、新增方法的影响:在抽象类中,新增一个方法的话,继承类中可以不用作任何处理;而对于接口来说,则需要修改继承类,提供新定义的方法。
    6、接口可以作用于值类型(枚举可以实现接口)和引用类型,抽象类只能作用于引用类型。
    7、接口不能包含字段和已实现的方法,接口只包含方法、属性、索引器、事件的签名,抽象类可以定义字段、属性、包含有实现的方法。
    5. 重载与覆盖的区别?
    重载:当类包含两个名称相同但签名不同(方法名相同,参数列表不相同)的方法时发生方法重载。用方法重载来提供在语义上完成相同而功能不同的方法。
    覆写:在类的继承中使用,通过覆写子类方法可以改变父类虚方法的实现。
    主要区别:
    1、方法的覆盖是子类和父类之间的关系,是垂直关系;方法的重载是同一个类中方法之间的关系,是水平关系。
    2、覆盖只能由一个方法,或只能由一对方法产生关系;方法的重载是多个方法之间的关系。
    3、覆盖要求参数列表相同;重载要求参数列表不同。
    4、覆盖关系中,调用那个方法体,是根据对象的类型来决定;重载关系,是根据调用时的实参表与形参表来选择方法体的。
    6. 在继承中new和override相同点和区别?看下面的代码,有一个基类A,B1和B2都继承自A,并且使用不同的方式改变了父类方法Print()的行为。测试代码输出什么?为什么?

     1 class Program
     2 {
     3     static void Main(string[] args)
     4     {
     5         B1 b1 = new B1();
     6         B2 b2 = new B2();
     7         b1.Print();
     8         b2.Print();//输出 B1、B2
     9         A ab1 = new B1();
    10         A ab2 = new B2();
    11         ab1.Print();
    12         ab2.Print();//输出 B1、A
    13         Console.ReadKey();
    14     }
    15 }
    16 public class A
    17 {
    18     public virtual void Print()
    19     {
    20         Console.WriteLine("A");
    21     }
    22 }
    23 public class B1 : A
    24 {
    25     public override void Print()
    26     {
    27         Console.WriteLine("B1");
    28     }
    29 }
    30 public class B2 : A
    31 {
    32     public new void Print()
    33     {
    34         Console.WriteLine("B2");
    35     }
    36 }
    View Code

    7.class中定义的静态字段是存储在内存中的哪个地方?为什么会说它不会被GC回收?
    随类型对象存储在内存的加载堆上,因为加载堆不受GC管理,其生命周期随AppDomain,不会被GC回收。

    常量、字段、属性、特性与委托

    1. const和readonly有什么区别?
    const关键字用来声明编译时常量,readonly用来声明运行时常量。都可以标识一个常量,主要有以下区别:
    1、初始化位置不同。const必须在声明的同时赋值;readonly即可以在声明处赋值,也可以在构造方法里赋值。
    2、修饰对象不同。const即可以修饰类的字段,也可以修饰局部变量;readonly只能修饰类的字段 。
    3、const是编译时常量,在编译时确定该值,且值在编译时被内联到代码中;readonly是运行时常量,在运行时确定该值。
    4、const默认是静态的;而readonly如果设置成静态需要显示声明 。
    5、支持的类型时不同,const只能修饰基元类型或值为null的其他引用类型;readonly可以是任何类型。

     1 namespace ConsoleApp
     2 {
     3     class Program
     4     {
     5         public readonly int PORT;
     6         static void Main(string[] args)
     7         {
     8             B a = new B();
     9             Console.WriteLine(a.PORT);
    10             Console.WriteLine(B.PORT2);
    11             Console.WriteLine(a.PORT3.ccc);
    12             Console.ReadKey();
    13         }
    14     }
    15     class B
    16     {
    17         //readonly即可以在声明处赋值,也可以在构造方法里赋值
    18         public readonly int PORT;
    19         //const必须在声明的同时赋值
    20         public const int PORT2 = 10;
    21         //public const A PORT3 = new A() error const只能修饰基元类型或值为null的其他引用类型
    22         //readonly可以是任何类型
    23         public readonly A PORT3 = new A();
    24         public B()
    25         {
    26             PORT = 11;
    27         }
    28     }
    29    class A
    30    {
    31        public string ccc = "aaaaa";
    32    }
    33 }
    View Code

    2. 字段与属性有什么异同?
    •属性提供了更为强大的,灵活的功能来操作字段
    •出于面向对象的封装性,字段一般不设计为Public
    •属性允许在set和get中编写代码
    •属性允许控制set和get的可访问性,从而提供只读或者可读写的功能 (逻辑上只写是没有意义的)
    •属性可以使用override 和 new
    3. 静态成员和非静态成员的区别?
    •静态变量使用 static 修饰符进行声明,静态成员在加类的时候就被加载(上一篇中提到过,静态字段是随类型对象存放在Load Heap上的),通过类进行访问。
    •不带有static 修饰符声明的变量称做非静态变量,在对象被实例化时创建,通过对象进行访问 。
    •一个类的所有实例的同一静态变量都是同一个值,同一个类的不同实例的同一非静态变量可以是不同的值 。
    静态函数的实现里不能使用非静态成员,如非静态变量、非静态函数等。
    4. 特性是什么?如何使用?
    特性与属性是完全不相同的两个概念,只是在名称上比较相近。Attribute特性就是关联了一个目标对象的一段配置信息,本质上是一个类,其为目标元素提供关联附加信息,这段附加信息存储在dll内的元数据,它本身没什么意义。运行期以反射的方式来获取附加信息。
    5. C#中的委托是什么?事件是不是一种委托?
    什么是委托?简单来说,委托类似于C或C++中的函数指针,允许将方法作为参数进行传递。
    •C#中的委托都继承自System.Delegate类型;
    •委托类型的声明与方法签名类似,有返回值和参数;
    •委托是一种可以封装命名(或匿名)方法的引用类型,把方法当做指针传递,但委托是面向对象、类型安全的;
    事件可以理解为一种特殊的委托,事件内部是基于委托来实现的。

    GC与内存管理

    1. 简述一下一个引用对象的生命周期?
    •new创建对象并分配内存
    •对象初始化
    •对象操作、使用
    •资源清理(非托管资源)
    •GC垃圾回收
    2. GC进行垃圾回收时的主要流程是?
    ① 标记:先假设所有对象都是垃圾,根据应用程序根Root遍历堆上的每一个引用对象,生成可达对象图,对于还在使用的对象(可达对象)进行标记(其实就是在对象同步索引块中开启一个标示位)。
    ② 清除:针对所有不可达对象进行清除操作,针对普通对象直接回收内存,而对于实现了终结器的对象(实现了析构函数的对象)需要单独回收处理。清除之后,内存就会变得不连续了,就是步骤3的工作了。
    ③ 压缩:把剩下的对象转移到一个连续的内存,因为这些对象地址变了,还需要把那些Root跟指针的地址修改为移动后的新地址。
    3. GC在哪些情况下回进行回收工作?
    •内存不足溢出时(0代对象充满时)
    •Windwos报告内存不足时,CLR会强制执行垃圾回收
    •CLR卸载AppDomian,GC回收所有
    •调用GC.Collect
    •其他情况,如主机拒绝分配内存,物理内存不足,超出短期存活代的存段门限
    4. using() 语法是如何确保对象资源被释放的?如果内部出现异常依然会释放资源吗?
    using() 只是一种语法形式,其本质还是try…finally的结构,可以保证Dispose始终会被执行。
    5. 解释一下C#里的析构函数?为什么有些编程建议里不推荐使用析构函数呢?
    C#里的析构函数其实就是终结器Finalize,因为长得像C++里的析构函数而已。
    有些编程建议里不推荐使用析构函数要原因在于:第一是Finalize本身性能并不好;其次很多人搞不清楚Finalize的原理,可能会滥用,导致内存泄露,因此就干脆别用了。
    6. Finalize() 和 Dispose() 之间的区别?
    Finalize() 和 Dispose()都是.NET中提供释放非托管资源的方式,他们的主要区别在于执行者和执行时间不同:
    •finalize由垃圾回收器调用;dispose由对象调用。
    •finalize无需担心因为没有调用finalize而使非托管资源得不到释放,而dispose必须手动调用。
    •finalize不能保证立即释放非托管资源,Finalizer被执行的时间是在对象不再被引用后的某个不确定的时间;而dispose一调用便释放非托管资源。
    •只有class类型才能重写finalize,而结构不能;类和结构都能实现IDispose。
    另外一个重点区别就是终结器会导致对象复活一次,也就说会被GC回收两次才最终完成回收工作,这也是有些人不建议开发人员使用终结器的主要原因。
    7. Dispose和Finalize方法在何时被调用?
    •Dispose一调用便释放非托管资源;
    •Finalize不能保证立即释放非托管资源,Finalizer被执行的时间是在对象不再被引用后的某个不确定的时间;
    8. .NET中的托管堆中是否可能出现内存泄露的现象?
    是的,可能会。比如:
    •不正确的使用静态字段,导致大量数据无法被GC释放;
    •没有正确执行Dispose(),非托管资源没有得到释放;
    •不正确的使用终结器Finalize(),导致无法正常释放资源;
    •其他不正确的引用,导致大量托管对象无法被GC释放;
    9. 在托管堆上创建新对象有哪几种常见方式?
    •new一个对象;
    •字符串赋值,如string s1=”abc”;
    •值类型装箱。

    多线程编程与线程同步

    1. 描述线程与进程的区别?
    •一个应用程序实例是一个进程,一个进程内包含一个或多个线程,线程是进程的一部分;
    •进程之间是相互独立的,他们有各自的私有内存空间和资源,进程内的线程可以共享其所属进程的所有资源;
    2. lock为什么要锁定一个参数,可不可锁定一个值类型?这个参数有什么要求?
    lock的锁对象要求为一个引用类型。它可以锁定值类型,但值类型会被装箱,每次装箱后的对象都不一样,会导致锁定无效。
    对于lock锁,锁定的这个对象参数才是关键,这个参数的同步索引块指针会指向一个真正的锁(同步块),这个锁(同步块)会被复用。
    3. 多线程和异步有什么关系和区别?
    多线程是实现异步的主要方式之一,异步并不等同于多线程。实现异步的方式还有很多,比如利用硬件的特性、使用进程或纤程等。在.NET中就有很多的异步编程支持,比如很多地方都有Begin***、End***的方法,就是一种异步编程支持,它内部有些是利用多线程,有些是利用硬件的特性来实现的异步编程。
    4. 线程池的优点有哪些?又有哪些不足?
    优点:减小线程创建和销毁的开销,可以复用线程;也从而减少了线程上下文切换的性能损失;在GC回收时,较少的线程更有利于GC的回收效率。
    缺点:线程池无法对一个线程有更多的精确的控制,如了解其运行状态等;不能设置线程的优先级;加入到线程池的任务(方法)不能有返回值;对于需要长期运行的任务就不适合线程池。
    5.Mutex和lock有何不同?一般用哪一个作为锁使用更好?
    Mutex是一个基于内核模式的互斥锁,支持锁的递归调用,而Lock是一个混合锁,一般建议使用Lock更好,因为lock的性能更好。

     

  • 相关阅读:
    在物理机上安装Ubuntu指南
    C语言程序设计-现代方法(笔记3)
    C语言程序设计-现代方法(笔记2)
    C语言程序设计-现代方法(笔记1)
    颠覆完美软件:软件测试必须知道的几件事(总结)
    颠覆完美软件:软件测试必须知道的几件事(读书笔记6)
    颠覆完美软件:软件测试必须知道的几件事(读书笔记5)
    颠覆完美软件:软件测试必须知道的几件事(读书笔记4)
    pycharm 引入虚环境
    性能测试之tsung
  • 原文地址:https://www.cnblogs.com/hendrix/p/10713029.html
Copyright © 2020-2023  润新知