C#详解类型,变量与对象
本节内容:
1.什么是类型(Type)
2.类型在C#语言中的作用
3.C#语言的类型系统
4.变量、对象与内存
1.什么是类型(type)
类型又名数据类型(Date Type),是数据在内存中存储时的“型号”,小内存容纳大数据会丢失精确度、发生错误大内存存纳小尺寸数据会导致浪费,编程语言的数据类型与数据的数据类型不完全相同
1.2.强类型语言(保证了数据的完整性)(数据受到数据类型的约束严格)与弱类型语言(灵活性与危险性并存)(保证数据的正确性)的比较
*C语言中这里的if里面得的值不为0其他都为真,即使不是bool类型也可通过编译,故为弱类型;
*相对的C#中if条件里面必须为bool类型的值,否则编译不过去。
C语言举例-if条件;JavaScript示例:动态类型(极弱类型语言);C#语言对弱类型?/动态类型的模拟
2.类型在C#语言中的作用
2.1.一个C#类型中所包含的信息有:
①存储此类型变量所需的内存空间大小;
②此类型值可表示的最大、最小值范围;
③此类型所包含的成员(如方法、属性、事件等);
④程序运行的时候,此类型的变量在分配内存的什么位置;
Stack简介:比较小,比较快,用于方法(函数)调用,(装多了会爆);
第一种情况为:算法没写好,函数调用过多,栈就爆了;
第二种情况:程序有错误,不小心在栈上分配了太多的内存,栈就爆了;
称为:Stack overflow。
Heap简介:即堆,比较大,用于存储对象(实例就放在堆里面),如果忘记回收对象会造成内存浪费,这种浪费学名叫做:内存泄漏。
使用Performance Monitor查看进程的堆(Heap)内存使用量;
关于内存泄漏:C#中存在垃圾回收机制,对象没人用了,有垃圾回收机制自动回收对象的内存。不用像java一样需要手动释放内存,换句话说C#较安全比C++安全得多。
⑤此类型所允许的操作符(运算)如: double result = 3 / 4;结果为0
而double result = 3.0 / 4.0;结果为0.75;
⑥静态的程序指的是没有在运行的程序。
程序的静态期(编辑器和编译器),动态期(运行期):静态的分析代码,动态的调试程序 。
可以这么说静态的程序是装在硬盘里的,动态的程序是装在内存里的。
双击一个应用程序(exe)就是一个把存储在硬盘里的静态程序动态加载到内存的过程。
Process(进程)概念介绍:一个程序从硬盘加载到内存开始执行之后,就形成一个进程,换句话说就是这个程序正在运行的实例。进程名与(exe)程序文件名字相同,且每个进程都有一个pid(Process ID)
Windows+R键可以打开运行窗口,输入perfmon(Performance Monitor)进入性能监视器,打开性能监视器窗口,该窗口默认监视总的进程,可以点“x”在“+”号中添加具体的进程,监视某一程序的内存占比变化。
3.C#语言的类型系统
①C#五大数据类型
类(Class):如Windows、Form、Console等
结构体(Structures):如Int32、Int64、Single、Double
如:在int中f12查看定义:public struct Int32 : IComparable
枚举(Enumerations):如HorizontalAlignment、Visbility
例如:public enum FormWindowState
{
//默认大小的窗口。
Normal = 0,
//最小化的窗口。
Minimized = 1,
//最大化的窗口。
Maximized = 2,
}
接口(Interfaces)
委托(Delegates)
②C#类型的派生谱系:
Object:引用类型(Reference Type)和值类型(Value Type)
引用类型:类、接口、委托
值类型:结构体、枚举
最右边的分类上下来看可以分为三组:第一组(与引用类型相关):object、string(这两个关键词基类(class)为本身)与class 、interface、delegate(这三个关键字不是具体的数据类型f12找不到定义的,而是运用这三个关键词去创建他们的数据类型);
第二组(与值类型相关):横线以上:对应的是真正数据类型;横线以下与第一组的横线以下的三个一样,都是去定义各自的类型,没有基类型。
第三组:true和false为bool类型的返回值;void表示不需要返回值,null表示一个引用变量里面的值是空的不属于任何数据类型;var与dynamic是用来声明变量的(以后会用到)。
(蓝色的字体表示为现成的数据类型,而且十分常用,甚至被归为关键字了;其次这些数据类型都是基本数据类型(即别的类型都是拿这些数据类型去构成的))
4.变量、对象与内存
*值类型与引用类型的内存存储方式是不同的,不分清将会造成很大错误!
①什么是变量
表面上来看(从C#代码的上下文行文来看),变量的用途是存储数据。
实际上,变量表示了存储位置(例如int x=100;x只是一个标签指向一个地址,数据100就存储在这个地址里面),并且每一个变量都有一个存储类型,以决定什么类型的值能够存入变量。*即变量名称表示(对应着)变量的值在内存中的存储位置
*认识一个事物是由浅入深,由表及里,由现象到本质的过程。
变量一共有7种:
静态变量(static),实例变量(也叫非静态变量)(成员变量、字段),数组元素、值参数、引用参数、输出形参、局部变量。
狭义的变量指的是局部变量,因为其它种类的变量都有自己的约定名称。
简单地讲,局部变量就是方法体(函数体)里面声明的变量。
变量的声明:
有效的修饰符组合(可无) 类型 变量名 初始化器(可无)如:int x;
*总结:变量=以变量名所对应的内存地址为起点,以其数据类型所要求的存储空间为长度的一块内存区域
②值类型的变量:
以byte/sbyte/short/ushort为例:
值类型没有实例,所谓“实例”与变量合二为一(因为是结构体struct)。
值类型变量的存储如:byte b;b=100;这两条语句作用为:首先确定byte数据类型需要的存储空间大小,然后去栈内存中找可以使用的(空的)内存地址,找到内存地址后,就把变量名与该内存地址对应起来,再把100转化为2进制数据写进该地址内存中。
③引用类型的变量与实例:
引用类型变量与实例的关系:引用类型变量里存储的数据是对象(实例)的内存地址解释如下:存储引用类型变量与实例时如:
*创建Student类的引用类型变量:Student stu;创建后便去栈内存(因为其为局部变量故由栈分配内存)中寻找空闲的内存并分得4个字节的内存空间(凡是引用类型固定分配4字节)。
*然后创建Student类的实例:new Student();这个时候要去找空闲的堆内存,找 到之后开始分配内存,这时需要计算需要多大的内存(类中的各种数据占内存的总 和),分完之后还要在该内存块内部进行分组:这块内存是存储哪种数据类型的, 那块属于哪种数据类型...。
*最后将其赋值给引用类型变量(把气球给孩子):stu = new Student();此时在内 存中把创建类的实例时分配到的内存地址(内存地址是一个编号不是二进制数)转化为二进制数,并将该二进制数存储到创建Student类引用类型变量stu时分配到的4个字节的内存空间里面(按高低原则)。完成引用类型变量的存储。
这样就可以解释两个不同的引用类型变量来引用同一个实例的情况(两个小朋友分别用自己的两根绳子牵着同一个气球),如下:
{
Student stu1;
Student stu2;
stu1=new Student();
Stu2=stu1;
}
④变量的默认值(局部变量(main函数外如类里面的变量)一旦创建后在内存块中全部刷为0);注意本地变量不能没有初始值(main函数里的变量),例如:
所谓的本地变量不能没有初始值,指的是不能没有初始值就被其他方法调用,如main函数中的如下语句:
Int x;
Console.WriteLind(x);
由于本地变量x没有初始值就被Console类的WriteLine方法调用了故会报错。
⑤常量(值不可改变的变量)
⑦局部变量是在Stack(栈)上分配内存的(其他一般在堆中分配)
⑥装箱与拆箱(Boxing&Unboxing)
补充说明:object是类可以派生出:数值类型和引用类型,但本身是类而类属于引用类型,所以object属于引用类型:内存空间存储被引用对象的地址。其定义为:
public class Object
{
public Object();
}
可见object一般当做引用类型使用。
*装箱:当引用变量引用栈上的数据的时候就要进行装箱,先把被引用的数据(下面指int x)拷贝一份,封装成一个实例,放在堆上的空闲内存中。
例如 {
int x=100;//注意局部变量是在栈上分配内存的
object obj;
obj=x;
}
为了使“ obj=x;”成立:obj为引用型变量分配到的4个字节内存存的是地址,如果直接把数值型变量x内存里存的数据复制给obj的内存空间,则obj会把内存空间里的数据当做是地址,然后去引用该地址的数据,显然错误。
正确做法为:先在栈上开辟两块内存空间给int x和obj(局部变量)再去堆中创建int 类型x的内存空间并把内容复制到里面去,然后把该段内存首地址转化为2进制数在复制给在栈上obj的内存,这个过程使“obj=x;语句合法的内存操作”叫做装箱。
*即把栈上的值类型的数据封装成一个object类型的实例放在堆上叫做装箱
*拆箱:例如 {
int x=100;
object obj;
obj=x;
Int y=[int]obj;
}
为了使“ Int y=[int]obj;(在堆内把obj的数据取出来)”成立:先在栈内存中找到空闲的内存开辟一个4字节(因为y为int型)的存储空间存放数值类型变量y,然后将某块堆内存中存放的obj对象数据的按int32格式把数据(转换)复制到y变量的栈内存空间中。
*即把堆中obj实例里面的值按要求拆成目标数据类型存储到栈上y(目标数据类型)的内存空间中。
*装箱装地址,拆箱按要求拆出数据(如:int y=obj;则obj要按int32的格式拆出数据)
*装箱和拆箱会损失程序的性能,慎用。