一、复杂数据类型

(一)枚举

它是一个被命名的整型常量的集合,一般用来表示状态、类型等等。
1、申明枚举
        命名规范 枚举名 以E或者E_开头
        声明在namespace语句块中(常用),也可以在class语句中,struct语句块中
        注意:枚举不能在函数语句块中申明

enum E_自定义枚举名{
    自定义枚举项名1,
    自定义枚举项名2,
    自定义枚举项名3,
}

2、申明枚举变量
        自定义的枚举类型 变量名 = 默认值;(自定义枚举类型.枚举项)

E_PlayerType playerType = E_PlayerType.Main;
if(playType == E_PlayerType.Main){
    Console.WriteLine("主玩家逻辑");
}else if(E_PlayType == E_PlayType.Other){
    Console.WriteLine("其它玩家逻辑");
}

3、枚举的类型转换
        枚举和int互转
        枚举可以直接强转成int,int转枚举可以直接赋值

//枚举转int
int i = (int)playType;
//int转枚举
playType = 0;

        枚举和string互转
        Parse后 第一个参数:你要转为的是哪个枚举类型 第二个参数:用于转换的对应枚举项的字符串
        转换完成后 是一个通用的类型 我们需要用括号强转成我们想要的目标枚举类型

//枚举转string
string str = playType.ToString();
//string转枚举
playType = (E_PlayType)Enum.Parse(typeof(E_PlayerType),"Other");

4、枚举的作用
        在游戏开发中,对象很多时候 会有很多的状态
        比如一个玩家 有一个动作状态 我们需要用一个变量或标识 来表示当前玩家处于的是哪种状态
        综合考虑 可能会使用int来表示他的状态
        1行走 2待机 3跑步 4跳跃 ......等等
        枚举可以帮助我们 清晰的分清楚状态的含义(提升代码的可读性)        
5、枚举练习题

定义QQ状态枚举,并提示用户选择一个在线状态,我们接受输入数字,并将其转换成枚举类型

using System;
namespace Systeam01
{
    /// <summary>
    /// QQ状态枚举
    /// </summary>
    enum E_QQType
    {
        /// <summary>
        /// QQ状态
        /// </summary>
        Online,
        Leave,
        Busy,
        Invisible,
    }
    class Program
    {
        static void Main(String[] args)
        {
            Console.WriteLine("请输入QQ状态(0在线,1离开,2忙,3隐身)");
            //ReadLine接收字符串类型,需要强转成int类型
            int type = int.Parse(Console.ReadLine());
            //int类型强转枚举类型
            E_QQType qqType = (E_QQType)type;
            Console.WriteLine(qqType);
        }
    }
}

千问给出代优码化
int.Parse 会抛出异常(如输入非数字字符串)。更安全的做法是使用 int.TryParse:

Console.WriteLine("请输入QQ状态(0在线,1离开,2忙,3隐身)");

// 定义一个变量来接收转换后的结果
int type; 

// 尝试转换:如果用户输入的是数字,返回 true 并赋值给 type;否则返回 false
if (int.TryParse(Console.ReadLine(), out type))
{
    // 转换成功,进行强转枚举
    E_QQType qqType = (E_QQType)type;
    Console.WriteLine("你选择的状态是:" + qqType);
}
else
{
    // 转换失败,给用户一个友好的提示
    Console.WriteLine("输入格式错误,请输入数字!");
}

(二)数组

数组是存储一组相同类型数据的集合
1、数组的申明 
        变量类型[ ] 数组名; 
        变量类型[ ]数组名 = new 变量类型[数组长度];
        变量类型[ ]数组名 = new 变量类型[数组长度]{内容1,2,3,......};
        变量类型[ ]数组名 = new 变量类型[ ]{内容1,2,3,......};
        变量类型[ ]数组名 = {内容1,2,3,......};
2、数组的使用
        数组长度:数组变量名.length
        获取数组中的元素:数组中的下标和索引是从0开始的,范围时0~length - 1;
        修改数组中的元素:直接赋值,注意数据类型
        遍历数组:用for循环遍历
        增加数组中的元素: 数组初始化后是不能够直接添加新元素的,需要新建一个数组,将原数组内容赋值给新数组。
        删除数组的元素:原理同上。
        查找数组中的元素:遍历数组,用if循环判断,最后返回i。

(三)二维数组

使用两个下标(索引) 行标和列标来确定元素的数组
1、二维数组的申明
        变量类型[ , ] 二维数组名;
        变量类型[ , ] 二维数组名 = new 变量类型 [ 行,列 ];
        变量类型[ , ] 二维数组名 = new 变量类型 [ 行,列 ]{{0行内容1,2,3,...},{1行内容1,2,3,...},{2行内容1,2,3,...},...};
        变量类型[ , ] 二维数组名 = new 变量类型 [ , ]{{0行内容1,2,3,...},{1行内容1,2,3,...},{2行内容1,2,3,...},...};
        变量类型[ , ] 二维数组名 = {{0行内容1,2,3,...},{1行内容1,2,3,...},{2行内容1,2,3,...},...};
2、二维数组的使用
        二维数组的长度:

//得到多少行
Console.WriteLine(array.GetLength(0));
//得到多少列
Console.WriteLine(array.GetLength(1));

        获得二维数组的元素:第一个元素索引是0,最后一个元素索引是长度-1;
        修改元素:索引位置后直接赋值;
        遍历二维数组:

for(int i = 0; i < array.GetLength(0); i++){
    for(int = j; j < array.GetLength(1);j++){
        Console.WriteLine(array[i,j]);
    }
}

        增加或删除数组中的元素:把数据搬进新数组;
        查找数组中的元素:遍历;

(四)交错数组

交错数组是数组的数组,每个维度的数量可以不同
1、交错数组的声明
        变量类型[ ] [ ] 交错数组名;
        变量类型[ ] [ ] 交错数组名 = new 变量类型[行数][ ];
        变量类型[ ] [ ] 交错数组名 = new 变量类型[行数][ ]{一维数组1,一维数组2,......};(内部一维数组的数据类型要和交错数组数据类型相同)
        
变量类型[ ] [ ] 交错数组名 = new 变量类型[ ][ ]{一维数组1,一维数组2,......};
        变量类型[ ] [ ] 交错数组名 = {一维数组1,一维数组2,......};
2、交错数组的使用
        数组的长度:

//行数
Console.WriteLine(array.GetLength(0));
//某一行的列数
Console.Write(array[0].Length);

        获取交错数组中的元素:

Console.WriteLine(array[i][j]);

        修改元素:索引后赋值
        变量交错数组

for(int i = 0; i < GetLength(0); i++){
    for(j = 0; j < array[i].Length; j++){
        Console.WriteLine(array[i][j] + " ");
    }
    Console.WriteLine();
}

二、值类型和引用类型

(一)值类型和引用类型的区别      

引用类型:string, 数组, 类;
值类型:除去string和数组之外学习到的其它数据类型(int,float,...), 结构体;
1、使用上的区别
        值类型 在相互赋值时 把内容拷贝给了对方 它变我不变
        引用类型 的相互赋值 时 让两者指向同一个值 它变我也变
2、为什么会有区别
        值类型 和 引用类型 存储的内存区域是不同的 存储方式是不同的
        值类型存储在 栈空间 —— 系统分配,自动回收,小而快
        引用类型 存储在 堆空间 —— 手动申请和释放,大而慢

(二)特殊引用类型string

string非常特殊 它具备 值类型的特征 它变我不变

string str1 = "123";
string str2 = str1;
str2 = "321";
Console.WriteLine(str1);
Console.WriteLine(str2);
//输出结果:
123
321

string重新赋值时 会在堆中重新分配空间
缺点:频繁的改变string 重新赋值 会产生 内存垃圾
优化替代方案 在C#核心中进行讲解

三、函数

(一)函数基础

本质是一块具有名称的代码块
可以使用函数(方法)的名称来执行该代码块
函数(方法)是封装代码块进行重复利用的一种机制
1、函数(方法)的主要作用:
        封装方法
        提升代码复用率
        抽象行为
2、函数写在哪里
        class或struct语句块中
3、基本语法
        static 返回类型 函数名(参数类型 参数名1,参数名2,......)
        {
        函数的代码逻辑;
        函数的代码逻辑;
        函数的代码逻辑;
        ......
        return 返回值;(如果有返回类型才返回)
        }
        (1)关于static 不是必须的 在没有学习类和结构体之前 都是必须写的
        (2)关于返回类型
        引出一个新的关键字 void(表示没有返回值)
        可以写任意的变量类型 14种变量类型 + 复杂数据类型(数组,枚举,结构体,类class)
        (3)关于函数名 使用帕斯卡命名法命名 MyName
        (4)参数不是必须的 可以有0~n个参数 参数类型也是可以是任意类型的 14种变量类型 + 复杂数据类型(数组,枚举,结构体,类class)
        参数命名 驼峰命名法 myName
        (5)当返回值类型不为void时 必须通过新的关键词 return返回对应类型的内容(注意:即使是void也可以选择性使用return)
 4、实际应用
        (1)无参无返回值函数

static void SayHello()
{
    Console.WriteLine("Hello World!");
}

        (2)有参无返回值

static void SayYourName(string name)
{
    Console.WriteLine("你的名字是:{0}",name};
    //return省略了
}

        (3)无参有返回值

Static string WhatYourName()
{
    return "ZCloud";
}

        (4)有参有返回值

static string Sum(int a,int b)
{
    return a + b;
}

        (5)有参有多返回值
        传入两个数 然后计算两个数的和 以及它们的平均数 得出计算结果返回出来
        函数的返回值 一定是一个类型 只能是一个内容

static int[] Clac(int a,int b)
{
    int sum = a + b;
    int avg = sum / 2;
    return new int[] {sum,avg};
}

5、关于return
        即使函数没有返回值,我们也可以使用return。
        return可以直接不执行之后的代码,直接返回到函数外部。

(二)ref和out

它们可以解决函数内部改变外部传输内容 里面变了外面也要变
1、ref的使用

class Program
{
    static void ChangeValueRef(ref int value)
    {
        value = 3;
    }

    static void Main(string[] args)
    {
        int a = 1;
        ChangeValueRef(ref a);
        Console.WriteLine(a);
    }
}
//输出结果:
3

2、out的使用

class Program
{
    static void ChangeValueOut(out int value)
    {
        value = 3;
    }

    static void Main(string[] args)
    {
        int a;
        ChangeValueOut(out a);
        Console.WriteLine(a);
    }
}
//输出结果:
3

3、ref和out的区别
ref传入的变量必须初始化 out不用

class Program
{
    static void ChangeValueRef(ref int value)
    {
        value = 3;
    }

    static void Main(string[] args)
    {
        int a;
        ChangeValueRef(ref a);
        Console.WriteLine(a);
    }
}
//会在ref a处报错,提示:使用了未赋值的局部变量'a';

out传入的变量必须在内部赋值 ref不用

class Program
{
    static void ChangeValueOut(out int value)
    {
        
    }

    static void Main(string[] args)
    {
        int a = 1;
        ChangeValueOut(out a);
        Console.WriteLine(a);
    }
}
//会在函数声明ChangeValueOut处报错,提示:控制离开当前方法之前必须对out参数'value'赋值

(三)变长参数和参数默认值

1、变长参数关键字 params
params int[ ]意味着可以传入n个int参数 n可以等于0 传入的参数会存在arr数组中
注意:
        params关键字后面必为数组
        数组的类型可以是任意类型
        函数参数可以有 别的参数和params关键字修饰的参数
        函数参数中只能最多出现一个params关键字 并且一定是在最后一组参数 前面可以有n个其它参数

2、参数默认值
有参数默认值的参数 一般称为可选参数
作用是 当调用函数时可以不传入参数 不传就会使用默认值作为参数的值

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

注意:
        支持多参数默认值 每个参数都可以有默认值
        如果要混用 可选参数 必须写在普通参数后面

(四)函数重载

1、基本概念
重载概念:
        在同一语句块中(class或struct)中
        函数(方法)名相同
        参数数量不同
        或者
        参数的数量相同,但参数的类型或顺序不同
作用:
        命名一组功能相似的函数,减少命名空间的污染
        提升程序的可读性
1、实例
注意:
        重载和返回值类型无关,只和参数类型,个数,顺序有关
        调用时 程序会自己根据传入的参数类型判断使用哪一个重载

 

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 b)
{
    return a + b;
}

static float Calcsum(float a,int b)
{
    return a + b;
}

使用ref和out算是一种新的参数类型,但是ref和out之间算是同一种类型

(五)递归函数

让函数自己调用自己
1、一个正确的递归数:
        必须有结束调用的条件
        用于条件判断的这个条件 必须改变 能够达到停止的目的
2、实例:
        用递归函数打印出0~10

static void Fun(int a)
{
    //第四步:结束条件
    if(a > 10)
    {
        return;
    }
    //第二步:完成要求 打印
    Console.WriteLine(a);
    //第三步:完成一个 递归的变化 作为我们的条件判断
    ++a;
    //第一步:构造一个递归
    Fun(a);
}

四、结构体

1、基本概念
        结构体 是一种自定义变量类型 类似枚举需要自己定义
        它是数据和函数的集合
        在结构体中 可以申明各种变量和方法
        作用:用来表现存在关系的数据集合 比如用结构体表现学生 动物 人类等等
2、基本语法
        结构体一般写在namespace语句块中
        结构体关键字 struct

struct 自定义结构体名
{
    //第一部分
    //变量

    //第二部分
    //构造函数(可选)

    //第三部分
    //函数
}

        注意:结构体名字 规范是 帕斯卡命名法
3、实例
        表现学生数据的结构体

struct Student
{
    //变量

    //年龄
    public int age;
    //性别
    public bool sex;
    //学号
    public int number;
    //姓名
    public string name;

    //构造函数

    //函数方法
    //表现这个数据结构的行为
    public void Speak()
    {
        //函数中可以直接使用结构体内部申明的变量
        Console.WriteLine("我的名字是{0},我今年{1}岁",name,age);
    }
}
    

        注意:结构体申明的变量 不能直接初始化
                   变量类型 可以写任意类型 包括结构体 但是 不能是自己的结构体
                   在结构体中的方法 目前不需要加static关键字
                   函数中可以使用结构体内部申明的变量

4、访问修饰符
        修饰结构体中变量和方法 是否能被外部使用
        public 公共的 可以被外部访问
        private 私有的 只能在内部使用  
        不写默认为 private
5、结构体的使用

static void Main(string[] args)
{
    Student s1;
    s1.age = 16;
    s1.sex = false;
    s1.number = 1;
    s1.name = "zcloud";
    s1.Speak();
}

6、结构体的构造函数
        基本概念:
                没有返回值
                函数名必须和结构体名相同
                必须有参数
                如果申明了构造函数 那么必须在其中对所有变量数据初始化

struct Student
{
    //变量

    //年龄
    public int age;
    //性别
    public bool sex;
    //学号
    public int number;
    //姓名
    public string name;

    //构造函数
    public Student(int age, bool sex, int number, string name)
    {
        //新的关键字 this
        //代表自己
        this.age = age;
        this.sex = sex;
        this.number = number;
        this.name = name;
    }

    //函数方法
    //表现这个数据结构的行为
    public void Speak()
    {
        //函数中可以直接使用结构体内部申明的变量
        Console.WriteLine("我的名字是{0},我今年{1}岁",name,age);
    }
}

        构造函数一般是方便外部初始化使用

static void Main(string[] args)
{
    Student s2 = new Student(18,true,2,"zcloud");
    s2.Speak();
}

五、排序初探

(一)冒泡排序

1、基本原理
        两两相邻
        不停比较
        不停交换
        比较n轮
2、代码实现

for(int m = 0; m < arr.Length; m++)
{    
    for(int n = 0; n < arr.Length - m - 1; n++)
    {
        if(arr[n] > arr[n + 1])
        {
            int temp = arr[n];
            arr[n] = arr[n + 1];
            arr[n + 1] = temp;
        }
    }
}

(二)选择排序

1、基本原理
        建立中间商
        依次比较
        找出极值(最大或最小)
        放入目标位置
        比较n轮
2、代码实现

//第五步 比较n轮
for(int m = 0; m < arr.Length; m++)
{
    //第一步 申明一个中间商 来记录索引
    //每一轮开始 默认第一个都是极值
    int index = 0;
    //第二步 依次比较
    for(int n = 1; n < arr.Length; n++)
    {
        //第三步 找出极值(最大值)
        if(arr[index] < arr[n])
        {
            index = n;
        }
    }
    //第四步 放入目标位置
    //Length - 1 - 轮数
    //如果当前极值所在就是目标位置 那就没有必要换
    if(index != arr.Length - 1 - m)
    {
        int temp = arr[index];
        arr[index] = arr[arr.Length - 1 - m];
        arr[arr.Length - 1 - m] = temp;
    }
}

更多推荐