什么是类型(Type)
又名数据类型(Data Type)
- A data type is a homogeneous collection of values,effectively presented,equipped with a set of operations which manipulate these values
- 是数据在内存中存储时的”型号”
- 小内存容纳大尺寸数据会丢失精确度、发生错误
- 大内存容纳小尺寸数据会导致浪费
- 编程语言的数据类型与数学中的数据类型不完全相同,如int x = 3/4;(结果为0),而在数学中为0.75
- 程序不运行时,存储在硬盘中,运行时,加载至内存中。打开软件,即将程序从硬盘加载至内存中,所以程序打开的快慢,与硬盘和内存都有关
强类型语言与弱类型语言的比较
C语言示例:if条件
int _tmain(int argc,_TCHAR* argv[])
{
int x = 100;
//以下可以编译通过,并打印It's OK!
//因为编译器会把x=200当成一个非0值,即为true
if(x = 200)
{
printf("It's OK!");
}
//以下编译会报错,需改成"200 == x"
if(200 = x)
{
printf("It's OK!");
}
return 0;
}
javascript语言示例:动态类型
<script>
function ButtonClicked(){
var myVar = 100;
myVar = "Mr.Okay!";
alert(myVar);
}
</script>
C#语言对弱类型/动态类型的模仿
static void Main(string[] args)
{
dynamic myVar = 100;
Console.WriteLine(myVar);
myVar = "Mr.Okay!";
Console.WriteLine(myVar);
}
类型在C#语言中的作用
- 一个C#类型中所包含的信息有
- 存储此类型变量所需的内存空间大小
- 此类型的值可表示的最大、最小值范围
- 数据大小关系:1GB(千兆)=1024MB,1MB(兆)=1024KB,1KB(千字节)=1024B,1B(字节)=8bit(比特位)
- 此类型所包含的成员(如方法、属性、事件等)
- 此类型由何基类派生而来。示例:利用反射动态地调用一个类型的属性和方法 ```csharp using System.Windows.Forms
namespace TypeSample { class Program { static void Main(string[] args) { Type myType = typeof(Form); Console.WriteLine(myType.BaseType.FullName);
PropertyInfo[] pInfos = myType.GetProperties();
MethodInfo[] mInfos = myType.GetMethods();
foreach(var p in pInfos)
{
Console.WriteLine(p.Name);
}
foreach(var m in mInfos)
{
Console.WriteLine(m.Name);
}
}
}
}
- 程序运行的时候,此类型的变量在分配在内存的什么位置
- stack(栈)简介:给方法调用使用,比较小(1-2MB,2MB=2*1024*1024=2,097,152byte)
- stack overflow:循环的递归会导致溢出,或在栈上切过大的内存,如下:
```csharp
namespace StackOverflow
{
class Program
{
static void Main(string[] args)
{
//循环的递归
BadGuy bg = new BadGuy();
bg.BadMethod();
//在栈上切过大的内存
//使用unsafe代码,需在build中允许不安全代码
unsafe
{
int* p = stackalloc int[9999999];
}
}
}
class BadGuy
{
public void BadMethod()
{
int x = 100;
this.BadMethod();
}
}
}
- Heap(堆)简介:存储对象,比较大(几个GB)
- 使用performance monitor查看进程的堆内存使用量,可以检查程序哪个操作占用较多内存
- 关于内存泄露(没有垃圾回收),C#有垃圾回收(GC)机制,C++需手动回收
- 此类型所允许的操作(运算),比如double x = 3/4;结果为0而非0.75,因为3和4都为int类型,除法运算后值为0,最后赋值给double类型的x变量
C#语言的类型系统
C#的五大数据类型
- 类(Classes):如Window,Form,Console,String
- 结构体(Structures):如Int32,Int64,Single,Double。(C#将常用的数据类型吸收为关键字了,int==Int32,long==Int64)
- 枚举(Enumerations):如HorizontalAlignment,Visibility。比如Form类型的引用变量,在表示窗口尺寸状态时,分为最大、最小、正常三种,这时就通过FormWindowState枚举类型来设置,如下:
- 接口(Interfaces)
- 委托(Delegates)
C#类型的派生谱系
变量、对象与内存
什么是变量
- 表面上来看(从C#代码的上下文行文上来看),变量的用途是存储数据
- 实际上,变量表示了存储位置,并且每个变量都有一个类型,以决定什么样的值能够存入变量
- 变量一共有7种
- 静态变量,实例变量(成员变量/字段),数组元素,值参数,引用参数,输出形参,局部变量
- 狭义的变量指局部变量,因为其他种类的变量都有自己的约定名称
- 简单地讲。局部变量就是方法体(函数体)里申明的变量
- 变量的申明
- 有效的修饰符组合opt +类型 +变量名 +初始化器opt
- 变量 = 以变量名所对应的内存地址为起点、以其数据类型所要求的存储空间为长度的一块内存区域
各变量在代码中的示例如下:
namespace TypeInCsharp { class Program { static void Main(string[] args) { Student.Amount = 3;//静态字段直接由类调用 Student stu = new Student(); stu.Age = 10;//字段由对象调用 int[] arr = new int[100];//100为数组元素 arr[0] = 10; int x;//x为局部变量,申明变量x } } class Student { public Static int Amount;//静态字段 public int Age;//字段 //a和b为值参数,如果a由ref修饰,则为引用参数。 //如果b由out修饰,则为输出形参 public double Add(double a,double b) { double resule = a + b;//resule为局部变量 return resule; } } }
值类型的变量
以byte/sbyte/short/ushort为例:
static void Main(string[] args) { //byte为8个比特位,一个字节,100的二进制为01100100 //由于一部分内存被系统和其他程序占用,内存在10000006地址开始有空闲,于是b的内存地址为10000006,值为01100100存在内存中 byte b = 100; //有符号值的存储,拿最高位作为符号,0为正,1为负。负数的二进制数为正数按位取反再加1 //-100的二进制为10011100,于是sb的内存地址为10000007,值为10011100存在内存中 sbyte sb = -100; //ushort为16个比特位,两个字节,1000的二进制为00000011 11101000 //us的内存地址为以100000008为开始地址,以100000009为结束地址,值为00000011 11101000存在内存中 ushort us = 1000; }
内存分析如下:
计算机内存最小单位为bit位,一个比特位只能存储一个二进制的数字(0或1),由于比特位存储能力有限,因此计算机科学将8个bit位组成为一个字节(byte),计算机内存以字节为基本单元进行存储数据和读取数据,而且计算机为每个字节都准备了唯一的编号,内存地址即为字节在计算机科学中的编号。
- 值类型没有实例,所谓的”实例”与变量合二为一
引用类型的变量与实例
引用类型变量与实例的关系:引用类型变量里存储的数据是对象的内存地址
namespace TypeInCsharp { class Program { static void Main(string[] args) { //引用类型变量stu,在栈内存中直接分4个字节大小的内存 //stu的内存地址为以10000006为开始地址,以10000009为结束地址,存储的值为实例在堆内存中的地址值00000001 00110001 00101101 00000001 Student stu; //在堆内存中找到空闲内存,地址为20000001(二进制值为00000001 00110001 00101101 00000001),存储实例中的值,ID为uint类型,4个字节,Score为ushort类型,2个字节,一共需要6个字节的空间,初始都为0 stu = new Student(); //引用类型变量stu2,在栈内存中寻找4个字节的空闲内存 //stu2的内存地址为以10000015为开始地址,以10000018为结束地址,存储的值和stu存储的值相同 Student stu2; stu2=stu } } class Student { uint ID; ushort Score; } }
内存分析如下:
局部变量是在stack(栈)上分配内存,实例变量(字段)会随着实例在Hrap(堆)上分配内存
变量的默认值
- 实例变量(字段)有默认值,如上例为0
局部变量无默认值,为了避免不安全代码,必须赋初始值,否者会编译不通过
常量
值不可改变
- 第一次申明赋值后,不可再次赋值:const x = 1;后续不可x = 2对x值进行改变;
装箱与拆箱(Boxing/Unboxing)
装箱,如下示例:
static void Main(string[] args) { int x = 100;//值类型变量 object obj;//引用类型变量 obj = x;//装箱 }
对上述代码进行分析,obj=x,由于x指向的内存地址,对应内存存储的值为100的二进制数,即01100100。如果obj指向的内存地址,对应内存存储的值也为01100100(obj为引用类型变量,存储的值为实例在堆内存的地址),而这个地址01100100有可能为操作系统中的值,此时会报错。
所以如果引用类型所引用的值不是堆上的实例,而是一个栈上的值类型时,它会先把值类型的值复制,在堆上找一个空闲可存储的空间,将其变为一个对象,然后把对象的地址转化为二进制,存储到obj变量所对应的内存空间里。
以上过程为装箱,装箱需要从栈上往堆上搬东西,会损耗性能。拆箱,如下示例:
static void Main(string[] args) { int x = 100;//值类型变量 object obj;//引用类型变量 obj = x;//装箱 int y = int(obj);//拆箱 }
int y = int(obj),表示将obj在堆上存储的值搬到y指向的内存地址所对应栈内存上,拆箱需要从堆上往栈上搬东西,也会会损耗性能。