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字节对齐,大小为4
    int 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 == 16
    char x;
    int y;
    char v[6];
};
struct stu1 {
    // size == 32
    char a;
    struct stu2 b;
    double f;
};
struct stu2 {
    // size == 24
    char x;
    int y;
    double z;
    char v[6];
};
struct stu1 {
    // size == 48
    char 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#结构体内存对齐

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.mfbz.cn/a/448391.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

现在可以在Mac桌面上快速打开C知道进行AI提问

看&#xff0c;我的Mac桌面有个C知道组件&#xff0c;点击即可快速打开C知道页面进行提问使用&#xff0c;再也不需要先打开浏览器&#xff0c;再输入csdn.net访问网站&#xff0c;然后点击页面上的C知道入口。 这是如何实现的呢&#xff1f; 首先&#xff0c;我们来做一些准…

微信聊天助手

最近我在负责公司的招聘工作时&#xff0c;真是深感繁琐与困扰。每当新添加求职者的微信&#xff0c;为了了解他们的基本情况以及求职意向&#xff0c;不得不反复地提出一系列固定的问题。比如询问他们的教育背景、工作经验、擅长技能、期望职位等等&#xff0c;这些必不可少却…

东京工业大学最新!一种具有多周期特征描述的精确ORB提取器

作者&#xff1a;小柠檬 | 来源&#xff1a;3DCV 在公众号「3DCV」后台&#xff0c;回复「原论文」可获取论文pdf 添加微信&#xff1a;dddvision&#xff0c;备注&#xff1a;3D高斯&#xff0c;拉你入群。文末附行业细分群 详细内容请关注3DCV 3D视觉精品课程&#xff1a;…

【每日一题】2834. 找出美丽数组的最小和-2024.3.8

题目&#xff1a; 2834. 找出美丽数组的最小和 给你两个正整数&#xff1a;n 和 target 。 如果数组 nums 满足下述条件&#xff0c;则称其为 美丽数组 。 nums.length n.nums 由两两互不相同的正整数组成。在范围 [0, n-1] 内&#xff0c;不存在 两个 不同 下标 i 和 j &…

P5266 【深基17.例6】学籍管理题解

题目 您要设计一个学籍管理系统&#xff0c;最开始学籍数据是空的&#xff0c;然后该系统能够支持下面的操作&#xff08;不超过条&#xff09;&#xff1a; 插入与修改&#xff0c;格式1 NAME SCORE&#xff1a;在系统中插入姓名为NAME(由字母和数字组成不超过20个字符的字符…

博客系统(SSM)

前端页面http://t.csdnimg.cn/zwKyG以上是之前写过的博客前端页面的内容&#xff0c;下面是通过SSM实现的后端内容。 目录 一.准备工作 1.1数据准备 1.2修改配置版本文件 1.3配置数据库 二.项目公共模块 2.1实体类 2.2操作数据库部分 三.功能开发 3.1博客列表 获取博…

【2024】使用Vuetifi搭建vue3+Ts项目,并使用tailwind.css

目录 使用Vuetifi搭建项目使用tailwind.css 只要跟着官方文档来就不会出错。 使用Vuetifi搭建项目 npm create vuetifyyarn create vuetifypnpm create vuetifybun create vuetify在终端运行一个就行&#xff0c;之后就可以选配置了。 使用tailwind.css 先运行&#xff1a; …

最大的单入口空闲区域

最大的单入口空闲区域 问题描述输入输出代码实现 问题描述 找到最大的单入口空闲区域。 空闲区域是由连通的’O’组成的区域&#xff0c;位于边界的’O’可以是入口&#xff0c; 单入口空闲区域即有且只有一个位于边界的’O’作为入口的由连通的’O’组成的区域。 如果两个元素…

救命!RPA隐藏的高效办公秘密居然被我发现了

在这个快节奏的社会&#xff0c;办公效率的重要性日益凸显。如何提升工作效率&#xff0c;成为了许多公司和个人思考的问题。RPA&#xff0c;即机器人流程自动化技术&#xff0c;正以其独特的优势&#xff0c;成为提升办公效率的重要工具。 RPA的核心是模拟人类在计算机系统中进…

哈希表|434.四数相加II

力扣题目链接 struct hashTable {int key;int val;UT_hash_handle hh; };int fourSumCount(int* A, int ASize, int* B, int BSize, int* C, int CSize, int* D, int DSize) {struct hashTable* hashtable NULL;for (int i 0; i < ASize; i) {for (int j 0; j < BSiz…

jdk1.8安装步骤及环境配置

jdk1.8安装步骤及环境配置 1.1&#xff1a;安装步骤 在Oracle官网下载jdk1.8&#xff0c;下载链接 &#xff0c;如果之前没有注册过还需要注册。下载好之后会得到如下的图标&#xff0c; 双击下载好的exe文件&#xff0c;选择更改安装路径&#xff1a;D:\Java\jdk1.8&#xf…

Facebook商城号为什么被封?如何防封?

由于Facebook商城的高利润空间&#xff0c;越来越多的跨境电商商家注意到它的存在。Facebook作为全球最大、用户量最大的社媒平台&#xff0c;同时也孕育了一个巨大的商业生态&#xff0c;包括广告投放、商城交易等。依托背后的大流量&#xff0c;Facebook商城起号较快&#xf…

MyBatis3源码深度解析(九)MyBatis常用工具类(二)ScriptRunnerSqlRunner

文章目录 3.2 使用ScriptRunner执行脚本3.2.1 ScriptRunner工具类简介3.2.2 ScriptRunner工具类示例3.2.3 ScriptRunner工具类源码 3.3 使用SqlRunner操作数据库3.3.1 SqlRunner工具类简介3.3.2 SqlRunner工具类示例3.3.3 SqlRunner工具类源码解读 3.2 使用ScriptRunner执行脚本…

基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的火焰检测系统(Python+PySide6界面+训练代码)

摘要&#xff1a;本研究详述了一种采用深度学习技术的火焰检测系统&#xff0c;该系统集成了最新的YOLOv8算法&#xff0c;并与YOLOv7、YOLOv6、YOLOv5等早期算法进行了性能评估对比。该系统能够在各种媒介——包括图像、视频文件、实时视频流及批量文件中——准确地识别火焰目…

RESTful API学习

RESTful API REST&#xff08;英文&#xff1a;Representational State Transfer&#xff0c;简称REST&#xff0c;直译过来表现层状态转换&#xff09;是一种软件架构风格、设计风格&#xff0c;而不是标准&#xff0c;只是提供了一组设计原则和约束条件。它主要用于客户端和…

大数据开发-Hive介绍以及安装配置

文章目录 数据库和数据仓库的区别Hive安装配置Hive使用方式Hive日志配置 数据库和数据仓库的区别 数据库&#xff1a;传统的关系型数据库主要应用在基本的事务处理&#xff0c;比如交易&#xff0c;支持增删改查数据仓库&#xff1a;主要做一些复杂的分析操作&#xff0c;侧重…

3.8_理解代码(3)

fliplr函数 其中fliplr函数为flip array left to right&#xff0c;此处fliplr(i)的输出结果为[4 3 2 1] 我的代码实验 area1 fill(i,u_up(i),cyan,FaceAlpha,0.3);把我都弄得无语了&#xff0c;就实现fill怎么这么难 真是不知道向量长度哪里不同&#xff0c;知道了哈哈 终于…

【并查集】一种简单而强大高效的数据结构

目录 一、并查集原理 二、并查集实现 三、并查集应用 1. LeetCode并查集相关OJ题 2. 并查集的其他应用及总结 一、并查集原理 并查集&#xff08;Disjoint Set&#xff09;是一种用来管理元素分组和查找元素所属组别的数据结构。它主要支持两种操作&#xff1a;查找&…

IntelliJ IDEA配置Tomcat

一、简介 概念&#xff1a;Tomcat是Apache 软件基金会一个核心项目&#xff0c;是一个开源免费的轻量级Wcb服分%&#xff0c;支持Servlet/JSP少量avaEE风范。 JavaEE: Java Enterprise Edition, Java企业版。指Java企业级开发的技术规范总和。包食13m技术规论&#xff1a;JDB…

22.1 分布式_线程池

线程池 1. 学习内容2. 简介2.1 池概念2.2 不使用线程池创建线程2.3 线程池的好处2.4 线程池应用场景****************************************************************************************************************1. 学习内容 2. 简介 2.1 池概念 <
最新文章