堆和栈 、装箱与拆箱、引用类型和值类型的区别


C#中的堆栈和堆和栈 这些词汇的定义,详细说明

在 C# 中,堆栈和堆是两个不同的概念,它们用于在内存中存储和管理数据。下面是这些词汇的定义以及它们在 C# 中的使用:

堆栈(Stack):
堆栈是一种内存结构,它具有“先进后出”的特性。当您声明一个值类型变量(如 int、float、bool 等)时,该变量将存储在堆栈上。堆栈通常是在编译时分配的,并且在程序执行期间使用。当一个值类型变量超出其作用域时,堆栈将自动弹出该变量,释放它所占用的空间。此外,当您在方法中调用另一个方法时,您的当前方法的堆栈帧将被压入堆栈中,以便您可以返回到当前方法的执行点。

堆(Heap):
堆是一种内存结构,它用于存储引用类型数据。当您创建一个对象(如一个类、一个数组或一个字符串)时,该对象将存储在堆上。堆通常是在运行时分配的,并且在程序执行期间使用。当一个对象不再被引用时,它将成为垃圾,等待垃圾回收器来清理它所占用的堆空间。

栈(Call Stack):
栈是一种数据结构,它用于跟踪方法调用顺序。当您在一个方法中调用另一个方法时,该方法的返回地址和局部变量将被压入栈中。当另一个方法返回时,返回地址和局部变量将从栈中弹出。栈通常是在运行时分配的,并且在程序执行期间使用。如果您在递归函数中调用一个方法,栈可能会很快增长,导致堆栈溢出。

在 C# 中,堆栈和堆是两个不同的内存结构,它们用于存储和管理不同类型的数据。堆栈用于存储值类型数据和方法调用堆栈帧,而堆用于存储引用类型数据。栈是一种数据结构,用于跟踪方法调用顺序。当您在一个方法中调用另一个方法时,该方法的返回地址和局部变量将被压入栈中。堆和堆栈通常是在运行时分配的,并且在程序执行期间使用。

——↓ 引自《C# 7.0核心技术指南》——————————————————
3.3 object类型
object类型object类型(System.Object)是所有类型的最终基类。任何类型都可以向上转换为object类型。

为了说明这个类型的重要性。首先介绍通用栈。栈是一种遵循LIFO(Last-In First-Out,后进先出)的数据结构。栈有两种操作:将对象压入(push)栈,以及将对象从栈中弹出(pop)。以下是一个可以容纳10个对象的栈的简单实现:

        public class Stack
        {
          int position;
          object[] data = new object[10];
          public void Push (object obj)   { data[position++] = obj;  }
          public object Pop()               { return data[--position]; }
        }
    

由于Stack类操作的对象是object,所以可以实现Push或Pop任意类型的实例的操作。

        Stack stack = new Stack();
        stack.Push ("sausage");
        string s = (string) stack.Pop();    // Downcast, so explicit cast is needed

        Console.WriteLine (s);              // sausage

object是引用类型,承载了类的优点。尽管如此,int等值类型也可以和object类型相互转换并加入栈中。C#这种特性称为类型一致化。以下是一个例子:

        stack.Push (3);
        int three = (int) stack.Pop();

当值类型和object类型相互转换时,公共语言运行时(CLR)必须进行一些特定的工作来对接值类型和引用类型在语义上的差异。这个过程称为装箱(boxing)和拆箱(unboxing)。[插图] 3.9节我们会介绍如何改进Stack类,使之能更好地处理同类型元素。

——↑ 引自《C# 7.0核心技术指南》——————————————————


C#中的装箱与拆箱

C# 中的装箱和拆箱是用于将值类型转换为引用类型和将引用类型转换为值类型的过程。

以下是一个简单的示例程序,演示了如何在 C# 中进行装箱和拆箱:

using System;

class Program {
    static void Main(string[] args) {
        int i = 42;              // 值类型
        object obj = i;         // 装箱:将值类型转换为引用类型
        int j = (int)obj;       // 拆箱:将引用类型转换为值类型

        Console.WriteLine("i = {0}, obj = {1}, j = {2}", i, obj, j);
    }
}

在这个示例中,我们首先声明一个 int 类型的变量 i,并将其初始化为 42。这是一个值类型,因为它直接包含值本身,而不是引用到值的地址。

接下来,我们将 i 变量赋值给一个 object 类型的变量 obj。由于 object 是一个引用类型,所以这个过程被称为装箱:将值类型转换为引用类型。装箱操作将在堆上为值类型创建一个新的对象,并将对象的引用存储在 obj 变量中。

然后,我们将 obj 变量赋值给一个 int 类型的变量 j。由于 j 是一个值类型,所以这个过程被称为拆箱:将引用类型转换为值类型。拆箱操作将从堆上获取 obj 对象的值,并将其存储在 j 变量中。

最后,我们使用 Console.WriteLine 方法打印出 i、obj 和 j 变量的值。您将看到 i 和 j 的值都是 42,但 obj 的值是一个引用类型,指向在堆上创建的 int 对象。


装箱是将值类型转换为引用类型的过程,可以使用以下方法来记忆:

  1. 值类型是存储在栈上的,而引用类型是存储在堆上的。

  2. 装箱操作会创建一个新的对象,并将值类型的值复制到该对象中。

  3. 装箱操作的结果是一个引用类型,可以传递给接受引用类型的方法,但需要进行拆箱操作才能访问内部的值。

  4. 装箱操作需要进行类型转换,因为值类型和引用类型是不同的类型。

  5. 装箱操作是一个相对较慢的操作,因为它涉及到内存分配、复制和类型转换等多个步骤。

  6. 了解装箱操作的细节和性能影响可以帮助你更好地使用C#语言来开发高效的应用程序。

记住这些重点之后,就可以很方便地记忆C#中的装箱操作了。

装箱拆箱中的复制语义

装箱是把值类型的实例复制到新对象中,而拆箱是把对象的内容复制回值类型的实例中。下面的示例修改了i的值,但并不会改变它先前装箱时复制的值:

int i=3;
 object boxed=i;
i=5;
Console.WriteLine(boxed);

C#中数组类型是引用类型嘛?

在 C# 中,数组类型既是值类型也是引用类型,具体取决于如何声明和使用它们。

如果您使用 new 关键字创建一个数组,那么它将是一个引用类型。例如:

int[] numbers = new int[5];
在这个例子中,numbers 数组是一个引用类型,因为它是使用 new 关键字创建的。这意味着,numbers 变量本身只是一个指向数组对象的引用,而不是数组本身。当您将数组传递给方法或赋值给另一个变量时,您实际上是传递或赋值对数组对象的引用。

另一方面,如果您声明一个数组变量并使用初始化列表来初始化它,那么它将是一个值类型。例如:

int[] numbers = { 1, 2, 3, 4, 5 };
在这个例子中,numbers 数组是一个值类型,因为它是通过初始化列表直接初始化的。这意味着,numbers 变量本身将包含数组的值,而不是对数组对象的引用。

总之,在 C# 中,数组类型既可以是值类型也可以是引用类型,具体取决于如何声明和使用它们。如果使用 new 关键字创建数组,则该数组是引用类型;如果使用初始化列表来初始化数组,则该数组是值类型。


  int val=12;
Console.WriteLine(Convert.ToString(val,2));
Console.WriteLine("Hello,World");

视频:034-引用类型 B站:IT萌叔Jack

什么是引用

在 C# 中,引用是指对某个对象的内存地址的引用。每个对象都存储在内存中的某个位置,并具有唯一的内存地址。引用是指向该位置的指针,它们允许代码访问和操作存储在该位置的对象。

可以将引用看作是对象在堆上的内存地址的别名。通过引用,可以在不复制整个对象的情况下访问和操作对象。这使得在程序中传递和操作大型、复杂对象(如图形、视频、音频等)更加高效。

在 C# 中,引用类型是使用引用来传递的。当您传递一个引用类型的变量时,实际上传递的是指向该变量对象的引用,而不是变量本身的副本。这意味着,如果您在方法中修改该变量的值,那么该变量的原始值也将被修改。

引用类型的变量在定义时必须使用关键字"ref"或"out"来明确说明它是一个引用类型。引用类型支持空引用,因此可以将它们初始化为null。

引用在C#中是一个非常重要的概念,尤其在面向对象编程中。学好引用将有助于你更好地理解对象的访问、内存管理、方法调用等概念。

例如,以下代码示例演示了如何创建一个引用类型的对象,并将其传递给方法:

class Program {
    static void Main(string[] args) {
        MyClass obj = new MyClass();
        obj.Value = 42;
        PrintValue(obj);
    }

    static void PrintValue(MyClass obj) {
        Console.WriteLine("The value is: " + obj.Value);
    }
}

class MyClass {
    public int Value;
}

在这个示例中,我们创建了一个 MyClass 类型的对象,并将其存储在名为 obj 的变量中。我们设置 obj 的 Value 属性为 42,然后将 obj 对象传递给 PrintValue 方法。在 PrintValue 方法中,我们将 obj 的 Value 属性打印到控制台。

注意,我们在 PrintValue 方法中传递了 MyClass 类型的参数,而不是该对象的副本。因此,当我们在 PrintValue 方法中访问 obj 的 Value 属性时,我们实际上是访问在 Main 方法中创建的 obj 对象的值。

总之,引用是指对某个对象的内存地址的引用,在 C# 中用于访问和操作存储在该位置的对象。



下面是一个使用ref关键字定义引用类型变量的例子:

public void Increase(ref int value)
{
    value++;
}

在上面的函数中,参数value是一个引用类型变量,使用了ref关键字来明确说明它是一个引用类型。这意味着当该函数被调用时,传递给value的是原变量的地址,函数可以通过传递的地址来直接修改原始变量的值。
下面是一个创建一个引用类型的对象的例子:

List<string> names = new List<string>();

在上面的代码中,names是一个引用类型的变量,它指向了一个List类型的对象,这个对象在内存中分配了堆空间,并存储了一个字符串列表。

区别在于,引用类型的变量本身只是一个指向对象的指针,不存储对象的实际内容,;而引用类型的对象是实际占用内存空间并存储了数据内容。引用类型的变量可以引用不同的对象,但引用类型的对象本身只能被分配一次。

另外需要注意的是,对于值类型,变量存储的是值本身,而不是指向一个值的指针。

C#中值类型和引用类型有什么区别

在 C# 中,变量可以是值类型或引用类型。它们之间的主要区别在于它们在内存中的存储方式和生命周期。

值类型:

值类型变量存储其值本身,而不是引用到值的地址。这些变量通常在堆栈上分配,因此在创建时需要为它们分配一定的内存空间。
值类型的数据类型包括整数类型(如int,long,byte),浮点数类型(如float,double),布尔类型(如bool),枚举类型和结构体类型等。

引用类型:

引用类型变量存储对实际值的引用,而不是值本身。这些变量通常在堆上分配,因此在创建时需要为它们分配一定的内存空间。
引用类型的数据类型包括类类型(如Object,String,Array)和接口类型(如IEnumerable,IQueryable)数组类型、委托类型和字符串类型等。

下面我们详细了解一下内存和堆栈在值类型和引用类型中的使用:

值类型:

当我们创建一个值类型变量时,该变量将直接存储在堆栈上。这意味着,当该变量超出其作用域时,它的内存空间将被自动释放。例如,在以下代码中,整数变量 i 是一个值类型变量,它被分配在堆栈上:

int i = 42;
当超出作用域时,i 变量将被自动销毁,释放其占用的堆栈空间。

引用类型:

与值类型不同,引用类型变量存储在堆上。当我们创建一个引用类型变量时,实际的值将存储在堆上,而变量本身仅存储对值的引用。例如,在以下代码中,字符串变量 s 是一个引用类型变量,它被分配在堆上:

string s = "Hello, World!";
在这个例子中,字符串 "Hello, World!" 是一个引用类型的对象,它被存储在堆上。变量 s 仅存储对该对象的引用。当 s 变量超出作用域时,s 变量本身将被销毁,但实际的字符串对象仍将存在于堆上,直到垃圾收集器将其清理。

因此,在 C# 中,值类型和引用类型的内存分配方式不同。在创建值类型变量时,该变量的实际值被存储在堆栈上。在创建引用类型变量时,实际值被存储在堆上,而变量本身仅存储对值的引用。在释放变量时,对内存的处理也不同:值类型变量在超出作用域时会自动销毁,而引用类型变量在超出作用域时仅销毁对值的

需要注意的是,结构体(struct)也是值类型。但是,结构体与其他值类型不同,因为它们可以包含方法和属性,并且它们被传递给方法或函数时,它们的行为类似于引用类型。

posted @ 2023-03-28 02:55  专心Coding的程侠  阅读(84)  评论(0编辑  收藏  举报