首页 > 编程学习 > C#语言基础

C#语言基础

发布时间:2022/10/1 22:16:36

C#语言基础

文章目录

  • C#语言基础
    • 1.编程语言介绍
    • 2.C# 基础语法
      • 1.输入输出
      • 2.变量
      • 3.变量类型转换
      • 4.异常捕获
      • 5.运算符
      • 6.条件语句与循环语句
      • 7.控制台,随机数以及调试
      • 8.枚举,数组
      • 9.值类型和引用类型
      • 10.OOP特性之封装
      • 11.OOP特性之继承
      • 12.OOP特性之多态
      • 13.面向对象补充
    • C#进阶语法
      • 1.ArrayList
      • 2.Stack和 Queue
      • 3.Hashtable(散列表)
      • 4.泛型
      • 5.泛型约束
      • 6.List
      • 7.Dictionary
      • 8.委托
      • 9.事件
      • 10.匿名函数
      • 11.Lambda表达式
      • 12.List排序
      • 13.协变逆变
      • 14.多线程
      • 15.俄罗斯方块实践

1.编程语言介绍

一些主流编程语言

  • 1.C:嵌入式硬件开发
  • 2.C++:游戏客户端,服务器,软件
  • 3.C#:游戏客户端,服务器,软件,网站
  • 4.Java:安卓,服务器,软件,网站
  • 5.JavaScript:网站,服务器
  • 6.PHP:网站,服务器
  • 7.Python:网站,服务器,辅助开发
  • 8.SQL:数据库
  • 9.Go:服务器
  • 10.Objective-C:mac,ios开发
  • 11.Swift:mac,ios开发

使用IDE:Visual Studio2019 Community

2.C# 基础语法

1.输入输出

//输出后不跳行
Console.Write("xxx");
//输出后跳行
Console.WriteLine("xxx");

//检测玩家的一键输入
Console.ReadKey("xxx");
//检测玩家的一系列输入
Console.ReadLine("xxx");

2.变量

折叠代码

//折叠代码
#region myRegion

#endregion

变量:可以变化的容器
语法变量类型 变量名 = 初始值;
一些常用变量类型:
有符号的整形变量

  • 1.sbyte:-128~127
  • 2.int:-21亿~21亿多
  • 3.short:-32768~32767
  • 4.long:-900w兆~900w兆
    无符号的整形变量
  • 5.byte:0~255
  • 6.uint:0~42亿
  • 7.ushort:0~65535
  • 8.ulong:0~1800w兆
    浮点数
  • 9.float:存储7/8位有效数字
  • 10.double:存储15~17位有效数字,抛弃的数字会 四舍五入
  • 11.decimal:存储27~28位有效数字
    特殊类型
  • 12.bool:表示真假的数据类型
  • 13.char:存储单个字符的变量类型
  • 14.string:字符串,用来存储多个字符,无上限
//声明整形变量
int i = 666;
//声明浮点变量
float f = 0.11111111f;
//声明decimal数据
decimal de = 0.12345678910111235548m;

变量的本质
变量中的存储单位所占字节数:(单位:字节byte)

  • sbyte,byte,bool:1
  • int,uint,float:4
  • short,ushort,char:2
  • long,ulong,double:8
  • decimal:16

变量的命名
四不能:

  • 1.不能重名
  • 2.不能以数字开头
  • 3.不能以程序关键字命名
  • 4.不能有特殊符号(下划线除外)

二规范:

  • 1.驼峰命名法:首字母小写,之后单词首字母大写(变量)
  • 2.帕斯卡命名法:所有单词首字母都大写(函数,类)

常量
语法:const 变量类型 变量名 = 初始值
特点:

  • 1.必须初始化
  • 2.不能被修改

作用:声明一些不变的变量。

const float PI = 3.1415926f;

转义字符
语法:\'

//定义字符串中有特殊字符
string str = "\'哈哈哈\'"; 
Console.WriteLine(str); //'哈哈哈'

\n:换行
\\:单斜杠
\t:输出一个制表符
\b:光标后退一格
\0:空
\a:警告音


3.变量类型转换

类型转换

隐式转换:不同类型之间自动转换(大范围装小范围高精度存低精度
注意,decimal不适用隐式转换原则。bool,string,char不存在隐式转换。

相同大类型转换:

//隐式转换
long l = 1;
int i = 1;
l = i;

不同大类型转换:
无符号无法装有符号数字。
有符号装无符号数字同需要符合范围完全覆盖原则。

浮点数可以转任何类型整数。
decimal不能隐式转换为float 和 double
但它可以存储整形。

char类型可以隐式转换为整形或浮点型,其转换后形成的是字符的 ASCII码。

显示转换

  • 1.括号强转
    可能会出现范围问题造成的异常!
    浮点数转整数时,会直接抛弃掉小数部分。
    bool和string无法通过括号强转。
short s = 1;
int i = 1;
s = (short)i;
  • 2.Parse强转
  • 考虑转换后的范围是否能够被新类型存储。
//将字符串转换成int
int i = int.Parse("123");
  • 3.Convert强转
    作用:更准确的将各个类型相互转换,其精度比括号强转高,遵循四舍五入。
int a = Convert.ToInt32("12");
int a = Convert.ToInt32("1.4999");//1
int a = Convert.ToInt32(true);//1
short s5 = Convert.ToInt16("1");
long l5 = Convert.ToInt64("1");

float f = Convert.ToSingle("13.2");
bool bo = Convert.ToBoolean("true");
  • 4.ToString强转
    语法:变量.ToString()
    转字符串还可以使用字符串拼接。

4.异常捕获

作用:
基本语法:try{ }catch{ }finally{ }

try
{
	//希望进行异常捕获的代码块
	//如果报错,则执行catch语块	
}catch(Exception e)
{
	//捕获异常(打印)
}
finally
{
	//最后执行的代码,不管有没有出错,都会执行其中的代码
}

5.运算符

6.条件语句与循环语句

7.控制台,随机数以及调试

控制台补充API:

//读取输入但不在控制台显示
char c = Console.ReadKey(true).KeyChar;

//清空控制台内容
Console.Clear();

//关闭控制台
Environment.Exit();

随机数相关API

//产生随机数对象
Random random = new Random();
//生成随机数
int i = r.Next();//生成一个非负随机数
i = r.Next(100); // 生成一个0~99的随机数

8.枚举,数组

9.值类型和引用类型

值类型:其他,结构体
引用类型:string,数组,类

ref 和 out区别:

  • 1.ref传入的变量必须初始化,但在内部可改可不改
  • 2.out传入变量不用初始化,但在内部必须修改该值

作用:解决值类型和引用类型在函数内部 改变值 或者 重新声明能够影响外面传入的变量,让其也更改。

变长参数关键字params
传入的参数都会参数在arr数组中,传入多少数字都不会报错。
params修饰的参数必须放在参数列表的最后位置且只能有一个。
用法示例

int Sum(params int[] arr)
{
	
}

//调用求和函数
int res = Sum();
int res1 = Sum(1,2,3,4,6,5);

10.OOP特性之封装

重点内容

  • 1.当在一个Person类中的属性出现同类时,无法直接在类内部赋值,会报 栈溢出异常
class Person 
{
	Person p = new Person(); //x

	//构造函数的复用
	public Person(int age){}
	public Person(int age,string name):this(age){
		//自动调用上面的age参数的函数
	}
}
  • 2.析构函数:用于需要手动释放内存的语言(cpp),c#存在垃圾回收机制。垃圾回收的时候调用析构函数。
~Person(){}

垃圾回收机制(GC):识别哪些 没有被任何变量,对象引用的内容,会被回收释放。常见相关算法有:引用计数,标记清除,标记整理,复制集合GC只负责堆(Heap)中内存的垃圾回收。而栈上的内存由系统自动管理,有自己的生命周期,会自动分配与释放。
c#中的垃圾回收机制详解
分代算法:内存分为0代,1代和2代。新分配的内存都会被分配到0代,每次分配都可能(0代内存满时)会进行垃圾回收以释放内存。一次内存回收过程开始时,垃圾回收会认为堆中全是垃圾,会进行以下两步:

  • 1.标记对象,从根(静态字段,方法参数)开始检查引用对象,标记后为可达对象,未标记为不可达对象。
  • 2.不可达对象被认为是垃圾,搬迁可达对象并压缩堆,并释放未标记的对象,修改可达对象的引用地址。

此外,大对象(83kb以上)总是被存在二代内存中,目的是 减少内存消耗,提高性能。
在游戏中,常常在Loading时会被调用,以提高玩家的体验

//手动触发垃圾回收
GC.Collect();
  • 3.成员属性
    语法:访问修饰符 属性类型 变量名{get{} set{}}
    注意:set,get语块不加访问修饰符,默认为属性的访问权限。加的访问修饰符权限必须低于属性的访问权限。不能让get和set的访问权限都低于属性权限。
private string name;

public string Name
{
	get
	{
		return name;
	}
	set
	{
		name = value;
	}
}

//自动属性(会自动生成一个成员变量height)
public float Height
{
	get;
	set;
}
  • 4.索引器
    概念:让对象可以像数组一样通过索引访问其中元素,程序看起来更加直观,更容易编写。
    索引器可以重载
class Person
{
	private Person[] friends;
	
	public Person this[int index]
	{
		get
		{
			return friends[index];
		}
		set
		{
			friends[index] = value;
		}
	}
}

class Person
{
	public  int this[int i,int j]{
		get;set;
	}
}

class Test
{
	Person p = new Person();
	p[0] = new Person();
}
  • 5.静态成员与程序同生共死,程序会为其开辟一块独有空间,静态成员中不能使用非静态成员(生命周期的差异性)。全局性与独有性。
  • 6.拓展方法:
    概念:为现有 非静态 变量类型 添加 新方法。
    作用
    – 1.提高程序拓展性
    – 2.不需要再对象中重写方法
    – 3.不需要继承来添加方法
    – 4.为别人封装的类型写额外的方法
    特点
    – 1.一定是写在静态类中
    – 2.一定是个静态函数
    – 3.第一个参数为拓展目标
    – 4.第一个参数用this修饰
    实例
//定义
static class Tools
{
	public static void speakValue(this int value)
	{
		Console.WriteLine("为int拓展的方法" + value);
	}
}

//使用
class Program
{
	static void Main(string[] args){
		int i = 10;
		i.speakValue();//为int拓展的方法10
	}	
}

补充:如果拓展的方法与原有方法重名,则后续调用的方法仍然是原方法逻辑。

  • 6.运算符重载(operator)
    概念:让自定义类和结构体能够使用运算符
    特点
    – 1.一定是个公有的静态方法
    – 2.返回值写在operator前
    – 3.逻辑处理自定义
    实例
class Point
{
	public int x;
	public int y;
	//点的相加
	public static Point operator +(Point p1,Point p2)
	{
		Point p = new Point();
		p.x = p1.x + p2.x;
		p.y = p1.y + p2.y;
		return p;
	}
}

补充:算术运算符,条件运算符都可以被重载,逻辑运算符中仅有 逻辑非 可重载。条件运算符必须成对出现,有 > 一定有 < 。不能用ref 或 out关键字


11.OOP特性之继承

特性

  • 1.单根性:子类只能有一个父类
  • 2.传递性:子类可以间接继承父类的父类

重点内容

  • 1.基础语法:class 类名 : 父类名

  • 2.里氏替换原则
    – 概念:任何父类出现的地方,子类都可以代替(父类容器装载子类对象)
    – 作用:方便对对象进行存储和管理
    – 实例:

//Player是GameObject的子类(父类容器装载子类对象)
GameObject player = new Player();

– is关键字:

//判断player是不是Player类型
if (player is Player){}

– as关键字:

Player p = player as GameObject;

– is和as的联合运用:

if (player is Player)
{
	Player p = player as Player;   //返回null或者player对象
}
  • 3.继承中的构造函数
    执行顺序:… —> 父类的父类 —> 父类 —>当前类
    **父类的无参构造函数很重要!!!**子类实例化时默认调用父类的无参构造,被其他构造函数顶掉会导致报错。
    通过base调用指定父类构造:
class Son:Father
{
//调用父类的i参数构造函数
	public Son(int i) : base(i)
	{
		//...
	}
}
  • 4.万物之父与装箱拆箱
    object:万物之父,可以用object容器装载一切类型的变量。
    装箱拆箱:
    装箱:用object存值类型数据
    拆箱:把object里面的值转换出来
    好处:不确定存储类型时可以使用,方便参数的传递与存储
    坏处:存在内存的迁移,增加了性能消耗。

  • 5.sealed关键字(密封类)
    概念:让一个类不能再次被继承(绝育)
    意义:加强面向对象程序设计的 规范性 结构性与安全性。

12.OOP特性之多态

概念:多种状态,让继承统一父类的子类们执行相同方法时有不同表现。
目的:同一父类的对象执行相同行为(方法)有不同表现 。
作用:让同一个对象有惟一行为的特征。
重点内容

  • 1.vob(virtual override base):
    – 实例:
class GameObject
{
	public string name;

	public virtual void Atk()
	{
		
	}
}

class Player : GameObject
{
	public override void Atk()
	{
		base.Atk();
	}
}
  • 2.抽象类与抽象方法
    – 抽象类概念:被abstract修饰的类,不能被实例化,可以包含抽象方法。
    – 抽象方法:没有方法体的纯虚方法,继承后必须去实现的方法

  • 3.接口
    – 概念:行为的接口规范
    – 接口声明的规范:
    — 1.不包含成员变量
    — 2.只包含方法,属性,索引器,事件
    — 3.成员不能被实现
    — 4.成员可以不用写访问修饰符,不能是私有的
    — 5.接口不能继承类,但是可以继承另一个接口

– 使用规范:
— 1.类可以继承多个接口,相当于行为合并
— 2.类继承接口后,必须实现接口中所有成员

– 特点:
— 1.他和类的声明类似
— 2.接口是用来继承的
— 3.接口不能被实例化,但是可以作为容器存储对象

13.面向对象补充

重点内容

  • 1.命名空间namespace
    – 基本语法:```namespace MyGame{}``
    – 理解:类比Java中的包管理

  • 2.类前面可以加哪些关键字
    – 1.public:公有的
    – 2.internal:只能在该程序集使用
    – 3.abstract:抽象类
    – 4.sealed:密封类
    – 5.partial:分部类

  • 3.命名空间总结:
    – 1.命名空间是个工具包,用来管理类
    – 2.不同命名空间张,可以有同名类
    – 3.不同命名空间中互相使用,需要using引用或者指明出处
    – 4.命名空间可以包裹命名空间

  • 4.object类详解
    – 1.bool Equals:判断两个对象是否相等,值类型比较是否相等,引用类型比较两个引用是否指向一个内存地址。
    – 2.bool ReferenceEquals:专门比较引用类型的数据,传入值类型会始终返回false。
    – 3.Object MemberwiseClone:获取浅拷贝对象,新对象引用变量和老对象一致。

  • 5.string类与StringBuilder类
    对比:SB相比string来说引用了容量的概念,减少了扩容的操作,使对一个字符串进行修改操作时性能提高了。当容量满溢时,SB会自动扩容(16->32)。

  • 6.结构体和类的区别
    – 1.二者最大的区别体现在存储空间上,结构体是值类型,存储在栈上,而类是引用类型,存储在堆上。结构体具有OOP中的封装特性,但不具备继承和多态,因此大大减少了其使用频率。
    – 2.一些细节区别:总结

1.结构体是值类型,类是引用类型
2.结构体存储在栈中,而类存储在堆中
3.结构体成员不能使用 protected关键字,而类可以
4.结构体成员变量不能声明初始值,而类可以
5.结构体不能申明午餐构造函数,类可以
6.结构体申名有参函数构造后,无参构造不会被顶替
7.结构体不能什么析构函数,类可以
8.结构体不能被继承,类可以
9.结构体需要再构造函数中初始化所有成员变量,类随意
10.结构体不能被static修饰,类可以
11.结构体不能再自己内部声明和自己一样的结构体变量,类可以

– 3.结构体特别之处
结构体可以继承接口,接口是行为的抽象

  • 7.抽象类和接口的区别
    – 1.相同点:

1.都可以被继承
2.都不能直接实例化
3.都可以包含方法声明
4.子类必须实现未实现的方法
5.都遵循里式转换原则

– 2.区别:

1.抽象类可以有构造函数,接口不行
2.抽象类只能被单一继承,接口可以被继承多个
3.抽象类可以有成员变量,接口中不能
4.抽象类可以申明成员方法,虚方法,抽象方法,接口中只能声明未实现的抽象方法
5.抽象类方法可以使用访问修饰符,接口中建议不写,默认public

– 3.如何选择使用

1.表示对象的用抽象类,表示行为拓展的用接口
2.不同对象拥有的共同方法,可以使用接口来实现
3.动物是一类对象,选择抽象类,而飞翔是一个行为,选择使用接口

  • 8.OOP七大原则
    目的:高内聚低耦合
    – 1.单一职责原则(SRP:Single Responsibility Principle):一个类只处理自己应该处理的内容
    – 2.开闭原则(OCP:Open-Closed Principle):对拓展开发,对修改关闭。
    – 3.里氏替换原则(LSP:Liskov Substitution Principle):任何父类出现的地方,子类都可以代替
    – 4.依赖倒转原则(DIP:Dependence Inversion Principle):要依赖于抽象,不要依赖于具体的实现
    – 5.迪米特原则(Law of Demeter):一个对象尽可能对其他对象少的了解,降低耦合度
    – 6.接口分离原则(ISP:Interface Segregation Principle):一个接口不需要提供太多行为,不要把所有行为都封装到一个接口
    – 7.合成复用原则(CRP:Composite Reuse Principle):尽量使用对象组合,而不是继承来达到复用的目的。(遵循迪米特原则)

C#进阶语法

1.ArrayList

本质:本质是 一个 Object 类的数组。
语法ArrayListList a = new ArrayList();
查询工具类:https://learn.microsoft.com/zh-cn/
补充ArrayList本质是一个Object数组,故在存储值类型数据时存在大量装箱拆箱操作,尽量少用该容器,之后会学习更好的数据容器。

2.Stack和 Queue

本质:本质也是 Object 类的数组,Stack是遵循 先进后出 的存储规则,而Queue遵循 先进先出 的存储规则。
语法
Stack stack = new Stack();
Queue queue = new Queue();

API不在此列举,养成自主查询文档的习惯!!!
补充:同样涉及到许多装箱拆箱操作,会降低效率!

3.Hashtable(散列表)

本质:基于哈希代码组织起来的 键值对存储。提高查询效率。
语法Hashtable hashtable = new Hashtable();
重点

  • 1.不能出现重复键
  • 2.查找的时候直接使用数组方式:hashtable["111"]
  • 3.遍历方式:
//遍历所有键值
//方案1
foreach(object item in hashtable.Keys)
{
	item;hashtable[item]
}
//方案2
foreach(object item in hashtable.Values){}
//方案3
foreach(DictionaryEntry item in hashtable)
{
	item.Key,item.Value;
}
//迭代器遍历
IDictionaryEnumerator myEnumerator = hashtable.GetEnumerator();
bool flag = myEnumerator.MoveNext();
while(flag)
{
	myEnumerator.Key;
	myEnumerator.Value;
	flag = myEnumerator.MoveNext();
}

4.泛型

概念:实现了类型参数化,达到代码复用的目的
泛型类和接口
泛型方法

public T Test<T>()
{
	return default(T);
}

作用

  • 1.不同类型对象的相同逻辑处理就可以选择泛型
  • 2.使用泛型可以一定情况下避免装箱拆箱
    总结
  • 1.申明泛型时,它是一个类型的占位符
  • 2.泛型真正起作用是在使用它的时候
  • 3.泛型占位字母可以又n个逗号隔开
  • 4.泛型占位字母一般是大写
  • 5.不确定泛型类型时,获取默认值 可以使用default(T)

5.泛型约束

作用:让泛型的类型有一定限制
基本用法

  • 1.值类型:where 泛型字母 : struct
  • 2.引用类型:where 泛型字母 : class
  • 3.存在无参公共构造函数:where 泛型字母 : new()
  • 4.某个类本身或者其派生类:where 泛型字母 : 类名
  • 5.某个接口的派生类型:where 泛型字母 : 接口名
  • 6.另一个泛型类型本身或派生类:where 泛型字母 : 另一个泛型字母

多个泛型约束

class Test8<T,K> where T : class,new() where K:struct{}

6.List

本质:可变类型的泛型数组
语法List<int> list = new List<int>();
增删改查API自查文档!!!
补充:查的操作同样可使用数组类似的下标法取出。

7.Dictionary

本质:拥有泛型的 Hashtable
声明Dictionary<K,V> dictionary = new Dictionary<K,V>();
补充

  • 1.不能出现相同键。
  • 2.如果查询查不到对应值,则会直接报错(和Hashtable的区别之一)
  • 3.键值对一起遍历的代码样例:
foreach(KeyValuePair<int,string> item in dictionary)
{
	item.Key,item.Value;
}

8.委托

定义:方法的容器。可以理解为表示函数的变量类型。用来存储,传递方法。
本质:本质上是一个类,用来定义方法的类型,不同函数必须对应各自格式(参数与返回值)一致的委托。

语法访问修饰符 delegate 返回值 委托名(参数列表);
重点内容

  • 1.一般写在namespace或者class语块中
  • 2.委托变量可以存储多个函数
  • 3.委托的使用方式:
//定义一个int参数的无返回值的委托
delegate void MyFun(int k);

class Solution
{
	MyFun myfun;

	public void haha(int param){...}
	public void hehe(int param){...}

	static void Main(string[] args)
	{
		//添加委托
		myfun += haha;
		myfun += hehe;
		//调用
		myfun(1);
	}
}
  • 4.系统自带委托
    Action:无参无返回值函数委托
Action action = Fun;
action += Fun2;
action();

Func:泛型返回值无参数委托

Func<string> funcString = Fun4;
Func<int> funcInt = Func5;

Action<>:可以传n个参数无返回值函数委托

Action<int,string,bool,K,V> action2 = Fun6;

Func<>:可以传n个参数有返回值的函数委托

//最后一个参数为返回值,带out关键字
Func<int ,string,bool,K,V,int> action3 = Fun7;

9.事件

概念:基于委托,是委托的安全包裹,让委托更具有安全性。是一种特殊的变量类型。
语法访问修饰符 event 委托类型 事件名
使用

  • 1.作为成员变量存在于类中
  • 2.委托怎么用,事件就怎么用
  • 3.与委托的区别:
    – 1.不能在类的外部赋值
    – 2.不能在类的外部调用
    – 3.事件不能被作为临时变量使用,委托可以。
    – 4.事件只可以使用+,-=进行添加或移除函数,委托任意。
  • 4.它只能作为成员存在于类,接口以及结构体中。

10.匿名函数

概念:没有名字的函数,主要配合委托和事件使用
语法delegate (参数列表)
样例

//声明匿名函数
Action a = delegate ()
{
	
}

a();
a.Invoke();

缺点:添加到委托或事件容器中不记录,无法单独移除。

11.Lambda表达式

概念:匿名函数的简写,与委托或者事件配合使用。
语法(参数列表)=>{}
使用

  • 1.无参无返回值
Action a = () => {...}
a();

//甚至参数类型可以省略,与委托容器一致
Action<int> a2 = (value) => {...}

缺点:和匿名函数一样。
补充

  • 1.内层函数可以引用包含在它外层的函数的变量,即使外层的函数执行已经终止。
  • 2.该变量提供的值并非变量创建时的值,而是在父函数范围内的最终值。

12.List排序

匿名函数排序案例

//声明一个List
List<Item> itemList = new List<Item>();
itemList.Add(...);

itemList.Sort(delegate (Item a ,Item b) => {
	if (a.id > b.id)
	{
		return 
	}
});

//lambda表达式写法
itemList.Sort(( a , b )=>{
	//升序
	return a.id > b.id ? 1 : -1; 
})

13.协变逆变

协变:和谐的变化,自然的变化,里氏替换原则,子类转父类。父类泛型委托装子类泛型委托
逆变:逆常规变化,父类转子类。子类泛型委托装父类泛型委托
作用

  • 1 仅能用于泛型接口和泛型委托中
  • 2.用out修饰的泛型,只能作为返回值
  • 3.用in修饰的泛型,只能作为参数
  • 4.遵循里氏替换原则的 用out和in修饰的 泛型委托 可以相互装载(有父子逻辑)。

14.多线程

进程:操作系统下 可以进行许多进程(前台,后台)。进程之间相互独立运行,互不干扰。也可以相互访问,操作…
线程:操作系统能够运算调度的最小单位。其被包含在进程之中,是进程的实际运作单位。一个进程可以并发多个线程。

语法:所需类:using System.Threading;
声明:Thread t = new Thread(无参无返回值委托函数);
开启线程:t.Start();
设置后台线程:t.IsBackground = true;
中止线程:t.Abort(); t = null
线程休眠:Thread.Sleep(1000);//线程休眠1s

锁机制lock

  • 1.语法lock(引用变量){...}
  • 2.用处:处理一些 寻路,网络通信等复杂计算 算法。

15.俄罗斯方块实践

场景模块核心类图
在这里插入图片描述

Copyright © 2010-2022 kler.cn 版权所有 |关于我们| 联系方式|豫ICP备15888888号