什么是结构?
了解面向对象编程是任何开发人员的必修课。面向对象的编程涉及到创建类,这些类作为对象的描述或蓝图。该对象通常是由几个变量或函数组成的。
在C、Go和Rust等语言中,类不是一个特征。相反,这些语言使用结构体,它只定义一组属性。虽然结构体不允许你定义方法,但Rust和Go都以提供访问结构体的方式定义函数。
在本教程中,我们将学习Rust中结构体操作的基本知识。让我们开始吧!
什么是Rust?
Rust是Mozilla创建的一种编程语言,它是一种快速、低级的语言,使用现代语法模式和中央包管理器,扮演着与C类似的角色。
在Rust中编写一个结构
在下面的代码中,我们将为Cat 类型写一个简单的结构,包括属性name 和age 。一旦我们定义了我们的结构,我们将定义我们的主函数。
我们将创建一个新的string 和一个新的结构实例,将name 和age 属性传给它。我们将打印整个结构,并将其属性插值在一个字符串中。对于name ,我们将使用Scratchy 。对于age ,我们将使用4 。
// This debug attribute implements fmt::Debug which will allow us
// to print the struct using {:?}
#[derive(Debug)]
// declaring a struct
struct Cat {
// name property typed as a String type
name: String,
// age typed as unsigned 8 bit integer
age: u8
}
fn main() {
// create string object with cat's name
let catname = String::from("Scratchy");
// Create a struct instance and save in a variable
let scratchy = Cat{ name: catname, age: 4 };
请注意,我们正在使用derive 属性(我们将在后面详细介绍),以自动实现我们结构上的某些特质。由于我们导出了debug 特质,我们可以使用{:?} 来打印整个结构。
// using {:?} to print the entire struct
println!("{:?}", scratchy);
// using individual properties in a String
println!("{} is {} years old!", scratchy.name, scratchy.age);
}
在这一节中,有几件重要的事情需要注意。首先,与Rust中的任何值一样,结构中的每个属性必须是types 。此外,一定要考虑字符串(字符串对象或结构)和&str (指向字符串的指针)之间的区别。由于我们使用的是字符串类型,所以我们必须从一个合适的字符串字面创建一个字符串。
derive 属性
默认情况下,结构体是不可打印的。一个结构必须实现stc::fmt::debug 函数才能使用{:?} 格式化的println! 。然而,在我们上面的代码示例中,我们使用了derive(Debug) 属性,而不是手动实现一个特质。这个属性使我们能够打印出结构,以方便调试。
属性的作用是向编译器发出指令,写出模板。在Rust中还有几个内置的derive 属性,我们可以用它来让编译器为我们实现某些特征。
[#derive(hash)]:将结构体转换为哈希值#derive(clone):添加了一个克隆方法来复制该结构[#derive(eq)]:实现了eq特质,将平等设定为所有属性的值都相同。
结构特质
我们可以创建一个带有属性的结构,但如何才能像其他语言中的类那样将其与函数绑定呢?
Rust使用了一种叫做traits的功能,它为结构定义了一捆要实现的函数。特质的一个好处是你可以用它们来进行类型化。你可以创建可以被任何实现相同trait的结构使用的函数。从本质上讲,只要你实现了正确的特质,你就可以在结构中建立方法。
使用特质来提供方法可以实现一种叫做组合的做法,这种做法在Go中也被使用。任何结构都可以混合和匹配它所需要的特质,而不是从一个父类中继承方法,而不是使用层次结构。
编写特质
让我们继续上面的例子,定义一个Cat 和Dog 结构。我们希望两者都有一个Birthday 和Sound 函数。我们将在一个名为Pet 的trait中定义这些函数的签名。
在下面的例子中,我们将使用Spot 作为Dog 的name 。我们用goes Ruff 作为Sound ,用0 作为age 。对于Cat ,我们将用goes Meow 作为声音,用1 作为age 。生日的函数是self.age += 1; 。
// Create structs
#[derive(Debug)]
struct Cat { name: String, age: u8 }
#[derive(Debug)]
struct Dog { name: String, age: u8 }
// Declare the struct
trait Pet {
// This new function acts as a constructor
// allowing us to add additional logic to instantiating a struct
// This particular method belongs to the trait
fn new (name: String) -> Self;
// Signature of other functions that belong to this trait
// we include a mutable version of the struct in birthday
fn birthday(&mut self);
fn sound (&self);
}
// We implement the trait for cat
// we define the methods whose signatures were in the trait
impl Pet for Cat {
fn new (name: String) -> Cat {
return Cat {name, age: 0};
}
fn birthday (&mut self) {
self.age += 1;
println!("Happy Birthday {}, you are now {}", self.name, self.age);
}
fn sound(&self){
println!("{} goes meow!", self.name);
}
}
// We implement the trait for dog
// we only define sound. Birthday and name are already defined
impl Pet for Dog {
fn new (name: String) -> Dog {
return Dog {name, age: 0};
}
fn birthday (&mut self) {
self.age += 1;
println!("Happy Birthday {}, you are now {}", self.name, self.age);
}
fn sound(&self){
println!("{} goes ruff!", self.name);
}
}
注意,我们定义了一个新的方法,就像一个构造函数。我们不需要像之前的片段那样创建一个新的Cat ,而是可以直接输入我们的新变量!
当我们调用构造函数时,它将使用该特定类型结构的新实现。因此,Dog 和Cat 将能够使用Birthday 和Sound 函数。
fn main() {
// Create structs using the Pet new function
// using the variable typing to determine which
// implementation to use
let mut scratchy: Cat = Pet::new(String::from("Scratchy"));
let mut spot: Dog = Pet::new(String::from("Spot"));
// using the birthday method
scratchy.birthday();
spot.birthday();
// using the sound method
scratchy.sound();
spot.sound();
}
关于特质,有几件重要的事情需要注意。首先,你必须为每个实现特质的结构定义该函数。你可以通过在trait定义中创建默认定义来做到这一点。
我们使用mut 关键字来声明结构,因为结构可以被函数变异。例如,birthday 可以增加年龄。因为birthday 会突变结构的属性,所以我们将参数作为一个可突变的引用传递给结构(&mut self) 。
在这个例子中,我们使用了一个静态方法来初始化一个新的结构,这意味着新变量的类型是由结构的类型决定的。
返回结构体
有时,一个函数可能会返回几个可能的结构,这发生在几个结构实现相同特征的情况下。要编写这种类型的函数,只需输入实现所需特征的结构的返回值。
让我们继续前面的例子,在一个Box 对象内返回Pet 。
// We dynamically return Pet inside a Box object
fn new_pet(species: &str, name: String) -> Box<dyn Pet> {
在上面的例子中,我们使用Box 类型作为返回值,这使我们能够为任何实现Pet 特质的结构分配足够的内存。我们可以在我们的函数中定义一个返回任何类型的Pet 结构的函数,只要我们把它包裹在一个新的Box 。
我们创建一个函数,通过传递一个类型为Pet 和name 的字符串来实例化我们的Pet ,而不指定age 。使用if 语句,我们可以确定要实例化什么类型的Pet 。
if species == "Cat" {
return Box::new(Cat{name, age: 0});
} else {
return Box::new(Dog{name, age: 0});
}
}
该函数返回一个Box 类型,这代表内存被分配给一个实现Pet 的对象。当我们创建Scratchy 和Spot ,我们不再需要对变量进行类型化。我们在函数中明确规定了逻辑,在那里将返回一个Dog 或Cat 。
fn main() {
// Create structs using the new_pet method
let mut scratchy = new_pet("Cat", String::from("Scratchy"));
let mut spot = new_pet("Dog", String::from("Spot"));
// using the birthday method
scratchy.birthday();
spot.birthday();
// using the sound method
scratchy.sound();
spot.sound();
}
总结
关于Rust中的结构,我们已经学到了以下内容。
- 结构允许我们在一个单一的数据结构中组合属性
- 使用特质,我们可以在一个结构上实现不同的方法
- 使用特质类型,我们可以编写可以接收和返回结构的函数
- 派生属性允许我们在结构中轻松地实现某些特质
现在,我们可以在Rust中使用组合而非继承来实现典型的面向对象的设计模式。
The post Fundamentalsfor using structs in Rust appeared first onLogRocket Blog.