【C#】知识汇总
目录
- 1 概述
- 1.1 GC(Garbage Collection)
- 1.1.1 为什么需要GC?
- 1.1.2 GC的工作原理
- 工作原理
- 什么是Root?
- GC算法:Mark-Compact 标记压缩算法
- GC优化:Generational 分代算法
- 1.1.3 GC的触发时间
- 1.1.4 如何减少垃圾回收
- 1.1.5 手动回收
- 1.1.6 需要特殊清理的类型*
- 1.2 内存分区
- 1.2.1 分区
- 1.2.2 为什么栈比堆快?
- 1.2.3 .NET&CLR*
- 2 数据结构
- 2.1 数组(Array)
- 2.1.1 一维数组
- 2.1.2 二维数组
- 2.1.3 交错数组
- 2.2 ArrayList&List
- 2.2.1 ArrayList
- 2.2.2 List
- 2.2.3 区别
- 2.2.4 List底层
- Add
- Remove
- 2.3 字符串(string)
- 2.3.1 string的不变性和驻留性
- 2.3.2 字符串的比较
- 2.3.3 字符串的连接
- +=
- StringBuilder
- string.Format
- 3 基础补充
- 3.1 值类型&引用类型
- 3.1.1 值类型
- 3.1.2 引用类型
- 3.1.3 值类型和引用类型的区别
- 3.1.4 装箱和拆箱
- 3.2 ref和out
- 3.3 字段和属性
- 3.4 结构体(struct)
- 3.4.1 定义
- 3.4.2 结构体VS类
- 区别
- 选择
- 3.5 委托(delegate)&Lambda&事件(event)
- 4 特性
- 4.1 面向对象三大特征
- 4.1.1 封装
- 4.1.2 继承
- 4.1.3 多态
- 重载(overload)
- 重写(override)
- 4.2 抽象
- 4.2.1 虚函数VS抽象函数
- 4.2.2 抽象类VS接口
- 4.3 反射(Reflection)
- 4.4 泛型(Generic)
- 4.4.1 为什么使用泛型?
- 4.4.2 泛型约束
- 4.4.3 协变和逆变
- 5 面试题
1 概述
1.1 GC(Garbage Collection)
C#-垃圾回收机制(GC)
1.1.1 为什么需要GC?
GC是CLR的一个组件,它控制内存的分配和释放,它的出现是为了简化程序员的内存管理工作。
在面向对象的环境中,每个类型都可以代表可供程序使用的一种资源,访问资源的步骤:
- 调用IL指令newObj,为代表资源的类型分配内存(一般使用new操作符来完成)。
- 初始化内存,设置资源的初始状态并使资源可使用。类型的实例构造器负责设置初始化状态。
- 访问类型的成员来使用资源。
- 摧毁资源的状态以进行清理。
- 释放内存。
上述的最后一步如果由程序员负责,可能会产生一些无法预测的问题(如:忘记释放不再使用的内存、试图使用已被释放的内存等等),因此GC被引入,单独负责这一步,简化了程序员的内存管理工作。
new
托管堆上有一个nextObjPtr指针,指向下一个对象在堆中分配的位置。
当应用程序执行new操作符后,若内存中有足够的可用空间,就在nextObjPtr处放入对象,接着调用对象的构造方法,并为应用程序返回一个该对象的引用。
nextObjPtr会加上当前对象占用的字节数,获得下一个对象放入托管堆时的地址。
1.1.2 GC的工作原理
工作原理
GC即垃圾回收。它是以应用程序的root为基础,遍历应用程序在托管堆(Heap)上动态分配的所有对象,通过识别它们是否被引用来确定哪些对象是已经死亡的,哪些是仍需要被使用的。其中,已经不再被引用的对象就是已经死亡的对象,即所谓的垃圾,需要被回收。这就是GC工作的原理。
什么是Root?
每个应用程序都包含一组root。每个root都是一个存储位置,其中包含指向引用类型对象的一个指针(可以理解为对象的引用)。该指针要么引用托管堆中的一个对象,要么为null。
在应用程序中,只要某对象变得不可达,也就是没有根(root)引用该对象,这个对象就会成为垃圾回收器的目标。
.NET中可以当作GC Root的对象有如下几种:
- 全局变量
- 静态变量
- 栈上的所有局部变量(JIT)
- 栈上传入的参数变量
- 寄存器中的变量
注意,只有引用类型的变量才被认为是root,值类型的变量永远不被认为是root。
GC算法:Mark-Compact 标记压缩算法
- 暂停进程中的所有线程。
- GC标记阶段
- CLR遍历堆中所有对象,将他们的同步索引块中的某一位设为0。
- 引用跟踪算法:CLR基于应用程序的root进行检查,查看它们引用了哪些对象,其中空引用(null)的被CLR忽略掉。 任何根如果引用了堆上的对象,CLR都会标记那个对象,将它的同步索引块中的一位设为1 。 那些未被标记为1 的对象即垃圾,被垃圾回收。
- GC压缩阶段
- 对象回收之后heap内存空间变得不连续,在heap中移动这些对象,使他们重新从heap基地址开始连续排列,类似于磁盘空间的碎片整理。
- 修复引用
- 压缩过程移动了堆中的对象,使对象地址发生变化,因此需要修复所有引用,即更新它们存储的堆内地址。
- 上一步有个类似于重定位表的东西,它记录了旧地址到新地址的映射,可以用在这一步。
- 恢复所有线程。
GC优化:Generational 分代算法
进行一次完整内存区域的GC(full GC)操作成本很高,因此我们采用分代算法对GC性能进行一定改善。
分代算法的思想:将对象按照生命周期分成新老对象,对新、老区域采用不同的回收策略和算法,加强对新区域的回收处理力度,争取在较短时间间隔、较小的内存区域内,以较低成本将执行路径上大量新近抛弃不再使用的局部对象及时回收掉。
分代算法的假设前提条件:
- 大量新创建的对象生命周期都比较短,而较老的对象生命周期会更长。
- 对部分内存进行回收比基于全部内存的回收操作要快。
- 新创建的对象之间关联程度通常较强。heap分配的对象是连续的,关联度较强有利于提高CPU cache的命中率。
.NET将heap分成3个代龄区域: Gen 0、Gen 1、Gen 2。
如果Gen 0 heap内存达到阀值,则触发0代GC,0代GC后Gen 0中幸存的对象进入Gen1。如果Gen 1的内存达到阀值,则进行1代GC,1代GC将Gen 0 heap和Gen 1 heap一起进行回收,幸存的对象进入Gen2。2代GC将Gen 0 heap、Gen 1 heap和Gen 2 heap一起回收。
Gen 0和Gen 1比较小,这两个代龄加起来总是保持在16M左右;Gen2的大小由应用程序确定,可能达到几G,因此0代和1代GC的成本非常低,2代GC称为fullGC,通常成本很高。
1.1.3 GC的触发时间
-
最常见的触发条件:CLR在检测第0代内存超过预算时触发一次GC。
-
代码显式调用GC.Collect()。
-
Windows报告低内存。
-
CLR正在卸载AppDomain。
-
CLR正在关闭。
1.1.4 如何减少垃圾回收
- 减少new产生对象的次数。
- 使用公用的对象(静态成员)。
- 将string换为stringBuilder(这部分详细看后面string的部分)。
1.1.5 手动回收
- GC.Collect();
- .Net的GC并不是实时性的,这会造成系统性能上的瓶颈和不确定性。所以有了IDisposable接口,IDisposable接口定义了Dispose方法,这个方法用来供程序员显式调用以释放非托管资源。
1.1.6 需要特殊清理的类型*
在编写应用程序中肯定会涉及例如:操作文件 FileStream、网络资源socket、互斥锁 Mutex 等这些本机资源。
创建对象时不仅也要为它分配内存资源,还要为它分配本机资源。那么包含本机资源的类型被GC 时,GC会回收对象在托管堆中使用的内存,但这个类型的本机资源不清理的话,就会造成本机资源的泄漏。
所以,CLR提供了称为终结的机制,允许对象在被判定为垃圾之后,但在对象内存被回收之前执行一些代码。任何包装了本机资源的类型都支持终结。
CLR 判定一个对象不可达是,对象将终结它自己,释放它包装的本机资源。之后,GC 会从托管堆回收对象。
对于使用了本机资源的对象,在废弃它的时候我们该如何处理呢?
终极基类 System.Object 定义了受保护的虚方法 Finalize。如果你创建的对象使用了本机资源,你可以要重写Object 的虚方法。在类名前添加~ 符号来定义Finalize方法。垃圾回收器判定对象是垃圾后,会调用对象的Finalize 方法。
internal sealed class SomeType{~SomeType(){//这里的代码会进入Finalize 方法}
}
拥有本机资源的对象经历垃圾回收的顺序是这样的:
- 拥有本机资源对象被标记为垃圾,等待GC清理。
- GC 将堆中其他垃圾回收完毕后才调用 Finalize方法,这些使用了本机资源的对象的内存没有被GC马上被回收,因为Finalize 方法可能要执行访问字段的代码。
- 上一步导致拥有本机资源的对象被提升到下一代,使对象活得比正常时间长。
- 当下一代对象被GC 回收时,拥有本机资源的对象的内存才会被回收。如果拥有本机资源的对象的字段引用了其他对象,那么它们也会被提升到下一代。
1.2 内存分区
1.2.1 分区
- 栈:由编译器自动分配释放 ,存放值类型的对象本身,引用类型的引用地址(指针)等。其操作方式类似于数据结构中的栈。
- 堆:用于存放引用类型对象本身。在c#中由.net平台的垃圾回收机制(GC)管理。栈,堆都属于动态存储区,可以实现动态分配。
- 静态区及常量区
- 如果一个类型是静态值类型或者常量对象,那么存储在静态区/常量区;如果一个类型是静态引用类型,那么引用存储在静态区/常量区,而对象本身存储在堆上。
- 由于存在栈内的引用地址都在程序运行开始最先入栈,因此静态区和常量区内的对象的生命周期会持续到程序运行结束时,届时静态区内和常量区内对象才会被释放和回收(编译器自动释放)。所以应限制使用静态类,静态成员(静态变量,静态方法),常量,否则程序负荷高。
- 代码区:存放函数体内的二进制代码。
1.2.2 为什么栈比堆快?
首先,栈是程序运行前就已经分配好的空间,所以运行时分配几乎不需要时间。而堆是运行时动态申请的,分配内存会有耗时。
其次,访问堆需要两次内存访问,第一次取得地址,第二次才是真正得数据,而栈只需访问一次。栈有专门的寄存器,压栈和出栈的指令效率很高,而堆需要由操作系统动态调度。
1.2.3 .NET&CLR*
C# 程序在 .NET 上运行,而 .NET 是名为公共语言运行时 (CLR) 的虚执行系统和一组类库。CLR 是 Microsoft 对公共语言基础结构 (CLI) 国际标准的实现。 CLI 是创建执行和开发环境的基础,语言和库可以在其中无缝地协同工作。
用 C# 编写的源代码被编译成符合 CLI 规范的中间语言(IL)。 IL 代码和资源(如位图和字符串)存储在扩展名通常为 .dll 的程序集中。
执行 C# 程序时,程序集将加载到 CLR。 CLR 会直接执行实时 (JIT) 编译,将 IL 代码转换成本机指令。 CLR 可提供其他与自动垃圾回收、异常处理和资源管理相关的服务。 CLR 执行的代码有时称为“托管代码”。而“非托管代码”被编译成面向特定平台的本机语言。
2 数据结构
2.1 数组(Array)
2.1.1 一维数组
int[] arrayA = new int[n];
int[] arrayB = new int[]{1,2,3};
2.1.2 二维数组
//两行三列的二维数组
int[,] arrayA = new int[2,3];
int[,] arrayB = new int[,]{{1,2,3},{3,2,1},
};
2.1.3 交错数组
交错数组可以理解为数组的数组。
//由于交错数组存放的数组长度可能各不相同,所以不指定第二维度
int[][] arrayA = new int[2][];
int[][] arrayB = new int[][]{new int[]{1,2,3},new int[]{3,2,1},
};
2.2 ArrayList&List
在C#中,由于数组都是固定长度的,所以常常不能满足我们开发的需求,因此出现了列表(可变长数组。
2.2.1 ArrayList
ArrayList是可变长数组,其内部维护的数组,当长度不足时,会自动扩容为原来的两倍。ArrayList存储的数据可以重复。
但ArrayList有一个缺点,就是存入ArrayList的数据都是Object类型的,所以将值类型存入和取出时会发生装箱拆箱操作,影响程序性能。
针对这一点,在.Net2.0泛型出现后,就提供了List。
2.2.2 List
List是ArrayList的泛型版本,它不存在装拆箱操作。不过需要在使用的时候先设置好类型。
List存储的数据可以重复,通过索引(下标)进行访问。
List<int> list = new List<int>();
2.2.3 区别
ArrayList非泛型而List是泛型,ArrayList在值类型存取时存在装拆箱操作,List则避免了这个问题。
2.2.4 List底层
List内部是由数组实现的,当没有给予指定容量时,初始容量为0。
Add
在List的Add()函数中,每添加一个元素,Add接口都会首先检查容量是否足够,如果不够,会调用EnsureCapacity()函数进行扩容。
在EnsureCapacity()函数中,有这样一行代码:
//_defaultCapacity:默认容量为4
//当list开始添加元素时,默认指定的最初容量为4,后续按2倍扩容
int newCapacity = _items.Length == 0? _defaultCapacity : _items.Length*2;
每次容量不够时,整个数组的容量都会扩充一倍,_defaultCapacity表示默认容量为4,因此整个扩充路线为4,8,16,32…以此类推。
List使用数组形式作为底层数据结构,优点是通过索引访问元素很快,缺点是在扩容时每次针对数组进行new操作都会造成内存垃圾,给垃圾回收(GC)带来了很多负担。
这里按2的指数扩容的方式,可以为GC减轻负担。但如果数组被连续申请扩容,还是会造成GC的不小负担。此外,如果数量使用不当,也会浪费大量空间,例如,当元素数量为520时,List会扩容到1024个元素,那么就会有504个空间单位被剩余,造成内存空间的大量浪费。
Remove
原理是先使用一个IndexOf函数以O(n)复杂度找到目标元素索引,再在RemoveAt()函数内使用Array.Copy对数组进行覆盖。
Array.Sort()
//将array1从index1开始的len个元素复制到array2(从index2开始
Array.Sort(array1,index1,array2,index2,len);
...
_size--;
if(index<_size) Array.Sort(_items,index+1,_items,index,_size-index);
...
其他接口原理与Add()接口和Remove()接口相似,可以看到,List效率并不高,只是通用性较强而已,大部分算法使用的都是线性复杂度的算法。
另外也可以从源码中看出,List是线程不安全的,它并没有对多线程做任何加锁或其他同步操作。由于并发情况下无法判断_size++的执行顺序,因此当我们在多线程间使用List时应加上安全机制。
2.3 字符串(string)
2.3.1 string的不变性和驻留性
string是一种比较特殊的引用类型。
不变性
字符串一经创建,值不可变。对字符串的各种修改操作都会创建新的字符串对象。
当你给一个字符串重新赋值,会在堆中重新开辟一块空间存储新值,并将栈内存储的地址修改为新开辟空间的地址,而老的值会继续存在于堆中,等到垃圾回收时再被销毁。
C#里其他不可变类型
驻留性
运行时将字符串值存储在“驻留池(字符串池)”中,相同值的字符串都复用同一地址。
一般只有两种情况下字符串会被驻留:
1.字面量的字符串,这在编译阶段就能确定的“字符串常量值”。相同值的字符串只会分配一次,后面的就会复用同一引用。例如 string str = “abc” 这种。
2.过 string.Intern(string) 方法主动添加驻留池。
驻留的字符串(字符串池)在托管堆上存储,大家共享,内部其实是一个哈希表,存储被驻留的字符串和其内存地址。驻留池生命周期同进程,并不受GC管理。
2.3.2 字符串的比较
==
重写了Equals()方法,会先比较字符串的地址,如果一致,返回true,如果不一致,则进一步比较字符串的值,如果一致,返回true。
Equals()
比较字符串的地址,不一致则返回false。
2.3.3 字符串的连接
C#字符串连接
C# string
+=
string str = tmpStr + “abc”;会被优化程string.Concat(…)。通过分析string.Concat(params string[] values)的实现可以知道:先计算目标字符串的长度,然后申请相应的空间,最后逐一复制,时间复杂度为o(n),常数为1。
固定数量的字符串连接效率最高的是+。
但是字符串的连+不要拆成多条语句,比如:
string sql = "update tableName set int1=";
sql += int1.ToString();
sql += ...
这样的代码,不会被优化为string.Concat,就变成了性能杀手,因为第i个字符串需要复制n-i次,时间复杂度就成了o(n^2)。
StringBuilder
StringBuilder 只分配一次内存,如果第二次连接内存不足,则修改内存大小;它每次默认分配16字节,如果内存不足,则扩展到32字节,如果仍然不足,继续成倍扩展。
如果频繁的扩展内存,效率大打折扣,因为分配内存,时间开销相对比较大。如果事先能准确估计程序执行过程中所需要的内存,从而一次分配足内存,效率大大提高。
如果字符串的数量不固定,就用StringBuilder,一般情况下它使用2n的空间来保证o(n)的整体时间复杂度,常数项接近于2。
因为这个算法的实用与高效,.net类库里面有很多动态集合都采用这种牺牲空间换取时间的方式,一般来说效果还是不错的。
string.Format
它的底层是StringBuilder,所以其效率与StringBuiler相似。
3 基础补充
3.1 值类型&引用类型
C#的类型有两种:值类型(value type)和引用类型(reference type)。值类型的变量存储数据,而引用类型的变量存储对数据的引用。
3.1.1 值类型
值类型直接包含值,换言之,变量引用的位置就是值在内存中实际存储的位置。将第一个变量的值赋给第二个变量,会在新变量的位置创建原始变量的值的一个内存副本。类似的,将值类型的实例传给方法,如Console.WriteLine(),也会生成一个内存副本,在方法内部对参数值进行任何修改都不会影响原始值。
对于值类型,每个变量都有它们自己的数据副本(ref和out参数除外)。
3.1.2 引用类型
相反,引用类型的变量存储的是对数据存储位置的引用,而不是直接存储数据。因此,为了访问数据,“运行时”要先从变量中读取内存位置,再“跳转”到包含数据的内存位置。
引用类型复制引用而不需要复制所引用的数据,表现为在栈内开辟一块新的空间,存储复制数据在堆中的地址,因此当有一个引用类型变量复制了另一个引用类型变量,该变量的改变也会引起另一个变量的改变。
3.1.3 值类型和引用类型的区别
- 值类型数据存储在栈上,而引用类型数据存储在堆上。
- 值类型的复制是按值传递的,引用类型的复制是按引用传递的,当我们对复制体进行修改时,值类型的原数据不会受到影响,而引用类型数据会随之改变。
- 值类型存取快,引用类型存取慢。因为首先值类型存储在栈上,栈上的内存是事先分配好的。其次,访问堆需要两次内存访问,第一次取得地址,第二次才是真正得数据,而栈只需访问一次。栈有专门的寄存器,压栈和出栈的指令效率很高,而堆需要由操作系统动态调度。
- 值类型继承自System.ValueType,引用类型继承自System.Object。
- 值类型的内存管理由编译器自动完成,而引用类型的内存管理由垃圾回收器完成。
- 值类型的生命周期由程序的作用域控制,而引用类型的生命周期由垃圾回收器控制。
3.1.4 装箱和拆箱
- 装箱
- 值类型转换为引用类型。
- 从托管堆中为新生成的引用对象分配内存,然后将值类型的数据拷贝到刚分配的内存中,并返回该内存的地址。这个地址存储在栈上作为对象的引用。
- 拆箱
- 引用类型转换为值类型。
- 首先获取托管堆中属于值类型那部分字段的地址,将引用对象中的值拷贝到位于栈上的值类型实例中。拆箱只是获取引用对象中指向值类型部分的指针,而内容拷贝是赋值语句触发。
装箱会产生较大性能损失(主要是构造新对象),拆箱的性能损失较小(主要是强制类型转换)。
using System;
class Test{static void Main(){int i = 123;object o = i; //Boxingint j = (int)o; //Unboxing}
}
3.2 ref和out
ref和out可以将值类型以引用的方式进行传递,从而改变原来变量中的值。
它们俩的区别在于:通过ref传入的参数必须在传入方法前对其进行初始化操作,而通过out传入的参数不需要在传入方法前对其初始化,即便初始化了,也会在传入方法时清空,然后再在方法内赋初值。
例如当我们使用Swap我们可以使用ref:
class Program{static void Main(string[] args){Program pg = new Program();int x = 10;int y = 233;pg.Swap(ref x,ref y);Console.WriteLine(x+" "+y);}static void Swap(ref int x,ref int y){int temp = x;x = y;y = x;}
}
3.3 字段和属性
public class Person{private string name;public string Name{get{return name;}set{name = value;}}
}
public class Program{Person preson = new Person();person.Name = "manqi";Console.WriteLine(person.Name);
}
属性是一种特殊的方法,用于控制对成员变量的访问和赋值。通过使用属性,可以对成员变量进行保护,使得外部代码无法对其直接访问和修改。
优点:
- 安全性:将读、写权限分开:get和set是分开实现的,保证了数据安全。
- 灵活性:给属性赋值或取值时,可以根据自己的需求实现各种不同的赋值或取值逻辑。
3.4 结构体(struct)
3.4.1 定义
结构体可以简单地视为值类型的类。
struct Point{public int x,y;public Point(int x,int y){this.x = x;this.y = y;}
}
3.4.2 结构体VS类
区别
- 结构体中声明的字段无法赋予初值,类可以。
- 结构体的构造函数中,必须为结构体所有字段赋值,类的构造函数则没有这个限制。
- 类为引用类型,可继承;结构体为值类型,不可继承(只能继承接口)。
选择
类是引用类型,因此类的对象存储在堆中。而结构体是值类型,结构体的对象存储在栈中。堆空间大,但访问速度慢。栈空间小,但访问速度快。因此,当我们描述的对象是一个轻量级对象时,选用结构体可提高效率。但如果我们希望传值的时候传递的是对象的引用而非对象的拷贝,那么我们可以选用类。
3.5 委托(delegate)&Lambda&事件(event)
委托看这里
4 特性
4.1 面向对象三大特征
4.1.1 封装
封装指的是隐藏类的实现细节,对外只暴露必要的接口和方法。它将数据和操作封装在一个类中,控制数据的访问权限。例如:事件就体现了封装的概念。
在C#中,封装的体现:
- 访问修饰符
- public:对任何类和成员都公开,无访问限制。
- internal:只能在包含该类的程序集中访问该类。
- protected:对该类和该类的派生类公开。
- protected internal:protected+internal。
- private:仅对该类公开。
- 属性
- 事件
- …
4.1.2 继承
继承允许我们创建一个新的类,去继承已有的类的属性和方法,并添加或修改自己的属性和方法。通过继承,可以实现代码的复用,提高程序的可维护性和可扩展性。
4.1.3 多态
重载(overload)
重载是编译时多态,指的是在同一个类中定义多个同名的方法,但参数列表不同,这样可以根据不同的参数列表调用不同的方法。
重写(override)
重写是运行时多态,指的是在继承关系中,子类可以重写基类中的虚方法和抽象方法。
4.2 抽象
4.2.1 虚函数VS抽象函数
- 虚函数用virtual修饰,在基类中定义;抽象函数用abstract修饰,在抽象类中定义。
- 虚函数有实现体,抽象函数没有实现体。
- 虚函数子类可以选择性重写,而子类必须实现抽象方法,否则子类也必须声明为抽象类。
- 虚函数可以通过基类对象的引用或子类对象的引用调用,而抽象函数只能通过子类对象的引用调用。
public abstract class Animal{public abstract void Eat();public virtual void Run(){Console.WriteLine("Animal is running.");}
}public class Dog : Animal{public override void Eat(){Console.WriteLine("Dog is eating.");}public override void Run(){Console.WriteLine("Dog is running.");}
}public static void Main(string[] args){Animal animal1 = new Dog(); // 通过基类对象的引用调用虚方法animal1.Run(); // 输出:Dog is running.Dog dog1 = new Dog(); // 通过子类对象的引用调用虚方法和抽象方法dog1.Run(); // 输出:Dog is running.dog1.Eat(); // 输出:Dog is eating.
}
4.2.2 抽象类VS接口
- 抽象类不能被实例化,而接口也不能被实例化,只有具体类才能被实例化。
- 抽象类可以包含抽象方法和具体方法,也就是说抽象类中可以有部分已经实现的方法,但接口不行,接口只能包含抽象方法,没有具体的实现。
- 抽象类可以被具体类继承,并且可以作为父类来提供一些通用的行为和属性,而接口可以被具体类实现,一个类可以实现多个接口,从而实现多重继承。
- 抽象类通常用于定义类的层次结构,并提供一些通用的方法和属性,而接口则通常用于定义一些规范或者协议,来保证实现该接口的类拥有一定的行为和属性。
4.3 反射(Reflection)
C# 反射(Reflection)超详细解析
允许程序在运行时检查和操作对象的类型信息。
System.Type是一个典型的反射的例子。
public void Method(Object obj){//在运行时获取类型信息Type t = obj.GetType();
}
4.4 泛型(Generic)
4.4.1 为什么使用泛型?
使用泛型是一种增强程序功能的技术,具体表现在以下几个方面:
- 它有助于您最大限度地重用代码、保护类型的安全以及提高性能。
- 您可以创建泛型集合类。.NET 框架类库在 System.Collections.Generic 命名空间中包含了一些新的泛型集合类。您可以使用这些泛型集合类来替代 System.Collections 中的集合类。
- 您可以创建自己的泛型接口、泛型类、泛型方法、泛型事件和泛型委托。
- 您可以对泛型类进行约束以访问特定数据类型的方法。
- 关于泛型数据类型中使用的类型的信息可在运行时通过使用反射获取。
最常用的泛型例子:
List<T>
编译时由编译器会对泛型进行类型替换。
4.4.2 泛型约束
public void Method<T>(T obj) where T : People{}
4.4.3 协变和逆变
协变和逆变
5 面试题
1.为什么构造函数不能是虚函数?
构造函数和虚函数在设计概念和实现上是存在冲突的。
构造函数的主要职责是初始化对象的状态,在一个类的对象被创建时,构造函数负责分配内存、初始化成员变量。构造函数在创建对象的过程中是按继承层次从基类到派生类依次调用的(这里其实很好理解,因为父类都还没初始化好,何来子类呢)。
而虚函数则不同。
1)从设计层面,虚函数是一种支持多态性的方法,在多态情况下只执行其中的一个,这点和构造函数不同。
2)从存储空间角度,虚函数对应一个虚函数表(vtable),可是这个vtable其实是存储在对象的内存空间的。问题出来了,如果构造函数是虚的就需要通过vtable来调用,可是对象还没有实例化,也就是内存空间还没有,无法找到vtable,所以构造函数不能为虚函数。
相关文章:
【C#】知识汇总
目录 1 概述1.1 GC(Garbage Collection)1.1.1 为什么需要GC?1.1.2 GC的工作原理工作原理什么是Root?GC算法:Mark-Compact 标记压缩算法GC优化:Generational 分代算法 1.1.3 GC的触发时间1.1.4 如何减少垃圾…...
1、Unity【基础】3D数学
3D数学 文章目录 3D数学1、数学计算公共类Mathf1、Mathf和Math2、区别3、Mathf中的常用方法(一般计算一次)4、Mathf中的常用方法(一般不停计算)练习 A物体跟随B物体移动 2、三角函数1、角度和弧度2、三角函数3、反三角函数练习 物…...
虚拟机ubuntu22的扩容记录
这里lsblk命令能看到, ubuntu逻辑分区只有29G, 但总分区60G,还有接近30G未使用。 rootx:/home/x# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS loop0 7:0 0 63.9M 1 loop /snap/core2…...
Docker 常用配置
Docker 常用配置 1. 配置方法 修改下面位置: Linux:vim /etc/docker/daemon.jsonmacOS:菜单栏图标->Settings->Docker Engine 注意:修改完需要重启Docker Linux:systemctl restart dockermacOS:…...
通过示例了解 .NET Core 中的依赖注入
依赖注入 (DI) 是一种用于实现 IoC(控制反转)的设计模式,可以更好地解耦应用程序内的依赖关系并更轻松地管理它们。.NET Core 内置了对依赖注入的支持,提供了一种有效管理依赖关系的强大方法。 一.什么是依赖注入? 依…...
fetch、FormData上传多张图片
利用fetch方法和FormData对象上传多张图片 formdata()对象可以序列化多张图片 <html><head><meta http-equiv"content-type" content"text/html;charsetUTF-8"/><title>测试fetch和formdata上传多张图片</title></head&…...
C++STL详解(五)——list类的具体实现
一.本次所需实现的三个类及其成员函数接口 链表首先要有结点,因此我们需要实现一个结点类。 链表要有管理结点的结构,因此我们要有list类来管理结点。 链表中还要有迭代器,而迭代器的底层其实是指针。但是我们现有的结点类无法完成迭代器的…...
鸿蒙(API 12 Beta3版)【使用投播组件】案例应用
华为视频接入播控中心和投播能力概述** 华为视频在进入影片详情页播放时,支持在控制中心查看当前播放的视频信息,并进行快进、快退、拖动进度、播放暂停、下一集、调节音量等操作,方便用户通过控制中心来操作当前播放的视频。 当用户希望通…...
【STM32项目】在FreeRtos背景下的实战项目的实现过程(一)
个人主页~ 这篇文章是我亲身经历的,在做完一个项目之后总结的经验,虽然我没有将整个项目给放出来,因为这项目确实也是花了米让导师指导的,但是这个过程对于STM32的实战项目开发都是非常好用的,可以说按照这个过程&…...
C#垃圾处理机制相关笔记
C#编程中的垃圾处理机制主要通过垃圾回收器(Garbage Collector,GC)实现自动内存管理。C#作为一种托管语言,其垃圾处理机制显著减轻了程序员的内存管理负担,与C语言等非托管语言形成鲜明对比。具体介绍如下:…...
C语言memcmp函数
目录 开头1.什么是memcmp函数?2.memcmp函数的内部程序流程图 3.memcmp函数的实际应用比较整型数组比较短整型二维数组比较结构体变量…… 结尾 开头 大家好,我叫这是我58。今天,我们要学一下关于C语言里的memcmp函数的一些知识。 1.什么是memcmp函数?…...
低代码: 组件库测试之Vue环境下的测试工具以及测试环境搭建
Vue Test Utils Vue Test Utils 1 targets Vue 2. Vue Test Utils 2 targets Vue 3. 特别注意要使用 版本 2.0.0 以上 提供特定的方法,在隔离的话环境下,进行组件的挂载,以及一系列的测试 配置开发环境 手动配置, 是比较麻烦的vue cli 是基于插件架构的, 插件可以: 安装对…...
【Vue3】高颜值后台管理模板推荐
ELP - 权限管理系统 基于Vue 3框架与PrimeVue UI组件库技术精心构建的高颜值后台权限管理系统模板。该模板系统已成功实现基于RBAC(Role-Based Access Control)模型的权限管理系统和字典数据管理模块,后端则使用了Spring Boot框架࿰…...
详细介绍Pytorch中torchvision的相关使用
torchvision 是 PyTorch 的一个官方库,主要用于处理计算机视觉任务。提供了许多常用的数据集、模型架构、图像转换等功能,使得计算机视觉任务的开发变得更加高效和便捷。以下是对 torchvision 主要功能的详细介绍: 1. 数据集(Dat…...
AI部署——主流模型推理部署框架
我们以最经典的Yolov5目标检测网络为例解释一下10种主流推理部署框架的大概内容,省略模型训练的过程,只讨论模型转换、环境配置、推理部署等步骤。 Intel的OpenVINO — CPUNvidia的TensorRT — GPU/CPUOpenCV DNN Module — GPU/CPUMicrosoft ONNX Runti…...
PyTorch之loading fbgemm.dll异常的解决办法
前言 PyTorch是一个深度学习框架,当我们在本地调试大模型时,可能会选用并安装它,目前已更新至2.4版本。 一、安装必备 1. window 学习或开发阶段,我们通常在window环境下进行,因此需满足以下条件: Windo…...
Vscode——如何实现 Ctrl+鼠标左键 跳转函数内部的方法
一、对于Python代码 安装python插件即可实现 二、对于C/C代码 安装C/C插件即可实现...
力扣热题100_回溯_78_子集
文章目录 题目链接解题思路解题代码 题目链接 78. 子集 给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 示例 1: 输入ÿ…...
浏览器如何工作(一)进程架构
分享cosine 大佬,版权©️大佬所有 浏览器的核心功能 浏览器,“浏览” 是这个产品的核心,浏览无非分为两步: 获取想浏览的资源 展示得到的资源 现代浏览器还增加了交互功能,这涉及到脚本运行。因此,…...
【LeetCode】两数之和
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。 你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。 你可以按任意顺序返回答案。 示例 1…...
UE5学习笔记11-为拿取武器添加动画
一、一点说明 动画实例通过扩展为所有机器上的每个字符都存在动画蓝图,动画实例只能访问该计算机上的变量。 二、思路 我在武器组件中有一个武器类的指针,判断当前指针是否为空去判断当前角色是否装备武器 三、实现 1.在角色C类中添加是否装备武器的函…...
68. 文本左右对齐【 力扣(LeetCode) 】
一、题目描述 给定一个单词数组 words 和一个长度 maxWidth ,重新排版单词,使其成为每行恰好有 maxWidth 个字符,且左右两端对齐的文本。 你应该使用 “贪心算法” 来放置给定的单词;也就是说,尽可能多地往每行中放置单…...
【中等】 猿人学web第一届 第6题 js混淆-回溯
文章目录 请求流程请求参数 加密参数定位r() 方法z() 方法 加密参数还原JJENCOde js代码加密环境检测_n("jsencrypt")12345 计算全部中奖的总金额请求代码注意 请求流程 请求参数 打开 调试工具,查看数据接口 https://match.yuanrenxue.cn/api/match/6 请…...
低、中、高频率段具体在不同应用中的范围是多少
1、低频率段(Low Frequency Range) ①建筑声学和噪声控制:通常将20 Hz 到 200 Hz 的频率范围视为低频段。在这一范围内,声音的波长较长,通常与低音(如重低音音乐)和建筑结构中的振动有关。 ②…...
Oxford Model600 Model400低温氦压缩机cryogenic helium compressor手侧
Oxford Model600 Model400低温氦压缩机cryogenic helium compressor手侧...
Golang面试题四(并发编程)
目录 1.Go常见的并发模型 2.哪些方法安全读写共享变量 3.如何排查数据竞争问题 4.Go有哪些同步原语 1. Mutex (互斥锁) 2. RWMutex (读写互斥锁) 3. Atomic 3.1.使用场景 3.2.整型操作 3.3.指针操作 3.4.使用示例 4. Channel 使用场景 使用示例 5. sync.WaitGr…...
计算机学生高效记录并整理编程学习笔记的方法
哪些知识点需要做笔记? 以下是我认为计算机学生大学四年可以积累的笔记。 ① 编程语言类(C语言CJava):保留课堂笔记中可运行的代码部分,课后debug跑一跑。学习语言初期应该多写代码(从仿写到自己写&#…...
【书生大模型实战】L2-LMDeploy 量化部署实践闯关任务
一、关卡任务 基础任务(完成此任务即完成闯关) 使用结合W4A16量化与kv cache量化的internlm2_5-7b-chat模型封装本地API并与大模型进行一次对话,作业截图需包括显存占用情况与大模型回复,参考4.1 API开发(优秀学员必做)使用Func…...
《编程学习笔记之道:构建知识宝库的秘诀》
在编程的浩瀚世界里,我们如同勇敢的探险家,不断追寻着知识的宝藏。而高效的笔记记录和整理方法,就像是我们手中的指南针,指引着我们在这片知识海洋中前行,不至于迷失方向。在这篇文章中,我们将深入探讨如何…...
DETR论文,基于transformer的目标检测网络 DETR:End-to-End Object Detection with Transformers
transformer的基本结构: encoder-decoder的基本流程为: 1)对于输入,首先进行embedding操作,即将输入映射为向量的形式,包含两部分操作,第一部分是input embedding:例如,在NLP领域&…...
网站建设优化论坛/北京seo优化分析
系统更新安装完系统之后,需要更新一些补丁。CtrlAltT调出终端,执行一下代码:$sudo apt-get update$sudo apt-get upgrade卸载libreOfficelibreoffice事ubuntu自带的开源office软件,体验效果不如windows上的office,于是…...
网站上的qq如何做悬浮/2022十大网络营销案例
射人先射马,擒贼先擒王 在我们学习sonic的过程中,无疑了解sonic的架构是非常重要的,然后再去了解各个模块的细节,总分学习模式。下面是我自我学习并翻译的链接https://github.com/Azure/SONiC/wiki/Architecture?spma2c6h.128736…...
什么网站做任务赚钱/制作网站需要什么软件
一、测试数据:手机上网日志1.1 日志假设我们如下一个日志文件,这个文件的内容是来自某个电信运营商的手机上网日志,文件的内容已经经过了优化,格式比较规整,便于学习研究。每一行不同的字段又有不同的含义,…...
六枝网站建设/天津做网站的公司
一、Spring面试题 1、Spring 在ssm中起什么作用? 答: Spring:Spring轻量级框架。 作用:Bean工厂,用来管理Bean的生命周期和框架集成。 Spring框架的两大核心: ①. IOC/DI(控制反转/依赖注入) …...
宁夏建设网站公司/免费培训机构
如果你遇到同事编写的难以阅读的代码会怎么处理?反正我是遇到过。这些代码很难维护,还会影响开发进度。而如果对相关源码了解透彻,就可以快速定位到问题。最近一直在研究MyBatis源码,作为国内经常使用的持久层框架,其内…...
济南比较好的网站建设公司/网络优化工程师工资
大家好,我是陈旸,也是极客时间《数据分析实战 45 讲》专栏作者。很荣幸接到极客时间的邀请,来到极客Live和大家分享关于“数据分析”的话题。这次分享会共分为五部分,来为大家答疑解惑。 我们为什么要学数据分析? 学习…...