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);
-
Dictionary
、HashTable
的key
如果为值类型且存在上面提到的问题,会发生装箱;- 因为会调用到
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()
{
}