跳到主要内容
版本:master

类型

理解数据类型是 GS 编程入门的一大步,它就像是给数据"贴标签",告诉 GS 该如何处理和存储这些数据。下面我会用尽可能清晰的方式为你介绍数据类型的核心概念,并通过一些相关的示例帮助你建立直观感受。本章适用于缺乏编程基础或需了解 GS 类型的同学。阅读完本章应了解类型的概念、判断、引用类型概念、显示转换、隐式转换、及 GS 类型的继承关系。

1 什么是类型?

GS 的数据类型定义了数据的性质以及数据间可以进行的操作。它规定了数据在 GS 中的表示形式,包括数据的存储方式、可以进行的操作以及数据范围等。

你可以把数据类型想象成​​生活中的各种容器​​:数字像是一个个标准的计数盒子,文字像是一串可以拼接的珠子,真假值则像是一个简单的开关。每种容器都有其特定的用途和操作方式。

2 为什么需要类型?

使用适当的数据类型主要能带来这些好处:

  • ​提高代码效率​​:不同类型的数据在内存中占用不同空间,选择合适的数据类型可以优化内存使用和提高运行速度。
  • ​增强代码安全性​​:数据类型可以防止不合理的操作,比如试图将文字与数字相加。
  • ​增加代码可读性​​:明确的数据类型使代码更易于理解和维护。
  • ​减少错误​​:类型系统可以在编译或运行阶段捕获许多常见错误。

3 类型的分类

数据类型主要分为两大类:​​基本数据类型​​(Primitive Data Types)和​引用类型​​(Reference Data Types),启动引用类型也称为容器类型。

3.1 GS的基本类型

这些 GS 的基本类型会在后续章节详细介绍其概念、基本操作等内容。

信息

所有的类型可以视为继承自混合类型mixed, 而枚举类型(enum)继承自int,扩充类型object则继承自handle。

3.2 GS内置类型继承关系示意图

mixed -+-- nil
|
+-- void
|
+-- int -+-- enum -+-- bool
|
+-- float
|
+-- string
|
+-- buffer
|
+-- array
|
+-- map -+-- class_map
|
+-- function
|
+-- handle -+-- object
| |
| +-- program
| |
| +-- domain
| |
| +-- ref_value
| |
| +-- coroutine
| |
| +-- timer
| |
| +-- socket
| |
| +-- share_value
| |
| +-- sync_object
| |
| +-- queue
| |
| +-- file
| |
| +-- socket
| |
| +-- iterator
| |
| +-- archive
| |
| +-- weak_table
|
+-- raw_pointer -+-- sockaddr

3.3 值类型与引用类型的区别

在 GS 中 int,float 这种完全存储在 ValueStack 中的变量被称为 值类型, 而 array,map 这种在 ValueStack 中存储指向相应数据结构的指针的类型被称为引用数据类型。 下面我们以函数参数传递为例子解释基本类型与引用类型的区别:

void array_add_one(array arr)
{
arr.push_back(1);
}

void int_add_one(int num)
{
num = num + 1;
}

void run_pass_arg_test()
{
array arr = [];
int num = 0;
int_add_one(num);
array_add_one(arr);
printf("num after call add one is:%d\n", num);
printf("array after call add one is:%O\n", arr);
}
run_pass_arg_test();

示例3-1:基本类型与引用类型的区别

如上示例的运行结果如下:

num after call add one is:0
array after call add one is:[ /* sizeof() == 1 */
1,
]

  • int_add_one 函数调用结束后 run_pass_arg_test 函数中的 num 变量仍是 0
  • array_add_one 函数调用结束后 run_pass_arg_test 函数中的 array 变量中数组由空数组变为长度为一带有一个整型 1 的数组。

为什么同样是作为函数参数调用函数,在函数调用完成后一个变量内容被修改但一个变量的内容未被修改?
因为 array 为引用类型变量,变量中存储的是指向数组数组的指针,作为参数传递时指针指向的数组空间仍是同一个。而此处 num 作为参数传递时,传递的实际是对应的整型数值。int_add_one 函数中 num 参数在 ValueStack 中的位置与 run_pass_arg_test 函数中 num 变量在 ValueStack 中的位置实际是不同的。

3.4 类型的判断

GS中内置了一个外部函数,可以对mixed类型进行判断,函数原型如下:

string mixed.type_name()// return 类型的string格式

基本用法:

int i = 1234;
write(i.type_name(), "\n"); // 输出 "int"
array arr = [1, "hello", 3.14];
write(arr.type_name(), "\n"); // 输出 "array"

示例3-2:类型的判断
输出的结果如下:

int
array

4 类型转换

在实际编程中,经常需要在不同数据类型之间进行转换:

4.1 隐式转换

由 GS 编译器自动完成,通常是从小范围类型向更大范围类型转换:

int score = 100;
float preciseScore = score; // 自动将int转换为float
write(preciseScore);

示例 4-1: 隐式类型转换

输出结果如下:

100.0

需要注意的是隐式类型转换通常是从小范围类型向更大范围类型转换,在 GS 中基本体现为从子类型向父类型的转换,一般从父类型转为子类型时会报告错误。

int num = 0;
bool flag = num;
write(flag);

示例 4-2: 报错的隐式类型转换 如上示例 4-2 所示,bool 为 int 整型的子类型型,直接由 int 类型向 bool 类型赋值导致的隐式类型转换会报错。

4.2 显式转换

需要明确手动指定的转换:

float preciseDamage = 15.75;
int actualDamage = (int)preciseDamage; // 显式转换为int,actualDamage变为15
write(actualDamage);

示例 4-3: int -> float显示类型转换

输出结果如下:

15

从示例 4-3 可以看到,float转为int会发生截断,取整数值,不存在四舍五入。

4.3 特殊转换

部分类型转换必须要手动调用对应的函数完成,比如如下示例:

string转为int

 string str = "1024";
int val = int.parse(str); // val = 1024
write(val);

示例 4-4: string -> int显示类型转换

int转为string

 int val = 1024;
string str = val.to_string(); // str = "1024"
write(str);

示例 4-5: int -> string显示类型转换

string转为float

string str = "3.14";
float val = float.parse(str); // val = 3.14
write(val);

示例 4-6: string -> float显示类型转换

float转为string

float val = 3.14;
string str = val.to_string(); // str = "3.14"
write(str);

示例 4-7: float -> string显示类型转换

5 总结

数据类型是 GS 中​​对数据进行分类和描述的方式​​,它决定了数据的可能值、操作和存储方式。理解数据类型的核心在于明白不同的数据需要不同的"容器"来存储和处理。现在我们对类型、隐式转换、显示转换有了基础的概念,接下来让我们了解一些 GS 中基础的运算符。