rust学习笔记 3
start at 2023/03/26.

复合结构

结构体

rust 中的结构体和元组都可以将若干个类型不相同的数据捆绑在一起,但结构体的每个成员和其本身都有一个名字,这样访问的时候就不用记住下标了,元组一般用来非定义的多值传递,而结构体是规范的

  • 定义
struct Site {
    domain: String,
    name: String,
    nation: String
}

和 C 语言有以下区别:

  1. 定义语句仅用来定义,不能定义实例 (初始化)
  2. 结尾不用 ;
  3. 每个字段定义后用 , 分隔
  • 创建实例
fn main() {
    let domain = String::from("paradoxskin.github.io");
    let paradox = Site {
        domain,
        nation: String::from("China"),
        name: String::from("paradoxless")
    };
}

实例化的过程类似创建 json,不需要按照定义的顺序输入,而且如果有字段名称和现存变量同名,就可以像上面的 domain 那样简写

  • 复制修改实例
let bing = Site {
    name: String::from("bing"),
    domain: String::from("cn.bing.com"),
    ..paradox
};

在两个实例属性差不多的时候就可以只修改有区别的字段,之后使用 ..实例 即可,这句之后不能加 ,

虽然书上说 不允许一成不变地复制另一个结构体实例,要至少修改一个字段的值,但是我全部复制还是可以通过编译

结构体所有权

上面第一段实例化代码中的 domain 变量的所有权在实例化的时候发生了转移,之后无法直接使用 domain 这一变量了

关于结构体还有一个知识点

struct Temp {
    some: &str,
    strr: &str
}

上面一段代码是无法通过编译的,编译器会提示需要命名的生命周期符号,正确写法就是加上生命周期符号:

struct Temp<'c> {
    some: &'c str,
    strr: &'c str
}

这个符号的含义是让被打上标记的点生命周期一样长,下面有一个会出现 不可预测生命周期 的函数例子

fn longer(s1: &str, s2:&str) -> &str {
    if s2.len() > s1.len() {
        return s2;
    }
    else {
        return s1;
    }
}

上面这段函数也是无法通过编译的,因为如果 s1比较长,那么函数的返回值的生命周期会和 s1 一样长,反之则会和 s2 一样长,编译器无法做出谁更长的判断,因此,函数必须声明 s1 和 s2 的生命周期

fn longer<'a>(s1: &'a str, s2:&'a str) -> &'a str {
    if s2.len() > s1.len() {
        return s2;
    }
    else {
        return s1;
    }
}

这样函数的两个参数的生命周期就一样长了

在结构体里也是一样的道理,要举出例子还是有点困难的,如果搞不清楚可以直接认为只要在结构体里使用引用类型,就一定要在 & 类型后面加个 'a 即可

结构体方法

相当于类中函数

struct Rect {
    width: u32,
    height: u32,
}

impl Rect {
    fn area(&self) -> u32 {
        self.width * self.height
    }
    fn wider(&self, rect: &Rect) -> bool {
        self.width > rect.width
    }
}
impl Rect {
    fn circum(&self) -> u32 {
        (self.width + self.height) * 2
    }
    fn xx() -> u32 {
        233
    }
}
fn main() {
    let rect1 = Rect { width: 30, height: 50 };
    let rect2 = Rect { width: 10, height: 50 };
    println!("rect1's area is {}", rect1.area());
    println!("rect2's circum is {}", rect2.circum());
    println!("Is rect1 wider than rect2: {}", rect1.wider(&rect2));
    println!("{}", Rect::xx());
}

结构体方法写在 impl 语句块里,语句块需要指定结构体,同一个语句块里可以写多个结构体方法,代码中也可以有有多个 impl 语句块,&self 代表结构体对象自己,如果没有 &self 参数就相当一个静态函数,不能基于对象被调用

注意,impl 不是接口的意思

元组结构体

fn main() {
    struct Color(u8, u8, u8);
    struct Point(f64, f64);

    let black = Color(0, 0, 0);
    let origin = Point(1.0, 0.0);

    println!("black = {} {} {}", black.0, black.1, black.2);
    println!("origin = {} {}", origin.0, origin.1);
}

元组结构体和元组类似,区别就是它有名字和固定的类型格式,这个数据存在的意义就是处理那些需要定义类型又不想太复杂的简单数据,简单的说,就是为了牺牲了点结构体的可读性获取便捷性( 颜色、坐标 )

单元结构体

这种结构体无须任何成员,我现在还不知道它存在的意义是什么(

struct UnitStruct;

枚举类

和 C++ 中的枚举类有所不同,rust 中的枚举类不是默认为 0, 1, 2…

但我也不知到 Rust 中的枚举类有什么用,就先简单抄一下就好了

#[derive(Debug)]
enum Color {
    Red,
    Green,
    Blue
}
fn main() {
    let color = Color::Red;
    println!("{:?}", color);
}

输出 Red

枚举类可以包含属性,并实例化

enum Book {
    Papery(u32),
    Electronic(String),
}
fn main() {
    let book = Book::Papery(1001);
    let ebook = Book::Electronic(String::from("https://xxx.xxx"));
}

如有必要,可以用结构体的键值命名枚举类的属性

enum Book {
    Papery {
        index: u32
    },
    Electronic {
        url: String
    }
}
fn main() {
    let book = Book::Papery { index: (1001) };
    let ebook = Book::Electronic { url: String::from("https://xxx.xxx") };
}
  • 使用 match 语法

”switch 语法就是为了枚举类而设计的“

enum Book {
    Papery(u32),
    Electronic {
        url: String
    }
}
fn main() {
    let book = Book::Papery(1001);
    match book {
        Book::Papery(index) => {
            println!("Papery book {}", index);
        },
        Book::Electronic { url } => {
            println!("E-book {}", url);
        }
    }
}

如果实例是哪个枚举项就执行哪个,如果是用结构体申明的,就用 Electronic 这样的格式,用元组语法定义就用 Papery 那种格式

  • if-let 语法

如果只是想要写一个简单的判断值为某个枚举项的话,就可以不费那么大劲去写 match,可以直接用 if-let 语法

if let Book::Electronic { url } = &book {
    println!("eeeee");
}
if let Book::Papery(index) = &book {
    println!("{}", index)
}

等价于

match book {
    匹配项 => {
        something
    },
    _ => {}
}

枚举类的方法

枚举类和结构体同样可以使用 impl 语句块编写方法

写面展示一个例子

#[derive(Debug)]
enum Signal {
    Red,
    Yellow,
    Green
}

impl Signal {
    fn red(&mut self) {
        *self = Signal::Red;
    }

    fn yellow(&mut self) {
        *self = Signal::Yellow;
    }

    fn green(&mut self) {
        *self = Signal::Green;
    }
}

fn main() {
    let mut signal = Signal::Red;
    println!("{:?}", signal);
    signal.yellow();
    println!("{:?}", signal);
    signal.green();
    println!("{:?}", signal);
}

泛型

和其他语言的泛型类似

Rust 中函数、结构体和枚举项都可以使用泛型

泛型函数

下面的泛型是用来返回数组最后一个元素的

fn get_last<T>(array: &[T]) -> &T {
    &array[array.len() - 1]
}
fn main() {
    let la = ["hwllo", "two", "one"];
    let lb = [1, 2, 3, 4, 233];
    println!("{}", get_last(&la));
    println!("{}", get_last(&lb));
}

这里的泛型函数的使用方式被简化了,完整的使用方法是 函数名::<泛型>(参数...)

泛型结构体

struct Point<T> {
    x: T,
    y: T
}

fn main() {
    let point = Point::<i32> {
        x: 1,
        y: 233,
    };
    let point2 = Point {
        x: 1.3,
        y: 2.333
    };
}

这里和上面类似,支持自动类型判断,当然,如果自动类型判断冲突的时候,编译器是不会让你通过的

// 错误代码
let point3 = Point {
    x: 1,
    y: 2.33
};

下面是多泛型的示范:

struct Point<T, X> {
    x: T,
    y: X
}

fn main() {
    let point = Point::<i32, i32> {
        x: 1,
        y: 233,
    };
}

泛型枚举类

枚举类和结构体类似

enum Shape<T> {
    Rectangle(T, T),
    Cube(T, T, T)
}

fn main() {
    let s1 = Shape::Rectangle(23, 32);
    let s2: Shape<i32> = Shape::Cube(1,2,3);
}

impl 泛型

用来为泛型复合类型编写方法,主要方法有两种,一种是对泛型类直接实现方法,另一种是对具体类实现方法

struct Rect<T> {
    x: T,
    y: T
}
// 对泛型直接实现方法
impl<A> Rect<A> { //可以不是T, 是A
    fn get_x(&self) -> &A {
        &self.x
    }
    fn get_y(&self) -> &A {
        &self.y
    }
}
// 对具体类实现方法
impl Rect<i32> {
    fn area(&self) -> i32 {
        self.x * self.y
    }
}

fn main() {
    let rect = Rect{y: 45, x: 24};
    println!("{} * {} = {}", rect.get_x(), rect.get_y(), rect.area());
}

在对泛型直接实现方法那里包含两个泛型声明,这两处的泛型声明是必要的,而且是有从属关系的,impl 后面的泛型从属于 Rect

泛型方法

既然函数有泛型,那么方法有泛型也不是奇怪的事情,以下是对一个非泛型类实现的一个泛型方法:

struct Rect {
    x: i32,
    y: i32
}

impl Rect {
    fn ffxx<T>(&self, a: T) -> T{
        a
    }
}
fn main() {
    let rect = Rect{ x: 32, y: 23};
    println!("{}", rect.ffxx("hello"));
}

以下是对泛型类的泛型方法:

#[derive(Debug)]
struct Data<A, B> {
    x: A,
    y: B
}

impl<A, B> Data<A, B> {
    fn mix<C, D>(self, other: Data<C, D>) -> Data<A, D> {
        Data { x: self.x, y: other.y }
    }
}

fn main() {
    let a = Data {
        x: 123.45,
        y: "hello"
    };
    let b = Data {
        x: 666,
        y: 2333
    };
    let c = a.mix(b);
    println!("{:?}", c);
}

对这个所有权的分析真是困难啊,上面经过 a.mix(b) 之后,ab 的所有权都给了 c ,确实是这个道理,但是要怎么写才能让 ab 不失去所有权呢?现在不是很熟练 :(

一脸懵逼地写出来了,不止所以然

#[derive(Debug)]
struct Data<A, B> {
    x: A,
    y: B
}

impl<A, B> Data<A, B> {
    fn mix2<'a, C, D>(&'a self, other: &'a Data<C, D>) -> Data<&A, &D> {
        let x = &self.x;
        let y = &other.y;
        Data { x, y }
    }
}

fn main() {
    let a = Data {
        x: 123.45,
        y: "hello"
    };
    let b = Data {
        x: 666,
        y: 2333
    };
    println!("{:?}", a.mix2(&b));
    println!("{:?}", a);
}

这章就先到这里吧,这周的计划还有三章,应该是来的及的(半场开香槟

感觉一年后的我会骂现在学 rust 的我,我学这东西单纯是拿准备实习面试的时间去学自己的爱好啊,不是面向市场的,保护保护保护

2023/03/28
> CLICK TO back <