All variables in a dynamically typed language are “variant”-like. This means that their type is not fixed, and is only modified through assignment. Example:
动态类型语言中的所有变量都是“可变”的。这意味着它们的类型不是固定的,而是通过赋值修改的。例如:
静态语言的:
int a; // Value uninitialized 变量没初始化
a = 5; // This is valid 合法
a = "Hi!"; // This is invalid 不合法
动态语言的:
var a # null by default 默认是空类型
a = 5 # Valid, 'a' becomes an integer 合法,a变成了整型
a = "Hi!" # Valid, 'a' changed to a string 合法,a改变成了字符串型
Functions are of dynamic nature too, which means they can be called with different arguments, for example:
函数也是动态的,这意味着它们可以用不同类型的参数调用,比如:
静态语言的:
void print_value(int value) {
printf("value is %i\n", value);
}
[..]
print_value(55); // Valid
print_value("Hello"); // Invalid
动态语言的:
func print_value(value):
print(value)
[..]
print_value(55) # Valid
print_value("Hello") # Valid
In static languages, such as C or C++ (and to some extent Java and C#), there is a distinction between a variable and a pointer/reference to a variable. The latter allows the object to be modified by other functions by passing a reference to the original one.
在静态语言(如C或C++)(以及在Java和C#一定程度上),变量和指针/引用与变量之间存在区别。后者通过传递对原始函数的引用,允许其他函数修改对象。
In C# or Java, everything not a built-in type (int, float, sometimes String) is always a pointer or a reference. References are also garbage-collected automatically, which means they are erased when no longer used. Dynamically typed languages tend to use this memory model, too. Some Examples:
在C# 或Java中,不是内置类型(int,float,有时的String)的任何东西都是指针或引用。引用也是自动内存垃圾回收,这意味着它们在不再使用时被删除。动态类型的语言也倾向于使用这种内存模型。示例:
C++
void use_class(SomeClass *instance) {
instance->use();
}
void do_something() {
SomeClass *instance = new SomeClass; // Created as pointer
use_class(instance); // Passed as pointer
delete instance; // Otherwise it will leak memory
}
JAVA
@Override
public final void use_class(SomeClass instance) {
instance.use();
}
public final void do_something() {
SomeClass instance = new SomeClass(); // Created as reference
use_class(instance); // Passed as reference
// Garbage collector will get rid of it when not in
// use and freeze your game randomly for a second
}
GDScript:
func use_class(instance); # Does not care about class type
instance.use() # Will work with any class that has a ".use()" method.
func do_something():
var instance = SomeClass.new() # Created as reference
use_class(instance) # Passed as reference
# Will be unreferenced and deleted
In GDScript, only base types (int, float, string and the vector types) are passed by value to functions (value is copied). Everything else (instances, arrays, dictionaries, etc) is passed as reference. Classes that inherit Reference (the default if nothing is specified) will be freed when not used, but manual memory management is allowed too if inheriting manually from Object.
在GDScript中,只有基本类型(int、float、string和vector类型)通过值传递给函数(值被复制)。其他所有(实例、数组、字典等)作为引用传递。继承 Reference (如果没有指定任何内容,则默认)的类在不使用时将被释放,对手动继承 :ref:class_Object
的类也允许手动管理内存。
Arrays in dynamically typed languages can contain many different mixed datatypes inside and are always dynamic (can be resized at any time). Compare for example arrays in statically typed languages
动态类型语言中的数组可以包含许多不同的混合数据类型,并且始终是动态的(可以随时调整大小)。 比较静态类型语言中的示例数组
int *array = new int[4]; // Create array
array[0] = 10; // Initialize manually
array[1] = 20; // Can't mix types
array[2] = 40;
array[3] = 60;
// Can't resize
use_array(array); // Passed as pointer
delete[] array; // Must be freed
// or
std::vector<int> array;
array.resize(4);
array[0] = 10; // Initialize manually
array[1] = 20; // Can't mix types
array[2] = 40;
array[3] = 60;
array.resize(3); // Can be resized
use_array(array); // Passed reference or value
// Freed when stack ends
在GDScript中:
var array = [10, "hello", 40, 60] # Simple, and can mix types
array.resize(3) # Can be resized
use_array(array) # Passed as reference
# Freed when no longer in use
在动态类型语言中,数组也可以是其他数据类型的两倍,例如列表:
var array = []
array.append(4)
array.append(5)
array.pop_front()
或无序集合:
var a = 20
if a in [10, 20, 30]:
print("We have a winner!")
Dictionaries are a powerful tool in dynamically typed languages. Most programmers that come from statically typed languages (such as C++ or C#) ignore their existence and make their life unnecessarily more difficult. This datatype is generally not present in such languages (or only in limited form).
Dictionaries can map any value to any other value with complete disregard for the datatype used as either key or value. Contrary to popular belief, they are efficient because they can be implemented with hash tables. They are, in fact, so efficient that some languages will go as far as implementing arrays as dictionaries.
字典是动态类型化语言中的一个强大工具。来自静态类型化语言(如C++或C#)的大多数程序员忽略了它们的存在,使它们的工作变得更加困难。这种数据类型通常不存在于此类语言中(或仅以有限的形式)。
字典可以将任何值映射到其他任何值,完全不考虑用作键或值的数据类型。与流行的观点相反,它们是有效的,因为它们可以通过哈希表实现。事实上,它们非常高效,一些语言甚至可以像实现字典一样实现数组。
var d = {"name": "John", "age": 22} # Simple syntax
print("Name: ", d["name"], " Age: ", d["age"])
字典也是动态的,键可以在任何一点添加或删除,花费很少:
d["mother"] = "Rebecca" # Addition
d["age"] = 11 # Modification
d.erase("name") # Removal
在大多数情况下,使用字典可以更容易地实现二维数组。这里有一个简单的战舰游戏的示例
# Battleship game
const SHIP = 0
const SHIP_HIT = 1
const WATER_HIT = 2
var board = {}
func initialize():
board[Vector2(1, 1)] = SHIP
board[Vector2(1, 2)] = SHIP
board[Vector2(1, 3)] = SHIP
func missile(pos):
if pos in board: # Something at that pos
if board[pos] == SHIP: # There was a ship! hit it
board[pos] = SHIP_HIT
else:
print("Already hit here!") # Hey dude you already hit here
else: # Nothing, mark as water
board[pos] = WATER_HIT
func game():
initialize()
missile(Vector2(1, 1))
missile(Vector2(5, 8))
missile(Vector2(2, 3))
Dictionaries can also be used as data markup or quick structures. While GDScript’s dictionaries resemble python dictionaries, it also supports Lua style syntax and indexing, which makes it useful for writing initial states and quick structs
字典还可以用作数据标记或快速结构。虽然GDScript字典类似于python字典,但它也支持Lua风格的语法和索引,这使得它对于编写初始状态和快速结构非常有用
# Same example, lua-style support.
# This syntax is a lot more readable and usable
# Like any GDScript identifier, keys written in this form cannot start with a digit.
var d = {
name = "John",
age = 22
}
print("Name: ", d.name, " Age: ", d.age) # Used "." based indexing
# Indexing
d["mother"] = "Rebecca"
d.mother = "Caroline" # This would work too to create a new key
Iterating in some statically typed languages can be quite complex:
在一些静态类型的语言中循环可能非常复杂
const char* strings = new const char*[50];
[..]
for (int i = 0; i < 50; i++)
{
printf("Value: %s\n", i, strings[i]);
}
// Even in STL:
for (std::list<std::string>::const_iterator it = strings.begin(); it != strings.end(); it++) {
std::cout << *it << std::endl;
}
这通常在动态类型语言中得到极大简化:
for s in strings:
print(s)
容器数据类型(数组和字典)是可迭代的。字典允许迭代键:
for key in dict:
print(key, " -> ", dict[key])
迭代索引也是可能的:
for i in range(strings.size()):
print(strings[i])
range()函数可以有3个参数:
range(n) # Will go from 0 to n-1
range(b, n) # Will go from b to n-1
range(b, n, s) # Will go from b to n-1, in steps of s
在一些静态类型的语言中迭代可能非常复杂:
for (int i = 0; i < 10; i++) {}
for (int i = 5; i < 10; i++) {}
for (int i = 5; i < 10; i += 2) {}
转变成:
for i in range(10):
pass
for i in range(5, 10):
pass
for i in range(5, 10, 2):
pass
反向循环是通过一个负计数器完成的
for (int i = 10; i > 0; i--) {}
变成:
for i in range(10, 0, -1):
pass
while()循环在任何地方都是相同的:
var i = 0
while i < strings.size():
print(strings[i])
i += 1
You can create custom iterators in case the default ones don’t quite meet your needs by overriding the Variant class’s _iter_init, _iter_next, and _iter_get functions in your script. An example implementation of a forward iterator follows:
你可以通过在脚本中覆盖Variant类的“_iter_init”、“_iter_next”和“_iter_get”函数来创建自定义迭代器
class FwdIterator:
var start, curr, end, increment
func _init(start, stop, inc):
self.start = start
self.curr = start
self.end = stop
self.increment = inc
func is_done():
return (curr < end)
func do_step():
curr += increment
return is_done()
func _iter_init(arg):
curr = start
return is_done()
func _iter_next(arg):
return do_step()
func _iter_get(arg):
return curr
它可以像任何其他迭代器一样使用:
var itr = FwdIterator.new(0, 6, 2)
for i in itr:
print(i) # Will print 0, 2, and 4
确保在 _iter_init
中重置迭代器的状态,否则使用自定义迭代器的嵌套for循环将无法正常工作
One of the most difficult concepts to grasp when moving from a statically typed language to a dynamic one is duck typing. Duck typing makes overall code design much simpler and straightforward to write, but it’s not obvious how it works.
As an example, imagine a situation where a big rock is falling down a tunnel, smashing everything on its way. The code for the rock, in a statically typed language would be something like:
当从静态类型语言迁移到动态类型语言时,最难掌握的概念之一是鸭子类型。鸭子类型使整个代码设计更加简单和直接,但是它的工作方式并不明显。
举个示例,想象一个大石头从隧道里掉下来,在路上砸碎了一切。在静态类型语言中石头的代码有点像:
void BigRollingRock::on_object_hit(Smashable *entity) {
entity->smash();
}
This way, everything that can be smashed by a rock would have to inherit Smashable. If a character, enemy, piece of furniture, small rock were all smashable, they would need to inherit from the class Smashable, possibly requiring multiple inheritance. If multiple inheritance was undesired, then they would have to inherit a common class like Entity. Yet, it would not be very elegant to add a virtual method smash() to Entity only if a few of them can be smashed.
With dynamically typed languages, this is not a problem. Duck typing makes sure you only have to define a smash() function where required and that’s it. No need to consider inheritance, base classes, etc.
这样,任何能被岩石砸碎的东西都必须继承Smashable。如果一个人物、敌人、家具、小石块都易碎,他们需要从Smashable类继承,可能需要多次继承。如果不希望进行多重继承,那么它们必须继承像Entity这样的公共类。然而,如果只是其中几个能被粉碎的话,仅仅在Entity中添加一个虚拟方法 smash() 并不十分优雅。
使用动态类型的语言,这将不是问题。鸭子类型确保您只需在需要的地方定义一个 smash() 函数,就行了。无需考虑继承、基类等。
func _on_object_hit(object):
object.smash()
And that’s it. If the object that hit the big rock has a smash() method, it will be called. No need for inheritance or polymorphism. Dynamically typed languages only care about the instance having the desired method or member, not what it inherits or the class type. The definition of Duck Typing should make this clearer:
就是这样。如果击中大岩石的对象有一个 smash() 方法,它将被调用。不需要考虑继承或多态性。动态类型化语言只关心具有所需方法或成员的实例,而不关心它继承什么类型。鸭子类型的定义应该使这一点更清楚:
“When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck”
In this case, it translates to:
“If the object can be smashed, don’t care what it is, just smash it.”
Yes, we should call it Hulk typing instead.
It’s possible that the object being hit doesn’t have a smash() function. Some dynamically typed languages simply ignore a method call when it doesn’t exist (like Objective C), but GDScript is stricter, so checking if the function exists is desirable:
当我看到一只鸟像鸭子一样走路,像鸭子一样游泳,像鸭子一样呱呱叫时,我就叫它鸭子”
在这种情况下,它可转变成:
“如果物体可以被砸碎,不要在意它是什么,只管砸碎它。”
是的,称它为“绿巨人”类型适合更合适。
有可能被击中的对象没有smash()函数。一些动态类型语言在方法调用不存在时简单地忽略它(如Objective C),但是GDScript更严格,因此需要检查函数是否存在:
func _on_object_hit(object):
if object.has_method("smash"):
object.smash()
然后,简单地定义这个方法,岩石触碰的任何东西都可以被粉碎了