百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

面试 | .NET基础知识快速通关(1) 面试零基础怎么学

bigegpt 2024-10-13 01:17 6 浏览


此系列文章为我在2015年发布于博客园的.NET基础拾遗系列,它十分适合初中级.NET开发工程师在面试前进行一个系统的复习,因此我将其搬到公众号分享与你。

本文为第一篇,我们会对.NET的基础类型和语法进行基础复习,全文会以Q/A的形式展现,即以面试题的形式来描述。

1 .NET中的所有类型的基类是什么?

在.NET中所有的内建类型都继承自System.Object类型。在C#中,不需要显示地定义类型继承自System.Object,编译器将自动地自动地为类型添加上这个继承申明,以下两行代码的作用完全一致:

public class A 
{ 
}


public class A : System.Object 
{ 
}

2 值类型和引用类型的区别是什么?

在.NET中的类型分为值类型和引用类型,它们各有特点,其共同点是都继承自System.Object,但最明显的区分标准却是是否继承自System.ValueType(System.ValueType继承自System.Object),也就是说所有继承自System.ValueType的类型是值类型,而其他类型都是引用类型。常用的值类型包括:结构、枚举、整数型、浮点型、布尔型等等;而在C#中所有以class关键字定义的类型都是引用类型。

严格来讲,System.Object作为所有内建类型的基类,本身并没有值类型和引用类型之分。但是System.Object的对象,具有引用类型的特点。这也是值类型在某些场合需要装箱和拆箱操作的原因。


(1)赋值时的区别

这是值类型与引用类型最显著的一个区别:值类型的变量直接将获得一个真实的数据副本,而对引用类型的赋值仅仅是把对象的引用赋给变量,这样就可能导致多个变量引用到一个对象实例上。

(2)内存分配的区别

引用类型的对象将会在堆上分配内存,而值类型的对象则会在堆栈上分配内存。堆栈空间相对有限,但是运行效率却比堆高很多。

(3)继承结构的区别

由于所有的值类型都有一个共同的基类System.ValueType,因此值类型具有了一些引用类型所不具有的共同性质,比较重要的一点就是值类型的比较方法:Equals。所有的值类型已经实现了内容的比较(而不再是引用地址的比较),而引用类型没有重写Equals方法还是采用引用比较。

3 装箱与拆箱的原理是什么?

(1)装箱

CLR需要做额外的工作把堆栈上的值类型移动到堆上,这个操作就被称为装箱。

(2)拆箱

装箱操作的反操作,把堆中的对象复制到堆栈中,并且返回其值。

装箱和拆箱都意味着堆和堆栈空间的一系列操作,毫无疑问,这些操作的性能代价是很大的,尤其对于堆上空间的操作,速度相对于堆栈的操作慢得多,并且可能引发垃圾回收,这些都将大规模地影响系统的性能。因此,我们应该避免任何没有必要的装箱和拆箱操作

如何避免呢?

首先分析装箱和拆箱经常发生的场合:

① 值类型的格式化输出

② System.Object类型的容器

对于第①种情况,我们可以通过下面的改动示例来避免:

int i = 10;
Console.WriteLine("The value is {0}", i.ToString());

对于第②种情况,则可以使用泛型技术来避免使用针对System.Object类型的容器,有效避免大规模地使用装箱和拆箱:

ArrayList arrList = new ArrayList();
arrList.Add(0);
arrList.Add("1");
// 使用泛型数据结构代替ArrayList
List<int> intList = new List<int>();
intList.Add(1);
intList.Add(2);

4 struct与class的区别是什么?

首先,struct(结构)是值类型,而class(类)是引用类型,所有的结构对象都分配在堆栈上,而所有的类对象都分配在堆上。

其次,struct与class相比,不具备继承的特性,struct虽然可以重写定义在System.Object中的虚方法,但不能定义新的虚方法和抽象方法。

最后,struct不能有无参数的构造方法(class默认就有),也不能为成员变量定义初始值。

public struct A
{
  public int a = 1; // 这里不能编译通过
}

结构对象在构造时必须被初始化为0,构造一个全0的对象是指在内存中为对象分配一个合适的空间,并且把该控件置为0。

问题来了:如何使用struct or class?

答:当一个类型仅仅是原始数据的集合,而不需要复杂的操作时,就应该设计为struct,否则就应该设计为一个class

5 C#中方法的参数传递有几种方式?

(1)ref关键字:引用传递参数,需要在传递前初始化;(ref 要求参数在传入前被初始化)

(2)out关键字:引用传递参数,需要在返回前初始化;(out 要求参数在方法返回前被初始化)

Note:ref和out这两个关键字的功能极其类似,都用来说明该参数以引用方式进行传递。大家都知道,.NET的类型分为引用类型和值类型,当一个方法参数是引用类型时,传递的本质就是对象的引用。所以,这两个关键字的作用都发生在值类型上

(3)params关键字:允许方法在定义时不确定参数的数量,这种形式非常类似数组参数,但形式更加简洁易懂。

But,params关键字的使用也有一定局限:当一个方法申明了一个params参数后,就不允许在其后面再有任何其他参数

例如下面一段代码,定义了两个完全相等的方法:NotParams和UseParams,使用由params修饰参数的方法时,可以直接把所有变量集合传入而无须先申明一个数组对象。

class Program
{
    static void Main(string[] args)
    {
        // params
        string s = "I am a string";
        int i = 10;
        double f = 2.3;
        object[] par = new object[3] { s, i, f };
        // not use params
        NotParams(par);
        // use params
        UseParams(s, i, f);


        Console.ReadKey();
    }


    // Not use params
    public static void NotParams(object[] par)
    {
        foreach (var obj in par)
        {
            Console.WriteLine(obj);
        }
    }


    // Use params
    public static void UseParams(params object[] par)
    {
        foreach (var obj in par)
        {
            Console.WriteLine(obj);
        }
    }
}

6 浅复制与深复制的区别是什么?

(1)浅复制

复制一个对象的时候,仅仅复制原始对象中所有的非静态类型成员和所有的引用类型成员的引用。(新对象和原对象将共享所有引用类型成员的实际对象)

(2)深复制

复制一个对象的时候,不仅复制所有非静态类型成员,还要复制所有引用类型成员的实际对象

下图展示了浅复制和深复制的区别:

在.NET中,基类System.Object已经为所有类型都实现了浅复制,类型所要做的就是公开一个复制的接口,而通常的,这个接口会由ICloneable接口来实现。ICloneable只包含一个方法Clone,该方法既可以被实现为浅复制也可以被实现为深复制,具体如何取舍则根据具体类型的需求决定。

此外,在System.Object基类中,有一个保护的MemeberwiseClone()方法,它便用于进行浅度复制。所以,对于引用类型,要想实现浅度复制时,只需要调用这个方法就可以了:

public object Clone()
{    
  return MemberwiseClone();
}

下面的代码展示了一个使用ICloneable接口提供深复制的简单示例:

public class DeepCopy : ICloneable
{
    public int i = 0;
    public A a = new A();


    public object Clone()
    {
        // 实现深复制-方式1:依次赋值和实例化
        DeepCopy newObj = new DeepCopy();
        newObj.a = new A();
        newObj.a.message = this.a.message;
        newObj.i = this.i;


        return newObj;
    }


    public new object MemberwiseClone()
    {
        // 实现浅复制
        return base.MemberwiseClone();
    }


    public override string ToString()
    {
        string result = string.Format("I的值为{0},A为{1}", this.i.ToString(), this.a.message);
        return result;
    }
}


public class A
{
    public string message = "我是原始A";
}


public class Program
{
    static void Main(string[] args)
    {
        DeepCopy dc = new DeepCopy();
        dc.i = 10;
        dc.a = new A();


        DeepCopy deepClone = dc.Clone() as DeepCopy;
        DeepCopy shadowClone = dc.MemberwiseClone() as DeepCopy;


        // 深复制的目标对象将拥有自己的引用类型成员对象
        deepClone.a.message = "我是深复制的A";
        Console.WriteLine(dc);
        Console.WriteLine(deepClone);
        Console.WriteLine();
        // 浅复制的目标对象将和原始对象共享引用类型成员对象
        shadowClone.a.message = "我是浅复制的A";
        Console.WriteLine(dc);
        Console.WriteLine(shadowClone);


        Console.ReadKey();
    }
}

其执行结果如下图所示,可以清楚地看到对深复制对象的属性的赋值不会影响原始对象,而浅复制则相反。

从上面的代码中可以看到,在深复制的实现中,如果每个对象都要这样去进行深度复制就太麻烦了,我们可以利用序列化/反序列化来对对象进行深度复制,即先把对象序列化(Serialize)到内存中,然后再进行反序列化,通过这种方式来进行对象的深度复制:

[Serializable]
public class DeepCopy : ICloneable
{
    ......


    public object Clone()
    {
        // 实现深复制-方式1:依次赋值和实例化
        //DeepCopy newObj = new DeepCopy();
        //newObj.a = new A();
        //newObj.a.message = this.a.message;
        //newObj.i = this.i;


        //return newObj;
        // 实现深复制-方式2:序列化/反序列化
        BinaryFormatter bf = new BinaryFormatter(); 
        MemoryStream ms = new MemoryStream(); 
        bf.Serialize(ms, this); 
        ms.Position = 0; 
        return bf.Deserialize(ms);
    }


    ......
}
[Serializable]
public class A
{
    public string message = "我是原始A";
}

Note:一般可被继承的类型应该避免实现ICloneable接口,因为这样做将强制所有的子类型都需要实现ICloneable接口,否则将使类型的深复制不能覆盖子类的新成员。

总结

本文总结复习了.NET的基础类型和语法相关的重要知识点,下一篇会总结.NET内存管理相关的重要知识点,欢迎继续关注!


参考资料(全是经典)

朱毅,《进入IT企业必读的200个.NET面试题》

张子阳,《.NET之美:.NET关键技术深入解析》

王涛,《你必须知道的.NET》

相关推荐

或者这些Joplin插件也可以帮助你的笔记应用再一次强大

写在前面距离上次分享《搭建私有全平台多端同步笔记,群晖NAS自建JoplinServer服务》已过去一段时间,大家是否开始使用起来了呢?如果你和我一样已经使用过Joplin有一段时间了,那或许你也会...

Three.JS教程4 threejs中的辅助类

一、辅助类简介Three.js提供了一些辅助类(Helpers)以帮助我们更容易地调试、可视化场景中的元素。ArrowHelepr:创建箭头辅助器;AxisHelper:创建坐标轴辅助器;BoxH...

第2章 还记得点、线、面吗(二)(第二章还能敲钟吗)

glbgltf模型(webvrmodel)-gltf模型下载定制,glb模型下载定制,三维项目电商网站在线三维展示,usdz格式,vr模型网,网页VR模型下载,三维模型下载,webgl网页模型下载我...

如何检查Linux系统硬件信息?从CPU到显卡,一网打尽!

你可能会问:“我为什么要关心硬件信息?”答案很简单:硬件是Linux系统的根基,了解它可以帮你解决很多实际问题。比如:性能调优:知道CPU核心数和内存大小,才能更好地调整程序运行参数。故障排查:系统卡...

SpriteJS:图形库造轮子的那些事儿

从2017年到2020年,我花了大约4年的时间,从零到一,实现了一个可切换WebGL和Canvas2D渲染的,跨平台支持浏览器、SSR、小程序,基于DOM结构和支持响应式的,高...

平时积累的FPGA知识点(6)(fpga经典应用100例)

平时在FPGA群聊等积累的FPGA知识点,第六期:1万兆网接口,发三十万包,会出现掉几包的情况,为什么?原因:没做时钟约束,万兆网接口的实现,本质上都是高速serdes,用IP的话,IP会自带约束。...

芯片逻辑调度框架设计 都需要那些那些软件工具

设计芯片逻辑调度框架通常需要使用以下软件工具:1.逻辑设计工具:例如Vivado、Quartus、SynopsysDesignCompiler等,用于设计和实现逻辑电路。2.仿真工具:例如Mo...

ZYNQ与DSP之间EMIF16通信(正点原子领航者zynq之fpga开发指南v3)

本文主要介绍说明XQ6657Z35-EVM高速数据处理评估板ZYNQ与DSP之间EMIF16通信的功能、使用步骤以及各个例程的运行效果。[基于TIKeyStone架构C6000系列TMS320C6...

好课推荐:从零开始大战FPGA(从零开始的冒险4399)

从零开始大战FPGA引子:本课程为“从零开始大战FPGA”系列课程的基础篇。课程通俗易懂、逻辑性强、示例丰富,课程中尤其强调在设计过程中对“时序”和“逻辑”的把控,以及硬件描述语言与硬件电路相对应的“...

业界第一个真正意义上开源100 Gbps NIC Corundum介绍

来源:内容由「网络交换FPGA」编译自「FCCM2020」,谢谢。FCCM2020在5月4日开始线上举行,对外免费。我们有幸聆听了其中一个有关100G开源NIC的介绍,我们对该文章进行了翻译,并对其中...

高层次综合:解锁FPGA广阔应用的最后一块拼图

我们为什么需要高层次综合高层次综合(High-levelSynthesis)简称HLS,指的是将高层次语言描述的逻辑结构,自动转换成低抽象级语言描述的电路模型的过程。所谓的高层次语言,包括C、C++...

Xilinx文档编号及其内容索引(部分)

Xilinx文档的数量非常多。即使全职从事FPGA相关工作,没有几年时间不可能对器件特性、应用、注意事项等等有较为全面的了解。本文记录了我自使用Xilinx系列FPGA以来或精读、或翻阅、或查询过的文...

Xilinx Vivado联合Modelsim软件仿真

引言:Xilinx公司Vivado开发软件自带仿真工具,可以实现一般性能的FPGA软件仿真测试,其测试执行效率以及性能都不如第三方专用仿真软件Modelsim强。本文我们介绍下如何进行Vivado20...

体育动画直播是怎么做出来的?从数据到虚拟赛场的科技魔法!

你是否见过这样的比赛直播?没有真实球员,却能看梅西带球突破?足球比赛变成动画版,但数据100%真实?电竞比赛用虚拟形象直播,选手操作实时同步?这就是体育动画直播——一种融合实时数据、游戏引擎和AI的...

Dialogue between CPC and political parties of neighboring countries held in Beijing

BEIJING,May26(Xinhua)--TheCommunistPartyofChina(CPC)inDialoguewithPoliticalPartiesof...