51. 下列代码在运行中会发生什么问题?如何避免?
List<int> ls = new List<int>(new int[]{ 1, 2, 3, 4, 5 });
foreach (int item in ls)
{
Console.WriteLine(item * item);
ls.Remove(item);
}
会产⽣运⾏时错误,因为foreach是只读的。不能⼀边遍历⼀边修改。
使用For循环遍历可以解决。
52. 什么是装箱拆箱,怎样减少操作
C#装箱是将值类型转换为引用类型;
拆箱是将引用类型转换为值类型。
牵扯到装箱和拆箱操作比较多的就是在集合中,例如:ArrayList或者HashTable之类。
值类型和引用类型互相转换:拆箱和装箱
装箱:值类型====》引用类型object
分配内存堆
值类型数据拷贝到新的内存堆中
栈中分配一个新的引用地址指向内存堆
拆箱:引用类型object====》值类型
检查确保对象是给定值类型的一个装箱值
将该值数据复制到栈中的值类型
53. MVC
MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范。
用一种业务逻辑、数据、界面显示分离的方法,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中。
Model(模型)是应用程序中用于处理应用程序数据逻辑的部分。
通常模型对象负责在数据库中存取数据。
View(视图)是应用程序中处理数据显示的部分。
通常视图是依据模型数据创建的。
Controller(控制器)是应用程序中处理用户交互的部分。
通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据
54. 非托管代码与不安全代码
托管代码: 在公共语言运行时(CLR)控制下运行的代码。
非托管代码: 不在公共语言运行时(CLR)控制下运行的代码。
不安全(Unsafe)代码: 不安全代码可以被认为是介于托管代码和非托管代码之间的。不安全代码仍然在公共语言运行时(CLR)控制下运行,但它将允许您直接通过指针访问内存。
55. C#中基本类型占用的字节数
类型 字节
bool true/false
byte、char 1字节
char、short 2字节
int,float 4字节
long、double 8字节
56. Heap与Stack有何区别?
heap是堆,stack是栈。
stack的空间由操作系统自 动分配和释放,heap的空间是手动申请和释放的, heap常用new关键字来分配。
stack空间有限,heap 的空间是很大的自由区。
57. 栈溢出一般是由什么原因导致
无限递归。函数递归调用时,系统要在栈中不断保存函数调用时的现场和产生的变量,如果递归调用太深,就会造成栈溢出,这时递归无法返回。再有,当函数调用层次过深时也可能导致栈无法容纳这些调用的返回地址而造成栈溢出。
无限循环。
大量局部变量分配。
58. Stack栈和Queue队列
相同点:
都是线性结构。
插入操作都是限定在表尾进行。
都可以通过顺序结构和链式结构实现。
插入与删除的时间复杂度都是O(1),在空间复杂度上两者也一样。
多链栈和多链队列的管理模式可以相同。
底层都是由泛型数组实现。
不同点:
栈先进后出,队列先进先出:删除数据元素的位置不同,栈的删除操作在表尾进行,队列的删除操作在表头进行。
顺序栈能够实现多栈空间共享,而顺序队列不能。
应用场景不同
常见栈的应用场景包括
括号问题的求解,
深度优先搜索遍历等;
函数调用和递归实现,
表达式的转换和求值
常见的队列的应用场景包括
计算机系统中各种资源的管理,
消息缓冲器的管理
广度优先搜索遍历等
59. 链表相关
单双向链表的区别:
指向不同:单向链表只有一个指向下一结点的指针,双向链表除了有一个指向下一结点的指针外,还有一个指向前一结点的指针。
功能不同:单向链表只能next ,双向链表可以return。
单双向不同:单链表只能单向读取,双向链表可以双向遍历。
单向链表优缺点:
优点:单向链表增加删除节点简单。遍历时候不会死循环;
缺点:只能从头到尾遍历。只能找到后继,无法找到前驱,也就是只能前进。
双向链表优缺点:
优点:可以找到前驱和后继,可进可退;
缺点:增加删除节点复杂,多需要分配一个指针存储空间。
60. 链表与数组的对比
数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费;数组可以根据下标直接存取,时间复杂度O(1)。
链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。(数组中插入、删除数据项时,需要移动其它数据项,非常繁琐)链表必须根据next指针找到下一个元素。
如果需要快速访问数据,很少或不插入和删除元素,就应该用数组;相反,如果需要经常插入和删除元素就需要用链表数据结构了。
61. 二叉树相关
计算深度(高度)
二叉树的高度是二叉树结点层次的最大值,也就是其左右子树的最大高度+1。当树为空时,高度为0;否则为其左右子树最大高度+1。
遍历:(看根节点的位置)
前序遍历:(根左右)先访问根节点,再访问左节点,再访问右节点。
中序遍历:(左根右)先访问左节点,再访问根节点,再访问右节点。
后序遍历:(左右根)先访问左节点,再访问右节点,再访问根节点。
62. 字典相关
介绍
Dictionary表示键和值的集合。
Dictionary是一个泛型。
他本身有集合的功能有时候可以把它看成数组。
他的结构是这样的:Dictionary<[key], [value]>。
他的特点是存入对象是需要与[key]值一一对应的存入该泛型,任何键都是唯一。
通过某一个一定的[key]去找到对应的值。查找元素的时间复杂度为O(1)。
增删查改时间复杂度
Dictionary字典类是hash表,Add操作是O(1)。
其Containskey方法是O(1),原因是通过hash来查找元素而不是遍历元素。
ContainsValue方法的时间复杂度是O(N),原因是内部通过遍历key来查找value,而不是通过hash来查找。
ltem[Key]属性根据key来检索value,其时间复杂度也是O(1)。
基本都是O(1)
底层实现原理
Dictionary在构造的时候做了以下几件事:
1.初始化一个桶数组this.buckets = new int[prime]
2.初始化一个this.entries = new Entry[prime]
Bucket和entries的容量都为大于字典容量的一个最小的质数
其中this.buckets主要用来进行Hash碰撞
this.entries用来存储字典的内容,并且标识下一个元素的位置。
详细过程
哈希表法:将不定长的二进制数据集映射到一个较短的二进制数据集,一个Key通过HashFunc得到HashCode。
Hash桶算法:对HashCode进行分段显示,常用方法对HashCode直接取余。
拉链法:分段则会导致key对应的哈希桶相同,拉链法的基本思想就像对冲突的元素,建立一个单链表,头指针存储在对应哈希桶的位置。反之就是通过hash桶对应后,遍历单链表,获取value值。
63. 哈希表与字典对比
字典:内部用了Hashtable作为存储结构
如果我们试图找到一个不存在的键,它将返回 / 抛出异常。
它比哈希表更快,因为没有装箱和拆箱,尤其是值类型。
仅公共静态成员是线程安全的。
字典是一种通用类型,这意味着我们可以将其与任何数据类型一起使用(创建时,必须同时指定键和值的数据类型)。
Dictionay 是 Hashtable 的类型安全实现, Keys和Values是强类型的。
Dictionary遍历输出的顺序,就是加入的顺序
哈希表:
如果我们尝试查找不存在的键,则返回 null。
它比字典慢,因为它需要装箱和拆箱。
哈希表中的所有成员都是线程安全的,
哈希表不是通用类型,
Hashtable 是松散类型的数据结构,我们可以添加任何类型的键和值。
HashTable是经过优化的,访问下标的对象先散列过,所以内部是无序散列的
64.关于List与字典的遍历与查询效率
List的底层,是一个泛型数组,连续且紧密的顺序存储,一般数据存储在缓存中。而字典是离散(散列)分布,由数组和哈希表共同组成,遍历的时候,会伴有换页的操作,且数组都存储在内存中。而读写速度是:缓存>内存>硬盘。因此List更适合遍历。
字典的查询效率是通过元素的key值进行取余操作,找的对应的哈希桶,判定哈希桶对应的哈希表的头节点是不是该元素,若不是进行next操作,对哈希表进行遍历,这两个过程都是常数级别的操作。所以是O(1)。而List的查询效率是先遍历,找到对应的值,因此是O(n)。所以字典更适合查询。