Mojo 学习 —— 结构体
文章目录
- Mojo 学习 —— 结构体
- 定义结构体
- 定义方法
- 静态方法
- 结构体与类的比较
- 特殊方法
- @value 装饰器
Mojo
的结构体是一种数据结构,可以对抽象概念(如数据类型或对象)的字段和方法进行封装。
字段是变量,用于保存与结构体相关的数据;方法是结构体内部的函数,通常用于处理字段数据。
在大多数情况下,Mojo
的结构体格式旨在为程序中使用的高级数据类型提供静态、内存安全的数据结构。
Mojo
标准库中的所有数据类型(如 Int
、Bool
、String
和 Tuple
等)都定义为结构体。
从前面的函数章节中,我们可以发现 Mojo
在 def
函数中提供了动态编程功能,同时在 fn
函数中加强代码安全性。
而对于结构体,Mojo
更倾向于安全的一面,虽然可以在结构体中使用 def
或 fn
声明函数,但所有的字段都必须使用 var
来声明
定义结构体
我们可以定义一个简单的结构体,只包含两个字段:
struct MyPair:
var first: Int
var second: Int
但是,由于该结构体没有构造函数,所以无法用其来实例化对象。类似于 Python
,我们可以定义一个 __init__
构造函数
struct MyPair:
var first: Int
var second: Int
fn __init__(inout self, first: Int, second: Int):
self.first = first
self.second = second
请注意,
__init__
方法的第一个参数是inout self
,声明了self
是一个可变引用
self
必须是第一个参数,它引用了当前的结构体实例。在调用构造函数时,你无需为 self
传递值,Mojo
会自动将其传递进来。
__init__
方法是许多预定义的特殊方法之一,Mojo
中有许多与 Python
的 class
中一样的用双下划线开头和结尾的特殊方法,像 __str__
和 __repr__
等
声明字段时不能对其赋值,您必须在构造函数中初始化结构体的所有字段。(如果不初始化字段,代码将无法编译)。
一旦你有了一个构造函数,就可以创建一个 MyPair
的实例,例如
var mine = MyPair(2,4)
print(mine.first)
定义方法
除了像 __init__
这样的特殊方法外,您还可以在结构体中添加任何其他方法。例如
struct MyPair:
var first: Int
var second: Int
fn __init__(inout self, first: Int, second: Int):
self.first = first
self.second = second
fn get_sum(self) -> Int:
return self.first + self.second
调用函数会打印一个 14
var mine = MyPair(6, 8)
print(mine.get_sum())
请注意,
get_sum
也使用了self
参数,这是在方法中访问结构体字段的唯一途径。self
只是一个约定俗成的名称,你可以替换成任何你想要的名称
使用隐式 self
参数的方法被称为实例方法,它们会作用于结构体的实例上
注意:
结构体方法中的
self
参数是fn
函数中唯一不需要类型的参数。如果你愿意,你也可以指定,但一般都会省略它,因为Mojo
知道它是什么类型(本例中为MyPair
)。
静态方法
结构体也可以有静态方法。静态方法可以在不创建结构体实例的情况下调用,即可以直接使用结构体名称调用的函数。
与实例方法不同,静态方法不接收隐式 self
参数,因此不能访问结构体上的任何字段。
要声明静态方法,请使用 @staticmethod
装饰器,并且不要包含 self
参数:
struct Logger:
fn __init__(inout self):
pass
@staticmethod
fn log_info(message: String):
print("Info: ", message)
您可以在结构体上调用静态方法,也可以在类型的实例上调用。两种形式如下所示:
Logger.log_info("Static method called.")
var l = Logger()
l.log_info("Static method called from instance.")
将会输出
Static method called.
Static method called from instance.
结构体与类的比较
如果您熟悉其他面向对象语言,那么结构体看起来很像类,两者有相似之处,但也有一定的区别。
Mojo
后面还会添加对类类的支持,以匹配 Python
类的行为。
Mojo
结构体和 Python
类之间的的共同之处在于,它们都支持方法、字段、操作符重载以及用于元编程的装饰器等,但主要区别如下:
-
Python
类是动态的,它们允许在运行时进行动态派发、monkey-patching
(或swizzling
)和动态绑定实例字段 -
Mojo
结构体是静态的,它们在编译时被绑定(不能在运行时添加方法)。用灵活性换取性能,同时又安全易用 -
Mojo
结构不支持继承,但结构可以实现trait
-
Python
类支持类属性,即类的所有实例共享的值,相当于其他语言中的类变量或静态数据成员 -
Mojo
结构不支持静态数据成员
在语法上,与 Python
类最大的不同是,结构体中的所有字段都必须用 var
明确声明
在 Mojo
中,结构体的结构和内容在编译时就已经确定,程序运行时无法更改。而在 Python
中,可以动态添加、删除或更改对象的属性
不过,结构体的静态特性有助于 Mojo
更快地运行代码。程序可以准确地知道在哪里找到结构体的信息以及如何使用它,而不会在运行时出现任何额外的步骤或延迟。
前面也说到,Mojo
的所有标准类型(Int
、String
等)都是用结构体创建的,而不是硬连接到语言本身。
这让你在编写代码时拥有更多的灵活性和控制权,也意味着你可以定义自己的类型,并拥有所有相同的功能
特殊方法
这里所说的特殊方法也就是 Python
中所说的 魔法方法,是具有特殊功能的方法,如构造函数方法 __init__
虽然这些特殊方法可以通过方法名来调用,但是你绝对不应该这样做
例如,在创建结构体实例时,Mojo
会调用 __init__
方法;当 Mojo
销毁实例时,它会调用 __del__
方法(如果它存在的话)
一些内置的操作符(+
、<
、==
、|
等)也是作为特殊方法实现的,Mojo
会隐式地调用这些方法
Mojo
支持的特殊方法与 Python
的所有特殊方法相匹配,而且通常具有下面两种功能的其中一种
- 操作符重载:
用于为结构体添加一些运算操作,如 <
、+
和 |
等
- 处理生命周期事件:
这些特殊方法主要用于处理实例的生命周期和值所有权。例如,__init__
和 __del__
标志着实例生命周期的开始和结束
大多数结构体都是其他类型的简单聚合,因此除非您的类型在创建、复制、移动或销毁实例时需要自定义行为,否则您可以通过添加 @value
装饰器来合成所需的基本生命周期方法(并为自己节省一些时间)。
@value 装饰器
当你为结构体添加 @value
装饰器时,Mojo
会为结构体添加基本的生命周期方法,使你的对象提供完整的值语义。
具体来说,它会生成 __init__
, __copyinit__
和 __moveinit__
方法,这些方法允许你以符合值语义并与 Mojo
所有权模型兼容的方式构造、复制和移动你的结构类型。
例如
@value
struct MyPet:
var name: String
var age: Int
Mojo
会注意到你没有成员构造函数、移动或复制构造函数,它会为你合成这些,就像下面的代码这样
struct MyPet:
var name: String
var age: Int
fn __init__(inout self, owned name: String, age: Int):
self.name = name^
self.age = age
fn __copyinit__(inout self, existing: Self):
self.name = existing.name
self.age = existing.age
fn __moveinit__(inout self, owned existing: Self):
self.name = existing.name^
self.age = existing.age
如果没有复制和移动构造函数,Mojo
不知道如何复制与移动 MyPet
的实例,下面的代码将会报错
var dog = MyPet("Charlie", 5)
var poodle = dog
print(poodle.name)
添加 @value
装饰器后,Mojo
只会为你添加未定义的构造函数,也就是说,你还是可以为每个方法实现自定义的版本