四、详解类型、变量与对象
类型
什么是强类型语言?
即对数据的约束很严格的编程语言
强类型和弱类型语言的比较
不能将高精度的数据,存放在低精度的类型中
不能将赋值的结果作为bool值
不能将其他类型的数据,简单的转换到bool类型中
以上的这几种不能通过的情况,在C和C++中都是可以通过的
所以C和C++都是比较弱类型语言
强类型语言的特点:保持数据的完整性、保持数据的准确性
JS的动态类型
完全算是弱类型语言
基本上数据类型不受约束
JavaScript中的 Var 和 C# 中的 Var 是不一样的
JS中的Var相当于任意数据类型,完全可以动态的变换
声明的变量,不受约束,可以装不同类型的数据。
弱类型语言,会更加的灵活。
强类型语言与弱类型语言可以说是各有优点的
C#对弱类型、动态类型的模仿
C#无论什么时候都是强类型的。
但是它为了使自己更加的灵活,对弱类型进行模仿,从而引入了一种机制。叫做dynamic
通过这种办法,我们获得了类似弱类型、动态类型
初学用不着这个dynamic,当与底层打交道的时候才能发挥它的作用。
类型在C#中的作用
变量的内存空间、值的范围
查看表格
float也是32位
程序的静态与动态
程序没有执行即为静态,执行起来即进入动态
编辑和编译代码都是静态的,Debug就是动态的
在编译的时候,编译器就可以拿着类型去检验代码(检验是否合法调用之类的),编译不通过就直接报错
查看基类,和自己的成员
后面只要拿到这个属性就可以通过反射技术访问这个属性的值
只要拿到这个方法,就可以动态的调用它
using System.Reflection; namespace DataTypeStudy { internal class Program { static void Main(string[] args) { Type myType = typeof(int); Console.WriteLine(myType.Name); //该类的全称 Console.WriteLine(myType.FullName); //基类的全称 Console.WriteLine(myType.BaseType.FullName); //获取全部属性 PropertyInfo[] pInfos = myType.GetProperties(); //获取所有方法 MethodInfo[] mInfo = myType.GetMethods(); //输出所有属性 foreach (var p in pInfos) { Console.WriteLine(p.Name); } foreach (var m in mInfo) { Console.WriteLine(m.Name); } } } }
此类所允许的操作
数据类型知道自己所允许做的操作
比如,整数除法做出来一定是整数,小数除法做出来一定是小数。
double a = 3.0 / 4.0;//此时计算出来一定是0.75 double b = 3 / 4;//此时计算出来则是0
这就代表着,数据类型是知道自己所允许做的操作的
程序运行时,查看变量在内存中的分配
Stack 栈
栈作用于函数调用
比较小只有1~2M
当函数调用太多就会爆栈
程序有错误,不小心在栈上分配了太多的内存,也会爆
爆栈,Stack overflow,栈溢出
无限递归爆栈
namespace StackOverflow { internal class Program { static void Main(string[] args) { BadGuy badGuy = new BadGuy(); badGuy.BadMethod(); } } class BadGuy { //无限递归调用,就会爆栈 public void BadMethod() { int x = 100; this.BadMethod(); } } }
切过多的内存也会爆掉
namespace StackOverflow { internal class Program { static void Main(string[] args) { unsafe { int * p = stackalloc int[9999999]; /*可以在不安全的模式下,使用指针 * stackalloc * 可以分配内存 * 切这么多内存会直接爆掉 */ } } } }
Heap 堆
用于存储对象
比较大,有几个G
堆不会爆掉,但是会造成浪费即,内存泄露
在C++中,创建对象,如果不去回收就会泄露,因此需要手动的回收
而C#中有垃圾回收机制,当没有使用该东西的时候,会自动回收
通过这个程序,在加上内存检测,就可以直观的看到堆的内存被占用,以及自动释放机制
using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace HeapSimple { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } //先消耗一些内存 List<Window> winList; private void Button1_Click(object sender, RoutedEventArgs e) { winList = new List<Window>(); for (int i = 0; i < 15000; i++) { Window w = new Window(); winList.Add(w); } /* * 直接生成15000个win对象 */ } private void Button2_Click(object sender, RoutedEventArgs e) { winList.Clear(); } } }
- 可以直观的看到内存被占用
C#语言的类型系统
蓝色的关键字,表示都是数据类型,并且是比较常用的数据类型,而且还是基本类型
枚举类型
什么时候用枚举类型?
用户从一个集合当中选择有效值的时候
例如窗口的状态,只有三种,默认,最大化,最小化。这就是枚举类型
可以对比着Java中的枚举类型来学习
C#语言的类型系统包括引用类型和值类型两种,其中引用类型包括,类、接口、委托,值类型包括,结构体和枚举。所有类型都以Object类型为自己的基类。
变量、对象与内存
变量名表示(对应着)变量的值在内存中的存储位置
short y = 200; int x; x = y; //这是可以通过的,可以用一个大空间,来存储规定空间小的值
参数变量
//其中的double a, double b 就是值参数 public double Add(double a, double b){ } //当前面加上ref 那么就是引用参数 public double Add(ref double a, double b){ } //当前面加上out 那么就是输出参数 public double Add(out double a, double b){ }
变量声明方式
有效的修饰符组合(可有可无) 类型 变量名 初始化器(可有可无)
public static int Amount = 1; /* public static 就是有效的修饰符组合 int 是类型 Amount 是变量名 = 1 就是初始化器 */
变量 = 以变量名所对应的内存地址为起点、以其数据类型所要求的存储空间为长度的一块内存区域
值类型的变量
值类型变量和引用类型变量在内存中的存储方式是不一样的
在内存中有一块区域是保留给操作系统使用的,当其他程序侵入这块内存,就会使操作系统崩溃
教程视频当中,变量在内存当中的存储方式这部分的讲解很重要
例如:byte 在内存中怎么存储的?
byte b;
b = 100;
//首先在内存中,找一块没有被使用的内存,然后找到该区域的内存编号。因为byte的长度是8个字节,并且无符号位
//所以当b被赋予100这个值的时候,就要把100转换成二进制1100100
//然后存入内存中
sbyte 有正负之分,所以其二进制的最高位是符号位,1表示负数,0表示正数
二进制存储负数,是将正数,按位取反,最后加1,就是负数
-128就是,128,的二进制形式,按位取反,最后加1,得到的,所以-128的二进制形式是,1000 0000
例子二:ushort
ushort us;
us = 1000;
//1000在二进制中表示,1111101000 有十位,因为是ushort一共有16位
//所以1111101000左边再补6个0
注意,是从内存编号大的开始存储的
值类型没有实例,或者说,所谓的“实例”是与变量合二为一的
int x = new int();//这样写虽然可以通过,但是我们一般都不这么写
//就是因为实例与变量合二为一了
引用类型的变量与实例
引用类型变量里面存储的数据是对象的内存地址
值类型是按照实际大小来分配内存的。
而引用类型分配内存则和值类型不一样。
系统会给引用类型分配4个字节的位置
例如:
namespace TypeSystem {
internal class Program {
static void Main(string[] args) {
Student st;//此时系统会分配4个字节给st
st = new Student();
/*
* 此时,会在堆中创建实例,
* 创建实例,它会根据这个类去计算,需要多少的空间
* 就这个例子中,ID需要4个字节,Score需要2个字节,于是会分配6个字节的位置
* 这个实例才是真正包含 ID 和 SCore 这两个字段的实体
* 创建了实例之后,会把这个在堆内存中的地址编号保存在st 中
* 图中,30000001编号会转换成二进制存在st中
* 所以我们可以用两个引用变量来引用同一个实例
* 因为这两个引用变量中都存的是同一个堆中的地址
*/
Student st2;
st2 = st;//这个过程就是把st存的那个地址给st2
}
}
class Student {
uint ID;
ushort Score;
}
}
一般值类型变量的默认值是0,引用变量的默认值则是null
本地变量如果不赋值,是不允许使用的。这点在C++中则可以使用。
const关键字表示不可改变,当变量被这个关键字修饰时,该变量则只能初始就定义不能先声明在赋值,并且值不可再改变。
装箱与拆箱
装箱
把栈中的数据,封装成堆上的实例
/*
* 装箱
* 当obj要引用的值不是堆上的实例,而是栈上的值类型的值的时候
* 此时会先将栈上面的这个值,封装到堆上的空白内存上的实例,
* 并且把这个堆上的地址返回给栈上的obj
* 所以从栈上往堆上搬东西,就叫做装箱,会损失一定的程序的性能
*/
int x = 100;
Object obj;
obj = x;
拆箱
把堆上的实例,拆成目标数据类型,存储在栈中
/*
* 装箱
* 当obj要引用的值不是堆上的实例,而是栈上的值类型的值的时候
* 此时会先将栈上面的这个值,封装到堆上的空白内存上的实例,
* 并且把这个堆上的地址返回给栈上的obj
* 所以从栈上往堆上搬东西,就叫做装箱,会损失一定的程序的性能
*/
int x = 100;
Object obj;
obj = x;
int y = (int)obj;
/*
* 把堆上面的数据拿到栈上
* 这个操作就叫做拆箱
* 把堆上面的数据直接复制到栈里面的y中
*
*/
装箱和拆箱都会损失,程序的性能