我们不会立即转到Godot中编写C#脚本,因为如果没有一些知识的铺垫,各种语法结构也会让你觉得一头雾水。当积累了足够的基础知识后,我们再边写Godot脚本边学习C#。因此建议先准备好一个C#控制台应用项目作为实验环境。
C#和很多C-like语言一样,单行注释用双斜杠表示:
var s = "pain" // Bread in French
/* A method
which does nothing
*/
void Slack(){}
但实际上很少用。因为这个玩意儿的格式很难调整得好看,所以有些工具会直接用多行//开头写多行注释。
任何编程语言的学习过程初期,我们都希望有个什么方法可以验证结果并输出到控制台。正如上一篇文章中了解到的,这个东西在C#中叫Console.WriteLine。所有东西都可以丢给它然后变成字符串输出到控制台。只不过它不像print那样可以直接丢给它任意多个参数。
在你的IDE或者VS Code中,输入cw然后按下tab可以快速输入Console.WriteLine。也有可能自动插入的是System.Console.WriteLine,但是效果是一样的。这个长一点的名字是它的全名。
GDScript中有各种不同的类型,这些类型可以体现变量(的值)到底是个什么东西。
var a = 1
a = "Hello"
在C#中声明一个变量时,需要先写出变量类型,再写名字:
int i = 3;
这样就定义了一个名叫i的int类型变量,初始化为3。在C#中int也是整数类型。
在C#中亦可使用var关键字进行变量声明。但是,可以使用var声明的前提是,等号右侧的表达式的类型是可以推断出来的:
var a; // 错误,未初始化,无明确类型
var b = "abc"; // 正确,b类型为string
var o = new Player();
// 正确。使用new关键字和Player类型的构造函数构造对象。o的类型为Player
// 类似于GDScript中的Player.new()
和GDScript不一样,GDScript总是使用var关键字,无论你标不标类型都用var。
C#的类型系统决定了它的一个变量一旦声明为某种类型,它一辈子就只能作为这个类型的值(准确地说是和这个类型兼容的类型的值)的名字。下面的代码在GDScript中是合法的:
var a = 2
a = "2"
但是在C#中,类似的代码会报错。因为a被定为int类型,所以无论你怎么修改它指代的值,也只能是某个int类型的值。这种行为是静态类型语言的特点之一。
GDScript中的基本类型在C#同样有对应的类型。但是C#的类型会分得更细。
其中最常用的还是int,即32位有符号整数。C#中的int并不等价于GDScript中的int。GDScript中的int实质上是64位有符号整数,取值范围是-2^63 to 2^63-1 即-9223372036854775808到9223372036854775807。因此,C#的int的取值范围是落在GDScript的int中的。
相应地,short是16位,long是64位(对应GDScript的int)。
和short、int、long对应的还有ushort、uint、ulong,u代表unsigned,即无符号。无符号整数类型的取值范围更大,因为容纳负数的部分全部变成更大的正整数了。
单纯的整数字面量只要int能装下就会被推断为int。如果int装不下就会尝试long甚至ulong:
var n = 100; // n为int
var np = 100_0000_0000; // long
var l = 10L; // 小写l也可以,但是容易看错
var ul = 100UL;
也可以选择使用l后缀指明这是long,ul表示ulong。整数字面量还可以在各位之间插入下划线来增加可读性。
var h = 0x2E;
var b = 0b101;
字节就是8位整数。这个类型很有用但没有那么常用,因为通常是在处理二进制数据的时候用得比较多。C#中的字节类型是byte和sbyte,s是signed(有符号)。
GDScript只有一种浮点数类型即float,它代表64位双精度浮点数。
而在C#中主要有float和double两种。float为32位单精度浮点数类型,double顾名思义为64位双精度浮点数类型。
var d = 1.2; // 默认为double类型
var f = 1.2f; // 指定为float类型
var dp = 1.2d; // 指定为double类型,但没必要
不过非常常用的向量类型的分量虽然在GDScript中也以float类型暴露,但是在它的实现中却是用的32位单精度表示。我们在后面会看到,Vector2的分量在C#中也表现为float类型,这是相匹配的。
此外C#实际上还有一种浮点数类型叫decimal,是所谓货币类型,精度更高。
C#的数值类型涉及不同宽度,从取值范围较窄的值到取值范围较大的值会发生隐式转换,不需要特殊的语法:
short s = 12;
int i = s;
long l = i;
但是,由于实际的值可能会超出类型取值范围,反过来则需要显式地转换:
short sp = (short)i;
显式转换意味着在对应的表达式前用括号把目标类型括起来。
从浮点数到整数也需要显式转换(反过来不需要),因为这会把小数部分直接砍掉。
上面介绍过,浮点数字面量不加后缀默认为double类型。如果你需要给一个显式声明为float的变量初始化的话,要么写上后缀f,要么直接转换:
float f = 1.2f; // 正确
float fp = 1.2; // 错误,double无法隐式转换为float
float fpp = (float)1.2; // 正确,但是懒得写
此外,涉及到除法时,C#的行为和很多编程语言一样:
int i = 3 / 2; // 结果为1
var d = 3.0 / 2; // d为double类型!
var f = 3 / 2.0f; // f为float
var fp = 3d / 2f; // fp为double
如果除号两边都是整数,结果如果不是整数那么就会把小数部分砍掉。如果参与除法的操作数有一个或以上浮点数,那么结果也是浮点数。结果的类型总是会往精度高的类型走。
C#中的布尔类型也叫bool,两个取值也是true和false。
C#中也有and、or、not这类关键字,但是它们不能用于布尔运算。对布尔表达式进行逻辑运算只能用&&、==、||,以及不那么常用的异或^。
数组亦是C#的基本类型。但是它和GDScript的Array并不等价。
GDScript中的Array是一个长度可变的、可以保存任意类型的值的数据结构。而Array[T]类型表示对元素类型的限定。
C#中的数组是一种长度固定的序列型数据结构。在构造数组时必须确定数组的长度:
int[] ns = new int[3]{1, 2, 3}; // 历史悠久的写法
int[] nsp = new int[]{1, 2, 3, 4}; // 省略长度4,自动推断长度
int[] nspp = {1, 2, 3, 4}; // 茴香豆的茴
int[] nsppp = [3, 4, 5]; // 新写法
数组类型以T[]的形式表示。其中T是元素的类型。构造数组时,最复杂、历史最悠久的写法是,首先用new关键字开头,然后写出数组类型,方括号中写入数组长度。最后在一对花括号中写出逗号分隔的各元素(类似于C++的初始化列表)。这种写法要求用于初始化的元素必须和前面方括号中的长度匹配。
在较新的C#版本中亦引入了集合表达式语法,也就是类似于GDScript和Python等语言的语法。如果类型确定,可以直接用方括号加值列表的语法写出数组的内容。因为这种语法实际上各种集合类型甚至于自定义类型都可以使用,所以使用这种语法的时候变量类型必须指明。
int[] ns = [1,2,3,4,5];
int two = ns[1]; // 索引从0开始
int last = ns[^1]; // 倒数第一个
GDScript的数组和Python中的列表一样可以用负整数索引表示从右往左第几个。C#的数组不支持负整数索引。在C#要从右往左访问第n个元素的话需要用array[^n]这种写法。^符号是Shift+数字键6。
现代C#同样支持Python那样的切片(slicing):
var firstTwo = ns[..2]; // 等价于ns[0..2]
..叫范围运算符,左右的整数表达式代表范围的边界。左开右闭,即包含起始位置的元素,但不包含结束位置的元素。切片的返回值类型亦为数组。
要获得数组的长度,我们不像在GDScript中那样有一个len函数,我们需要访问数组的Length属性。
const float PI = 3.14;
enum Direction
{
North,
South,
West,
East,
}
C#中的枚举类型实质上定义了一系列整数常量。但是枚举类型在C#中也是一个具体的类型,无法隐式转换。并且访问枚举的具体值时必须通过枚举类型和点语法来访问:
var d = Direction.North;
int i = (int)Dirction.South; // 正确,显式转换
Direction direction = (Direction)1;
默认情况下,枚举可选值对应的整数从0开始,依定义中出现的顺序递增。也就是说上面的North为0,East为3。你也可以自行用等号指定取值:
enum StatusCode
{
Ok = 200,
NotFound = 404,
}
enum Week
{
Monday = 1,
// ...
}
enum TheEnum
{
A, // 0
B = 3,
C, // 4
D = 1,
E, // 2
}
如果你没有给每个可选值指定值,那么跟着有指定值后面的项目也就默认递增。但是一般不建议给不同的项目设置同一个值,因为这样不方便从整数转换回枚举类型的值。
enum E : long {}
字符串,字面上来说就是一串字符(仅限中文)。很多编程语言实际上不区分字符和字符串,因为“字符”也可以视作“长度为1的字符串”。而将字符和字符串视作两个类型的编程语言其实都是C-like的(rust除外)。比如如下的GDScript代码:
var s: string = "海内存知己"
var c: string = s[2]
var s = "海内存知己";
string c = s[2]; // 类型应为char
因为通过下标(索引)语法从字符串中取到的结果在C#中为字符类型char。
由于字符串和字符的区分,字面量也要做区分。包括GDScript和Python在内的不区分字符和字符串的语言中,大部分情况下双引号和单引号是一样的。但是在C#中需要用单引号表示字符,双引号表示字符串:
char c = '草';
string s = "草";
但是C#的char和它的前辈的char并不等价。如果你把鼠标放到你的IDE或者编辑器中的char上面,可以看到它的注释明确表示了char是用“UTF-16字符码位表示的字符”。因此,C#的char是可以存储包括汉字在内的各种Unicode字符的。而C、C++中的char本质上相当于“8位无符号整数”,因为它是用来表示ASCII字符的。
可能有点反直觉的是,字符用加号求得的结果并不是字符串,而是一个整数:
Console.WriteLine('草' + '草'); // 67218
这里的'草'被转换成了Unicode码点值,即一个整数。C#中的char虽然不等价于8位无符号整数,但是它可以隐式地转换为ushort、 int、 uint、 long、 ulong也就是对应的Unicode码点值。
和很多编程语言一样,C#的字符串也可以直接用加号连接。
如果要连接的东西较多,可以使用插值字符串语法类似于Python的f字符串:
int i = 10;
var name = "John";
var s = $"Hello {name}, it's your {i}th time to log in";
插值字符串需要在双引号前加上一个$表示这是插值字符串。在双引号内部,花括号中间的表达式会被转换为字符串并作为整个字符串的一部分。
C#中的所有类型的值都可以转换为字符串,因为它们身上都有一个叫ToString的方法。Console.WriteLIne本质上就利用了这个方法,在后面我们可以了解到,这个方法可以在不同的类型上有不同的行为,且可以有不同的参数列表。
和很多编程语言一样,C#也有表示什么都没有的null常量。但是,C#中不是所有东西都可以为null。
一些类型的变量如果没有初始化,就默认为null。比如上面介绍过的数组和字符串都是这个范畴。
如果编译器可以分析出你在未给这类变量赋初值的情况下调用其方法会在编译时直接报错。
而上面介绍的所有数值类型都不能为null。但是对于这类类型你不用担心在未初始化的情况下调用了它们的方法,因为它们有默认值。上述数值类型的默认值为0。
以上便是对C#中的变量声明、基本类型的一个介绍。从GDScript转到C#除了要记得写分号之外,就是要学会识别类型、选择类型。
也可以选择到知乎看我的文章,有代码语法高亮,好看一点。
评论区
共 条评论热门最新