C#自学笔记:数据类型原理与实践

基础

复杂数据类型特点

  1. 数据集合

一般是多个数据(变量)集合在一起构成的数据

  1. 自定义

一般可以自己取名字,可以自定义的数据(变量)

类型:

  • 枚举:整形变量的集合,可以自定义
  • 数组:任意变量类型顺序存储的数据
  • 结构体:任意变量的数据集合,可以自定义

枚举

整形常量的集合,可以自定义

基本概念

枚举是一个比较特别的存在

它是一个被命名的整形常量的集合

一般用它来表示状态、类型等待

注意:声明枚举和声明枚举变量是两个概念

  • 声明枚举:相当于是创建一个自定义的枚举类型
  • 声明枚举变量:使用声明的自定义枚举类型,创建一个枚举变量

声明枚举语法

enum E_自定义枚举名
{
    //自定义枚举项名字,		枚举中包裹的整形常量,第一个默认值是0,下面的变量会依次累加
    //自定义枚举项名字1,
    //自定义枚举项名字2
}

声明位置

  1. namespace语句块中(常用)
  2. class语句块中或者struct语句块中

注意:枚举不能在函数语句块中声明!!

namespace {
    enum E_MonsterType
    {
        Normal,	//0
        Boss,	//1
    }

    enum E_PlayerType
    {
        Main,
        Other,
    }
}

枚举的使用

枚举和switch是天生一对
E_PlayerType playerType = E_PlayerType.Main;
switch(playerType) {
    case Main:
        //代码
        break;
    case Other:
        //代码
        break;
    default:
        break;
}

也可以用if进行判断类型

枚举的类型转换

  1. 枚举和int互转

直接强转

int i = (int)playerType;

int转枚举

playerType = (E_PlayerType)0;
  1. 枚举和string相互转换(极少用)

转换之后是类型的名字,不是值

string str = playerType.ToString();
Console.WriteLine(str);//Main;

string转枚举

//Parse方法
//第一个参数:要转换的枚举类型
//第二个参数:用于转换的枚举变量名
//转换完毕后是一个通用类型,也就是Object
playerTypr = (E_PlayerType)Enum.Parse(typeof(E_PlayerType), "Other");
Console.WriteLine(playerType);//Other

枚举的作用

在游戏开发中,对象很多时候会有许多的状态

枚举可以帮助我们清晰地分清楚状态的含义

数组

任意变量类型顺序存储的数据

类型:一维、多维、交错数组

一般情况下一维数组就简称为数组

(一维)数组的声明

//(用的比较少)
//变量类型[] 数组名;
int[] arr1;
arr1 = new int[5];

//(比较严谨)
//变量类型[] 数组名 = new 变量类型[数组长度]
int[] arr2 = new int[5];

//(比较严谨)
//变量类型[] 数组名 = new 变量类型[数组长度]{内容1, 内容2, ...}
int[] arr3 = new int[5]{1, 2, 3, 4, 5};

//变量类型[] 数组名 = new 变量类型[]{内容1, 内容2, ...}
int[] arr4 = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9};

//(最方便,也是用的最多的)
//变量类型[] 数组名 = {内容1, 内容2, ...}
int[] arr5 = {1, 2, 3, 4, 5, 6};
  1. 数组的长度

数组名.Length
arr.Length

  1. 获取数组元素

arr[0]

  1. 遍历数组
for (int i = 0; i < array.Length; i++) {
	Console.WriteLine(array[i]);
}
  1. 增加数组的元素
// 数组初始化以后 是不能够直接添加新的元素的
int[] array2 = new int[6];
//搬家
for(int i=0;i<array.Length; i++) {
    array2[i]= array[i];
}
array = array2;
array[5] = 999;
  1. 删除数组的元素
// 数组初始化以后 是不能够直接删除元素的
// 搬家的原理
int[] array3 = new int[5];
//搬家
for(int i=0;i< array3.Length; i++) {
    array3[i]= array[i];
}
array = array3;

二维数组的声明

//(用的比较少)
//变量类型[,] 二维数组名;
int[,] arr1;
arr1 = new int[5, 5];

//(比较严谨)
//变量类型[,] 二维数组名 = new 变量类型[行, 列];
int[,] arr2 = new int[3, 3];

//(比较严谨)
//变量类型[,] 二维数组名 = new 变量类型[行, 列]{{0行内容1, 0行内容2, ...}, {1行内容1, 1行内容2,...}}
int[,] arr3 = new int[3, 3] {	{1, 2, 3}, 
                                {4, 5, 6}, 
                                {7, 8, 9} };

//变量类型[,] 二维数组名 = new 变量类型[,]{{0行内容1, 0行内容2, ...}, {1行内容1, 1行内容2,...}}
int[,] arr4 = new int[,] {  {1, 2, 3}, 
                            {4, 5, 6}, 
                            {7, 8, 9} };

//(最方便,也是用的最多的)
//变量类型[,] 二维数组名 = {{0行内容1, 0行内容2, ...}, {1行内容1, 1行内容2,...}}
int[,] arr5 = {	{1, 2, 3}, 
                {4, 5, 6}, 
                {7, 8, 9} };
  1. 二维数组的长度

得到多少行

array.GetLength(0)

得到多少列

array.GetLength(1)

  1. 获取二维数组的元素

array[0, 1]//第一行第二列

  1. 遍历二维数组
for (int i = 0; i < array.GetLength(0); i++)
{
    for (int j = 0; j < array.GetLength(1); j++)
    {
        Console.WriteLine(array[i, j]);
    }
}
  1. 增加数组的元素
// 数组初始化以后 就不能在原有的基础上 直接添加新的元素的或者删除
int[,] array2 = new int[3, 3];//原来的数组是2行3列
for (int i = 0; i < array.GetLength(0); i++)
{
    for (int j = 0; j < array.GetLength(1); j++)
    {
        array2[i, j] = array[i, j];
    }
}
array = array2;
array[2, 0] = 7;

交错数组(用的极少)

  1. 基本概念

交错数组是数组的数组,每一个维度的数量可以不同

  • 假设int数组的每一个元素是int变量,而int数组的数组,也就是交错数组的每一个元素是一个int数组

注意:二维数组的每行的列数相同,交错数组每行的列数可能不同

  1. 数组的声明
//变量类型[][] 交错数组名;
int[,] arr1;
arr1 = new int[5, 5];

//变量类型[][] 交错数组名 = new 变量类型[行][];
int[][] arr2 = new int[3][];

//变量类型[][] 交错数组名 = new 变量类型[行][]{一维数组1, 一维数组2,...}
int[][] arr3 = new int[3][] { 	new int[] {1, 2, 3}, 
                                new int[] {4, 5,}, 
                                new int[] {6} };

//变量类型[][] 交错数组名 = new 变量类型[][]{一维数组1, 一维数组2,...}
int[][] arr4 = new int[][] {	new int[] {1, 2, 3}, 
                                new int[] {4, 5,}, 
                                new int[] {6} };

//变量类型[][] 交错数组名 = {一维数组1, 一维数组2,...}}
int[][] arr5 = {    new int[] {1, 2, 3}, 
                    new int[] {4, 5,}, 
                    new int[] {6} };
  1. 数组的长度

array.GetLength(0)//行

array[0].Length//第一行的列数

  1. 获取交错数组的元素

array[0][1]

值类型和引用类型

分类

值类型:
  • 无符号: byte,ushort,uint,ulong
  • 有符号: sbyte,short,int,long
  • 浮点数: float,double,decimal
  • 特殊: char,bool
  • 枚举: enum
  • 结构体: struct

引用类型:string、数组、class、interface、委托

区别

  1. 使用上的区别
int a = 10;
int[] arr = new int[] {1, 2, 3, 4};

int b = a;
//引用赋值会指向同一个内存空间,值是开辟两个内存空间
int[] arr2 = arr;
Console.WriteLine("a = " + a + ", b = " + b);//a = 10, b = 10
Console.WriteLine("arr[0]=" + arr[0] + ", arr2[0]=" + arr2[0]);//arr[0]=1, arr2[0]=1

b = 20;
arr2[0] = 5;
Console.WriteLine("a = " + a + ", b = " + b);//a = 10, b = 20
Console.WriteLine("arr[0]=" + arr[0] + ", arr2[0]=" + arr2[0]);//arr[0]=5, arr2[0]=5
  1. 为什么有以上区别

类型存储在空间 —— 系统分配,自动回收,小而快

引用类型存储在空间 —— 手动申请和释放,大而慢

如何判断值类型和引用类型

F12进到类型的内部去查看
  • 是class就是引用
  • 是struct就是值

特殊的引用类型string

string具有值类型的特征,string不可变,当重新赋值时会开辟一个新的空间

image

string虽然方便,但是有一个小缺点,频繁的改变string时会产生内存垃圾

语句块

命名空间 -> 类、接口、结构体 -> 函数、属性、索引器、运算符重载等(类、接口、结构体) -> 条件分支、循环
  • 上层语句块: 类、结构体
  • 中层语句块: 函数
  • 底层的语句块: 条件分支,循环等

我们的逻辑代码写在哪里?

函数、条件分支、循环-中底层语句块中

我们的变量可以申明在哪里?

上、中、底都能申明变量

上层语句块中:成员变量

中、底层语句块中:临时变量

变量的生命周期

在中底层声明的临时变量(函数、条件分支、循环语句块等),语句块执行结束,没有被记录的对象将被回收或变成垃圾

值类型:被系统自动回收

引用类型:栈上用于存地址的房间被系统自动回收,堆中具体内容变成垃圾,待下次GC回收

想要不被回收或者不变成垃圾,必须将其记录下来

如何记录?

在更高级记录或者使用静态全局变量记录

结构体中的值和引用

结构体本身是值类型

前提:该结构体没有作为其他类的成员

在结构体中的值,栈中存储值具体的内容

在结构体中的引用,堆中存储引用具体的内容

引用类型始终存储在堆中

真正通过结构体使用其中引用类型时只是顺藤摸瓜

类中的值和引用

类本身是引用类型

在类中的值,堆中存储具体的值

在类中的引用,堆中存储具体的值

值类型跟着大哥走,引用类型一根筋。意思就是值类型如果在类中的话就在堆里,如果在结构体中的话就在栈里

数组中的存储规则

数组本身是引用类型

值类型数组,堆中房间存具体内容

引用类型数组,堆中房间存地址

结构体继承接口

利用里氏替换原则,用接口容器装载结构体存在装箱拆箱

函数

ref和out

目的是为了改变传入的参数自己的值

ref使用范例:

//函数参数的修饰符
//当传入的值类型参数在内部修改时 或者引用类型参数在内部重新申明时,外部的值会发生变化
static void ChangeValueRef(ref int value)
{
    value = 3;
}

static void Main(string[] args)
{
    int a = 1;
    ChangeValue(ref a);
    Console.WriteLine(a);//3,没有用ref时打印出来是1
}

out使用和声明和ref一模一样

区别:

  1. ref传入的变量必须初始化 out不用
  2. out传入的变量必须在内部赋值 ref不用

简单来说,ref就是引用,out就是指针

还有一种形容是买票上车和上车买票

变长参数

关键字params

举例:函数要计算n个整数和

static int Sum(int a, int b,...)

用变长参数关键字params

static int Sum(params int[] arr)
{
    int sum = 0;
    for (int i = 0; i < arr.Length; i++)
    {
        sum += arr[i];
    }
    return sum;
}

注意:

  1. params关键字后面必须是数组,意味着只能是同一种类型的可变参数
  2. 数组的类型可以是任意类型
  3. 函数参数可以同时有params关键字修饰的参数和别的参数
  4. 函数参数中最多只能出现一个params关键字,并且一定是在最后一组参数
  5. 是一个可变参数数组,它要求你提供一个数组或多个对象。即使你没有传入任何参数,也必须按照params的要求提供一个参数:即null或者空数组。否则,编译器会提示你需要传递一个参数。

参数默认值

有参数默认值的参数一般称为可选参数

作用是可以不传入参数,不传的话就会使用默认值作为参数的值

例子:

static void Speak(string str = "我没什么话可说")
{
    Console.WriteLine(str);
}

static void Main(string[] args)
{
    Speak();//我没什么话可说
    Speak("112233");//112233
}

注意:

  1. 支持多参数默认值,每一个参数都可以有默认值
  2. 如果要混用,可选参数必须写在普通参数后面

重载

  • 重载和返回值类型无关,只和参数类型,个数,顺序有关
  • 调用时,程序会自己根据传入的参数类型判断使用哪一个重载

例子:

//初始函数
static int CalcSum(int a, int b);
{
    return a + b;
}

//参数数量不同
static int CalcSum(int a, int b, int c)
{
    return a + b + c;
}

//参数数量相同,返回类型不同
//初始函数
static float CalcSum(float a, float b)
{
    return a + b;
}

//参数数量相同,参数类型不同
static float CalcSum(int a, float f)
{
    a + f;
}

//参数数量相同,参数顺序不同
static float CalcSum(float f, int a)
{
    return f + a;
}

//ref和out(相当于传入的是变量的地址,而不是变量的值)
//不能同时在一个地方修饰
static float CalcSum(ref float f, int a)
{
    return f + a;
}

//可变参数
static float CalcSum(int a, int b, params int[] arr)
{
    return 1;
}
//参数默认值不算重载

结构体

任意变量的数据集合,可以自定义

基本概念

是数据和函数的集合

在结构体中,可以声明各种变量和方法

作用:用来表现存在关系的数据集合,比如学生、动物、人类等等

基本语法

  1. 结构体一般写在namespace语句块中
  2. 结构体关键字struct
  3. 不写访问修饰符的话默认就是private

例子:

struct Student
{
    //变量
    //在结构体中声明的变量不能直接初始化
    //变量类型可以是任意类型,包括结构体,但是不能是自己的结构体
    private int age;
    private bool sex;
    private string name;
    //构造函数(可选)
    
    //函数方法
    //表现这个数据结构的行为
    public void Speak()
    {
        //函数中可以直接使用结构体内部声明的变量
        Console.WriteLine("我的名字是{0},我今年{1}岁", name, age);
    }
    
}    

结构体的使用

class Program
{
    static void Main(string[] args)
    {
        //变量类型 变量名
        Student s1;
        s1.Speak();
    }
}

结构体和类的区别

结构体声明的变量不能直接初始化,不能声明无参构造函数,是值类型

类的变量可以直接初始化,可以声明无参构造函数,是引用类型

构造函数

  1. 没有返回值
  2. 函数名必须和结构体名相同
  3. 必须有参数
  4. 如果声明了构造函数,那么必须在其中对所有变量数据初始化
  5. 可以重载(比较少用,一般都是所有属性一起赋值)
struct Student
{
    //.....

    public Student(int age, bool sex, int number, string name)
    {
        this.age = age;
        this.sex = sex;
        this.number = number;
        this.name = name;
    }
}

class Program
{
    static void Main(string[] args)
    {
        Student s2 = new Student(18, true, 2, "柠凉");
        s2.Speak();//我的名字是柠凉,今年18岁
    }
}
posted @ 2025-06-30 16:14  柠凉w  阅读(23)  评论(0)    收藏  举报