C#笔试⾯试知识点整理(吐⾎整理)
⼀、接⼝
可以包含⽅法、属性、事件、索引器或这四种成员类型的任意组合。 接⼝不能包含常量、字段、运算符、实例构造函数、终结器或类型。接⼝成员会⾃动成为公共成员,不能包含任何访问修饰符。 成员也不能是静态成员。若要实现接⼝成员,实现类的对应成员必须是公共、⾮静态,并且具有与接⼝成员相同的名称和签名。
接⼝既不能有构造函数(如何构建不能实例化的对象?)也不能有字段(因为这隐含了某些内部的实现⽅式)。
在接⼝定义中不允许声明成员的修饰符。接⼝成员总是隐⽰为public,不能修饰为virtual,如果需要就应该由实现的类来声明。
接⼝中的⽅法⽤来定义对象之间通信的契约,指定接⼝中的⽅法为私有或保护没有意义。
⼆、类和结构体的区别
1、结构与类的区别是他们在内存中的存储⽅式、访问⽅式(类是存储在堆(heap)上 的引⽤类型,⽽结
构是存储在栈(stack)上的值类型)。 较⼩的数据类型使⽤结构可提⾼性能。 但在语法上,结构与类⾮常相似,主要的区别是使⽤关键字 Struct代替 Class来声明结构。
2、结构的⼀个限制是不⽀持继承,但每个结构都派⽣⾃System.ValueType.结构不能实现类型层级的结构,但结构可以实现接⼝。换⾔之,结构并不⽀持继承,但⽀持接⼝继承。⽽类可以派⽣⾃另⼀个类和任意多个接⼝。
事实上定义结构和类可以总结为:
结构总是派⽣⾃Sytem.ValueType,它们还可以派⽣⾃任意多个接⼝
类总是派⽣⾃⽤户选择的另⼀个类,它们还可以派⽣⾃任意多个接⼝
3、结构体派⽣⾃ValueType,ValueType派⽣⾃Object,可访问Object的⽅法
结构体的默认构造不能显⽰给出,不然编译器会报错,当类有⾃定义的构造函数时,默认构造会被隐藏,要想使⽤默认构造,必须显⽰给出;结构体的⾃定义构造函数必须初始化所有的实例字段和未初始化的属性,⽽类不必。结构体默认的构造函数是⾃动定义的,且不能被改变。
4、在结构体中可以声明字段,但是声明字段的时候是不能给初始值的。类中声明1个字段的同时给这个字段赋初始值,
5、创建结构体对象可以不使⽤new关键字.直接声明1个变量就可以.但是这样的话,结构体对象中的字段是没有初始值的,所以在使⽤字段之前必须要为这个字段赋值.
原因很简单.因为声明的时候就不能给初始值,虽然构造函数中为对象的字段赋值,但是此种⽅式创建结构体对象,没有调⽤构造函数,所以必须要程序员在使⽤之前⼿动赋值。
另外1种创建结构体对象的⽅式和类⼀样,使⽤new关键字来创建,与不使⽤new关键字创建不同的是,通过使⽤new关键字创建结构体对象后,这个结构体对象的字段就已经有值了.原因不难理解,new关键字调⽤了构造函数,⽽结构体构造函数要求必须要为所有的字段赋值.
三、C#默认的修饰符
1、直接声明在命名空间下的类和结构体,默认是internal的
2、类和结构体的成员,包括内嵌的类和结构体,默认是private的
3、接⼝默认是internal的
4、enum的成员是public的访问权限
5、类的成员,默认是private的访问权限
6、接⼝的成员,默认是public的访问权限
7、结构体的成员,和class类似,默认的访问权限也是private的
*
四、基类和⼦类构造函数的执⾏顺序*
1、当实例化⼀个⼦类时,⽗类以及⼦类的变量和构造函数的执⾏顺序如下:
01⼦类的静态变量->02⼦类的静态构造函数->03⼦类的变量->04⽗类的静态变量(此时因为要执⾏⼦类的构造函数,会与⽗类发⽣关系)->05⽗类静态构造函数->06⽗类变量->07⽗类构造函数->10⼦类构造函数->结束。
class A
{
public A()
{
PrintFields();
}
public virtual void PrintFields() { }
}
class B : A
{
int x = 1;
int y;
public B() { y = -1; }
public override void PrintFields()
{ Console.WriteLine("x={0},y={1}", x, y); }
}
new B()输出结果为 x=1,y=0;
⾸先初始化⼦类变量 x=1,y=0;
调⽤基类构造函数A() A调⽤了函数PrintFields() 由于是虚函数调⽤⼦类的PrintFields()由于B的构造函数还没开始执⾏
于是输出 x=1,y=0;
五、C#访问修饰符
其他修饰符
六、ASP.Net中页⾯传值的⼏种⽅式
⼤致概括⼀下,ASP.NET 页⾯之间传递值得⽅式⼤致可以分为如下⼏种:
Request.QueryString[“name”],Request.Form(“name”),Session,Cookie,Cache,Application,Server.Transfer,Database,HttpC ontext的Item属性,Files,DataBase等等。
七、线程和进程的主要区别
(1)进程是操作系统资源分配的基本单位,⽽线程是操作系统能够进⾏运算调度的最⼩单位。它被包含在进程之中,是进程中的实际运作单位。
(2)同⼀个进程中可以包括多个线程,并且线程共享整个进程的资源(**同⼀进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,⽂件描述符和信号处理等等。但同⼀进程中的多个线程有各⾃的调⽤栈(call stack),⾃⼰的寄存器环境(register context),⾃⼰的线程本地存储(thread-local storage)),⼀个进⾏⾄少包括⼀个线程。
⼀条线程指的是进程中⼀个单⼀顺序的控制流,⼀个进程中可以并发多个线程,每条线程并⾏执⾏不同的任务。
(3)线程与进程的区别可以归纳为以下4点:
1)地址空间和其它资源(如打开⽂件):进程间相互独⽴,同⼀进程的各线程间共享。某进程内的线程在其它进程不可见。
2)通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进⾏通信——需要进程同步和互斥⼿段的辅助,以保证数据的⼀致性。
3)调度和切换:线程上下⽂切换⽐进程上下⽂切换要快得多。
4)在多线程OS中,进程不是⼀个可执⾏的实体。
7、重载、重写、虚⽅法、抽象⽅法
1、重载(overload): 在同⼀个作⽤域(⼀般指⼀个类)的两个或多个⽅法函数名相同,参数列表不同的⽅法叫做重载,它们有三个特点(俗称两必须⼀可以):
⽅法名必须相同
参数列表必须不相同
返回值类型可以不相同
例如:
public void Sleep()
{
Console.WriteLine("Animal睡觉");
}
public int Sleep(int time)
{
Console.WriteLine("Animal{0}点睡觉", time);
return time;
}
2、重写(override):⼦类中为满⾜⾃⼰的需要来重复定义某个⽅法的不同实现,需要⽤ override 关键字,被重写的⽅法必须是虚⽅法,⽤的是 virtual 关键字。它的特点是(三个相同):
相同的⽅法名
相同的参数列表
相同的返回值
如:⽗类中的定义:
public virtual void EatFood()
{
Console.WriteLine("Animal吃东西");
}
⼦类中的定义:
public override void EatFood()
{
Console.WriteLine("Cat吃东西");
//base.EatFood();
}
3、虚⽅法:即为基类中定义的允许在派⽣类中重写的⽅法,使⽤virtual关键字定义。如:
public virtual void EatFood()
{
Console.WriteLine(“Animal吃东西”);
}
注意:虚⽅法也可以被直接调⽤。如:
Animal a = new Animal();
a.EatFood();
writeline特点
执⾏输出结果为:
Animal吃东西
4、抽象⽅法:在基类中定义的并且必须在派⽣类中重写的⽅法,使⽤ abstract 关键字定义。如:
public abstract class Biology
{
public abstract void Live();
}
public class Animal : Biology
{
public override void Live()
{
Console.WriteLine("Animal重写的抽象⽅法");
//throw new NotImplementedException();
}
}
注意:抽象⽅法只能在抽象类中定义,如果不在抽象类中定义,则会报出如下错误:
虚⽅法和抽象⽅法的区别是:因为抽象类⽆法实例化,所以抽象⽅法没有办法被调⽤,也就是说抽象⽅法永远不可能被实现。
5、隐藏⽅法:在派⽣类中定义的和基类中的某个⽅法同名的⽅法,使⽤ new 关键字定义。如在基类 Animal 中有⼀⽅法 Sleep():
public void Sleep()
{
Console.WriteLine(“Animal Sleep”);
}
则在派⽣类 Cat 中定义隐藏⽅法的代码为:
new public void Sleep()
{
Console.WriteLine(“Cat Sleep”);
}
或者为:
public new void Sleep()
{
Console.WriteLine(“Cat Sleep”);
}
注意:
(1)隐藏⽅法不但可以隐藏基类中的虚⽅法,⽽且也可以隐藏基类中的⾮虚⽅法。
(2)隐藏⽅法中⽗类的实例调⽤⽗类的⽅法,⼦类的实例调⽤⼦类的⽅法。
(3)和上⼀条对⽐:重写⽅法中⼦类的变量调⽤⼦类重写的⽅法,⽗类的变量要看这个⽗类引⽤的是⼦类的实例还是本⾝的实例,如果引⽤的是⽗类的实例那么调⽤基类的⽅法,如果引⽤的是派⽣类的实例则调⽤派⽣类的⽅法。
8、多态
多态性的体现:同⼀操作作⽤于不同的对象,可以有不同的解释,产⽣不同的执⾏结果。
9、⽐较接⼝和抽象类
抽象类可以有实现代码或没有实现代码的抽象成员,然⽽接⼝不能有任何实现代码;它是纯粹抽象的,所以接⼝不需要abstract关键字。
10、C#堆和栈
C# 中有两种类型:引⽤类型和值类型。 引⽤类型的变量存储对其数据(对象)的引⽤,⽽值类型的变量直接包含其数据。 对于引⽤类型,两种变量可引⽤同⼀对象;因此,对⼀个变量执⾏的操作会影响另⼀个变量所引⽤的对象。 对于值类型,每个变量都具有其⾃⼰的数据副本,对⼀个变量执⾏的操作不会影响另⼀个变量(in、ref 和 out 参数变量除外;
要访问存储在内存的某个空间中的⼀个值,就需要提供表⽰该存储单元的数字。在任何复杂的⾼级语⾔中,编译器负责把⼈们可以理解的变量名转换为处理器可以理解的内存地址。
在处理器的虚拟内存中,有⼀个区域称为栈。栈存储不是对象成员的值数据类型。在释放变量时,其顺序总是与给它们分配内存的顺序相反,这就是栈的⼯作⽅式。栈指针(操作系统维护的⼀个变量)表⽰栈中下⼀个空闲存储单元的地址。程序第⼀次执⾏时,栈指针指向为栈保留的内存卡末尾。栈实际上是向下填充的,即从⾼内存地址向低内存地址填充。
尽管栈有⾮常⾼的性能,但它还没有灵活到可以⽤于所有的变量。变量的沈村期必须嵌套,在许多情况下这种要求过于苛责。通常我们希望使⽤⼀个⽅法分配内存,来存储⼀些数据,并在⽅法退出后的很长⼀段时间内数据仍是可⽤的。只要是⽤new运算符来请求分配存储空间,就存在这种可能性——例如,对于所有的引⽤类型。此时就要使⽤托管堆。
托管堆(简称为堆)是处理器的可⽤内存中的另⼀个内存区域。看个例⼦
```csharp
void DoWork()
{
Customer arabel;
arabel = new Customer();
Customer otherCoutomer = new EnhancedCustomer();
}
存在两个类 EnhancedCustomer扩展了Customer。
1、⾸先声明了⼀个Customer引⽤arabel,在栈上给这个引⽤分配空间,但这仅是⼀个引⽤,⽽不是实际的Customer对象。arabel引⽤占4个字节的空间,⾜够包含Customer对象的存储地址。
2、然后再看下⼀⾏代码
arabel = new Customer();
这⾏代码⾸先它分配堆上的内存,以存储堆对象(⼀个真正的对象,不⽌是⼀个地址)。然后把变量arabel 的值设置为分配给新customer 对象的内存地址(它还调⽤合适的Customer()构造函数初始化实例中的字段)。
与栈不同,堆上的内存是向上分配的,所以空闲空间在已⽤空间的上⾯。当⼀个⼀引⽤变量超出作⽤域时,它会从栈中删除,但引⽤对象的数据扔保留在堆中,⼀直到程序结束,或者垃圾回收器删除它为⽌,⽽只有在该数据不再被任何变量引⽤时,它才会被删除。这说明我们可以对数据的⽣存周期进⾏⾮常强⼤的控制,因为只要保持对数据的引⽤,该数据肯定存在于堆上。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论