C#学习 - 类型、变量、方法

类型(Type)

又称为数据类型(Data Type)
数据类型是数据在内存中存储时的“型号”
小内存容纳大尺寸数据会丢失精确度、发生错误
大内存容纳小尺寸数据会导致浪费
编程语言的数据类型与数学中的数据类型不完全相同
编程时数据受到数据类型的约束 - 强类型编程语言
编程时数据受到的数据类型的约束并不严格或数据不受数据类型的约束 - 弱类型编程语言
强类型(C#):

int x;//一个32bit位变量
x = 100L;//存放64bit位的数据时出错
bool y;
y = 100;//bool类型的变量无法存放整型数据
if (x = 200)
{	//C#语言规定if后是一个bool运算,不能是赋值,这里会报错
	Console.WriteLine(x);
}

弱类型(C):

int x = 100;
if (x = 200)
{	//C语言中的bool只有0和非0,所以if后可以接赋值
	printf(x);
}

C#模仿弱类型:

dynamic x = 100;
Console.WriteLine(x);
x = "Hello,world!";
Console.WriteLine(x);
//dynamic是动态类型

类型所包含的信息

存储此类型变量所需要的内存空间大小
此类型的值所能表示的值的范围
此类型所包含的成员(如属性、方法、事件等)
此类型由何基类派生而来
程序运行时,此类型的变量分配在内存中的位置(会把内存分为栈区和堆区)
此类型所允许的操作(运算)

栈(Stack)

用于方法调用
栈区比较小,速度比较快
程序代码出错时,在栈上使用了过多的内存时,就会造成栈溢出(Stack overflow)

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            StackOverflow so = new StackOverflow();
            so.Method();
        }
    }
    class StackOverflow
    {
        public void Method()
        {
            this.Method();
            //一直在递归调用方法消耗栈区内存
        }
    }
}

上段代码会出现“Process is terminated due to StackOverflowException.”,就是StackOverflow错误

堆(Heap)

用来存储对象
堆区比较大
在堆区各处上分配对象内存后,之后没有回收内存时,就是造成了内存泄漏
C#中有垃圾收集器,发现内存没有使用时会自动回收

using System.Collections.Generic;
using System.Windows;

namespace WpfApp
{
    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 < 10000; i++)
            {
                Window w = new Window();
                winList.Add(w);
            }
        }
        private void Button2_Click(object sender, RoutedEventArgs e)
        { //第二个按钮点击后将 Window 实例变成“垃圾”
            //C# 中的垃圾收集器会在一个合适的时机将这些内存回收
            winList.Clear();
        }
    }
}

进程(Process)

一个程序从硬盘加载到内存中开始运行时就会有一个进程
Win + R 调出运行窗口,输入 perfmon 就能打开性能监视器,可以进行监视软件占用内存
上段代码中程序名为WpfApp,运行时进程名字也叫WpfApp
在性能监视器中选择监视工具 - 性能监视器 - Ctrl+N添加
上面选择Process - Private Bytes
下面选择WpfApp(进程名)
添加后在最下方选择只显示WpfApp,双击WpfApp,选择图表,更改垂直比例可以观察更多
点击第一个按钮就能消耗内存,红线上升;点第二个按钮,在一个合适的时候垃圾收集器就能回收内存呢,红线下降

类型系统

C#五大数据类型:类(Classes)、结构体(Structures)、枚举(Enumerations)、接口(Interfaces)、委托(Delegates)
其中类、接口、委托是引用类型(Reference Type);结构体和枚举是值类型(Value Type)
鼠标对准数据类型右键 - 转到定义,在数据类型前就写有其自身的类型
注:class、struc、enum、interface、delegate不是具体的数据类型,而是用来定义自己的数据类型

变量

变量是以变量名所对应的内存地址为起点、以其数据类型所要求的存储空间为长度的一块内存区域
变量名表示变量的值在内存中的存储位置,并且每个变量都有一个类型,用来决定存入变量的值的类型
变量分为静态变量、实例变量(成员变量、字段)、数组元素、值参数、引用参数、输出形参、局部变量
一般来说,变量只指局部变量
局部变量就是方法体里声明的变量

internal class Program
    {
        static void Main(string[] args)
        {
            Tool.Wrench = "Wrench";//静态变量
            
            Tool tool = new Tool();
            tool.ID = 1;//字段
            
            int[] arr = new int[10];
            //arr就是数组,类型后接[]就是声明数组,arr[0]是第一个元素

            int x = 10;//局部变量,在 Main(方法体)中声明的
        }
    }
    class Tool
    {
        public static string Wrench;
        public int ID;
        public double Add(double a,double b)//这里 a 和 b 就是值参数变量
        {   //在参数前用 ref 修饰就是引用参数变量;用 out 修饰就是输出参数变量
            return a + b;
        }
    }

变量的声明

int a;//这就是声明变量
//此处声明了一个整型变量

需要先声明变量才能使用
格式:有效的修饰符组合(可省略) + 变量类型 + 变量名 + 初始化器(可省略)

public static/*有效的修饰符组合*/ string/*变量类型*/ Wrench/*变量名*/ = "Wrench"/*初始化器*/;

值类型的变量存储

byte a = 100;
//byte类型占用8bit,就是占用一个字节
//100的二进制是 110 0100,只有7位,少的一位用0补齐,就是 0110 0100
sbyte b = 100;
//sbyte占用8个bit,但最高位为符号位(0为正数,1为负数),就只有7位进行存储
//所以存储后是 0110 0100
//如果是 -100,就先以100按位取反再+1,0110 0100 -> 1001 1011 +1 = 1001 1100

其余都类似
值类型没有实例,所谓的“实例”与变量合而为一

引用类型的变量与实例的关系

internal class Program
    {
        static void Main(string[] args)
        {
            Human human;
            human = new Human();
           	//创建一个Human的实例,再将地址保存在变量human中
           	Human human2;
           	human2 = human
        }
    }
    class Human
    {
        uint ID;
        byte Age;
    }

上段代码中Human的实例 一共有 32+8bit空间,就是一个 ulong 和一个 byte 的空间,然后将存入内存中的第一个字节的地址转换为二进制,这个二进制存入变量human中,比如:
实例存入内存中的3000 0001到3000 0005(此处为十进制,为了方便计算),第一个地址是3000 0001,转换为二进制就是1 1100 1001 1100 0011 1000 0001,分为4个字节,如果不足用0补齐(0补在最高位),这些数据存入变量human中就是:0000 0001 | 1100 1001 | 1100 0011 | 1000 0001,地址中存放顺序是从后到前。而因为human2 = human,所以变量human2存放的也是Human的实例的地址

注意

  • 局部变量实在Stack(栈区)上分配内存
  • 成员变量在声明后会有一个默认值,变量分配在内存中时,默认内存中全部存放 0。本地变量没有默认值,不赋值无法编译,如:
int x;
Console.WriteLine(x);
//此段代码会报错
  • 常量无法更改数值,声明常量时还必须带有初始化器
 const int x = 100;
 x = 200;
 //x因为是常量,代码报错
  • 装箱与拆箱(Boxing & Unboxing),如:
int x = 100;
object obj = x;//装箱
//装箱将值类型转换为引用类型
//变量 x 存放100的二进制形式
//如果发现变量 obj 不是堆区上的实例,而是栈区上的值类型的值时
//会先将变量 x 存放的数据复制一份到堆区上
//再将复制的数据占用的第一个地址转换为二进制存放在变量 obj 中

int y = (int)obj;//拆箱
//现在栈区中找一块内存作为变量 y
//再将 obj 存放的地址所指向的堆区上的数据复制到变量 y 中

你可能感兴趣的:(C#学习,c#,学习,开发语言)