Unity笔记:C#基础(1)

杂项

虚函数

CSDN - C++虚函数详解
cnblog - C#中的虚函数virtual

常量池与new

在C#中,string是不可变的,这意味着对string对象的操作通常会返回一个新的string对象,而不会修改原始的string对象。因此,几乎所有涉及更改string内容的方法都会返回一个新的string对象。
String s = new String("xyz")在内存中产生了多少份字符串?2个。

  1. “xyz” 字符串的常量池中的字符串对象
  2. new出来的新字符串

这种方式创建的字符串对象不会被放入常量池中,正确的操作是下面这样

string s = "xyz";

在C#中,常量池(intern pool)通常是被放置在堆中。而Substring这类操作均不会改变原来的字符串,而是创建新的。

拆箱与装箱

拆箱(Unboxing)和装箱(Boxing)是与值类型(Value Type)和引用类型(Reference Type)之间的转换相关的两个概念。

  1. 装箱(Boxing)
    • 装箱是指将值类型转换为引用类型的过程。在装箱中,值类型的实例被封装在一个对象中,并在堆上分配内存空间。
    • 例如,将一个整数值装箱为 object 类型的实例,或者将一个结构体实例装箱为 System.ValueType 类型的实例。
  2. 拆箱(Unboxing)
    • 拆箱是指将引用类型转换为值类型的过程。在拆箱中,封装在对象中的值类型实例被提取出来,放入到一个新的值类型变量中。
    • 例如,将一个装箱的整数对象拆箱为一个整数值,或者将一个装箱的结构体对象拆箱为原始的结构体实例。

装箱和拆箱操作可能会引起性能开销,因为它们涉及到数据的复制和内存分配。因此,在编写高性能的代码时应该谨慎使用。

注1:在装箱过程中,值类型的数据会被复制到堆上新分配的内存空间中,而引用会指向这个新分配的内存空间,因此装箱后的引用指向的是堆上的对象。

注2:当创建一个新的结构体时,编译器会隐式地为它添加继承自 System.ValueType 的基类,并在必要时自动实现一些与值类型相关的功能,比如装箱、拆箱等。


List会发生拆装箱吗?会,List<object>就会发生

List<object> objectList = new List<object>();
objectList.Add(20); // 添加一个整数(值类型)
objectList.Add("World"); // 添加一个字符串(引用类型)

可以使用is关键字或as关键字来检查List<object>中的某个元素的类型。从 List<object> 中取出元素时,元素的类型会被视为 object 类型,因此任何按值类型进行的操作都需要显式手动转换类型。

C#关键字

C# 中的 sealed 关键字类似于 Java 中的 final 关键字,用于类(防止继承)或方法(防止重写)

readonlyconst区别在于const是编译时常量,而readonly是运行时常量:

  1. const关键字用于声明常量,常量在声明时必须进行初始化,并且一旦初始化后,其值将无法更改。const变量在编译时会被直接替换为其值,因此它们的值必须在编译时就能确定。
  2. readonly关键字用于声明只读字段,只读字段可以在声明时或构造函数中进行初始化,一旦初始化后,其值将无法更改。与const不同,readonly字段的值是在运行时确定的,因此可以用于在构造函数中初始化。

partial关键字

partial关键字用于指示一个类、接口、结构体或方法是“部分定义”的。这意味着该类、接口、结构体或方法的定义可以分散在多个文件中。

// File1.cs
partial class MyClass
{public void Method1(){Console.WriteLine("Method1");}
}// File2.cs
partial class MyClass
{public void Method2(){Console.WriteLine("Method2");}
}

不要试图在不同文件中重复定义某些方法或者变量,会报错。

System.Object

在C#中,所有引用类型的基类System.Object,该类实现了几个方法

在这里插入图片描述

Try

try
{// 可能会抛出异常的代码块
}
catch (ExceptionType1 ex)
{// 处理特定类型的异常
}
catch (ExceptionType2 ex)
{// 处理另一种类型的异常
}
finally
{// 无论是否发生异常,都会执行的代码块
}

如果catch后面没有括号里的条件,那就会捕获 try 块中抛出的任何类型的异常。自定义异常需要创建一个继承自 System.Exception 类的新类

using System;// 定义自定义异常类
public class MyCustomException : Exception
{// 可以添加自定义的构造函数和属性public MyCustomException(string message) : base(message){}
}public class Program
{public static void Main(string[] args){try{// 在适当的情况下,抛出自定义异常throw new MyCustomException("This is a custom exception.");}catch (MyCustomException ex){// 捕获并处理自定义异常Console.WriteLine("Custom Exception Caught: " + ex.Message);}catch (Exception ex){// 捕获其他类型的异常Console.WriteLine("Exception Caught: " + ex.Message);}}
}

注意在C#中,构造函数的调用顺序是由派生类向基类的方向,所以是派生类先调用基类构造函数执行,执行完才执行本类的,所以执行顺序是基类到派生类。

对于C#中的多重继承,基类构造函数的执行顺序是由派生类中基类声明的顺序决定的,一般就是这句话:

public class DerivedClass : Base1, Base2
{// ... 
}

例如下题结果为6

int x = 0;
try
{ throw new Exception(); }
catch
{ x += 1; }
finally
{ x += 2; }
x += 3;

观察者模式、委托与Unity

CSDN - Unity中关于委托与事件的使用及区别
c# 事件和委托,再也不忘了

事件是函数的容器,类似C的函数指针但不太一样。声明事件时需要先声明一个委托类型

  • 委托通常用于实现回调函数、事件处理等场景,它可以直接被调用。
  • 事件通常用于实现发布-订阅模式,它只能在声明类的内部触发,外部无法直接调用。

一般在OnEnable()OnDisable()中注册和移除事件的订阅而非Start(),这样不会在计算机内存中留下任何无法访问的Object。事件的调用如同调用函数一般,但是在那之前需要测试事件是否为 null,只有当任何类中没有函数订阅该事件时,该事件才会为 null

委托的本质可以看作是观察者模式的一种实现方式。委托的核心是事件,用到事件的地方就可以使用委托,例如UI交互;捡到某个物品时触发一个事件,该事件将为玩家提供升级等效果;或者触发了碰撞器能够打开门;还有就是状态管理。

实际上物体的碰撞事件通常是通过委托来实现的,如OnCollisionEnterOnCollisionStayOnCollisionExit等方法

C#与多继承

C#不直接支持多继承,一般使用接口实现类似效果。

内存对齐

CSDN - 【C/C++】内存对齐(超详细,看这一篇就够了)
有必要注意的是,这篇的例5讲的不太对,图也错了,我把我的理解放在了下面小节“结构体嵌套的对齐”

使用 #pragma pack(n) 指令会将结构体的对齐方式设置为 n 字节的整数倍,其中n是2的次方。例如一个大小为13的变量通常会对齐到16。

使用 #pragma pack()则取消强制对齐,恢复默认

注意:填充的缝隙也算在结构体/类的大小内,下面是个例子:

// sizeof(Base) == 8
// int4字节,bool按最大对齐
// 这里的策略是编译器在对结构体进行对齐时,按照结构体中最大的成员大小进行对齐。
class Base { int a; bool b; };

常见的对齐策略包括:

  1. 最严格对齐原则(Strictest Alignment):按照结构体中任何成员的要求,选择最严格的对齐方式。这意味着所有成员都按照自身的对齐要求进行对齐。
  2. 平均对齐原则(Average Alignment):根据结构体中所有成员的对齐要求的平均值进行对齐。这种策略可能会导致一些成员需要额外的填充来满足对齐要求。
  3. 特定对齐方式(Specified Alignment):有些编译器允许在结构体定义中指定对齐方式,例如使用 #pragma pack 或者 __attribute__((packed))。在这种情况下,结构体的对齐将根据指定的方式进行,而不是根据成员的大小。
  4. 默认对齐方式(Default Alignment):一些编译器有默认的对齐方式,可能会在不指定特定对齐方式的情况下应用。这通常会是一个合理的默认值,可以满足大多数情况下的性能和内存使用需求。

基本原则

知乎 - C/C++中内存对齐问题的一些理解
CSDN - 计算结构体大小(内存对齐原则)struct、union、class

就原则来讲,第二篇CSDN的博客是很详细的(其实很多东西我在Cppreference上没查到)

  1. 数据成员对齐规则,结构体(struct)(或联合(union))的数据成员,第一个数据成员存放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员(只要该成员有子成员,比如数组、结构体等)大小的整数倍开始(如:int 在 64bit 目标平台下占用 4Byte,则要从4的整数倍地址开始存储)
  2. 结构体的总大小,即sizeof的结果,必须是其内部最大成员长度(即前面内存对齐指令中提到的有效值)的整数倍,不足的要补齐(似乎union也需要满足这点)如果结构体A作为结构体B的成员,B的对齐大小总是按照#pragma pack(n)进行,其中n = max{A最大元素, B最大元素}

声明顺序的影响

struct st1 {char a[5];char b[3];int c;
};
struct st2 {char a[5];int c;char b[3];
};
// st1 == 12
// st2 == 16

这个归根到底是因为:相同类型的成员会连续存储在一起,不会因为对齐要求而产生间隔。数组内的n个成员视作相同类型的n个成员

struct st1 {int a[1];double p;char b[3];
};
struct st2 {char b[3];int a[1];double p;
};
// st1 == 24
// st2 == 16

类的对齐(虚函数与空类)

  1. 按照结构体对齐原则
  2. class含有成员变量和成员函数:计算大小的时候只与成员变量有关。与成员函数和静态成员无关,即普通成员函数、静态成员函数、静态成员变量。对类的大小没有影响。
  3. 虚函数对类的大小有影响,因为虚表指针的影响。在32位系统占4个字节,64位系统占8个字节。
  4. 多个虚函数也只算一个的影响。

在 C++ 中,对于空类(没有任何成员),其大小通常是 1 字节,这是因为 C++ 编译器会确保每个实例都有一个唯一的地址。

class Base{};
class Drived
{Base a;  // 类内没东西,按4字节对齐,大小为4int b;  // 一个int为4字节
};
cout << sizeof(Drived) << endl;  // 输出8

可以尝试修改Base类再输出:

// 这种情况输出也是8
class Base { int a; };
// 这种情况输出是12
class Base { int a; int b; };

在C#中,类的实例化在内存中会被对齐。即使一个类没有任何成员,它也会在内存中被对齐,其大小通常是一个指针的大小,因为每个类实例在CLR(Common Language Runtime)中都会关联一个类型对象指针。

枚举的对齐

枚举类型的对齐与其底层类型一致,在C++一般是int,但是C++11可以指定为其他合法的整数类型,如unsigned intcharshort,用法如下:

enum class MyEnum : underlying_type {VALUE1,VALUE2,VALUE3
};

例子如下:

enum DAY {MON = 1, TUE, WED, THU, FRI, SAT, SUN
};
// C++11新特性允许显式地指定枚举的底层类型
enum class DAY1 : char {MON = 'a', TUE, WED, THU, FRI, SAT, SUN
};
struct st1 {DAY b;
};
struct st2 {DAY1 b;
};
cout << sizeof(st1) << endl;	// 4
cout << sizeof(st2) << endl;	// 1

更复杂的情况,即枚举与其他的组合,就把enum当做某种整数类型计算即可。

union的大小

联合体和结构体一样,存在内存对齐

Cppreference:联合体只大到足以保有其最大成员(亦可能添加额外的尾随填充字节)。
上面的某博客:当联合体中有数组时,一方面要保证空间能够存储这个数组的大小,另一方面要保证最终的结果是最大数据类型的整数倍。

union MyUnion {char a[10];int b;double c;
};
// 大小输出是16,而不是10

如果加上#pragma pack(1)就是输出10了

不知道算不算参考的参考:MSDN - x64 ABI 约定概述

结构体嵌套的对齐

结合这两段代码对比:

struct stu2 {// size == 16char x;int y;char v[6];
};
struct stu1 {// size == 32char a;struct stu2 b;double f;
};
struct stu2 {// size == 24char x;int y;double z;char v[6];
};
struct stu1 {// size == 48char a;struct stu2 b;int c;int d;int f;
};
// 如果stu1去掉一个int,大小为40,去掉2个int大小也为40

换句话说,结构体嵌套的情况下,在默认对齐方式的情况下,总是n的整数倍,其中n = max{A中最大元素, B中最大元素}

c#的sizeof

C#无法直接使用 sizeof 操作符来获取结构体的大小

在 C# 中,sizeof 操作符用于获取未托管类型或静态成员的大小,但它不能用于获取托管类型(如结构体或类)的大小。这是因为托管类型的大小在编译时并不总是已知的,而是在运行时由 CLR (Common Language Runtime) 动态确定的。

要获取托管类型(如结构体)的大小,通常可以使用 System.Runtime.InteropServices.Marshal.SizeOf 方法,该方法在运行时动态计算类型的大小。

C#结构体布局

先看上一小节内存对齐

CSDN - C#-StructLayoutAttribute(结构体布局)

在 C# 中,结构体的布局方式可以通过 StructLayoutAttribute 特性来控制,而 LayoutKind 枚举类型用于指定这种布局方式的具体类型。

  1. Auto:自动布局。编译器根据目标平台和类型成员的排列顺序来确定结构体的布局方式
  2. Sequential:顺序布局。结构体的成员按照声明的顺序依次排列,不考虑对齐和填充。
  3. Explicit:显式布局。需要手动指定每个成员的偏移量,可以使用 FieldOffsetAttribute 特性来指定偏移量。
[StructLayout(LayoutKind.Sequential)]
public struct Point
{public int x;public int y;
}[StructLayout(LayoutKind.Explicit)]
public struct Rect
{[FieldOffset(0)] public int left;[FieldOffset(4)] public int top;[FieldOffset(8)] public int right;[FieldOffset(12)] public int bottom;
}

Golang选手看这个

如果是Golang选手就看这篇拿Golang讲的:CSDN - 详解内存对齐

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/522210.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

(未解决)macOS matplotlib 中文是方框

reference&#xff1a; Mac OS系统下实现python matplotlib包绘图显示中文(亲测有效)_mac plt 中文值-CSDN博客 module ‘matplotlib.font_manager‘ has no attribute ‘_rebuild‘解决方法_font_manager未解析-CSDN博客 # 问题描述&#xff08;笑死 显而易见 # solve 找到…

开源文生图大模型Playground v2.5发布:超越SD、DALL·E 3和 Midjourney

前言 在AI技术迅速发展的今天&#xff0c;文生图模型成为了艺术创作、设计创新等领域的重要工具。Playground v2.5的发布&#xff0c;不仅在技术上取得了突破&#xff0c;更在开源文化的推广与实践上迈出了重要一步。 Huggingface模型下载&#xff1a;https://huggingface.co/…

虚拟机环境搭建

搭建vm环境&#xff0c;配置虚拟机&#xff0c;期间遇到不支持&#xff0c;重启电脑后还是没用 此主机支持 AMD-V&#xff0c;但 AMD-V 处于禁用状态。 如果已在 BIOS/固件设置中禁用 AMD-V&#xff0c;或主机自更改此设置后从未重新启动&#xff0c;则 AMD-V 可能被禁用。 确…

PDF文件中有多个文件如何一次性的全部分割出来? 这个办法绝对能够帮到你

PDF作为一种常用的文件格式&#xff0c;广泛应用于各种文档、报表、合同等文件的制作和传输。但有时候&#xff0c;我们可能会遇到一个问题&#xff1a;PDF文件中包含了多个文件&#xff0c;我们需要单独提取其中的一个或几个文件。那么&#xff0c;该如何操作呢&#xff1f;下…

常见BUG如何在测试过程中分析定位

前言 在测试的日常工作中&#xff0c;相信经常有测试的小伙伴遇到类似的情况&#xff1a;在项目上线时&#xff0c;只要出现问题&#xff08;bug&#xff09;&#xff0c;就很容易成为“背锅侠”。 软件测试人员在工作中是无法避免的要和开发人员和产品经理打交道的&#xff…

黑马点评-发布探店笔记

探店笔记 探店笔记类似点评网站的评价&#xff0c;往往是图文结合。 对应的表有两个&#xff1a; tb_blog&#xff1a;探店笔记表&#xff0c;包含笔记中的标题、文字、图片等 tb_blog_comments&#xff1a;其他用户对探店笔记的评价 流程如下&#xff1a; 上传接口&#…

005-事件捕获、冒泡事件委托

事件捕获、冒泡&事件委托 1、事件捕获与冒泡2、事件冒泡示例3、阻止事件冒泡4、阻止事件默认行为5、事件委托6、事件委托优点 1、事件捕获与冒泡 2、事件冒泡示例 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /…

【嵌入式高级C语言】9:万能型链表懒人手册

文章目录 序言单向不循环链表拼图框架搭建 - Necessary功能拼图块1 创建链表头信息结构体 - Necessary2 链表头部插入 - Optional3 链表的遍历 - Optional4 链表的销毁 - Necessary5 链表头信息结构体销毁 - Necessary6 获取链表中节点的个数 - Optional7 链表尾部插入 - Optio…

如何做代币分析:以 ARB 币为例

作者&#xff1a;lesleyfootprint.network 编译&#xff1a;mingfootprint.network 数据源&#xff1a;ARB 代币仪表板 &#xff08;仅包括以太坊数据&#xff09; 在加密货币和数字资产领域&#xff0c;代币分析起着至关重要的作用。代币分析指的是深入研究与代币相关的数据…

【人工智能课程】计算机科学博士作业三

【人工智能课程】计算机科学博士作业三 来源&#xff1a;李宏毅2022课程第10课的作业 1 图片攻击概念 图片攻击是指故意对数字图像进行修改&#xff0c;以使机器学习模型产生错误的输出或者产生预期之外的结果。这种攻击是通过将微小的、通常对人类难以察觉的扰动应用于输入…

为什么说鸿蒙开发就业面广?人才遭“爆抢”的背后说明什么?

鸿蒙开发&#xff0c;作为华为推出的全新操作系统&#xff0c;自其诞生以来就备受关注。而鸿蒙开发就业面广&#xff0c;人才遭“爆抢”的现象&#xff0c;更是引发了业界的广泛讨论。那么&#xff0c;这一现象背后究竟隐藏着怎样的原因和深意呢&#xff1f; 首先&#xff0c;鸿…

【infiniband监控】grafana变量使用细化优化监控指标

本站以分享各种运维经验和运维所需要的技能为主 《python零基础入门》&#xff1a;python零基础入门学习 《python运维脚本》&#xff1a; python运维脚本实践 《shell》&#xff1a;shell学习 《terraform》持续更新中&#xff1a;terraform_Aws学习零基础入门到最佳实战 《k8…