首先呢,关于值类型与引用类型在内存布局存储方面的不同之处:
对于内存,我们人为的将其分为了五大部分:
1、BSS段:通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。
2、数据段:通常是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。
3、代码段:通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。
4、堆(heap):堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。
5、栈 (stack):栈又称堆栈, 是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。
通常说的五大区为:
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放
4、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放
5、程序代码区—存放函数体的二进制代码。
而程序猿经常用到的有三个区:栈区,堆区、以及静态区
我们用 堆 栈 存储数据,用静态区存储静态数据
值类型的值存储在栈中,引用类型的值存储在堆中
值类型: int double char decimal bool enum struct
引用类型:string 数组 自定义类 集合 object interface
关于值类型的传递与引用类型的传递:对于值类型传递,其传递的是值本身。对于引用类型传递,其传递的是值在堆中的地址。但是对于string类型而言,每次创建新对象都会开辟一块新的内存,所以说,对于引用类型的传递不使用于string类型,即便string类型也是引用类型,这是因为字符串的不可变性
在数据传递中,有两个关键字需要注意一下:
关于 ref 关键字:ref
关键字通过引用(而非值)传递参数。 通过引用传递的效果是,对所调用方法中的参数进行的任何更改都反映在调用方法中。
不要混淆通过引用传递的概念与引用类型的概念。 这两种概念是不同的。 无论方法参数是值类型还是引用类型,均可由 ref修改。
当通过引用传递时,不会对值类型装箱。
若要使用 ref参数,方法定义和调用方法均必须显式使用 ref关键字 .
传递到 ref形参的实参必须先经过初始化,然后才能传递。 这与 out形参不同,在传递之前,不需要显式初始化该形参的实参。
如果对类的函数进行重载,那么两者不能仅仅是只在ref和out方面具有不同的签名,如果类型的两个成员之间的唯一区别在于其中一个具有 ref参数,而另一个具有 out参数,则会发生编译错误。但是,当一个方法有 ref或 out参数,另一个方法具有值参数时,则可以完成重载
对值类型使用ref关键字:
class RefExample
{
static void Method(ref int i)
{
i = i + 44;
}
static void Main()
{
int val = 1;
Method(ref val);
Console.WriteLine(val);
// Output: 45
}
}
对引用类型使用ref关键字:
class RefExample2
{
static void ChangeByReference(ref Product itemRef)
{
itemRef = new Product("Stapler", 99999);
itemRef.ItemID = 12345;
}
static void Main()
{
Product item = new Product("Fasteners", 54321);
System.Console.WriteLine("Original values in Main. Name: {0}, ID: {1}\n",
item.ItemName, item.ItemID);
ChangeByReference(ref item);
System.Console.WriteLine("Back in Main. Name: {0}, ID: {1}\n",
item.ItemName, item.ItemID);
}
}
class Product
{
public Product(string name, int newID)
{
ItemName = name;
ItemID = newID;
}
public string ItemName { get; set; }
public int ItemID { get; set; }
}
// Output:
//Original values in Main. Name: Fasteners, ID: 54321
//Back in Main. Name: Stapler, ID: 12345
其实,也就是说,ref是对参数的地址进行操作的,如果参数是值类型,那么就针对参数在栈上的地址(值类型的值是在栈中,但是其对应在栈中的位置是一个地址),如果是引用类型,那么就针对堆上的地址操作。
关于 out 关键字:out
关键字通过引用传递参数。 这与 ref 关键字相似,只不过 ref要求在传递之前初始化变量。
若要使用 out参数,方法定义和调用方法均必须显式使用 out关键字。
尽管作为 out参数传递的变量无需在传递之前初始化,调用方法仍要求在方法返回之前赋值
尽管 ref和 out关键字会导致不同的运行时行为,它们并不被视为编译时方法签名的一部分。 因此,如果唯一的不同是一个方法采用 ref参数,而另一个方法采用 out参数,则无法重载这两个方法。
class OutExample
{
static void Method(out int i)
{
i = 44;
}
static void Main()
{
int value;
Method(out value);
// value is now 44
}
}
也就是说,其实out和ref的用法是类似的,但是ref需要参数在传递之前初始化,而out不用初始化,但是out需要在函数执行过程中对参数进行赋值
关于装箱和拆箱:
什么是装箱:
装箱就是将值类型转换为引用类型
什么是拆箱:
拆箱就是将引用类型转换为值类型通常,
我们都是将值类型转换为object,object不止是所有类型的基类,它也是引用类型,比如:在ArrayList这个集合中,我们Add的时候,存入的就是object类型,也就是会进行装箱操作。
但是频繁的装箱和拆箱对性能会造成损耗,而且还会拖慢程序执行的时间
关于序列化与反序列化
什么是序列化?
序列化就是将对象转换为二进制
什么是反序列化?
反序列化就是将二进制转换为对象
作用:传输数据(在网络传输中,传送的都是二进制)
如何将对象转换为二进制?
首先你要将对象所属的类标记为[System.Serializalbe]
然后可以用BinaryFormatter类的对象,对其进行序列化
BinaryFormatter是属于System.IO命名空间下的一个针对二进制文件的类
比如,我们创建一个对象:BinaryFormatter bf = new BinaryFormatter();
然后,我们就可以调用 bf.Serialize()这个方法来进行序列化
还可以调用 bf.Deserialize()来进行反序列化,
不过在反序列的时候,该方法会返回一个object类型的对象,我们需要进行拆箱操作,将其强转成我们需要的类型对象。