【C#】C#学习笔记(面向U3D游戏开发)

前言

本文仅用于记录C#学习过程中的知识点,由于有一定计算机基础,所以不会面面俱到。
.NET 8文档:https://learn.microsoft.com/zh-cn/dotnet/api/system


C#命名规范

参考:C#操作手册(一):命名规范
1、【强制】类名命名规则:大驼峰命名法【DTO、POCO、VO等除外】

public class UserInfo{}

2、【强制】属性命名规则:大驼峰命名法

public string UserInfo { get; set; }

3、【强制】字段、参数、成员变量、局部变量命名规则:小驼峰命名法

public string userName;
public string GetUserName(string userId) { return "userName"; }

4、【强制】方法/函数命名规则:大驼峰命名法

public int GetUserInfo() { }

5、【强制】常量命名规则:名称全部大写,单词间用下划线_分开

public const string USER_NAME = "userinfo";

6、【强制】DTO、POCO、VO命名规则:大驼峰命名法+DTO/VO/POCO等

public class UserInfoDTO { }
public class UserInfoVO { }
public class UserInfoPOCO { }

7、【强制】命名空间命名规则:大驼峰命名法

namespace UserInfo { }

8、【强制】枚举命名规则(枚举名称采用大驼峰命名规则,枚举成员所有名称也使用大驼峰命名法)(没有特殊情况的话,枚举成员建议从默认值0开始)

public enum UserState
    {
        Success,
        Fail
    }

9、【强制】代码中所有成员禁止直接使用中文的命名方式,禁止使用中文拼音命名(一些通用的命名除外:比如城市可以采用beiJing、shangHai这样的命名规则是可以的),禁止使用中英文混合命名方式,禁止出现a、b、c、aa、ss、x、xx等毫无意义的命名方式
10、【推荐】复数类型(集合类、数组等)命名规则:优先以小写字符s结尾,如果单词最后的字母就是s或其他不适合s结尾的单词,可以使用复数类型的类型名称结尾(如List、Array等结尾)。前面规则如果都不好命名,可自行命名

public List<string> userNames { get; set; }
public string[] userNameArray { get; set; }
public List<string> userNameList { get; set; }

11、【强制】接口命名规则:以大写字母I开头+类名称

public interface IUserInfo { }

12、【强制】异常类命名规则:大驼峰命名法+Exception

public class UserInfoException { }

13、【强制】项目命名规则:大驼峰命名法,各个字母之间用字母(.)隔开

XiongZe.ProjectManagement.Services

14、【推荐】业务层和数据层名命名规则:业务层类库名称命名以Service结尾、数据层类库命名以Repository结尾


C#基础知识

一、C#的数据类型

1.1 值类型

类型描述范围默认值
bool布尔值true 或 falsefalse
byte8位无符号整数0 ~ 2550
sbyte8 位有符号整数类型-27 ~ 27 - 10
char16 位 Unicode字符U+0000 ~ U+FFFF‘\0’
decimal128 位精确的十进制值,28-29 有效位数-7.9 x 1028 ~ 7.9 x 1028 / 100 ~ 280.0M
float32 位单精度浮点型,6-7 有效位数-3.4 x 1038 ~ +3.4 x 10380.0F
double64 位双精度浮点型,15-16 有效位数±5.0 x 10-324 ~ ±1.7 x 103080.0D
short16 位有符号整数类型-215 ~ 215 - 10
int32 位有符号整数类型-231 ~ 232 - 10
long64 位有符号整数类型-263 ~ 263 - 10L
ushort16 位无符号整数类型0 ~ 216 - 10
uint32 位无符号整数类型0 ~ 232 - 10
ulong64 位无符号整数类型0 ~ 264 - 10

注:C#中bool和int之间无法直接进行转换。

1.2 引用类型

1.2.1 字符串类型(String)

字符串的定义过程中,String和string在使用过程中没有本质区别,最终都会映射到 System.String 类。

// 下面两种定义方式是等价的
string str = "abc";
String str = "abc";

在使用字符串路径过程中,若需要用到反斜杠,为方便使用,可以在前面加上@,此时不会再将反斜杠编译为转义符,类似于Python的r"x:\xxx"。当然也可以使用正斜杠,就不存在该问题了。

// 下面两种定义方式是等价的
string path = @"C:\Windows";
string path = "C:\\Windows";

@ 字符串中还可以任意换行,换行符及缩进空格都计算在字符串长度之内。类似于Python的"““xxxx””"。

// 下面两种定义方式是等价的
string str = @"aaa
bbb
ccc";
string str = "aaa\nbbb\nccc";

字符串的拼接可以采用+的方式。

// 下面两种拼接方式是等价的
string str = 5 + "5";
string str = "5" + "5";
1.2.2 对象类型(Object)

参考:object(C# 参考)
在 C# 的统一类型系统中,所有类型(预定义类型、用户定义类型、引用类型和值类型)都是直接或间接从 Object 继承的。 可以将任何类型的值赋给 object 类型的变量。

将值类型的变量转换为对象的过程称为“装箱”;将对象类型的变量转换为值类型的过程称为“取消装箱”。
在下面的示例中,整型变量num装箱并赋给对象obj。

int num = 123;
object obj = num;

然后,可以对对象obj取消装箱并将其赋值给整型变量num:

object obj = 123;
num = (int)obj;
1.2.3 动态类型(Dynamic)

用户自定义引用类型有:class、interface和delegate。

1.3 指针类型

在C#中,指针类型要在不安全的上下文中才可以使用。

二、C#运算符优先级

下表按最高优先级到最低优先级的顺序列出 C# 运算符,每行中运算符的优先级相同。

运算符类别或名称
x.y、f(x)、a[i]、x?.y、x?[y]、x++、x–、x!、new、typeof、checked、unchecked、default、nameof、delegate、sizeof、stackalloc、x->y主要
+x、-x、x、~x、++x、–x、^x、(T)x、await、&&x、*x、true 和 false一元
x…y范围
switch、with switch 和 with表达式
x * y、x / y、x % y乘法
x + y、x – y加法
x << y、x >> yShift
x < y、x > y、x <= y、x >= y、is、as关系和类型测试
x == y、x != y相等
x & y布尔逻辑 AND 或按位逻辑 AND
x ^ y布尔逻辑 XOR 或按位逻辑 XOR
x | y布尔逻辑 OR 或按位逻辑 OR
x && y条件“与”
x || y条件“或”
x ?? yNull 合并运算符
c ? t : f条件运算符
x = y、x += y、x -= y、x *= y、x /= y、x %= y、x &= y、x |= y、x ^= y、x <<= y、x >>= y、x ??= y、=>赋值和 lambda 声明

三、分支语句和循环控制语句

除去switch语句以外,与C++语法基本一致。
在C++中,switch语句可以不加break;但在C#中一定要加break。


附:foreach用法

/* 将collection当中的所有元素逐个取出, 每次取出的元素被命名为item
类似于Python当中的
for item in collection:
	# 循环体
但是foreach可以将二维数组当中的所有元素也逐个取出, 而不需要两层遍历
*/
foreach (var item in collection) {
	//循环体
}

四、数组

4.1 一维数组

动态初始化

// length个默认值的数组, 对于int, 就是length个0
int[] nums = new int[length];
// 也可以直接设定好数值, 这时长度必须为常量, 且与后面的数字个数一致
int[] nums = new int[3]{1, 2, 3};
// 在设定好数值时, 可以隐去设定长度
int[] nums = new int[]{1, 2, 3};

静态初始化

int[] nums = {1, 2, 3};

访问数组的长度

// 数组的长度
nums.Length

4.2 二维数组

动态初始化

int[,] nums = new int[2, 3];

静态初始化

int[,] nums = {{1, 2, 3}, 
			   {4, 5, 6}
			  };

五、自定义类型

5.1 枚举类型

// 枚举类型的定义
enum Fruit {
    Apple, 
    Banana, 
    Orange
}

// 枚举类型的实例化
Fruit myFruit = Fruit.Apple;

其中Fruit定义好之后,会自动按照从上到下的顺序为其分配0 ~ n - 1的值。也就是说,此时定义好的myFruit的值为0。
还可以手动给各种枚举值进行赋值

// 可以采用整型和字符型进行赋值, 其他的还有类似于byte, sbyte, long, short等都是可以的
enum Fruit {
    Apple = 10, 
    Banana = 'a', 
    Orange = 1
}

对于枚举类型,是不可以隐式转换为整型的,所以需要强制类型转换

// 以下两种转换方式都可以
int num = (int)myFruit;
int num = Convert.ToInt32(myFruit);
// 将整型强制转换回枚举类型
myFruit = (Fruit)5;

5.2 结构体

结构体的创建与初始化

// 结构体的定义
struct Student {
	public string name;
	public int age;
}

// 实例化一个结构体
Student xiaoMing;
Student xiaoMing = new Student();
// 初始化数据
xiaoMing.name = "小明";
xiaoMing.age = 18;

结构体的构造函数。可以给出默认值,若给出默认值,不放入参数初始化时将对其赋予默认值。

// 带有构造函数的结构体
struct Student {
	public string name;
	public int age;
	// 结构体的构造函数, 并且可以直接赋上默认值
	public Student(string name="", int age=0) {
		// 这里的this与C++的this指针相仿
		this.name = name;
		this.age = age;
	}
}

// 此时就可以直接调用构造函数初始化
Student xiaoMing = new Student(name, age);

当然,结构体当中除了构造函数,还可以写其他的函数。

// 带有构造函数的结构体
struct Student {
	public string name;
	public int age;
	// 结构体的构造函数
	public Student(string name="", int age=0) {
		this.name = name;
		this.age = age;
	}
	
	// 打印学生所有信息
	public void PrintStudentInfo() {
        Console.WriteLine(name);
        Console.WriteLine(age);
    }
}

// 打印小明的信息
Student xiaoMing = new Student("小明", 18);
xiaoMing.PrintStudentInfo();

附:结构体不能用作链表、二叉树等节点的定义,这些应该用类去定义。

六、访问修饰符

6.1 访问修饰符简介

public:同一程序集中的任何其他代码或引用该程序集的其他程序集都可以访问该类型或成员。
protected:同一类或结构此类的派生类中的代码才可以访问的类型或成员。
private:同一类或结构中的代码可以访问该类型或成员。
internal:同一程序集中的任何代码都可以访问该类型或成员,但其他程序集中的代码不可以。
protected internal:同一程序集中的任何代码或其他程序集中的任何派生类都可以访问该类型或成员。

6.2 默认访问修饰符

类:默认修饰符为internal。
接口:默认的修饰符为internal。
结构体:默认的修饰符为internal。
枚举:默认的修饰符为internal。
委托:默认的修饰符为interna。

枚举:默认的修饰符为public,且不可修改。
结构体:默认的修饰符为private,不可使用protected。
类成员:默认修饰符为private。
接口成员:默认的修饰符为public,且不可修改。
委托:默认的修饰符为private。

七、函数

7.1 函数的基础运用

最简单的函数使用

/* 基本的函数语法
修饰符 返回值类型 函数名 (参数1, 参数2, ...) {
	// 函数体
}
*/
// 打印str
public static void Print(string str) {
	Console.WriteLine(str);
}

7.2 ref的使用

ref的作用是将值类型的参数转化引用类型来用。加上ref的前缀以后,原本传入的值将变为该值的地址。类似于C++的&

// 交换两数, 其中参数类型前加上ref就可以强制变为引用类型
public static void Swap(ref int num1, ref int num2) {
    int temp = num2;
    num2 = num1;
    num1 = temp;
}

int num1 = 1;
int num2 = 2;
// 调用函数时也要加上ref
Swap(ref num1, ref num2);

注:使用ref的变量必须赋予初值,否则在未分配内存地址的状态下是无法使用ref的。

7.3 out的使用

out与ref的使用时的写法一致,都需要在函数的定义和调用中写明。out的作用是将参数当做一个输出通道,可以将结果放入该变量。

// 两数之和
public static Add(int num1, int num2, out int ans) {
	ans = num1 + num2;
}

int num1 = 5;
int num2 = 10;
int ans;
// 调用函数
Add(num1, num2, out ans);

注:使用out的变量必须在函数中赋值

7.4 函数的重载

参数不同的同名函数定义便是重载

public int Add(int num1, int num2) {
	return num1 + num2;
}
public float Add(float num1, float num2) {
	return num1 + num2;
}

7.5 递归函数

递归函数就是自己调用自己。计算阶乘就是一个简单的应用。

// 计算阶乘
public static int Factorail(int num) {
    if (num == 0) {
        return 1;
    }
    else {
        return Factorail(num - 1) * num;
    }
}

还可以实现类似二叉树遍历的算法。

// 中序遍历
public void InOrder(TreeNode root, IList<int> ans) {
	// 该节点遍历结束
    if (root == null) {
        return;
    }
    // 左根右顺序遍历
    InOrder(root.left, ans);
    ans.Add(root.val);
    InOrder(root.right, ans);
}

// 记录中序遍历结果
public IList<int> InorderTraversal(TreeNode root) {
	// 记录结果
    IList<int> ans = new List<int>();
    InOrder(root, ans);
    return ans;
}

八、类和对象

8.1 类的基础运用

类是一系列拥有相同属性方法的实例的抽象,每个对象都是类的一个实例,一个类可以有很多个对象。

// 类的创建
public class Person {
	// 成员变量
	public string name;
	public int age;
	// 构造函数
	public Person(string name="", int age=0) {
		this.name = name;
		this.age = age;
	}

	// 类内方法
	public void Eat(string food) {
		Console.WriteLine($"{name}吃了一个{food}");
	}
}

// 实例化一个对象
Person xiaoMing = new Person("小明", 18);
// 调用方法
xiaoMing.Eat("苹果");

类内为了方便读写私有成员变量,设计了属性机制,类似于Python的property()。

class Person {
    // 成员变量
    private string name;
	// name的属性
    public string Name {
        get { return name; }
        set { name = value; }
    }
    
    // 构造函数
    public Person(string name) {
        this.name = name;
    }
}

Person xiaoMing = new Person("小明");
// 读取名字
string name = xiaoMing.Name;
// 写入名字
xiaoMing.Name = "小茗";

还有一些简写属性的写法

// 可读可写的两种简写写法
public string Name { get; set; }
public string Name {
	get => name;
	set => name = value;
}
// 可读不可写的三种简写写法
private string name;
public string Name => name;
public string Name { get; }
public string Name { get; private set; }

自动实现的属性,像{ get; }这种写法在类型是string时会出现警告CS8618,有时无法正常工作。所以简写时需要注意场合。


附:二叉树节点的定义

// 以下两种定义方式是等价的
public class TreeNode {
    public int val;
    public TreeNode? left;
    public TreeNode? right;
    public TreeNode(int val=0, TreeNode? left=null, TreeNode? right=null) {
        this.val = val;
        this.left = left;
        this.right = right;
    }
}

class TreeNode(int val=0, TreeNode? left=null, TreeNode? right=null) {
    public int val = val;
    public TreeNode? left = left;
    public TreeNode? right = right;
}

类弥补了结构体不能引用自身的缺陷,所以可以使用类来定义类似的节点。

8.2 继承

基础的继承语法

// 这里表示学生类继承人类
class Person {}
class Student: Person {}

继承自父类的子类可以继承其访问修饰符为public和protected的字段、属性、方法等。
另外,如果父类的构造函数只有有参数的构造函数,而没有重载无参构造函数时,那么在子类写构造函数的时候应当用base()。这时才能在实例化子类时正常构造父类,否则父类在构造时无法调用有参的构造函数,而由于没有无参构造函数,也没有可以调用的无参构造函数,所以就无法调用构造函数。因为在实例化子类时,一定是先构造父类再构造子类的,所以无法调用父类构造函数,自然就表示无法实例化成功子类。
这里的name是string类型的,要保证在退出构造函数时,字段name必须包含非null值(CS8618警告),所以这个例子当中必须有参,至少也要对name进行初始化。

class Person {
	public string name;
	protected char sex;
	private int age;

	// 只有有参构造函数
    public Person(string name, char sex, int age) {
        this.name = name;
        this.sex = sex;
        this.age = age;
    }

	public int Age {
		get => age;
		set => age = value;
	}

	// 打印个人信息
    protected void PrintPerInfo() {
        Console.WriteLine(name);
        Console.WriteLine(sex);
        Console.WriteLine(age);
    }
}

class Student: Person {
    public string school;

	// 这时需要用base函数给父类构造函数传参, 以此来调用父类构造函数
	public Student(string name, char sex, int age, string school): base(name, sex, age) {
        this.school = school;
    }

	// 打印学生信息
    public void PrintStuInfo() {
    	// 可以调用父类的函数
        PrintPerInfo();
        Console.WriteLine(school);
    }

	// 修改信息
	public void UpdateInfo(string name, char sex, int age, string school) {
		// public和protected直接调用没问题
		this.name = name;
		this.sex = sex;
		// private字段不能直接调用
		Age = age;
		this.school = school;
	}
}

C#不支持多重继承,不过可以用接口来实现多重继承。

8.3 多态

多态分为静态多态性和动态多态性。

8.3.1 静态多态性

C#提供了函数重载和运算符重载来实现静态多态性。
1)函数重载见7.4 函数的重载
2)运算符重载的实现方法:

class Line {
    public double length;
    public Line(double length) {
        this.length = length;
    }

	// 实现了两个线段相加, 此时就可以直接用Line + Line的操作了
    public static Line operator+ (Line l1, Line l2) {
        return new Line(l1.length + l2.length);
    }
}
8.3.2 动态多态性

动态多态性是用抽象类、抽象方法和虚方法来实现的,即abstract和virtual。
1)抽象类和抽象方法实现的动态多态性。

// 抽象类不可以被实例化, 且不可以与sealed关键字同时使用
abstract class Shape {
	// 抽象方法一定要在抽象类中, 且抽象方法是不允许声明主体的
    abstract public double Area();
}

// 正方形类
class Square: Shape {
    public double width;
    // 类内一定要实现继承的抽象类当中的抽象方法, 实现的方法前面要加上override
    public override double Area() {
        return width * width;
    }
}

// 长方形类
class Rectangle: Shape {
    public double width;
    public double height;
    // 类似的长方形面积计算实现
    public override double Area() {
        return width * height;
    }
}

2)虚方法实现的动态多态性

// 抽象类当中是可以放除了抽象方法之外的方法的
abstract class Shape {
	// 相比抽象方法, 虚方法是一定要声明主体的
    public virtual void Draw() {
        Console.WriteLine("开始绘画图形……");
    }
}

class Square: Shape {
    public override void Draw() {
    	// 先执行Shape的绘画函数(这里语法上不强制执行父类的函数)
        base.Draw();
        // 再执行自己的语句
        Console.WriteLine("绘制正方形成功");
    }
}

class SquareToRectangle: Square {
    public override void Draw() {
    	// 这里执行时会执行Square的绘画函数
        base.Draw();
        // 由于Square的绘画函数会调用Shape的绘画函数, 所以这里是三句话都打印
        Console.WriteLine("将正方形修改为长方形成功");
    }
}

附:static和sealed的使用方法
static修饰符可用于类、字段、方法、属性、运算符、事件和构造函数,但不能用于索引器、析构函数或类以外的类型。

// 静态类无法被实例化, 一般用来写工具类
static class 类名 {
	// 静态成员(静态类不能有非静态的成员)
	public static 变量类型 变量名;
	// 静态函数的声明方法
	public static 返回类型 函数名() {
		// 函数体
	}
}
// 调用方法
类名.变量名;
类名.函数名();

sealed的使用方法:

// 此时表示该类为密封类, 任何其他类都不得继承它
sealed class 类名 {}
// 此时表示该函数为密封函数, 继承自该函数所在类的子类都无法重写该函数
访问修饰符 sealed override 返回类型 函数名() {}

九、接口

接口的定义与类类似,不过能存放的只有属性、方法、委托。

interface IUSB {
    public int USBType { get; set; }
    // 接口内是不能实现方法的
    public void WriteFile(string content);
}

// Computer实现接口ISUB
class Computer: IUSB {
	// 一般类内一定要实现所有接口内的成员
	public int USBType { get; set; }
    public void WriteFile(string content) {
		Console.WriteLine("写入文件成功");
	}
}

// 抽象类内的抽象方法便不用实现
abstract class Computer: IUSB {
    public int USBType { get; set; }
    abstract public void WriteFile(string content);
}

interface IVGA {}

// 接口可以实现多个其他接口, 但内部仍然无法声明, 只能定义
interface ITypeC: IUSB, IVGA {}

class Machine {}

// 假如一个类要继承另一个类和若干个接口, 那么应该先写类
class Computer: Machine, IUSB, IVGA {}

十、泛型

泛型类似于C++的模板类,可以不用指定特定的类型,只给出一个抽象的类型,在调用的时候再指定类型去调用。

// 其中T便是给出的抽象类型
public static void Print<T>(T content) {
	Console.WriteLine(content);
}

// 调用
Print<int>(1);
// 还可以简写
Print(1);

// 若需要不止一个抽象类型时, 就用逗号隔开各抽象类型即可
public static void PrintTwoInfo<T, V>(T content1, V content2) {
	Console.WriteLine(content1);
	Console.WriteLine(content2);
}

// 调用
PrintTwoInfo<string, int>("abc", 123);
// 也可以简写
PrintTwoInfo("abc", 123);

泛型还可以使用在类和接口上,使用方法都是在类名或接口名后面加上<类型名, …>。

class Test<T> {}
interface ITest<T> {}

// 在继承父类或实现接口时, 就需要对这些泛型进行确定, 即使确定的类型还是泛型也可以
class Test2: Test<int> {}
interface ITest2<T>: ITest<T> {}

泛型还可以被约束,采用where关键字来约束。
约束的类型有以下几种:
1)new()约束:表示不能为抽象类型。
2)基类/接口约束:表示<>里面必须是基类/接口本身或者是派生自基类的其他类/接口。
3)引用/值类型约束:用class/struct来约束只能为引用/值类型。
4)组合约束:第一项必须是引用/值类型约束或基类约束,接下来是接口约束,最后是new()约束。
class、struct、unmanaged、notnull、default约束不能组合或重复,并且必须先在约束列表中进行指定。

// 语法: 名称<泛型>(参数) where 泛型1: 约束1, 约束2, ... where 泛型2: 约束, ...
public static Print<T, V>(T content1, V content2) 
	where T: new()
	where V: class {
	// 类的主体部分
}

注:构造函数不用加<泛型>

十一、委托和事件

委托是一种引用类型,表示对具有特定参数列表和返回类型的方法的引用。 在实例化委托时,你可以将其实例与任何具有兼容签名和返回类型的方法相关联。 你可以通过委托实例调用方法。

public static void BuyApple() {
    System.Console.WriteLine("买了一个苹果");
}

public static void BuyBanana() {
    System.Console.WriteLine("买了一个香蕉");
}

// 定义一个委托
public delegate void BuyFruitEventHandle();
// 实例化一个委托
BuyFruitEventHandle buyApple = new BuyFruitEventHandle(BuyApple);
// 调用
buyApple();

// 委托还可以多播
BuyFruitEventHandle buyFruit = new BuyFruitEventHandle(BuyApple);
buyFruit += BuyBanana;
// 这时调用就会先后调用BuyApple和BuyBanana
buyFruit();

事件就是一个特殊的委托对象,它在类内可以=、+=、-=和调用,但是在类外只能+=和-=。且事件的定义应该在函数的外面。

class BuyFruit {
	public delegate void BuyFruitEventHandle();
	public event BuyFruitEventHandle buyFruitEvent = BuyApple;
	public static void BuyApple() {
        System.Console.WriteLine("买了一个苹果");
    }

    public void TestEvent() {
    	// 类内四种操作都可以做
        buyFruitEvent = BuyApple;
        buyFruitEvent += BuyApple;
        buyFruitEvent -= BuyApple;
        buyFruitEvent();
    }
}

class Test {
    static void Main(string[] args) {
        BuyFruit bf = new BuyFruit();
        // 类外就只能做这两种操作
        person.buyFruitEvent += Person.BuyApple;
        person.buyFruitEvent -= Person.BuyApple;
        // 想要调用只能通过类内的其他方法间接调用
        bf.TestEvent();
    }
}

十二、其他

使用throw抛出异常

// 提醒内容为content的异常抛出
throw new SystemException(string content);

常用数据结构

一、动态数组(ArrayList和List)

类似于Python的list,可以存放任意类型,可以自由添加删除元素,但效率较低。

// 定义一个动态数组
ArrayList list = new ArrayList();
// 数组元素个数
int n = list.Count;
// 添加一个元素, 返回元素添加的序列
int list.Add(object? value);
// 删除所有元素
void list.Clear();
// 判断元素是否在动态数组中
bool list.Contains(object? item);
// 在index位置插入一个元素
void list.Insert(int index, object? value);
// 删除第一个匹配项
void list.Remove(object? obj);
// 删除指定序列的元素
void list.RemoveAt(int index);
// 删除从index开始的count个元素
void list.RemoveRange(int index, int count);
// 反转列表元素
void list.Reverse();
// 反转从index开始的count个元素
void list.Reverse(int index, int count);
// 对内部元素进行排序
void list.Sort();

动态数组还有类似的List,它的用法除了固定了元素的类型,其余基本与ArrayList一致,就不展开说明了。

二、栈(Stack)和队列(Queue)

栈的使用

// 实例化一个栈
Stack stack = new Stack();
// 栈元素个数
int n = stack.Count;
// 删除栈内所有元素
void stack.Clear();
// 查找元素obj是否存在于栈内
bool stack.Contains(object? obj);
// 返回栈顶元素, 但不会弹出该元素
object? stack.Peek();
// 将栈顶元素弹出并返回
object? stack.Pop();
// 将一个元素压入栈
void stack.Push(object? obj);

队列的方法与栈基本一致,只是入队和出队有些差别。

// 实例化一个队列
Queue que = new Queue();
// 队列元素个数
int n = que.Count;
// 将一个元素放到队尾
void que.Enqueue(object? obj);
// 弹出并返回队首元素
object? que.Dequeue();

三、集合(HashSet和SortedSet)

集合的基本操作依旧是那几个方法,只是多了一些集合特有的方法,例如并、交。

// 实例化一个集合
HashSet<int> set = new HashSet<int>();
// 判断other是否为set的完全子集
bool set.IsProperSubsetOf(IEnumerable<int> other);
// set ∪ other
void set.UnionWith(IEnumerable<int> other);
// set ∩ other
void set.IntersectWith(IEnumerable<int> other);
// set - other
void set.ExceptWith(IEnumerable<int> other);

SortedSet和HashSet相比,就是多了一个元素自动排序的功能,放入的元素将按由小到大的顺序排列。

四、哈希表(Hashtable)和字典(Dictionary)

哈希表和字典都是键值对应的一种数据结构,只不过字典必须指定类型,哈希表不需要指定类型。也因此,哈希表需要装箱拆箱的操作,而字典则不需要。所以论效率是字典的速度更快,而想要什么类型都能放就需要用哈希表了。

// 实例化一个哈希表
Hashtable map = new Hashtable();
// 哈希表键值对个数
int n = map.Count;
// 哈希表所有的键
map.Keys;
// 哈希表所有值
map.Values;
// 添加一个键值对
void map.Add(object key, object? value);
// 判断是否存在key为键的键值对
bool map.Contains(object key);
bool map.ContainsKey(object key);
// 判断是否存在value为值的键值对
bool map.ContainsValue(object? value);
// 删除以key为键的键值对
void map.Remove(object key);

字典与哈希表常用的方法类似,只是字典在访问不存在的键时会报错,这点需要注意。

// 实例化一个字典
Dictionary<int, int> dict = new Dictionary<int, int>();
// 尝试获取key所对应的值, 并将获取的值给value, 返回是否成功获取值
bool dict.TryGetValue(int key, out int value);

五、堆/优先队列(PriorityQueue)

堆又称优先队列,分为小根堆和大根堆。在C#中,PriorityQueue默认为小根堆。
PriorityQueue有两个泛型,第一个TElement是堆存放的元素,并不是堆化时优先级判断的标准;TPriority是用来判断堆化的优先级的,由于是小根堆,所以越小越靠近根。

// 实例化一个堆
PriorityQueue<int, int> heap = new PriorityQueue<int, int>();
// 将element放入堆
void heap.Enqueue(int element, int priority);
// 弹出头一个元素
int heap.Dequeue();

六、双向链表(LinkedList)

双向链表可以用来模拟双端队列。

LinkedList<T> linkedList = new LinkedList<T>();
# 添加
linkedList<T>.AddFirst(LinkedListNode<T> node);
linkedList<T>.AddFirst(T value);
linkedList<T>.AddLast(LinkedListNode<T> node);
linkedList<T>.AddLast(T value);
# 查看
linkedList.First.Value
linkedList.Last.Value
# 删除
linkedList.RemoveFirst();
linkedList.RemoveLast();

System命名空间

包含定义常用值和引用数据类型、事件和事件处理程序、接口、属性以及处理异常的基本类和基类。

一、Console类

表示控制台应用程序的标准输入流、输出流和错误流。 此类不能被继承。

1.1 输出语句

// 末尾不会输出\n, 类似于Python的print(end="")
Console.Write();
// 末尾默认输出\n, 类似于Python的print()
Console.WriteLine();

输出字符串需要拼接时可以采用+或占位符进行输出。

// 下面两种输出方式等价
Console.WriteLine("aaa" + "bbb");
Console.WriteLine("{0}{1}", "aaa", "bbb");

其中占位符的0和1可以互换。

Console.WriteLine("{1}{0}", "bbb", "aaa");

还有一种更好用的方法,就是在字符串的前面加上$符号,类似于Python的f"xxx"。
并且这种方法不仅仅可以用于输出,由于它本身就是一个字符串,所以也可以参与运算。

int num = 5;
// 输出时使用
Console.WriteLine($"这里有{num}片叶子");
// 赋值时使用
string str = $"这里有{num}片叶子";

1.2 输入语句

// 读取一个键
Console.ReadKey();
// 读取一行输入
Console.ReadLine();

我运行这两句话时候没问题,但是不能让其返回值赋值到变量上,有一个警告说返回可能为null和一个报错说无法转换类型,不过我基本用不到这个语句,就没再理会了。

二、Convert类

将一个基本数据类型转换为另一个基本数据类型。

// 比较典型的类型转换函数, 其他还有很多
Convert.ToBoolean();
Convert.ToChar();
Convert.ToDouble();
Convert.ToInt32();
Convert.ToSingle();

除了这种转换方式,还可以用数据类型自带的方法Parse()来转换。

bool num = bool.Parse(val);
char num = char.Parse(val);
int num = int.Parse(val);
float num = float.Parse(val);
double num = double.Parse(val);

将其他类型变为字符串可以用数据类型自带的方法ToString()来转换。

string str = val.ToString();

三、Random类

表示伪随机数生成器,这是一种能够产生满足某些随机性统计要求的数字序列的算法。

// 首先要实例化
Random rand = new Random();
// 制定一个种子进行初始化
Random rand = new Random(int seed);
// 随机返回一个32位非负整数
int rand.Next();
// 在0 ~ maxValue的左闭右开的区间随机返回一个整数, 如果maxValue == 0, 那么返回0
int rand.Next(int maxValue);
// 在minValue ~ maxValue的左闭右开的区间随机返回一个整数, 如果minValue == maxValue, 那么返回minValue
int rand.Next(int minValue, int maxValue);
// 随机返回一个64位非负整数
long rand.NextInt64();
// 在0 ~ maxValue的左闭右开的区间随机返回一个整数, 如果maxValue == 0, 那么返回0
long rand.NextInt64(long maxValue);
// 在minValue ~ maxValue的左闭右开的区间随机返回一个整数, 如果minValue == maxValue, 那么返回minValue
long rand.NextInt64(long minValue, long maxValue);

四、String类

将文本表示为 UTF-16 代码单元的序列。

// 判断strA与strB的字典序, strA在前返回-1, 一样返回0, strA在后返回1
int String.Compare(string strA, string strB);
// 类似的, 与Compare一致
int strA.CompareTo(string strB);
// 判断strA中是否包含strB
bool strA.Contains(string strB);
// 返回strA中第一次出现strB的位置, 若从未出现过, 则返回-1
int strA.IndexOf(string strB);
// 返回strA中从startIndex位置开始第一次出现strB的位置, 若从未出现过, 则返回-1
int strA.IndexOf(string strB, int startIndex);
// 将strA在startIndex处插入一个strB, 并返回结果
string strA.Insert(int startIndex, string strB);
// 返回序列为0 ~ startIndex - 1的字符串
string str.Remove(int startIndex);
// 将str当中的oldValue替换为newValue, 并返回结果
string str.Replace(string oldValue, string newValue);
// 将strA在strB处切开, 并返回切分后的结果
string[] strA.Split(string strB);
// 从strA中在startIndex开始的位置取出length长度的字符串并返回
string strA.Substring(int startIndex, int length);
// 去除str首尾的空白, 包括空格, \n, \t等, 并返回结果
string str.Trim();
// 去除str尾部的空白, 包括空格, \n, \t等, 并返回结果
string str.TrimEnd();
// 去除str首部的空白, 包括空格, \n, \t等, 并返回结果
string str.TrimStart();

System.IO命名空间

包含允许读取和写入文件和数据流的类型,以及提供基本文件和目录支持的类型。

一、File类

提供用于创建、复制、删除、移动和打开单一文件的静态方法,并协助创建 FileStream 对象。

// 将content写入path指向的文件
File.WriteAllText(string path, string content);

System.Text命名空间

包含表示 ASCII 和 Unicode 字符编码的类;用于字符块和字节块相互转换的抽象基类;以及无需创建 String 的中间实例即可操作 String 对象并设置其格式的帮助程序类。

一、StringBuilder类

表示可变字符字符串。 此类不能被继承。在需要经常修改字符串时常用。

using System.Text
// 创建字符串
StringBuilder str = new StringBuilder(string value);
// 追加字符串strB
StringBuilder strA.Append(string strB);
// Format形式追加函数和示例
StringBuilder str.AppendFormat(string format, params object[] args);
str.AppendFormat("吃{0}喝{1}", "饭", "水");
// 将str在index处插入一个value
StringBuilder str.Insert(int index, string value);
// 删除str从序列startIndex开始长度为length的子串
StringBuilder str.Remove(int startIndex, int length);
// 将str当中的oldValue替换为newValue
StringBuilder str.Replace(string oldValue, string newValue);

UnityEngine命名空间

一、MonoBehaviour类

继承的变量:

变量作用
enabled当前对象的可用性
isActiveAndEnabled报告游戏对象及其关联的行为是否处于活动状态并启用
gameObject当前游戏对象
tag对象的标签
transform挂载到该对象的Transform组件
hideFlags该对象应该隐藏、随场景一起保存还是由用户修改
name对象的名称

常见的生命周期方法包括以下几种:
Awake:当脚本实例被加载时调用。这个方法在所有的Start方法之前调用,即使该脚本的对象未被启用也会调用。
OnEnable:当对象被启用时调用,这发生在Awake之后,Start之前。
Start:当游戏开始后,对象被启动时调用,但是整个游戏过程只调用一次。
FixedUpdate:在每个物理步骤中调用,与帧率无关,适合进行物理相关的计算,默认间隔时长0.02s。
Update:每帧调用一次,适合进行常规的游戏逻辑更新。
LateUpdate:在所有Update函数调用后调用,适合进行依赖于Update的操作,例如摄像机追踪。
OnGUI:OnGUI在GUI渲染时调用,大概是每帧两次。
OnDisable:当对象被禁用或者脚本被销毁时调用。
OnDestroy:当脚本或者游戏对象被销毁时调用。

其他的方法:

// 和Debug.Log()一个效果
MonoBehaviour.print();

二、GameObject类

// 寻找其他对象, 返回对应名字的对象
GameObject.Find(string name);
// 寻找对应标签的对象
GameObject.FindWithTag(string tag);
GameObject.FindGameObjectWithTag(string tag);
// 寻找所有对应标签的对象
GameObject.FindGameObjectsWithTag(string tag);
// 游戏对象的激活状态, 只读
gameObject.activeSelf
// 设置对象的激活状态
gameObject.SetActive(bool value);
// 添加组件
gameObject.AddComponent<T>();
// 获取组件
gameObject.GetComponent<T>();

三、Vector3结构体

Vector3 vector = new Vector3(float x, float y, float z);
// 标准化
Vector3.Normalize();
vector.normalized
// 向量的模
vector.magnitude
// 模的平方
vector.sqrMagnitude
// (1, 0, 0)
Vector3.right
// (0, 1, 0)
Vector3.up
// (0, 0, 1)
Vector3.forward
// (0, 0, 0)
Vector3.zero
// (1, 1, 1)
Vector3.one
// 两点距离
Vector3.Distance(Vector3 a, Vector3 b);
// 两个向量的夹角
Vector3.Angle(Vector3 a, Vector3 b);
// 两个向量的叉乘
Vector3.Cross(Vector3 a, Vector3 b);
// 两个向量的点乘
Vector3.Dot(Vector3 a, Vector3 b);
// 插值函数, 在两个点之间差值, 位置是a + (b - a) * (1 + t)
Vector3.Lerp(Vector3 a, Vector3 b, float t);

四、Quaternion结构体

// 空旋转
Quaternion.identity
// z轴朝向forward所指方向
Quaternion.LookRotation(Vector3 forward);
// 插值方法
Quaternion.Lerp(Quaternion a, Quaternion b, float t);

五、Time类

// 上帧结束到这帧开始的时间间隔
Time.deltaTime
// FixedUpdate两帧间隔
Time.fixedDeltaTime
// 游戏开始到现在的时间间隔
Time.time
// 时间快慢比例
Time.timeScale

六、Mathf结构体

包含各种数学操作

七、Input类

// 按住key
Input.GetKey(KeyCode key);
// 按下key
Input.GetKeyDown(KeyCode key);
// 松开key
Input.GetKeyUp(KeyCode key);

// 0->左, 1->右, 2->中
// 按住鼠标按键
Input.GetMouseButton(int button);
// 按下鼠标按键
Input.GetMouseButtonDown(int button);
// 松开鼠标按键
Input.GetMouseButtonUp(int button);

// 虚拟轴, 八方向, 世界坐标移动
// Horizontal横向, -1左, 1右, 0中
// Vertical纵向, -1下, 1上, 0中
// Mouse X和Mouse Y
Input.GetAxis(string axisName);
// 虚拟按键
// 按住按键
Input.GetButton(string buttonName);
// 按下按键
Input.GetButtonDown(string buttonName);
// 松开按键
Input.GetButtonUp(string buttonName);

八、Object类

// 生成一个实例
Instantiate(GameObject original, Vector3 position, Quaternion rotation);
// 在t秒后销毁obj
Destroy(Object obj, float t);

九、Ray结构体

Ray ray = new Ray(Vector3 origin, Vector3 direction);
// 检测射线方向的物体
bool Physics.Raycast(Ray ray, out RaycastHit hitInfo);
// 检测射线方向所有物体
RaycastHit[] Physics.RaycastAll(Vector3 origin, Vector3 direction, float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction);

十、GUI/GUILayout类

GUILayout与GUI的区别在于前者会自动布局。

// 标签
void GUI.Label(Rect position, string text);
// 按钮, 返回是否按下
bool GUI.Button(Rect position, string text);
// 宽输入框
string GUI.TextArea(Rect position, string text);
// 单行输入框
string GUI.TextField(Rect position, string text);

Unity组件

一、Transform组件

// 世界坐标
transform.position
// 相对父级的坐标
transform.localPosition
// 世界旋转
transform.eulerAngles
transform.rotation
// 相对父级的旋转
transform.loaclEulerAngles
transform.localRotarion
// 相对父级缩放
transform.localScale

// 移动方法
transform.Translate(Vector3 translation);
transform.position += new Vector(0f, 0f, 1f);
// 旋转方法
transform.Rotate(Vector3 eulers);
transform.RotateAround(Vector point, Vector3 axis, float angle);
// 三个方向向量(朝向物体自身的前、上、右)
transform.forward
transform.up
transform.right

// 父级transform
transform.parent
// 根对象transform
transform.root
// 子对象个数
transform.chilfCount
// 获取子物体transform
transform.GetChild(int index);
// 设置父物体transform
transform.SetParent(Transform p);
// 查找子物体, 用/来标记子物体层级
// 例: transform.Find("A/B/C");
transform.Find(string n);

二、Rigidbody组件

// 添加力
Rigidbody.AddForce(Vector3 force);
// 添加爆炸力
Rigidbody.AddExplosionForce(float explosionForce, Vector3 explosionPosition, float explosionRadius, float upwardsModifier, ForceMode mode)

三、Collider组件

// 三个碰撞回调函数
void OnCollisionEnter(Collision other) {}
void OnCollisionStay(Collision other) {}
void OnCollisionExit(Collision other) {}
// 三个交叉(触发)回调函数
void OnTriggerEnter(Collider other) {}
void OnTriggerStay(Collider other) {}
void OnTriggerExit(Collider other) {}
// 碰撞点
ContactPoint[] Collision.contacts { get; }

四、Shadow/Outline组件

文字特效组件


C#基础大致完成,还有些零碎知识点没写,学到了再继续更新……

  • 21
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值