C#语言基础
文章目录
1.编程语言介绍
一些主流编程语言:
- 1.
C
:嵌入式硬件开发,操作系统底层 - 2.
C++
:游戏客户端,服务器,软件 - 3.
C#
:游戏客户端,服务器,软件,网站 - 4.
Java
:安卓,服务器,软件,网站 - 5.
JavaScript
:网站,服务器 - 6.
PHP
:网站,服务器 - 7.
Python
:爬虫,AI,机器学习,数据分析,自动化运维测试… - 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.俄罗斯方块实践
场景模块核心类图