Unity客户端开发(初级)面试总结-- C# 基础


前言

本文并非全面的面试题总结,仅仅记录本人在面试过程中遇到的知识盲点。


一、数据类型

1.引用类型

  • delegate(委托)
  • interface(接口)
  • string(字符串)

2.值类型

  • struct(结构)
  • enum(枚举)

扩展:元组类型,C#4.0的Tuple为引用类型,C#7.0的ValueTuple为值类型(面试不会问,因为我自己有用到,所以写在这里)。

二、类型转换

1.隐式转换与显式转换

  • 隐式转换:位数更少的类型转换为位数更多的类型,数据或精度不会丢失。
short a = 1;
int b = a;	// b能盛下a的值,a可直接隐式转换

面试题遇到的,不建议细究

short a = 1;
short b = 2;
short c = b + a;	// 编译报错:无法将类型“int”隐式转换为“short”
/// b + a 类型自动升为int ???
/// 原因:
/// 1.short + short 可能会出现数值溢出
/// 2.“+” 运算符操作最小的数值单位是 int
  • 显式/强制转换
// 无符号类型转换为有符号类型需要显式转换
uint a = 1;
int b = (int)a;	
byte c= 3;
sbyte d = (sbyte)c;
// 位数更多的类型转换为位数更少的类型,数据或精度可能会丢失
double e = 5;
float f = (float)e;

2.装箱和拆箱*

发生装箱的几个需要注意的点:

  • 结构实现了接口,结构转换为接口时会发生装箱;
public struct A : IA
{
}

A a = new A();
IA ia = a;
  • 值类型调用GetType()GetHashCode()ToString()Equals()MemberwiseClone()时,如果显式或隐式调用到基类(System.Object)的该方法,会发生装箱;

    • 解决方法是重写上述方法,且不调用(base.XXX);
  • DictionaryHashTablekey如果为值类型且存在上面提到的问题,会发生装箱;

    • 因为会调用到Equals()GetHashCode()方法;
  • 参数类型不统一,使用可变参数params object[],会产生一个临时object数组,可能会装箱;

    • 解决方法是使用不同参数列表的重载函数。

三、结构(struct)

1.继承关系

  • 结构不能派生出其它结构,也无法从其它结构继承;
  • 结构可以实现一个或多个接口(interface)。

2.构造函数

以下规则仅适用于非静态成员,静态成员见下面“五、静态(static)”。

  • 结构不能有显式的无参构造函数,显式实现构造函数必须带有参数;
  • 结构不允许在字段声明时直接赋值(包括自动实现的属性);
  • 带有参数的构造函数里必须为所有字段赋值(包括自动实现的属性)。
public struct Node
{
	// 字段
	public string Key;
    public int Value;		// public int Value = 0;	// 编译错误
    // 自动实现的属性
	public int Id { get; set; }		// public int Id { get; set; } = 0;	// 编译错误
    // 有参构造函数
    public Node(string key)		// 需要为Value和Id赋值,否则编译错误
    {
    	Key = key;
    	Value = 0;
    	Id = 0;
    }
}

3.赋值

  • 结构不能与null作比较或赋值为null
  • 把一个结构赋值给另一个结构,就是将一个结构的值复制给另一个结构。
public struct A
{
    public int a;
}

public static void Change(A a)
{
    a.a = 1;
    a = new A();
    a.a = 2;
}

static void Main(string[] args)
{
    A a = new A();
    a.a = 0;
    Change(a);
    Console.WriteLine(a.a);	// 输出:0
}

四、类(class)

1.接口(interface)

  • 接口只能继承一个或多个其它接口;
  • 接口本身不能被实例化,只能被继承;
  • 接口声明只有函数成员(只允许有“抽象”成员),且成员不允许有任何访问修饰符;
  • 接口声明不允许有静态成员。

2.抽象类(abstract class)

与接口相比:

  • 抽象类可以继承一个其它类(抽象或非抽象类均可),可以继承一个或多个其它接口;
  • 抽象类本身不能被实例化,只能被继承;
  • 抽象类声明可以有抽象函数成员(允许有普通成员,允许没有“抽象”成员),且抽象成员允许除私有(private)以外的访问修饰符;
  • 抽象类声明允许有静态成员,但静态成员不能声明为抽象。

与普通类相比:

  • 抽象类本身不能被声明为静态(static)或封闭(sealed);
  • 抽象类可以有有参或无参构造函数,如果只有参构造函数,继承它的子类也必须显式实现参数列表相同的有参构造函数并调用
public abstract class A
{
    public A(int a)
    {
    }
}

public class B : A
{
	// 必须实现参数列表相同的构造函数并调用父类构造函数,否则编译报错
    public B(int a) : base(a)	
    {
    }
}

抽象方法与虚方法相比:

  • 虚方法(virtual)可以在非抽象类和抽象类中声明,抽象方法只能在抽象类中声明;
  • 虚方法必须有实现,抽象方法必须没有实现;
  • 虚方法和抽象方法都不能声明为私有(private);
  • 派生的子类,可以不实现虚方法,但必须实现抽象方法。

3.赋值

上面的程序,如果A为class:

public class A
{
    public int a;
}

public static void Change(A a)	// 形参a是外部实参a的副本
{
    a.a = 1;
    a = new A();	// 这里改变的是外部实参a的副本的引用,不影响外部实参a
    a.a = 2;
}

static void Main(string[] args)
{
    A a = new A();
    a.a = 0;
    Change(a);
    Console.WriteLine(a.a);	// 输出:1
}

如果想要输出结果为2呢?需要用到引用参数(ref):

public class A
{
    public int a;
}

public static void Change(ref A a)	// 形参a是外部实参a的“别名”
{
    a.a = 1;
    a = new A();	// 这里直接改变了外部实参a本身的引用
    a.a = 2;
}

static void Main(string[] args)
{
    A a = new A();
    a.a = 0;
    Change(ref a);
    Console.WriteLine(a.a);	// 输出:2
}

4.多态

public class A
{
    public virtual void Print()
    {
        Console.WriteLine("This is A");
    }
    public void PrintA()
    {
        Console.WriteLine("Fire A");
    }
}

public class B : A
{
    public override void Print()
    {
        Console.WriteLine("This is B");
    }
    public void PrintB()
    {
        Console.WriteLine("Fire B");
    }
}

static void Main(string[] args)
{
    B b = new B();
    A a = b;    		// 子类对象赋值给父类变量
	// 或直接写为
	// A a = new B();
    a.Print();  		// 输出:This is B
    a.PrintA();			// 输出:Fire A
    // a.PrintB();		// 编译错误:“A”未包含“PrintB”的定义。
}
  • 子类对象赋值给父类变量:

    • 方法列表(可调用的方法)由父类变量决定;
    • 方法实现由子类对象决定。

五、静态成员(static)

  • 静态成员不能引用实例成员;
  • 不存在类实例,静态成员也存在。

1.静态类

  • 不能继承或派生任何类或接口;
  • 无法实例化;
  • 只包含静态成员。

2.静态构造函数

  • 作用:初始化类级别的项(初始化静态字段);

    • 普通类、抽象类、静态类、结构,都可以有静态构造函数;
    • 一个类/结构只能有一个无参静态构造函数;
    • 只能用static声明,不能有任何访问修饰符。
      static A()
      {
      }
      
  • 静态构造函数的执行时机:在引用任何静态成员之前,在创建普通类/结构的任何实例之前;

    • 无法显式调用静态构造函数;系统(CLR)会在上述时机自动调用,只调用一次

3.静态字段

  • 如果静态字段有初始化语句,静态字段初始化时机:在引用任何静态成员之前,只执行一次

    • 如果有多个静态字段,那么初始化的先后顺序是书写顺序
public class F
{
    public static F inst = new F();
    public static int x = 3;
    
    private F()
    {
        Console.WriteLine(x);
    }
}
static void Main(string[] args)
{
    Console.WriteLine(F.x);
}
// 输出结果为:
// 0
// 3

4.初始化顺序

静态字段(变量)初始化发生在静态构造函数执行之前

public class F
{
    public static int x = 3;
    static F()
    {
        Console.WriteLine(x);
        x = 5;
    }
}
static void Main(string[] args)
{
    Console.WriteLine(F.x);
}
// 输出结果为:
// 3
// 5

六、析构函数

析构函数,在垃圾回收,对象被释放(销毁)时,由垃圾回收器调用

~A()
{
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值