C# .Net面试题库总结(二)

1、转义符指的就是一个'\'+一个特殊的字符,组成了一个具有特殊意义的字符。

\n:表示换行
\":表示一个英文半角的双引号
\t:表示一个tab键的空格
\b:表示一个退格键,放到字符串的两边没有效果
\r\n:windows系统不认识\n,只认识\r\n
\\:表示一个\

2、@符号

1、取消\在字符串中的转义作用,使其单纯的表示为一个'\'
2、将字符串按照编辑的原格式输出

3、类型转换

A:我们要求等号两边参与运算的操作数的类型必须一致,如果不一致,满足下列条件会发生自动类型转换,或者称之为隐式类型转换。
1)两种类型兼容。(比如int 和double兼容,他们都是数字类型)
2)目标类型范围大于源类型的范围。 (比如double>int)
举例:
int number=10;
double d=number;//这里就是自动类型转换,或者叫隐式类型转换

B:如果我们想把范围大的转成小的,比如想把102.6转成106,这种就叫做强制转换,也就显示转换,这种显示转换语法如下:
(待转换的类型)要转换的值;
double d=102.6
int  n=(int)d;

总结:
自动类型转换:int----->double
显示类型转换:double----->int    当然显示类型转换会丢失精度

C:关键字as类型转换

通过as的方式进行类型转换,即便转换失败也不会抛异常,而是返回一个null。

4、Convert类型转换

如果想把字符串数值转成int呢?不能用上面的类型转换,因为现在的这两个类型的变量不兼容,可以用Convert转换工厂进行转换。
string  s=“123”;
double d=Convert.ToDouble(s);

5、算数运算符

+、-、=、*、/(取相除后的整数部分)、%(取相除后的余数部分)、++、--
备注:
1)一元运算符++和--要比二元的+、-等的优先级高
2)对于像++、--这样的只需要一个操作数就能运算的运算符,我们叫做一元运算符。而+、-、*、/等需要两个操作数才能运算的这些运算符,我们叫做二元运算符。
3)关于前++和后++的区别
int  number=10;
int  result=10+ number++;//输出2个值,number=11,result=20
int  result=10+ ++number;//输出2个值,number=11,result=21
总结:++:分为前++和后++,不管是前++还是后++,最终的结果都是给这个变量加1。区别表现在表达式当中:(1)如果是前++,则先给这个变量自身加1,然后带着这个加1后的值去参与运算。(2)如果是后++,则先拿原值参与运算,运算完成后,再将这个变量自身加1。

6、复合运算符

+=、-=、*=、/=、%=

7、封装

(1)、什么是封装
遥控器刚出来的时候很神奇,点个按钮就能换台、切换音量等。而我们使用它的人不需要知道遥控器是怎么实现的,我们只需要知道每个按钮的功能即可。和遥控器类似,面向对象的封装就是把事物的状态和行为封装在类中,使用类的人不需要知道类的内部是怎么实现的,只要调用其中的属性和方法实现功能就行。类和对象本身就是封装的体现。
(2)、我们程序中有哪些是封装呢
A、属性封装了字段。
为什么要封装属性呢?如果不封装直接写private外界就不能访问了,如果写public外界就可以随便赋值了,比如把age赋值2500岁。如果封装的话,就可以在里面把age做限制了。
B、方法的多个参数封装成了一个对象

C、将一堆代码封装到一个方法中
D、将一些功能封装到了几个类中
E、将一些具有相同功能的代码封装到了一个程序集中(dll、exe),并且对外提供统一的访问接口(属性名、方法名等)

8、继承

(1)、如何判断一个继承关系是否合理?
就看子类是否是父类,比如学生类(子类)是个人类(父类),人类(子类)是动物(父类)。
(2)、表示父类的意思(基类、父类),表示子类的意思(派生类、子类)。
(3)、为什么要继承?
把一些东西抽象出来一个共同具有的一小部分特征,也就是说父类是个更抽象的封装,如果你觉得不够用,那就继承我,然后你再扩展。继承的目的就是为了扩展。
(4)、继承带给我们的好处?
一是代码重用、二是多态。
备注:任何一个类不写继承是谁的话默认都是继承object类。另外继承具有单根性,只能继承一个类,而接口可以多继承。
(5)、里氏替换原则
需要一个父类类型时,给一个子类类型对象是可以的,这个就叫做里氏替换原则。

想要一个子类时,直接new一个子类对象赋值给子类就可以了,干嘛还要申明一个父类的对象来存放子类对象呢,为什么要这么做呢?就是为了后面的多态,多态的目的是为了增加程序的可扩展性和灵活性。
(6)、继承中的构造函数
如果父类中写了一个有参的构造函数,子类也写了有参的构造函数,编译会报错,会提示父类中没有找到无参数的构造函数。那是因为子类继承父类的时候,子类中所有的构造函数在执行之前都会先调用父类中的无参数的构造函数,如果父类中没有这个构造函数就会报错。解决方案如下:
A、父类添加一个无参数的构造函数
B、子类调用父类的有参构造函数,用:base(参数1,参数2),如下图所示:

也就是说子类的构造函数必须指明调用父类的哪个构造函数。

(7)、使用this调用构造函数
作为当前类的对象,this可以调用类中的成员,比如this.成员名。
其二首先来说一个类中是可以有多个构造函数的,上面说的用base调用父类中的函数,这里可以用this来调用本类中的构造函数。

(8)、通过this与base来调用类中的成员

9、访问修饰符

private:只有在当前类的内部可以访问。
protected:只有在当前类的内部,以及所有子类的内部可以访问。
protected internal:具有protected或者internal的访问权限,是或的关系,不是且的关系。
internal:当前程序集内部(也就是不管是在哪个类,只要是一个程序集就可以访问),即使在另外一个程序引用了这个程序集,还是不能访问。
public:任何地方都可以访问。
备注:
(1)、类的成员变量,如果不写访问修饰符默认是private。
(2)、命名空间中直接定义的数据类型的访问修饰符只能是public和internal,但是这个只是对我们程序员的限制,微软内部是可以的,可以用reflector查看源码。
(3)、可访问性不一致的问题(修改成员的访问修饰符)
方法的访问修饰符和方法的参数的类型的访问修饰符、以及方法的返回值的访问修饰符得一致,不一致编译会报错。

10、虚方法实现重写-多态

比如说现在有4个类,分别是人类、中国人类、美国人类、日本人类。后面三个国籍类都继承前面的人类。这个时候向人类里面插入国籍数据,然后循环获取人类中每条数据的国际类的方法,只能判断类型后再获取。如果有200多个国家呢?或者后续会新增国家呢?这里的代码不但繁琐而且需要改动。

如果我想只用1句代码写呢?那就在person人类中加上一个SayNationality的方法,但还是无法区分是哪个子类国籍的人。解决方案如下:
(1)、首先在父类的SayNationality的访问修饰符后面加上virtual关键字,把方法变为虚方法。什么是虚方法呢?虚方法就是将来它的子类可以把这个父类的方法重写。
(2)、上面说了重写,那也就是在子类同名方法SayNationality的访问修饰符后面加上override关键字,也就是子类把父类中的虚方法重写为子类想要的方法了。
备注:其实重写后,再用for循环,只用1句话就循环出了子类中重写的SayNationality方法了,这里就体现了多态。方法重写的话,方法的签名要保持一致,也就是返回值和参数、返回值要和虚方法保持一致。

定义一个parentclass的父类,里面有个M1的虚方法。然后A类去继承parentclass类,这个时候A类可以选择重写M1的虚方法,也可以选择不重写。这个时候有个B类,B类又继承自A类,B类也可以选择重写或者不重写M1的方法。

如果子类A也有一个跟父类parentclass相同的虚方法M1,那么就会错误提示,得在子类中的M1方法前使用new关键字去隐藏父类继承过来的M1方法。此时,子类A中将只有一个M1方法,通过this.M1()调用的一定是子类中的全新的这个M1方法,而用base.M1()则调用的是父类中原来的那个M1方法。

is可以用来验证继承关系中是否合理,if(obj is 类型A)  obj是父类的类型对象,而类型A是子类的类型。

11、static静态成员静态类

(1)、在静态类中,所包含的所有成员(不管是属性还是方法)必须都是静态成员,也就是类里面的成员前面都要加上static。
(2)、不是所有的静态成员都必须写在静态类中。
(3)、实例成员是属于具体某个对象的。静态成员是属于类的,不是属于具体对象的。所以访问静态成员的时候不能通过对象来访问,只能通过类名.来访问。
(4)、静态成员访问的是同一块成员,静态方法和静态变量创建后始终使用同一块内存空间(静态存储区),在程序的任何一个地方访问静态成员,其实访问的都是同一块内存,所以有一个地方把该值改变,则所有的地方获取到的值都变了。
(5)、静态成员的数据直到程序退出后才会被释放资源,而实例对象,只要使用完毕就可以执行垃圾回收。所以说要少使用静态类、静态成员,毕竟是程序退出才释放内存。
(6)、C#中的静态类:Math、Console、Convert等。
(7)、静态构造函数
一个类中,不管这个类是静态类还是实例类,只要有静态成员属性,如果没有写静态构造函数,只要静态成员在类中被赋值了,编译器就会自动生成一个静态函数,没有赋值就不会生成。

静态构造函数不能手动来调用,而是在第一次使用静态成员的时候自动调用的,所以不能为静态构造函数添加访问修饰符,默认为private。

因为静态构造函数时系统自动调用的,所以也不能添加任何参数。

以上的代码执行过程如下:首先类在创建的时候,会初始化里面的成员,所以进入MyClass这个类的MyClass的静态构造函数中,所有最后打印的结果分别是:***静态构造函数被执行***、***$$$***、1000.

以上的代码执行过程如下:首先在调用类中的静态成员n1的时候,首次会调用静态的构造方法,静态构造方法调用完后面再实例化MyClass类都不会再去调用里面的静态构造方法了,所有最后打印的结果分别还是:***静态构造函数被执行***、***$$$***、1000.

备注:由此可见,可得出如下结论,静态构造函数只执行一次,在第一次new类的时候或者第一次使用静态成员的时候执行。另外一个类中有静态构造函数和非静态构造函数,会先执行静态的构造函数然后再去执行非静态的。

12、多态

多态的作用:把不同的子类对象都当作父类来看,可以屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,以适应需求的不断变化。
备注:如何理解上面这句话呢?可以参照前面虚方法实现多态的例子。

实现多态的方式有三种:virtual虚方法(子类重写父类中的方法)、抽象类(子类重写父类中的方法)、接口(实现接口的类,将接口中的方法实现)。

13、抽象类

(1)、什么是抽象类?
在类class前面加上abstract。抽象类是不能被实例化的类。抽象类中可以有普通成员。
(2)、抽象类存在的意义?
1、抽象类不能被实例化,只能被其他类继承。2、继承抽象类的子类必须把抽象类中的所有抽象成员重写(实现),除非子类也是抽象类。这里跟虚方法不一样,虚方法可以重写可以不重写。3、抽象类就是为了重写-->多态(代码重用)。4、抽象类中可以有实例成员也可以有抽象成员。5、抽象类没有默认实现,虚方法有。6、抽象成员必须在抽象类里面。
(3)、抽象类跟实例类的区别就是,它比实例类更抽象。

14、值类型、引用类型

值类型:int、char、double、float、long、short、byte、bool、enum、struct、decimal。
值类型都是派生自ValueType,值类型不能继承,只能实现接口(struct可以实现接口)。
注意:申明一个整型变量,赋值1,和赋值99999,在内存里面占用的空间都是4个字节。也就是说值类型在内存里占用的大小是固定的,所以值类型存储都是存在栈上的。为什么可以存在栈上而不是堆上呢?比如说现在申明了个int、char、double,因为他们占用的字节大小是固定的,所以可以连续的分配空间。


引用类型:string、数组、类、接口、委托
引用类型都派生自:Object,引用类型可以继承(类之间可以继承)
注意:引用类型的值存在堆中,像string,它就跟int不一样了,不知道strng的大小会是多少,所以引用类型都是在栈上给它开辟一个固定大小的地址,实际数据是存到另外一块内存当中,这块内存可以无限大,但是并不保证它是连续的,正是因为不是连续的,所以查起来要比栈上要慢些。

以上代码解说:先是在堆中new了个person的对象,然后把堆中这个对象的地址返回给变量p,也就是说实际上p是栈上的一个地址,用来指向堆中的对象的。然后p.name去堆中的对象中赋值,再把p这个地址的值赋给p1,p1和p保存的地址相同,都是执行堆中的同一个person对象,所以最后打印的p.name的值是许正龙。

举例:

1)打印的结果是{1,2,3}。

2)打印结果是苏坤

以上的例子都是值传递,下面来说引用传递。

15、引用传递

只有加了ref才叫引用传递,其他都叫值传递。

引用传递说白了,就是把栈上的地址赋值给另外一个别名,并没有在栈中开辟另外的内存空间。

16、接口

(1)、什么是接口?
接口就是一种规范协议,约定好遵守某种规范就可以写通用代码。定义了一组具有各种功能的方法。(只是一种能力,没有具体实现,像抽象方法一样,光说不做)
(2)、接口存在的意义?
存在的意义就是为了多态。多态的意义:程序可扩展性。最终让程序变得更灵活,节省成本,提高效率。
(3)、接口解决了类的多继承的问题。
(4)、接口解决了类继承以后体积庞大的问题。
(5)、接口之间可以实现多继承。
(6)、从语法角度看接口,跟抽象类类似。

(7)、显示实现接口。
显示实现接口就是为了解决方法重名的问题。比如一个类同时实现2个接口,正好2个接口都有一个同名的方法。显示实现接口后只能通过接口变量来调用,因为显示实现接口默认为private。

17、异常处理

18、弱引用

弱引用除非用到大的对象里面,平常一般不会去用它。
如下图所示,在把对象地址设置为空之前,该对象之前的引用赋给一个弱引用,后续可再判断弱引用是否被回收,没有回收可以取出来再用。

19、垃圾回收

(1)、垃圾回收是.net中的运行时CLR这个库中的一个核心功能。垃圾回收的目的就是为了提高内存的利用率。什么是内存的利用率呢?内存是有限的,当你不断的使用的时候,如果每次分配变量都不去回收、不去释放,运行一会儿,整个计算机内存就不够用了,不够用的话,程序就奔溃了。除非内存特别大,不断的加内存,但是也没那么多钱,所以需要内存重复利用起来。
(2)、什么样的内存会被回收呢?
像值类型这些变量,用完以后系统就立刻把这个内存销毁了。那哪些资源需要垃圾回收呢?是堆里面的资源。
(3)、堆里面的内存如何被回收释放?
不确定,一般都是当程序需要新内存,或者内存不够的时候开始执行回收。当然什么对象被回收什么不会被回收,垃圾回收机制它有选择,当这个对象没有用的时候,也就是没任何地方引用这个对象的时候。一般不需要手动干预,别每次写完一个代码就手动调垃圾回收GC.Collect(),这样做并不是一个高效的办法,有可能会让整体性能变得更低。就好比垃圾回收有自己的机制,它正在回收的时候,你让它跑过来收你的,而你这里就你一块垃圾,这样来回跑,GC也受不了。

那什么样的可以手动调用垃圾回收呢?比如说你接下来要写一段代码,你预测这段代码会耗费大量内存,而且这段代码性能要求比较高,需要连续的执行完毕,在执行这段代码的时候不要中途进行垃圾回收,因为中途进行垃圾回收,程序会短暂的暂停。这个时候在写代码之前调用下垃圾回收GC.Collect(),给这段代码预留足够的内存,而且因为在这段代码之前已经执行了一次垃圾回收,所以执行这段代码的过程中再次执行垃圾回收的可能性就会变小。
(4)、垃圾回收器中"代"的概念,算法是怎样的?
代的机制来回收,大大提高垃圾回收的性能。共3代:第0代、第1代、第2代。

创建对象的时候,会分为3个代,每个代都有个初始大小,比如1M,每次创建新对象的时候都是向第0代开始创建。当第0代内存满后,就会执行垃圾回收机制,把没有任何引用的对象给回收销毁,然后把有引用的对象,也就是没被回收活下来的对象放到第1代,然后再次创建对象还是向第0代里面放,如此类推。如果说到了要回收第2代的时候,活下来的对象还是放到第二代,程序继续运行,直到这3代空间都满了,就会尝试把每一代的内存容量扩大。也不是一直扩充,后续扩充后内存还是满了就会抛异常了。

(5)、下面来说析构函数以及dispose方法。
析构函数的函数名跟构造函数一致,前面加了~符号,.net里面其实把析构函数叫终结函数,析构函数会被编译成Finalize函数。它是干什么用的呢?一般我们不用它,我们普通的托管资源垃圾会自动回收,但是假设我们类中访问了操作系统的资源,非托管资源(),垃圾是不会回收的,而在执行垃圾回收之前会先调用我们的析构函数进行非托管资源的释放然后再进行垃圾回收。我们的非托管资源,一般当对象不用的时候就要立马回收,而不是等待回收。所以多了dispose函数,一般我们类中有调用非托管资源,类继承IDisposable接口,然后实现Dispose方法,在Dispose中去释放非托管资源。在调用非托管资源的时候,再去调Dispose方法就会立马进行垃圾回收,所以一般我们不会去使用析构函数这个终结器。如果使用using的话,那就连Dispose方法都不需要写了,会自动调用,执行垃圾回收。

20、ref、out、params

(1)、可变参数如下

(2)、有了ref,为什么还用out,因为使用场景不一样,out可以返回多个值。

 

  • 6
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值