第一天:C# 值类型、引用类型、装箱与拆箱

C#学习笔记(基础知识回顾)之值类型与引用类型转换(装箱和拆箱),

一:值类型和引用类型的含义参考前一篇文章

  C#学习笔记(基础知识回顾)之值类型和引用类型

  1.1,C#数据类型分为在栈上分配内存的值类型和在托管堆上分配内存的引用类型。如果int只不过是栈上的一个4字节的值,该如何在它上面调用方法?

C#中类型分为两类:

一:C#把数据类型分为值类型和引用类型

一:值类型和引用类型的含义参考前一篇文章

  C#学习笔记(基础知识回顾)之值类型和引用类型

  1.1,C#数据类型分为在栈上分配内存的值类型和在托管堆上分配内存的引用类型。如果int只不过是栈上的一个4字节的值,该如何在它上面调用方法?

二:值类型转换为引用类型——装箱

  2.1CLR对值类型进行装箱时:新分配托管堆内存,将值类型的实例字段拷贝到新分配的内存中,返回托管堆中新分配对象的地址。这个地址就是一个指向对象的引用。

int i = 10;
Object obj = i;

图片 1

  • 值类型(Value Type)
  • 引用类型(Reference Type)

  1.1:从概念上来看,其区别是值类型直接存储值,而引用类型存储对值的引用。

二:值类型转换为引用类型——装箱

  2.1CLR对值类型进行装箱时:新分配托管堆内存,将值类型的实例字段拷贝到新分配的内存中,返回托管堆中新分配对象的地址。这个地址就是一个指向对象的引用。

int i = 10;
Object obj = i;

图片 2

三:将引用类型转换为值类型——拆箱

  3.1只能对以前装箱的变形进行拆箱,拆箱是将对象转换为原来的类型

int i = 10;
Object obj = i;
int j = (int)obj;

图片 3

值类型和引用类型是以它们在计算机内存中是如何被分配的来划分的。

  1.2:这两种类型在内存的不同地方,值类型存储在堆栈中,而引用类型存储在托管对上。存储位置的不同会有不同的影响。

三:将引用类型转换为值类型——拆箱

  3.1只能对以前装箱的变形进行拆箱,拆箱是将对象转换为原来的类型

int i = 10;
Object obj = i;
int j = (int)obj;

图片 4

四:为什么需要装箱拆箱?

   4.1一种最普通的场景是,调用一个含类型为Object的参数的方法,该Object可支持任意为型,以便通用。当你需要将一个值类型传入时,需要装箱。例如:AddOne接收一个Object类型参数,如果是int32类型则数值加1,如果是string类型则加字符串“1”。

static void Main(string[] args)
        {
            int i = 10;
            string str = "10";
            Console.WriteLine(AddOne(i));//输出11
            Console.WriteLine(AddOne(str));//输出101
            Console.ReadKey();
        }
        public static string AddOne(Object o)
        {
            if (o.GetType() == typeof (Int32))
            {
                return ((int) o + 1).ToString();
            }
            else if(o.GetType()==typeof(String))
            {
                return o+ "1";

            }
            else
            {
                return "1";
            }
        }

  4.2另一种用法是,一个非泛型的容器,同样是为了保证通用,而将元素类型定义为Object。于是,要将值类型数据加入容器时,需要装箱。例如:

var array = new ArrayList();
            array.Add(1);
            array.Add("2");

            foreach (var value in array)
            {
                Console.WriteLine("value is {0}", value);
            }
            //结果输出是:value is 1
            //            value is 2
            Console.ReadKey();

值类型包括结构和枚举,值类型又包含一种特殊的值类型,称为简单类型,如:int
byte

例如int类型是值类型:int x,y;
x=10;y=x;y=20;前三个语句会在内存的两个地方存储值10。改变y的值不会影响x。

四:为什么需要装箱拆箱?

   4.1一种最普通的场景是,调用一个含类型为Object的参数的方法,该Object可支持任意为型,以便通用。当你需要将一个值类型传入时,需要装箱。例如:AddOne接收一个Object类型参数,如果是int32类型则数值加1,如果是string类型则加字符串“1”。

static void Main(string[] args)
        {
            int i = 10;
            string str = "10";
            Console.WriteLine(AddOne(i));//输出11
            Console.WriteLine(AddOne(str));//输出101
            Console.ReadKey();
        }
        public static string AddOne(Object o)
        {
            if (o.GetType() == typeof (Int32))
            {
                return ((int) o + 1).ToString();
            }
            else if(o.GetType()==typeof(String))
            {
                return o+ "1";

            }
            else
            {
                return "1";
            }
        }

  4.2另一种用法是,一个非泛型的容器,同样是为了保证通用,而将元素类型定义为Object。于是,要将值类型数据加入容器时,需要装箱。例如:

var array = new ArrayList();
            array.Add(1);
            array.Add("2");

            foreach (var value in array)
            {
                Console.WriteLine("value is {0}", value);
            }
            //结果输出是:value is 1
            //            value is 2
            Console.ReadKey();

五:装箱拆箱的性能影响

从原理上可以看出,装箱时,生成的是全新的引用对象,这会有时间损耗,也就是造成效率降低。 
所以,应该尽量避免装箱。 

比如4.1的情况可以通过方法重载避免,4.2尽量使用泛型规避装箱拆箱操作。

 

所有值类型都隐式(在C#代码中,无法看到继承关系,但通过MSIL代码才可以看到)继承自System.ValueType,而System.ValueType和引用类型都继承自System.Object基类

图片 5

五:装箱拆箱的性能影响

从原理上可以看出,装箱时,生成的是全新的引用对象,这会有时间损耗,也就是造成效率降低。 
所以,应该尽量避免装箱。 

比如4.1的情况可以通过方法重载避免,4.2尽量使用泛型规避装箱拆箱操作。

 

一:值类型和引用类型的含义参考前一篇文章 C#学习笔记(基础知…

Tips:C#不支持多重继承,结构 已经隐式继承至System.ValueType,所以
结构 不支持继承。

图片 6

什么是堆和栈?

 

堆和栈的概念:

  • 栈(Stack)是一种后进先出的数据结构,在内存中,变量会被分配在栈上来进行操作
  • 堆(heap)是用于为引用类型的实例(对象),分配空间的内存区域,在堆上创建一个对象,会将对象的地址传给栈上的变量(反过来叫变量指向此对象,或者变量引用此对象)—–也就是栈上的变量指向了堆上地址为XXX的实例(对象)

如果已经定义了一个类Test,Test是一个引用类型,有一个int类型的成员变量value,执行完如下代码后,只有一个Test对象。x和y都指向包含该对象的内存位置,申明这两个对象只保留一个引用而不会实例化给定类型的对象。创建对象必须使用new关键字。由于x和y引用同一个对象,所以对y的修改会影响x。因此结果为10和20

值类型

public struct ValPoint{
    public int x;  // 该值类型中的字段
}
ValPoint vPoint1; // ValPoint:结构值类型,vPoint1:变量,此时并没有被压到栈上
vPoint1.x = 10;  // 进行入栈操作
Console.WriteLine(vPoint1.x);   //进行出栈操作

变量包含了值类型中所有字段,该变量vPoint1 被分配在线程堆栈(Thread
Stack)上。

Tips:只有在对变量操作时,变量才会进行入栈。对变量的操作,实际上是一系列的出栈和入栈操作。

Test x, y;
x=new Test();
x.Value = 10;
y = x;
Console.WriteLine("X的值为:"+x.Value);
y.Value = 20;
Console.WriteLine("X的值为:" + x.Value);
Console.ReadKey();

结构特点1:

定义的结构内所有字段都必须初始化赋值,否则会报出“使用了可能为赋值的字段x”.因为这是
.Net 的一个约束,所有的元素使用前都必须初始化,如:

int i;
Console.WriteLine(i);  // 未初始化变量,使用结构int中内部成员在使用前都必须对它赋值

图片 7

结构特点2:

调用结构内的方法前,需要对结构内所有字段进行赋值

// 修改ValPoint结构
public struct ValPoint{
    public int x;
    public void Block( ){ }
}
//下面代码将会编译错误
ValPoint vPoint1;
// vPoint1.x = 200;  // 在声明变量vPoint1后给结构中x变量赋值,才能编译通过
vPoint1.Block();  //使用了为赋值的局部变量vPoint1
Console.WriteLine(vPoint);    //使用了为赋值的局部变量vPoint1

Tips:如果结构中有多个字段,则都需要为多个字段进行赋值

Q:上面例子中,结构内方法都没有使用字段x,为什么还要进行初始化赋值?这样子后如果结构中有若干个字段,初始化赋值岂不麻烦?
A:编译器隐式地为结构类型创建无参的够着函数,在这个构造函数中会对结构成员进行初始化,所有值类型被赋予0或相当于0的值,引用类型被赋予为null值(因此,Struct类型不可自行声明无参数的构造函数),所以通过隐式声明的构造函数去创建一个ValPoint类型变量:

ValPoint vPoint1 = new ValPoint();
Console.WriteLine(vPoint1.x);   // 输出为 0

图片 8

引用类型

声明一个引用类型变量,并使用new操作符创建引用类型实例的时候,该引用类型的变量会被分配到线程栈上,该变量只保存了位于堆上的引用类型的实例的内存地址,变量本身不包含任何类型的数据。

仅仅定义了变量,没有使用new操作符的话,由于没有在堆上创建类型的实例,因此,该变量值为null,不指向任何对象(堆上对象的实例)

Tips:容易混淆的:变量(Variable)、对象(Object)、实例(Instance)。变量可以是值类型,也可以是引用类型,如果是引用类型的话,由于本身只包含对实例对象的引用(内存地址),因此也叫对象引用;而在堆上创建的对象,称为对象的实例。

public class RefPoint{
    public int x;

    public RefPoint(int x){this.x = x;}
    public RefPoint(){}
}

仅写下面一条声明语句时:

RefPoint rPoint1;  // 在线程栈上创建一个不包含任何数据,也不指向任何对象(不包含内存地址)的变量

当使用new操作符时:

rPoint1 = new RefPoint(1);
  1. 在应用程序堆上创建一个引用类型对象的实例,并为它分配内存地址
  2. 自动传递该实例的引用给构造函数(正因如此,在构造函数中才能使用this来访问这个实例)
  3. 调用该类型的构造函数
  4. 返回该实例的引用内存地址,复制给 rPoint1 变量,该rPoint
    引用对象保存的数据是指向在堆上创建改类型的实例地址。

二:CTS类型

简单类型

想比较两个int类型是否相等,通常会:

int i = 3;
int j = 3;
if(i == j){ Console.WriteLine("i equals to j");}

但是,对于自定义的引用类型,如结构,就不能使用
“==”来判断它们是否相等,而需要 Equals() 方法来完成。

string 类型是引用类型,如果:

string a = "123";
string b = "123";
if(a == b){Console.WirteLine("a equals to b");}

其比较的是他们是否指向堆上同一个对象。上面显然不同,但他们所包含的数值相同,所以,对string类型用
‘==’比较,实际上是比较引用类型内包含的值,而不是其引用。

  2.1C#认可的基本预定义类型没有内置于C#语言中,而是内置于.NETFramework中,例如申明一个int类型的数据时,实际上是System.Int32的一个实例,其意义在于可以把所有的基本数据类型看成支持某些方法的类。例如把int
i转换为string,可以使用string s = i.ToString();

装箱和拆箱

  • 装箱: 值类型 -> 等价的引用类型

int i = 1;
Object boxed = i;
Console.WriteLine("Boxed Point:" + boxed);
  1. 在堆上为新的对象实例分配内存。该对象实例包含数据,但没有名称;
  2. 在栈上值类型变量的值复制到堆上的对象中
  3. 将堆上创建的对象的地址返回给引用类型变量
  • 拆箱:将一个已装箱的引用类型 -> 值类型

int i = 5;
Object boxed = i;
j = (int) boxed;
Console.WriteLine("UnBoxed Point : " + j);
  1. 获取已装箱的对象的地址
  2. 将值从堆上的对象中复制到堆栈上的值变量中

可见,装箱与拆箱需要反复在堆上进行操作,所以,在程序中尽量避免无意义的装箱与拆箱

  引用类型和值类型都继承自System.Object类。不同的是,几乎所有的引用类型都直接从System.Object继承,而值类型则继承其子类,即直接继承System.ValueType。

  预定义的值类型包括整形、浮点类型,decimal类型,bool类型,字符类型等。预定义的引用类型
根类型Object类型,string类型。

  2.2
string类型,string类型是引用类型,但是与引用类型常见的行为有一些区别。字符串是不可改变的,修改其中一个字符串就会创建一个全新的string对象,而另一个字符串不发生变化。string类型的很多操作反而看起来和值类型相似,例如:

图片 9

       

图片 10

          

 三:关于值类型和引用类型判断是否相等。

  3.1 当比较两个值类型时,进行的是内容比较;而比较两个引用类型时,进行的是引用比较。示例如下:

图片 11

图片 12

  3.2 string
是一个例外,string比较可以直接使用==或Equal(),CLR对string类型的比较会比较其值而不是引用。这点看起来更像是值类型的特性,而实际上是重载了运算符
“==” 和方法Equals ,系统解析的时候会直接比较String 的内容。

 图片 13

 关于值类型和引用类型的值传递和引用传递可看下一篇:

C#学习笔记(基础知识回顾)之值传递和引用传递

相关文章