【C#学习】07详解类型,变量与对象(下)值类型变量与引用类型变量存储机制详解

课件

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

C#的数据类型系统

1.数据类型构成数据类型系统,C#有五大数据类型,分别是:

引用类型:
(1)类
(2)接口
(3)委托
值类型:
(4)结构体
(5)枚举

2.初学者常用的三种数据类型

(1)“类”类型:用 Class 声明
(2)“结构体”类型:用 Struct 声明
如常见的:byte , sbyte , int , double , char , short 等
(3)“枚举类型”类型:用 Enum 声明
在这里插入图片描述

3.课件1中蓝色字体的含义

(1)都是真正的数据类型,且因太常用,C#将其吸收为关键字。
(2)都是最基础的数据类型,是构成其他数据类型的基本单元。

4.学会使用 “go to definition”

光标放在数据类型上右击鼠标,点击" go to definition "可看到定义其的源代码,从而清楚地知道它是属于那种数据类型。
在这里插入图片描述

变量

1.如何理解 “变量表示了存储位置,并且每个变量都有一个类型,以决定什么样的值能够存入变量”?

示例代码:

int x;
x = 100;

(1)变量表示了存储位置
也就是说:变量名表示(对应着)变量的值在内存中的存储位置。
在代码中,x就是一个标签,对应着内存中的一个地址,“100”这个值就存在这个地址中。

(2)每个变量都有一个类型,以决定什么什么样的值能够存入变量
也就是说:变量类型会告诉计算机系统:从这个位置开始,往后数多少个字节,这段内存用来保存该变量的值。
在代码中,int x ,声明了x为整数类型,即 int32(32表示4个字节的内存空间),表示只有int类型的值才能存入x所指示的地址中去。

2.变量的7种类型

变量分为7种类型,分别是:静态变量,实例变量,数组元素,值参数,引用参数,输出形参,局部变量。每种变量都有其约定俗成的其他名称,所以当我们说“变量”时,通常指的都是“局部变量”。

(1)静态变量:用 Static 声明
(2)实例变量(成员变量,字段)

未用 static 修饰符声明的字段称为实例变量 (instance variable)

(3)数组元素

a.声明方法:数据类型 [ ]
示例:

//声明一个长度为100(400个字节)的整形数组
int [] array = new int [100];

b.访问数据元素
访问第一个数:array[0]
访问最后一个数:array[99]

c.怎么理解数组元素也是变量
从array[0]——array[99],这100个整数,都是变量;每四个字节为一个隔间,每个隔间中可装一个整数的值。

(4)值参数,引用参数,输出形参

示例:

double Add (double a, double b);
{
	return a + b;
}
//double a,double b,就是值参数变量
//如果在 double a前用 ref 修饰:ref double a ,就是引用参数变量
//如果在 double a前用 out 修饰:out double a ,就是输出形参变量
(5)局部变量

在函数体中声明的变量称为局部变量

3.如何声明一个变量?

声明一个变量:int a ;就是告诉编译器:我需要一个整数类型的变量,一会儿我要用它存储一个值,你记住这是存在的。

声明变量的格式:
有效的修饰符组合(opt) 类型 变量名 初始化器(opt)
示例:

class Student
//声明一个静态变量
{
	public static int Amount  = 0;
}
//public static:有效的修饰符组合
//int:类型
//Amount:变量名
// = 0:初始化器

4.到底什么是变量?

变量 = 以变量名所对应的内存地址为起点,以其数据类型所要求的存储空间为长度的一块内存区域。

5.需要搞清楚的两个问题

(1)数据类型是怎样要求存储空间的?
(2)在这段存储空间中,是怎样来存储这个值的?

值类型变量在内存中是如何存储的?(以结构体变量为例)

1.需要明确的知识

(1)计算机内存中是以字节为基本单元进行存储,读取数据的,并且,计算机为每个字节都准备了唯一的编号,内存地址就是字节的编号
(2)计算机保留了一部分内存,只供给操作系统使用,其他程序不允许使用,剩余内存成为自由内存,如下图:橘色部分表示操作系统所占用的内存,剩余的空白部分就是自由内存。
在这里插入图片描述

2.例1:byte

byte b;
b = 100;

存储过程
(1)计算机在内存中寻找空余,找到了一个空余地址:编号10000015的字节,将其作为起始点。
(2)由于变量类型是byte,需一个字节(8Bit),于是计算机为其切出一个字节的空间。
(3)该变量的值为十进制的100,将其转化为二进制:110 0100,占不够8个字节从高位往下补0:0110 0100
(4)找到变量b所在的内存地址:编号10000015,将二进制数值写入。
在这里插入图片描述

3.例2:sbyte

sbyte sb;
sb = -100;
//前有"s"表示有符号位,最高位是符号位,0表示正,1表示负

负数的存储原则:按位取反再加一
+100:0 1 1 0 0 1 0 0
取反: 1 0 0 1 1 0 1 1
再加1:1 0 0 1 1 1 0 0 (从低位加1 ,进位)

4.例3:ushort

ushort us;
us = 1000;
//前有"u"表示无符号位

1000换算成二进制:1111 1010 00,ushort数据类型在内存中被分配到的内存空间为2个字节,不足位从高位往下补0
在这里插入图片描述

引用类型变量在内存中是如何存储的?(以类类型为例)

1.引用类型变量与引用类型实例之间的关系

引用类型变量,就相当于一个小孩儿
引用类型实例,就相当于一个气球
小孩儿牵着气球。
如果只创建了实例,而没有赋值给变量,那么该实例不久后就会被垃圾收集器回收(气球没有小孩儿牵着就飞了)

2.值类型变量与引用类型变量存储的区别

值类型按数据类型的实际大小(bit)分配内存,而引用类型则不是,引用类型变量存储的是实例(对象)的地址,由此达到引用变量对实例的引用。

3.代码示例说明:

class program
{
	static void Main(string[] args)
	{
		Student stu;      //创建一个引用类型的变量stu
		stu = new Student(); //将stu实例化
		Student stu2;
		stu2 = stu;
	}
}
class Student            //创建一个Student类
{	
	uint ID;         //创建实例变量 ID,Score(是值类型变量)
	ushort Score;
}

(1)计算机一看到待分配内存空间的 “stu” 是一个引用类型的变量,立刻就在找到的空余空间的起点位置为其切出4个字节的内存。
(2)在这4个字节的内存空间中,计算机将这个变量的32个比特位的值全部刷成0,表示该引用变量没有引用任何实例。(这个小孩没有牵气球)
在这里插入图片描述

(3)当变量“ stu “引用了实例后( stu = new Student();),在堆内存中就创建了Student这个类的实例,这个实例,才是真正包含实例字段ID,Score 的实体。

ID,uint,需4个字节
Score,ushort,需2个字节
于是给这个实例分配了6个字节,前四个属于ID,后两个属于Score。安全符合值类型变量分配内存的机制。

(4)在堆内存上分配了实例的空间,就会有这段堆内存所对应的地址,把这个地址保存在 “stu” 这个变量里。

————————————————————————————————————

如何理解 “两个不同的引用变量引用着同一个引用实例”?

创建引用类型变量 stu2
(5)计算机去寻找空余内存分配引用变量 stu2,找到后立刻为其切出4个字节
(6)将 stu 中的值copy 到 stu2 中,从而实现 stu ,stu2 共同引用一个实例

4.清晰的图片说明

在这里插入图片描述

本节课的一些其他知识点

1.局部变量在栈(stack)上分配内存

如以上的stu,stu2引用变量,由于是声明在Main方法中,所以也是局部变量,被分配在栈内存上

2.实例变量会随着类的实例在堆(Heap)上分配内存

3.变量的默认值

一旦变量在内存中分配好之后,把其内存块全部刷成0,就是默认值

4.常量

值不可改变的变量,用 “const” 声明,对于常量来说,初始化器不能省略。

const int x = 1;

5.装箱与拆箱

数据类型在内存中是如何起作用的?
代码示例

//装箱操作:
int x = 100;
object obj;
obj = x;
//拆箱操作:
int y = (int)obj;

" object " 实际上是引用类型
既然 obj = x,那是否应该把 x 内存中的值复制到 obj 变量中呢?
当然是不对的
如果这样做,就相当于 obj 这个引用类型变量所引用的实例处在内存位置为编号100的地方,而内存编号为100的区域归操作系统所用

什么是装箱?
obj 为引用类型变量,计算机会立刻为其切出4个字节的内存空间,当计算机发现obj所要引用的值不是堆上的实例,而是栈上的值类型的值时,它要做的就是把 栈上的这个值类型的值(100)copy,在堆上找一块可以存储的空间,把该空间变成一个对象,把值paste上去,并将这块内存空间的起点地址存储在obj 变量中,从而构成obj这个引用类型变量对堆上实例的引用。
所以所谓装箱,就是把栈上的值类型的值封装成一个object类型的实例放在堆上。

什么是拆箱?
把堆上object类型的实例里的值,按照要求拆成目标数据类型,并存储在栈上。

装箱,拆箱会损失程序性能,现在已不多用了。

一些需要知道的其他知识

1.关于静态成员和实例成员

静态成员(static member):又叫类成员,指的是在成员类型或返回值类型前用static关键字修饰的变量或方法,包括静态数据和静态方法。

实例成员(instance member):又称非静态成员、对象成员,是没有用static修饰的变量或方法,包括实例数据和实例方法。

不同点:表面看就是有没有被static修饰,其次是在内存分配上,类加载时,静态成员只只分配一次,有且只有一份存储空间。而对于实例成员,每产生一个实例都要重新分配一个存储空间。

静态成员的特点:

1)静态成员(包括静态数据和静态方法)必须由类名调用,不能使用对象调用(静态数据可以由实例方法调用)。

2)静态数据属于类的级别,当类加载时,一个静态数据在内存只分配一个存储空间,无论new出多少个实例,它也只是有那一个空间。

3)静态方法只能调用静态数据,不能调用对象。

实例成员的特点:

1)实例成员(包括实例数据和实例方法)必须通过对象来调用,不能使用类名调用。

2)类的实例数据属于类的实例级别,每新创建一个对象,都会在内存中为实例成员开辟一块新的存储空间。

3)实例方法可以调用实例数据和静态数据。

参考资料:

c# 静态成员 实例成员

发布了29 篇原创文章 · 获赞 3 · 访问量 956

猜你喜欢

转载自blog.csdn.net/weixin_44813932/article/details/103741488