Skip to content

变量

变量就像一个盒子,是用于存储数据的容器,它可以存储不同类型的数据,如数字、字符串、布尔值等。

给数据起个名字,我们就可以通过这个名字来获取这个数据,这就是变量。


变量的声明

变量的声明语法如下:

var 变量名

var 变量名 = 初始值

var 变量名: 数据类型 = 初始值

示例:

gdscript
var name
var foo = 100
var age: int = 18

除此之外,对于变量名(标识符)的命名也有如下要求:

  • 可以包含字母字符(a - zA - Z

  • 可以包含数字(0 - 9

  • 可以包含下划线(_

  • 不能以数字开头

  • 不能包含空格

  • 不能是语言内保留关键字(比如truefalseiffunc等)

  • 不能与当前作用域已有变量重名

  • 大小写敏感(fooFoo 被认为是两个不同的变量)

  • 可以使用部分Unicode字符(也就是说,可以使用非英文字符作为标识符使用)

  • 易与ASCII字符混淆的字符以及颜文字无法作为标识符使用

如上规则也同样适用于函数、信号、类等标识符的命名

在名字前添加下划线,表示是私有 (private) 的变量,或丢弃(不使用)

在GDScript中,变量的标识符一般使用蛇形命名(snake_case),即全小写字母并以下划线分割单词


变量的赋值

GDScript的变量赋值,使用两套规则

1. 非静态类型的变体赋值

GDScript是一门动态语言,其变量的类型可以不是固定的,这意味着你可以反复给一个变量赋不同类型的值

来看个例子:

gdscript
var foo

func _ready() -> void:
    foo = 10
    foo = "Foo"
    foo = Node.new()
    foo = 3.14

上面的代码中,我们声明了一个变量 foo,然后在 _ready() 函数中不断更改它的值:

  • foo = 10:将整数 10 赋给变量 foo

  • foo = "Foo":再把字符串 "Foo" 赋给它

  • foo = Node.new():把一个新建的 Node 实例赋给 foo

  • foo = 3.14:最后又变成了一个浮点数

可以看出,变量 foo类型是动态变化的,这就是变体类型的特性

那么问题来了~如果我们声明变量时不给它赋值,它的类型会是什么呢?

我们可以使用print()函数来试试:

gdscript
var foo

func _ready() -> void:
    print(foo) # 会输出<null>

运行后,就会发现控制台输入了如下内容:

Variant(变体)

像上面这样,声明变量但不既不赋予其初始值,也不指定其类型,那么这个变量的类型就会是Variant变体类型

Variant 是一种特殊的万能类型,它可以被赋为任意类型的值,也能表示为任意类型的值。

可以说它是 GDScript 中所有类型的「祖宗」

但要注意,Variant不是一个确切的类型,它更像是 “我暂时还不知道你是什么,但在我使用你之前先允许你可以是任何东西” 的状态

这在某些场景下可能会引发问题,比如写错类型调用了不存在的方法,就会报错!

Null 和 NIL

Variant 类型的变量没有被赋予任何值时,其值为null,其类型为NIL

null 的字面意思是「空值」或「未知值」~大多数情况下它表示某种错误或“还没初始化”,所以我们不能对它执行任何操作,因为它除了代表“没有”,啥都没有


2. 静态类型的赋值

什么叫静态类型呢?

区别于上文提及的动态变体类型,静态类型在声明变量时,其值的类型就是固定的,也就是说,在你声明变量时就明确指定其类型时,这个变量之后就只能保存这种类型的值,不能再像Variant那样变来变去

gdscript
var integer: int

func _ready() -> void:
    integer = 10
    print(integer)
    integer = 3.14 # 浮点数可以直接转换成整数,所以不会报错,但是有副作用
    print(integer) # 可以看到输出的是3,而不是3.14
    integer = Node.new() # 解析错误,不能将`Node`类型赋值给整数类型
    integer = "Hello" # 解析错误,不能将字符串类型赋值给整数类型

上面的示例代码中,我们使用var integert: int声明了一个整数类型的变量

又在_ready()函数中尝试将不同类型的值赋值给它

在编写过程中我们就能看到,第8行以及第9行的内容,会因类型错误而无法解析脚本,阻止你运行游戏

而浮点数赋值不会导致报错,是因为可以直接转换为整数(但是有副作用,会导致小数位丢失,比如将3.14变为3)

关于类型的知识,我们在后面的章节再讲,本文不过多赘述

所以,当我们使用var 变量名: 数据类型的语法声明变量时,其类型就是固定的,不能将不兼容的值赋值给它

相比于动态类型,使用静态类型可以让编辑器提供更好的代码补全和检查能力,还能减少运行时错误

变量的作用域

作用域决定了变量可以在程序的哪些地方使用

变量是当前代码块中声明的,就只能在这个代码块中使用

即便类的成员是属于同一个家庭的,但也总有些老死不相往来的家庭成员,这辈子都不会见一面,也不会去不想去不能去的地方

举个例子:

gdscript
var father # 类成员变量
var mother# 类成员变量

func mather_room():
    mother.give_gift(father) # 不会产生错误

func father_room():
    father.talk_to(brother) # 解析错误,标识符`brother`未在此作用域内声明
    brother.hug(sister) # 解析错误,标识符`brother`, `sister`未在此作用域内声明
    
func sister_room():
    var sister # 局部变量
    
func brother_room():
    var brother # 局部变量

上面的例子中,你可以想象这是一个关系不合的家庭

  • 父母夫妻关系很好

  • 父母与子女之间关系不合

  • 子女之间关系不合

类成员变量:定义在函数外部,可以被类内的所有函数访问,像经常在客厅里活动的父母,谁都能看见他们

局部变量:定义在函数内部,只能在函数内部当前代码块使用,像躲在房间里的小孩子,其他人根本找不到,ta也不想出去

遮蔽:当局部变量命名与成员变量相同时,会遮蔽成员变量,导致在当前作用域访问的该名称的变量始终是局部变量


小练习

  1. 下面这几行代码中,哪些变量的命名是非法的?为什么?
gdscript
var 1apple = "fruit"
var my variable = 123
var _score = 99
var func = 3.14
var isReady = true
var True = false
  • 不运行代码,尝试直接说出下面代码的控制台输出结果
gdscript
var a

func _ready() -> void:
    print(a)
    a = 3
    print(a)
  • 下面这段代码会报错吗?如果会,是为什么?
gdscript
var count: int

func _ready() -> void:
    count = "five"
    var index = 0
    
func method():
    index = -1
    print(index)
  • 这段代码中有哪些变量是局部变量,哪些是成员变量
gdscript
var outer = 100

func example():
    var inner = 50
    print(outer + inner)
   
var val = "value"
    
func example_2():
    var inner_2 = 100
    print(outer + inner_2)
  • 如何理解下面这段话?

Variant 是一种可以容纳任何类型的变量,但它本身没有固定的类型。”