赞
踩
值类型的数据被保存在栈(stack)上,而引用类型的数据被保存在堆(heap)上,当值类型作为参数传递给函数时,会将其复制到新的内存空间中,因此在函数中对该值类型的修改不会影响原始值类型。
装箱是指将值(int double)类型转换为对应的引用(object)类型,从内存角度上来说是将栈中的内容迁移到堆上
拆箱就是把引用转成值类型,把堆中的内容转移到栈上去
int i=3;
objcet o=i;//装箱
i=(int)o //拆箱
值类型变量赋值会创建一个新的拷贝,而引用类型变量赋值会创建一个新的引用指向同一个对象。
事件相对与委托来说,事件在外部使用时只能使用+= -=
一个类同时继承这两个接口,应该如何处理他们的同名方法?
显式实现接口
IA.Test()
IB.Test()
在C#中数据类型分为值类型和引用类型
值类型:结构体、枚举
引用类型:类、接口、数组、委托
当 List 需要扩容时,它会创建一个新的更大的内部数组,并将现有元素复制到新数组中,然后使用新数组替换旧数组
Equals继承自Object,一般用于比较对象内容是否相等,而==在不重载的前提下如果是值类型更倾向于比较值是否相等,引用倾向于比较地址是否相等,运算效率也不同,Equals比较的内容多,所以效率会差一点
浅拷贝只拷贝指针(C++)引用(C#),拷贝完的数据修改了原数据也会修改,但是深拷贝就是拷贝了一份新的,修改并不会影响原数据
方式2:因为List会不断扩容,扩容时会反复拷贝造成性能损耗
void Start() { int i=GetInt(); Debug.Log("第A处 i= "+i); } static int GetInt() { int i=10; try{ return i; } finally{ i=11; Debug.Log("第B处 i= "+i); } }
不论try块中的代码是否抛出异常,finally块中的代码都会被执行
先输出B=11然后输出A=10
public class CsharpLearing : MonoBehaviour { void Start() { Test t=GetObj(); Debug.Log("第A处 i= "+t.i); } static Test GetObj(){ Test t=new Test(); try{ return t; } finally{ t.i=11; Debug.Log("第B处 i= "+t.i); } } } class Test{ public int i=10; }
这里和上面不一样,先输出B=11,然后输出A=11
这两段程序输出不同的原因是因为Getint返回的是值类型,而getObj返回的是引用类型
闭包是指有权访问另一个函数作用域中变量的函数(用lambda或匿名方法实现,可以捕获不属于其作用域的值)
string str = null
string str = “”
string str = string.Empty
三者的区别
str = null 在堆中没有分配内存地址
str = “” 和 string.Empty 一样都是在堆内存中分配了空间,里面存储的是空字符串
string.Empty是一个静态只读变量
string是不可变的,意味着一旦创建了字符串对象,就无法更改其内容。任何对字符串的修改都会创建一个新的字符串对象。线程更安全
StringBuilder是可变的,可以通过对其进行操作来修改其内容,而不必创建新的对象。这使得在需要频繁修改字符串时,使用StringBuilder比使用string更高效。线程不安全
string在每次拼接时都会产生垃圾
而StringBuilder在拼接时,是在原空间中进行修改,不会产生垃圾,会自动帮助我们扩容
所以当字符串需要频繁修改拼接时,我们使用StringBuilder
.Net制定了了CLI公共语言基础结构的规则
只要是按照该规则设计的语言在进行.Net相关开发时
编译器会将源代码(C#、VB等等)编译为CIL通用中间代码。
也就是说不管什么语言进行开发,最终都会统一规范变为中间代码
最终通过CLR(公共语言运行时或者称为.Net虚拟)将中间代码翻译为对应操作系统的原生代码(机器码)
在操作系统(Windows)上运行
Action和Func是System命名空间下 C#为我们提供的两个写好的委托
Action本身是一个无参无返回值的委托
对应的Action<>泛型委托支持最多16个参数
Func本身是一个无参有返回值的委托
对应的Func<>泛型委托支持最多16个参数,并且有返回值
UnityAction是UnityEngine.Events命名空间下 Unity为我们提供的写好的委托
UnityAction本身是一个无参无返回值的委托
对应的UnityAction<>泛型委托支持最多4个参数
public void Dosomething (A a){
a.id=6;
a.name="Bob";
a.children[0]=7;
}
void Start()
{
var a=new A();
a.name="Alick";
a.children=new int[]{1,2,3};
Dosomething(a);
Debug.Log(a.name+a.id+a.children[0]);
}
输出Alick07
两块
"123"一块
"1234"一块
使用密封关键字sealed修饰该类
1.可以为不同类型对象的相同行为进行通用处理,提升代码复用率
2.避免装箱拆箱,提升性能
可以在不用写数据结构类的情况下
利用元组处理多返回值,或者临时数据的集合
GC产生的原因是 避免堆内存溢出而产生的回收机制
当不再使用的堆内存占用达到一定上限时,将会进行垃圾回收
避免方式:
1.尽量减少new对象,尽量复用对象(可使用缓存池)
2.用StringBuilder替换String,避免字符串拼接时产生垃圾
3.公共对象用静态声明
通过C#的拓展方法相关知识点进行添加
1.引入命名空间
2.安全使用引用对象
全是10:当委托最终执行时,他们使用的i,都是for循环中声明的i,此时的i已经变成了10
使用一个临时变量接当前值
内存抖动指短时间内有大量的对象被创建或者被回收的现象
频繁的内存抖动会造成 GC 频繁运行,造成卡顿
IDispose接口是 C# 中用于手动释放资源的机制
通过显式调用 Dispose() 方法来实现资源的释放,避免资源泄漏和浪费
它允许对象在不再需要时显式地释放资源,而不依赖于垃圾回收器的自动内存管理
C#中的垃圾回收机制,只会回收托管堆上分配的对象。
对于非托管资源以及其它需要显示释放的资源,垃圾回收是无法自动处理的,因为这些资源不属于托管堆,因此垃圾回收器无法自动识别和回收。这种情况下我们就需要显示的手动释放这些资源了。
而IDispose接口就提供了一种通用的机制来进行资源清理,主要用于释放非托管资源。
非托管资源:
属性一般可以用来封装字段
属性相对字段来说,属性具有封装性,允许对字段进行封装,提供更多的控制和逻辑。
相比直接访问字段来说,属性允许我们在字段访问的过程汇总添加验证、计算等逻辑
属性还可以在其中对set和get设置不同的访问级别,使得字段的读取和写入可以收到更精细的控制
虚函数的重写是可选的,当需要在子类中修改逻辑时可以选择重写
抽象函数必须重写
当我们使用里式替换原则,用父类容器装载子类对象时
我们通过该父类容器调用其中的一个虚函数,执行的逻辑是父类中的还是子类中的逻辑呢?
Father Eat
Son :Father
Son Eat
Father f = new Son();
f.Eat();
var i = 10;
float f = 5.5f;
不会,var在编译时会被推断为正确的类型,所以在运行时不会引入额外的性能开销。
相当于在编译阶段var就会被翻译为指定的类型。
var声明临时变量只会影响代码的可读性和可维护性(双刃剑)
对象实例化
对于需要频繁创建的对象,比如角色、敌人、道具、特效、音效等等,工厂模式可以将实例化逻辑封装到一个工厂类中,提供统一方法给外部调用
12个,0个
闭包可能会捕获并持有外部作用域变量的引用。
这些引用会导致外部作用域的对象持续存在,即使它们不再需要。
@符号:让转义字符失效
$符号:插值字符串,可以在字符串中加入 {变量名} 插入变量值
异或加密是一种简单的对称加密算法。原理就是基于异或运算,相同为0,不同为1。
异或加密时会有一个固定的密钥
想要加密的数据与该密钥进行异或运算得到加密后的数据
加密后的数据再和密钥进行异或运算得到原数据(解密后的数据)
举例:
假设原数据为:1010
密钥为:1100
加密后的数据为:1010^1100 = 0110
解密数据:0110^1100 = 1010
public Father<Father<int>> Test()
{
return new Son<Son<int>>();
}
这个写法是否正确?
不正确,会报错
因为Son<Son>并不是Father<Father>的子类型
如果要修改
我们可以返回
return new Son<Father>();
占用,但是它不再我们通常说的堆栈中
而是存储在内存中的文本段(Text Segment)
它是存储可执行程序的代码的内存段
CLR(公共语言运行时)会按需加载程序集和其中的类型和成员
程序执行到需要调用某个类的函数时
CLR 会负责将该函数的 IL 代码编译成本机代码
并将其加载到 Text Segment 中
一旦函数的代码被加载到内存中,它通常会一直存在于内存中,直到程序退出
不会包含。
函数中的临时变量,不管是值类型还是引用类型都是在函数执行期间动态分配的。
这些临时变量会在堆栈中进行内存分配
栈上的内存由CLR动态管理,用完会被销毁
堆上的内存有GC垃圾回收机制统一进行管理
委托的本质是一个类,当我们声明一个委托时,相当于声明了一个类。
它会默认继承System.MulticastDelegate类(多播委托类)
而这个System.MulticastDelegate类 又继承Delegate类(委托类)
这些父类当中的一些方法,就是当我们对委托进行操作时真正会调用的内容
通过这些父类中的方法我们其实可以简单推测出
委托中存储函数的本质,是通过一个委托类对象来存储对象的引用或者静态类的类型
然后再记录一个函数的名字。
但是实际上,在内部会根据这些信息定位到
1.函数的引用(函数的内存地址)
2.函数所在对象的引用(如果是实例方法)
而当我们进行 += 操作时,其实C#内部会调用父类中的Combine结合方法
在底层帮助我们声明一个新的委托对象来记录对应函数的相关信息
我们在使用事件的时候往往是在类中声明成员变量时
当我们声明一个事件时,本质就是对委托进行私有访问限制的封装
事件的本质其实就是委托
子类中
虚函数的重写是可选的,当需要在子类中修改逻辑时可以选择重写
抽象函数必须重写
我们通过该父类容器调用其中的一个虚函数,执行的逻辑是父类中的还是子类中的逻辑呢?
Father Eat
Son :Father
Son Eat
Father f = new Son();
f.Eat();
不会,var在编译时会被推断为正确的类型,所以在运行时不会引入额外的性能开销。
相当于在编译阶段var就会被翻译为指定的类型。
属性一般可以用来封装字段
属性相对字段来说,属性具有封装性,允许对字段进行封装,提供更多的控制和逻辑。
相比直接访问字段来说,属性允许我们在字段访问的过程汇总添加验证、计算等逻辑
属性还可以在其中对set和get设置不同的访问级别,使得字段的读取和写入可以收到更精细的控制
深度测试用于确定哪些像素应该被绘制到屏幕上,并决定它们的可见性。
深度测试的主要目标是解决遮挡关系,确保前面的对象覆盖后面的对象,从而正确呈现场景
会写入颜色缓冲区
因为深度写入和颜色写入是两个独立的操作
只要通过了深度测试,不管是否写入深度缓冲区
该片元的颜色信息都会写入到颜色缓冲区中
接口:
1.不同对象的共同行为
2.需要多继承时
抽象类:
1.同类对象的共同行为
2.共享成员变量
比如我们平时声明的 unsafe语句块中的指针成员,数据库链接对象,Socket通讯对象,文件流等对象都存在非托管内存,需要我们自己释放
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。