以下的关于老赵分享的面试题解答参考了手头的书籍和视频教程,以及网上的资料,现整理出来希望对大家有所帮助,不对或欠佳的地方望大家指出来我好改正。
01
1.什么是.NET?什么是CLI?什么是CLR?IL是什么?
(1).net用于代码编译和执行的集成托管环境,换句话,它管理应用程序运行的方方面面,包括首次运行的编译,为程序分配内存 存储数据和指令,对于应用程序授予或拒绝相应的权限,并启动管理应用程序的执行,剩余内存的在分配。由于所有.net应用程序 都是在.net framework上面执行,所以开发人员只需考虑与.net framework打交道,而不必关系和底层操作系统上面的实现 包括CLR和BCL (2).CLI(common language infrastructure)公共语言基础结构,一项国际性的标准,没有规定标准具体如何实现。相反,它描述了一个 CLI平台在符合标准的前提下应该具有什么行为。包含了:运行时(CLR),公共中间语言(CIL),公共类型系统(CTS), 公共语言规范(CLS),元数据(Metadata),框架(framework) (3)CLR:公共语言运行时,负责加载和运行程序 IL:中间语言,C#编译器将C#代码转换成IL,运行时能够理解IL,并编译成机器码
02
2.JIT是什么,它是如何工作的?GC是什么,简述一下GC的工作方式?
JIT:Just in time,C#或者是VB.NET的代码首先被编译为IL存储在本地,当要运行这些代码的时候,CLR对IL进行第二次编译转换成机器码运行。好处:可移植性,而且IL在加载到内存中时将受到类型安全性方面检查,这实现了更好的安全性和可靠性。
GC:垃圾回收(garbage collection),是根据程序的需要自动分配和回收内存的过程。垃圾回收器处理的是引用对象,而且只回收堆上的内存。这意味着假如维持对一个对象的引用,就会阻止GC重用对象使用的内存。在.NET中,垃圾回收器采用的是mark-and-compact算法。在一次垃圾回收周期开始的时候,它要识别对象的所有跟引用,根据这个引用可以遍历每个根引用所标识的一个树形结构,并递归确定所有引用指向的对象。这样一来,垃圾回收器就可以识别所有可达的对象,在执行回收的时候,GC不是枚举所有访问不到的对象,相反,通过压缩所有相邻的可达的对象来执行垃圾回收。不可访问的对象就会被覆盖。垃圾回收的宗旨是提高内存的利用率,它并不是用来清理文件句柄,和数据库连接字符串,端口或者其他有限的资源(终接器finalizer,不能被显示调用,不能传递任何参数,即不能被重载,只有垃圾回收器才能调用终接器,使用Using语句进行确定性终结
03
3.类(class)和结构(struct)的区别是什么?它们对性能有影响吗?
1. 值类型与引用类型 结构是值类型:值类型在栈上分配地址,所有的基类型都是结构类型,例如:int 对应System.int32 结构,通过使用结构可以创建更多的值类型 类是引用类型:引用类型在堆上分配地址堆栈的执行效率要比堆的执行效率高,可是堆栈的资源有限,不适合处理大的逻辑复杂的对象。所以结构处理作为基类型对待的小对象,而类处理某个商业逻辑因为结构是值类型所以结构之间的赋值可以创建新的结构,而类是引用类型,类之间的赋值只是复制引用 注:1.虽然结构与类的类型不一样,可是他们的基类型都是对象(object),c#中所有类型的基类型都是object 2.虽然结构的初始化也使用了New 操作符可是结构对象依然分配在堆栈上而不是堆上,如果不使用“新建”(new),那么在初始化所有字段之前,字段将保持未赋值状态,且对象不可用
2.继承性 结构:不能从另外一个结构或者类继承,本身也不能被继承,虽然结构没有明确的用sealed声明,可是结构是隐式的sealed . 类:完全可扩展的,除非显示的声明sealed 否则类可以继承其他类和接口,自身也能被继承注:虽然结构不能被继承 可是结构能够继承接口,方法和类继承接口一样
3.内部结构: 结构:没有默认的构造函数,但是可以添加构造函数没有析构函数没有 abstract 和 sealed(因为不能继承)不能有protected 修饰符可以不使用new 初始化在结构中初始化实例字段是错误的 类:有默认的构造函数 有析构函数 可以使用 abstract 和 sealed 有protected 修饰符 必须使用new 初始化
04
4..NET BCL里有哪些是类(结构),为什么它们不是结构(类)?
结构:System.Boolean Byte Char Decimal Double Int32 堆栈的空间有限,对于大量的逻辑的对象,创建类要比创建结构好一些 大多数情况下该类型只是一些数据时,结构时最佳的选择 类:String Object Delegate 接口 等等 包含了大量的逻辑对象,表现抽象
05
5.在自定义类型时,您如何选择是类还是结构?
1). 堆栈的空间有限,对于大量的逻辑的对象,创建类要比创建结构好一些 2). 结构表示如点、矩形和颜色这样的轻量对象,例如,如果声明一个含有 1000 个点对象的数组,则将为引用每个对象分配附加的内存。在此情况下,结构的成本较低。 3). 在表现抽象和多级别的对象层次时,类是最好的选择 4). 大多数情况下该类型只是一些数据时,结构时最佳的选择
06
6.在.NET程序运行过程中,什么是堆,什么是栈?
栈通常保存着我们代码执行的步骤,而堆上存放的则多是对象,数据等。我们可以把栈想象成一个接着一个叠放在一起的盒子。当我们使用的时候,每次从最顶部取走一个盒子。栈也是如此,当一个方法(或类型)被调用完成的时候,就从栈顶取走(called a Frame,译注:调用帧),接着下一个。堆则不然,像是一个仓库,储存着我们使用的各种对象等信息,跟栈不同的是他们被调用完毕不会立即被清理掉。 栈内存无需我们管理,也不受GC管理。当栈顶元素使用完毕,立马释放。而堆则需要GC(Garbage collection:垃圾收集器)清理
07
7.什么情况下会在堆(栈)上分配数据?它们有性能上的区别吗?“结构”对象可能分配在堆上吗?什么情况下会发生,有什么需要注意的吗? 1)值类型一般分配在对上面,引用类型分配在堆上面。栈的效率要高于堆。 2)可能,当在类中定义一个结构类型时,该结构就分配在堆上
08
8.泛型的作用是什么?它有什么优势?它对性能有影响吗?它在执行时的行为是什么?
作用:为了促进代码的重用,尤其是算法的重用 优势:(1)可重用性(2)类型安全,在参数化的类中只有成员明确希望的数据类型才可以使用(3)性能:避免了从Object的强制转换和值类型的装箱(4)减小了内存消耗:避免装箱也就不在需要消耗堆上的内存。 执行时的行为:泛型也是对象,泛型类的“类型参数”变成了元数据;CLR会在需要的时候构造利用它们的类。一个泛型类经过编译好之后和普通的类并没有什么区别。编译的结果只有元数据和CIL。基于值类型的泛型实例化:CLR会讲指定的类型参数放到CIL中合适的位置,从而创建一个具体化的泛型类型。所以CLR会为没个新的参数值创建具体的泛型类型 基于引用类型的实例化:CLR会创建一个具体化的泛型类型。以后,每次用一个引用类型参数来说实例化一个构造好的类型时,并在CIL中用Object引用替换类型参数,CLR都会重用以前生成好的泛型版本
09
9..NET BCL中有哪些泛型类型?举例说明平时编程中您定义的泛型类型。
List<T>:通过索引访问强类型的列表 Dictionary<T>:表示键值对的集合 Queue<T>:队列 Stack<T>: 栈 购物车用Dictionary模拟,OA中获取员工列表等数据的时候,返回值是泛型的
010
10.异常的作用是什么?.NET BCL中有哪些常见的异常?在代码中您是如何捕获/处理异常的?
在“catch (ex)”中,“throw”和“throw ex”有什么区别?您会如何设计异常的结构,什么情况下您会抛出异常?
(1)C# 语言的异常处理功能可帮助您处理程序运行时出现的任何意外或异常情况 (2)throw会保留堆栈信息。throw ex 不会。当然,如果你抛出新的异常之前设置innerException的话,可以通过innerException的堆栈访问原有的堆栈。 (3)靠异常才能发现错误的,通过try catch finally来捕获异常。如果是未预料到的则不处理(内存不足,删除文件)直接报错更容易发现错误catch块从最具体到常规排列
011
11.List和T[]的区别是什么,平时你如何进行选择?
Dictionary<TKey, TValue>是做什么的?.NET BCL中还有哪些常用的容器?它们分别是如何实现的(哪种数据结构)?分别是适用于哪些场景?
1.List<T>arrylist的泛型版本,大小是可变的,T[]继承自Array,大小是固定的。如果大小没有怎么变化,选择T[],一般情况下选择List<T> 2.Dictionary是hashtable的泛型版本,用来存储键值对的.例如:sortlist,stack等
012
12抽象类和接口有什么区别?使用时有什么需要注意的吗?
如何选择是定义一个“完全抽象”的抽象类,还是接口?什么是接口的“显式实现”?为什么说它很重要? 相同点:都不能被直接实例化,都通过继承实现其抽象方法 不同点: (1) 接口支持多继承;抽象类不能实现多继承。 (2) 接口只能定义行为;抽象类既可以定义行为,还可能提供实现。 (3) 抽象类允许包含实现的virtual成员,所以能为派生类成员提供一个默认的实现,而接口所有的成员自动成为virtual成员,而且不能包含任何实现
013
13.字符串是引用类型类型还是结构类型?引用类型
它和普通的引用类型相比有什么特别的地方吗?不可变的 使用字符串时有什么需要注意的地方?为什么说StringBuilder比较高效? 当拼接两个字符串时,系统先是把两个字符串写入内存,接着删除原来的String对象,然后创建一个String对象,并读取内存中的数据赋给该对象。这一来二去的,耗了不少时间。而使用System.Text命名空间下面的StringBuilder类就不是这样了,它提供的Append方法,能够在已有对象的原地进行字符串的修改,简单而且直接。 在连接多个字符串时,它无论何时都比直接相加更高效吗? 不一定,在1000个字符以内效果一样,达到10000时StringBuilder类的效率会显著提升 如何高效地进行数组复制?“二维数组”和“数组的数组”有什么区别? 数组复制的方法:for CopyTo() 静态CopyTo() Clone
014
14.什么是元编程,.NET有哪些元编程的手段和场景?什么是反射?
能否举一些反射的常用场景?有人说反射性能较差,您怎么看待这个问题?有什么办法可以提高反射的性能吗?
学着做OA的时候,动态加载不同的DataProvider(Oracle和Sqlserver),方便,可以随时替换不用重新编译程序
015
15.委托是什么?匿名方法是什么?
在C# 3.0中,Lambda表达式是什么?扩展方法是什么?LINQ是什么?您觉得C# 3.0中还有哪些重要的特性,它们带来了什么优势?BCL中哪些类库和这些特性有关?您平时最常用哪些?
委托可以把一个方法作为参数代入另一个方法。 委托可以理解为指向一个函数的指针。 匿名方法:就是没有实际方法声明的委托实例。或者说,它们的定义是直接内嵌在代码中的。 Lambda表达式:是比匿名方法更加简洁的一种匿名函数语法 委托和事件没有可比性,因为委托是类型,事件是对象,下面说的是委托的对象(用委托方式实现的事件)和(标准的event方式实现)事件的区别。事件的内部是用委托实现的。因为对于事件来讲,外部只能“注册自己 =、注销自己-=”,外界不可以注销其他的注册者,外界不可以主动触发事件,因此如果用Delegate就没法进行上面的控制,因此诞生了事件这种语法。事件是用来阉割委托实例的,类比用一个自定义类阉割List。事件只能add、remove自己,不能赋值。事件只能 =、-=,不能= 。事件内部就是一个private的委托和add、remove两个方法。
016
16.工作之外您看哪些技术相关的书、网站、社区、项目等等?
您还接触哪些.NET以外的技术,能和.NET或.NET中有针对性的部分做个对比吗? C#本质论,SQL Server2008实战,数据结构,ASP.NET揭秘,Javascript深入浅出
017
Http是请求-响应模型,服务器不会读取浏览器的网页,能够得到的就是网页提交过来的数据。
018
get与post提交的比较
Get:通过URL传递表单的值(默认),?...&,安全性低,传递比较小的数据。 Post:传递的值隐藏在http报文中,URL中看不到,刷新页面会弹出提示对话框如果
019
实现div内文本自增
因为服务器不记得上次给浏览器的值是多少,而且不像input那样会将上次的值重新提交回来,因此浏览器需要用一个隐藏的字段将上次的值保存下来
020
Cookie
表单是和页面相关的,只有浏览器提交了这些数据服务器才能得到,Cookie是和站点相关的,每次向服务器请求的时候除了发送表单数据外,还会将和站点相关的所有Cookie都提交给服务器,这是强制性的 缺点:不能存储过多的信息,安全性差 针对互联网的优化:图片服务器和主站域名不一样
021
http请求
css,js,图片,单独请求,200表示处理成功,301重定向,400错误请求
307临时重定向,404页面未找到,403禁止,401未认证,500server内部错误,503访问人数过多。
022
目录
/:网站根目录,../上一级目录,./当前目录,~/应用程序根目录
023
数据库查询性能优化
1)select中只返回需要的列 2)在减少使用列的同时,考虑减少行,使用where子句 3)只在需要的时候用order by 4)避免在from,where和having子句中隐式数据类型的转换
常见的排序算法:
代码语言:javascript复制using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace Sort
{ public class Sort
{ ///<summary>
/// 插入排序--稳定排序 ///</summary>
///<param name="list"></param>
public static void InsertSort(List<int> list)
{ int j, tmp; for (int i =1; i < list.Count; i )
{
j = i;
tmp = list[i]; while (j >0&& tmp <= list[j -1])
{
list[j] = list[j -1];
j--;
}
list[j] = tmp;
}
} ///<summary>
/// 希尔排序 ///</summary>
///<param name="list"></param>
public static void ShellSort(List<int> list)
{ int j, tmp; int h =3; while (h>0)
{ for (int i = h; i < list.Count; i )
{
tmp = list[i];
j = i; while ((j > h -1) && tmp < list[j - h])
{
list[j] = list[j - h];
j -= h;
}
list[j] = tmp;
}
h = (h -1) %3;
}
} ///<summary>
/// 冒泡排序--稳定排序 ///</summary>
///<param name="list"></param>
public static void BubbleSort(List<int> list)
{ if (list ==null|| list.Count <1)
{ return;
} int tmp; for (int i =0; i < list.Count; i )
{ bool flag=false; for (int j = i 1; j <list.Count; j )
{ if (list[i] > list[j])
{
tmp = list[i];
list[i] = list[j];
list[j] = tmp;
flag=true;
} if(flag==false)
{ return;
}
}
}
} ///<summary>
/// 选择排序--直接选择排序 ///</summary>
///<param name="list"></param>
public static void SelectSort(List<int> list)
{ int min, tmp; for (int i =0; i < list.Count; i )
{
min = i; for (int j = i 1; j < list.Count; j )
{ if (list[j] < list[min])
{
min = j;
}
} if (i != min)
{
tmp = list[i];
list[i] = list[min];
list[min] = tmp;
}
}
} ///<summary>
/// 堆排序 ///</summary>
///<param name="list"></param>
public static void HeapSort(List<int> list)
{ int n = list.Count; for (int i = n/2-1; i >=0; i--)
{
Sift(list, i, n -1);
} for (int i = n -1; i >=1; i--)
{ int tmp = list[0];//取堆顶元素
list[0] = list[i];//让堆中最后一个元素上移到堆顶位置
list[i] = tmp;//此时list[i]已不在堆中,用于存放排好序的元素
Sift(list, 0, i -1);//重新调整堆 }
} private static void Sift(List<int> list, int low, int high)//建堆过程 { //i为欲调整子树的根结点的索引号,j为这个结点的左孩子
int i = low, j =2* i 1; int tmp = list[i];//记录双亲结点的值
while (j<=high)
{//如果左孩子小于右孩子,则将欲交换的孩子结点指向右孩子
if (j < high && list[j] < list[j 1])
{
j ;//j指向右孩子 } if (tmp < list[j])//如果双亲结点小于它的孩子结点 {
list[i] = list[j];//交换双亲结点和它的孩子结点
i = j;//以交换后的孩子结点为根,继续调整它的子树
j =2* i 1;//j此时代表交换后的孩子结点的左孩子 } else//调整完毕 { break;
}
}
list[i] = tmp;//使最初被调整的结点放入正确的位置 } ///<summary>
/// 归并排序--稳定排序 ///</summary>
///<param name="list"></param>
///<param name="low"></param>
///<param name="high"></param>
public static void MergeSort(List<int> list, int low, int high)
{ if (low < high)
{ int mid = (low high) /2;
MergeSort(list, low, mid);
MergeSort(list, mid 1, high);
Merge(list, low, mid, high);
}
} private static void Merge(List<int> list, int low, int mid, int high)
{//listTmp为临时存放空间,存放合并后的数据
List<int> listTmp =new List<int>(high - low 1); int i = low, j = mid 1, k =0;//k为listTmp的下标
while (i <= mid && j <= high)
{
listTmp[k ] = (list[i] < list[j]) ? list[i ] : list[j ];
} while (i <= mid)
{
listTmp[k ] = list[i ];
} while (j <= high)
{
listTmp[k ] = list[j ];
} for (i = low, k=0; i <= high;i ,k )
{
list[i] = listTmp[k];
}
} ///<summary>
/// 快速排序 ///</summary>
///<param name="list"></param>
public static void QuickSort(List<int> list)
{
QuickSort(list, 0, list.Count -1);
} public static void QuickSort(List<int> list, int low, int high)
{ if (low < high)
{ int i = low, j = high, tmp = list[i]; while (i < j)
{ while (i < j && list[j] >= tmp)
{
j--;
}
list[i] = list[j]; while (i < j && list[i] <= tmp)
{
i ;
}
list[j] = list[i];
}
list[i] = tmp;
QuickSort(list, low, i -1);
QuickSort(list, i 1, high);
}
}
}
}