Rust 基础知识7 - Struct 相关知识

简介

  • 接上回书

什么是struct

  • struct,是结构体,是一个自定义的数据类型,目的是关联相关不同变量。

定义struct

  • 使用struct 关键字,并为整个struct命名。
  • 在花括号内,为所有字段定义名称和类型。
  • 使用struct 需要创建 struct 的实例
1、为每个字段制定具体值。
2、无需按声明顺序制定。
  • 获取struct 的值,通过点标记法即可 struct.filed_name
  • 特别需要注意如果 struct 声明为可变的那么实例中的所有字段都是可变的。
  • 举例:
fn main() {
    struct Employee {
        username: String,
        age: i16,
        email: String,
    }

    let emp = Employee {
        username: String::from("LinHai."),
        age: 39i16,
        email: String::from("[email protected]"),
    };

    println!("Winer is : {} {} {}", emp.username, emp.age, emp.email);
}

image.png
  • 字段初始化简写,当字段名与字段值的变量名称相同时可以使用简写方式,例如:(注意username、age变量的给值方法)
fn main() {
    struct Employee {
        username: String,
        age: i16,
        email: String,
    }

    let username = String::from("linhai");
    let age:i16 = 39;

    let emp = Employee {
        username, // 如果变量名称与结构定义的变量名相同则可以直接赋值
        age, // 如果变量名称与结构定义的变量名相同则可以直接赋值
        email: String::from("[email protected]"),
    };

    println!("Winer is : {} {} {}", emp.username, emp.age, emp.email);
}

  • 还有一种比较特殊的 struct 的更新语法,可以基于某个已有的结构实例初始化新的对象,如下例子中注意 ..emp 的用法:
fn main() {
    struct Employee {
        username: String,
        age: i16,
        email: String,
    }

    let username = String::from("linhai");
    let age:i16 = 39;

    // 定义基础实例对象
    let emp = Employee {
        username, // 如果变量名称与结构定义的变量名相同则可以直接赋值
        age, // 如果变量名称与结构定义的变量名相同则可以直接赋值
        email: String::from("[email protected]"),
    };

    let mut emp_child = Employee {
        username: String::from("林巍"),
        ..emp // 这种拓展语法可以基于某个结构建立结构
    };
    
    // 修改年龄
    emp_child.age = 3;

    println!("Winer is : {} {} {}", emp_child.username, emp_child.age, emp_child.email);
}

image.png

Tuple struct

  • 这是一个简化版的 struct,具体形式大致如下:
struct Color(i32,i32,i32);
struct Point(i32,i32);
let black = Color(0,0,0);
let origin = Point(0,0,0);
  • 还有一种更特殊的叫做 Unit-Like Struct 比如 struct Man {};

数据的所有权

  • 你会发现如果定一个变量,是String类型,但是随着赋值所有权会转移到结构中,之后在使用就会出错,比如:


    image.png
image.png
  • 具体的解决需要引入声明周期的概念,稍后再讲。

一个具体的实例

  • 接下来将根据这个程序进行改写。
fn main() {
    // 初始化结构
    let rec = Rectangle {
        width: 30,
        height: 50,
    };

    let result = area(&rec);
    println!("矩形:{:?},的面积是:{}", rec, result);
}

// 定一个个矩形结构
#[derive(Debug)]
struct Rectangle {
    width: i32,
    height: i32,
}

// 定一个区域函数
fn area(rectangle:&Rectangle) -> i32{
    rectangle.width * rectangle.height
}

Struct 的方法

  • 方法和函数类似,通过fn关键字定义,名称、参数和返回值。
  • 方法与函数不同的地方:
1、方法是在struct(enum、trait对象)的上下文中定义的。
2、方法的第一个参数是self,self指向被调用的struct 实例。
3、方法在 impl (implement)块中定义的` impl StructName {}`
4、方法的第一个参数可以使 &self,也可以获得其所有权或可变借用,和其他参数一样。
  • 改进上面的实例,主要是把 area 方法整合到 struct 中,这样整体结构更好:

fn main() {
    // 初始化结构
    let rec = Rectangle {
        width: 30,
        height: 50,
    };

    let result = rec.area(); // 改变了计算形式。
    println!("矩形:{:?},的面积是:{}", rec, result);
}

// 定一个个矩形结构
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}
// 这个就是给Struct 加方法的方式
impl Rectangle {
    // 定一个区域函数(从外面挪到块里面)
    fn area(&self) -> u32{
        self.width * self.height
    }
}

image.png
  • 方法调用的是的特别说明
/*
C/C++中: object->something() 等于 (*object).something() 也就是解构指针,Rust并不需要这样,Rust会自动引用或解引用。
在调用方法时,Rust根据情况自动添加 &、&mut 或 * ,以便 object 可以匹配方法的签名。
*/
p1.distance(&p2);
// 等于
(&p1).distance(&p2);

  • Struct 后面还可以跟随多个不同参数:
fn main() {
    // 初始化结构
    let rec = Rectangle {
        width: 30,
        height: 50,
    };

    let rec2 = Rectangle {
        width: 20,
        height: 30,
    };

    let rec3 = Rectangle {
        width: 70,
        height: 30,
    };

    let result = rec.area();
    println!("矩形:{:?},的面积是:{}", rec, result);

    println!("rec 能放下 rec2 ? {}", rec.can_hold(&rec2) );
    println!("rec 能放下 rec3 ? {}", rec.can_hold(&rec3) );
}

// 定一个个矩形结构
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    // 定一个区域函数
    fn area(&self) -> u32{
        self.width * self.height
    }

    fn can_hold(&self, rec: &Rectangle) -> bool {
        self.width > rec.width && self.height > rec.height 
    }
}
image.png

关联函数(类似于静态方法)

  • 可以在impl 块中定义一个不以 self 作为第一个参数的函数,这种形式叫做关联函数,调用方式类似 String::from()
  • 关联函数通常用于构造器,举例:
fn main() {
    // 初始化结构
    let width = 60u32;
    let square = Rectangle::create_square(width);

    let result = square.area();
    println!("矩形:{:?},的面积是:{}", square, result);
    println!("开始定义的变量 width = {} 的所有权被交回来了因此并不会消失", width);
}

// 定一个个矩形结构
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    // 定一个区域函数
    fn area(&self) -> u32{
        self.width * self.height
    }

    // 创建一个正方形,注意这里的参数 width 传递的是所有权
    fn create_square(width: u32) -> Rectangle {
        // 初始化后实际上把所有权返回了。
        Rectangle {
            width,
            height: width,        
        }
    }
}
image.png

Struct 的智能指针 Box

  • 参考文档:https://rustcc.cn/article?id=76e5f3fb-20b9-48c9-8fc6-a0aad40ced8c

  • 智能指针

1、智能指针的概念起源于C++,智能指针是一类数据结构,他们的表现类似指针,但是拥有额外的元数据和功能。
2、在Rust中,引用和智能指针的一个的区别是引用是一类只借用数据的指针;相反,在大部分情况下,智能指针拥有他们指向的数据。Rust标准库中不同的智能指针提供了比引用更丰富的功能:
------------------
Box,用于在堆上分配数据。
Rc,一个引用计数类型,其数据可以有多个所有者。
Ref 和 RefMut,通过RefCell访问,一个在运行时而不是在编译时执行借用规则的类型。
  • 智能指针Box
1、在Rust中,所有值默认都是栈上分配。
2、通过创建Box,可以把值装箱,使它在堆上分配。
3、Box类型是一个智能指针,因为它实现了Dereftrait,它允许Box值被当作引用对待。
4、当Box值离开作用域时,由于它实现了Droptrait,首先删除其指向的堆数据,然后删除自身。
  • Box 的举例,如果一个类型指向他自己就可以通过Box来实现,比如一个树的节点表示
#[derive(Debug)]
struct TreeNode {
    data :i32, 
    left_node : Box,
    right_node : Box,
}

结束

  • 感谢阅读。

你可能感兴趣的:(Rust 基础知识7 - Struct 相关知识)