国外乡村建设网站,什么是网络营销啊,著名设计师网站,黄骅市市长杂项
虚函数
CSDN - C虚函数详解 cnblog - C#中的虚函数virtual
常量池与new
在C#中#xff0c;string是不可变的#xff0c;这意味着对string对象的操作通常会返回一个新的string对象#xff0c;而不会修改原始的string对象。因此#xff0c;几乎所有涉及更改string内…杂项
虚函数
CSDN - C虚函数详解 cnblog - C#中的虚函数virtual
常量池与new
在C#中string是不可变的这意味着对string对象的操作通常会返回一个新的string对象而不会修改原始的string对象。因此几乎所有涉及更改string内容的方法都会返回一个新的string对象。 String s new String(xyz)在内存中产生了多少份字符串2个。
“xyz” 字符串的常量池中的字符串对象new出来的新字符串
这种方式创建的字符串对象不会被放入常量池中正确的操作是下面这样
string s xyz;在C#中常量池intern pool通常是被放置在堆中。而Substring这类操作均不会改变原来的字符串而是创建新的。
拆箱与装箱
拆箱Unboxing和装箱Boxing是与值类型Value Type和引用类型Reference Type之间的转换相关的两个概念。
装箱Boxing 装箱是指将值类型转换为引用类型的过程。在装箱中值类型的实例被封装在一个对象中并在堆上分配内存空间。例如将一个整数值装箱为 object 类型的实例或者将一个结构体实例装箱为 System.ValueType 类型的实例。 拆箱Unboxing 拆箱是指将引用类型转换为值类型的过程。在拆箱中封装在对象中的值类型实例被提取出来放入到一个新的值类型变量中。例如将一个装箱的整数对象拆箱为一个整数值或者将一个装箱的结构体对象拆箱为原始的结构体实例。
装箱和拆箱操作可能会引起性能开销因为它们涉及到数据的复制和内存分配。因此在编写高性能的代码时应该谨慎使用。
注1在装箱过程中值类型的数据会被复制到堆上新分配的内存空间中而引用会指向这个新分配的内存空间因此装箱后的引用指向的是堆上的对象。
注2当创建一个新的结构体时编译器会隐式地为它添加继承自 System.ValueType 的基类并在必要时自动实现一些与值类型相关的功能比如装箱、拆箱等。 List会发生拆装箱吗会Listobject就会发生
Listobject objectList new Listobject();
objectList.Add(20); // 添加一个整数值类型
objectList.Add(World); // 添加一个字符串引用类型可以使用is关键字或as关键字来检查Listobject中的某个元素的类型。从 Listobject 中取出元素时元素的类型会被视为 object 类型因此任何按值类型进行的操作都需要显式手动转换类型。
C#关键字
C# 中的 sealed 关键字类似于 Java 中的 final 关键字用于类防止继承或方法防止重写
readonly与const区别在于const是编译时常量而readonly是运行时常量
const关键字用于声明常量常量在声明时必须进行初始化并且一旦初始化后其值将无法更改。const变量在编译时会被直接替换为其值因此它们的值必须在编译时就能确定。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交互捡到某个物品时触发一个事件该事件将为玩家提供升级等效果或者触发了碰撞器能够打开门还有就是状态管理。
实际上物体的碰撞事件通常是通过委托来实现的如OnCollisionEnter、OnCollisionStay和OnCollisionExit等方法
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; };常见的对齐策略包括
最严格对齐原则Strictest Alignment按照结构体中任何成员的要求选择最严格的对齐方式。这意味着所有成员都按照自身的对齐要求进行对齐。平均对齐原则Average Alignment根据结构体中所有成员的对齐要求的平均值进行对齐。这种策略可能会导致一些成员需要额外的填充来满足对齐要求。特定对齐方式Specified Alignment有些编译器允许在结构体定义中指定对齐方式例如使用 #pragma pack 或者 __attribute__((packed))。在这种情况下结构体的对齐将根据指定的方式进行而不是根据成员的大小。默认对齐方式Default Alignment一些编译器有默认的对齐方式可能会在不指定特定对齐方式的情况下应用。这通常会是一个合理的默认值可以满足大多数情况下的性能和内存使用需求。
基本原则
知乎 - C/C中内存对齐问题的一些理解 CSDN - 计算结构体大小内存对齐原则struct、union、class
就原则来讲第二篇CSDN的博客是很详细的其实很多东西我在Cppreference上没查到
数据成员对齐规则结构体(struct)(或联合(union))的数据成员第一个数据成员存放在offset为0的地方以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员(只要该成员有子成员比如数组、结构体等)大小的整数倍开始(如int 在 64bit 目标平台下占用 4Byte则要从4的整数倍地址开始存储)结构体的总大小即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类的对齐虚函数与空类
按照结构体对齐原则class含有成员变量和成员函数计算大小的时候只与成员变量有关。与成员函数和静态成员无关即普通成员函数、静态成员函数、静态成员变量。对类的大小没有影响。虚函数对类的大小有影响因为虚表指针的影响。在32位系统占4个字节64位系统占8个字节。多个虚函数也只算一个的影响。
在 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#中类的实例化在内存中会被对齐。即使一个类没有任何成员它也会在内存中被对齐其大小通常是一个指针的大小因为每个类实例在CLRCommon Language Runtime中都会关联一个类型对象指针。
枚举的对齐
枚举类型的对齐与其底层类型一致在C一般是int但是C11可以指定为其他合法的整数类型如unsigned int、char、short用法如下
enum class MyEnum : underlying_type {VALUE1,VALUE2,VALUE3
};例子如下
enum DAY {MON 1, TUE, WED, THU, FRI, SAT, SUN
};
// C11新特性允许显式地指定枚举的底层类型
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#结构体内存对齐
CSDN - C#-StructLayoutAttribute(结构体布局)
在 C# 中结构体的布局方式可以通过 StructLayoutAttribute 特性来控制而 LayoutKind 枚举类型用于指定这种布局方式的具体类型。
Auto自动布局。编译器根据目标平台和类型成员的排列顺序来确定结构体的布局方式Sequential顺序布局。结构体的成员按照声明的顺序依次排列不考虑对齐和填充。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 - 详解内存对齐