rust 集合

Created

2024-06-02 13:34:56

Updated

2024-10-31 10:41:09

1 vector

Tip
  1. 只能存储相同类型的数据
  2. 在内存中连续存放
  3. 容量可以在执行过程中动态扩大和缩放
    1. 既然是动态的,那么它必定是要在堆上分配的.

1.1

fn main() {
    // 第一种方式,存放的类型是i32,
    // 也可以不指定是什么类型的(去掉Vec<i32>),push 后 就推断出什么类型了
    let mut v: Vec<i32> = Vec::new();
    // 添加
    v.push(5);
    v.push(6);
    // 第二种定义方式
    // 宏等价于调用 Vec::new 创建一个新的空 vector,然后向其中添加元素
    let mut y=vec![1,2,3,4,5];
    // 第三种 通过迭代器创建
    let v: Vec<i32> = (0..5).collect();
    println!("{:?}", v);
    // 第四中 设置 capacity
    let mut v = Vec::with_capacity(5);
    v.push(1);
    println!("{}-{}", v.capacity(), v.len());

    // 插入和删除
    let mut v = vec![10, 20, 30, 40, 50];
    // 在索引为 3的地方插入 35
    v.insert(3, 35);
    assert_eq!(v, [10, 20, 30, 35, 40, 50]);
    // 移除索引为 1的元素
    v.remove(1);
    assert_eq!(v, [10, 30, 35, 40, 50])
}

1.2 读单个元素

#[test]
fn test() {
    let v = vec![1, 2, 3, 4, 5];

    let third: &i32 = &v[2];
    println!("The third element is {third}");

    // get的返回类型是  Option
    let third: Option<&i32> = v.get(2);
    match third {
        Some(third) => println!("The third element is {third}"),
        None => println!("There is no third element."),
    }
}

越界,报错

#[test]
fn test() {
    let v = vec![1, 2, 3, 4, 5];
    // 会直接panic: index out of bounds
    let does_not_exist = &v[100];
    // 这个不会报错
    let does_not_exist = v.get(100);
}

1.3 存储不同类型的数据?

enum SpreadsheetCell {
    Int(i32),
    Float(f64),
    Text(String),
}
fn main() {
    let row = vec![
        SpreadsheetCell::Int(3),
        SpreadsheetCell::Text(String::from("blue")),
        SpreadsheetCell::Float(10.12),
    ];
}

1.4 遍历

#[test]
fn test() {
    let v = vec![100, 32, 57];
    // 不可变借用 遍历
    for i in &v {
        println!("{i}");
    }

    let mut v2 = vec![100, 32, 57];
    // 可变借用,然后修改元素
    for i in &mut v2 {
        *i += 50;
    }
    println!("{:?}", v2)
}

2 hashmap

use std::collections::HashMap;
fn main() {
    // 如果仅仅只有这个语句, 则会报错,因为编译器无法知道数据类型,就无法分配内存了
    // let mut s = HashMap::new();
    // 这样就不会了
    let mut scores: HashMap<String, i32> = HashMap::new();
    // 插入元素
    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Yellow"), 50);

    // 读取
    let team_name = String::from("Blue2");
    // get() 返回 类型是Option<&V>,没有的话就是 返回 None
    // copied() 将 返回的Option<&i32> 变成 Option<i32>
    // unwrap_or(0)  表示如果该key ,则返回一个 0
    let score = scores.get(&team_name).copied().unwrap_or(2);
    println!("{}", score);

    // 遍历
    for (key, value) in &scores {
        println!("{key}: {value}");
    }

    // 更新元素, 还是用insert 会覆盖
    scores.insert(String::from("Blue"), 12);

    // key Yellow 不存在,才添加
    // 返回一个value的可变借用
    scores.entry(String::from("Yellow")).or_insert(50);

    let text = "hello world wonderful world";
    let mut map = HashMap::new();
    for word in text.split_whitespace() {
        // 第一次 word  key 不存在的时候, 插入 0, 返回value的可变借用
        // 下面的操作才会是  *count +=1
        // 第二次来, 存在, 返回旧 value 的可变借用 ,后面 还是 *count +=1
        let count = map.entry(word).or_insert(0);
        *count += 1;
    }

    println!("{:?}", map);
    println!("{:#?}",map); // 格式化打印
}

所有权

use std::collections::HashMap;
fn main() {
    let field_name = String::from("Favorite color");
    let field_value = String::from("Blue");

    let mut map = HashMap::new();
    map.insert(field_name, field_value);
    //  field_name, field_value  已经被move 了. 报错了. 看值的类型
    // println!("{}-{}", field_name, field_value);
}

3 字符串

Diagram
Important

rust中字符串默认编码是utf-8,是变长的, 字母ab这些是一个字节, 中文3个字节

字符串相关的几种定义

Diagram

3.1 str

fn main(){
    let a = "abc";
    let b = "hello";
}
  • 我们可以得到这个ab的类型是 &str,在没有学习切片前,仅从这个类型名称看,我们可以推断 &str 是一个指向字符串字面量的指针.
  • 既然是 &这个操作, 那么*a 指向的类型似乎就是str了,ab指向的str类型占用的空间不同,这样说来str类型的长度是可变的.
  • 所以我们无法在代码中直接使用str类型, 因为编译期间需要确定变量的大小.
  • 那么不难推断出&str这种引用可变长度类型的类型, 除了有指针指向str外, 还必须有一个信息来存储你引用的长度.

3.2 &str

字符串切片: 只是对一块内存空间的借用,无所有权

use std::mem;
fn main() {
    // 字符切片 &str , abc 这个是 字符串字面量
    let x = "abc";
    println!(
        "x指向的字符串实际地址:{:p}--长度:{}--占用字节数: {}",
        x.as_ptr(),
        x.len(),
        mem::size_of_val(&x)
    );
    let y: &'static str = "abc";
    println!(
        "y指向的字符串实际地址:{:p}--长度:{}--占用字节数: {}",
        y.as_ptr(),
        y.len(),
        mem::size_of_val(&y)
    );
    // 字符串 String
    let z = String::from(x);
    println!("z指向的字符串的地址:{:p}", z.as_ptr());
    let s = String::from("hello world");
    // 字符串切片
    let s2 = &s[0..4]; // s2 内容是 "hell"
    println!("String s:{:p}", s.as_ptr());
    println!("&str s2:{:p}", s2.as_ptr());
    println!("{s}-{s2}");
}

看看是否能修改

fn main() {
    // 会直接提示不需要mut
    let mut a = "abc";
    // let mut a = "abc".to_string();
    let b = a.as_ptr() as *mut i8;
    unsafe {
        println!("11");
        *b = 98; // 修改字符串内容
        println!("22"); // 上一步无法成功执行,但是没报错, 这里不会执行打印
    }
    println!("x{}", a);
}
fn main() {
    // 不会转义, 原样输出
    let a = r"\a\b";
    println!("{}", a);
    println!(
        r#"hello
world \n ('"')
"#
    )
}

3.3 String

字符串:有这块内存空间的所有权

是一种集合类型,所以前面集合类型的操作也适用

Important

函数参数用 &str, 结构体成员图方便的话,可以直接用 String

3.3.1 基础操作

use std::mem;
fn main() {
    let s1 = "hello".to_string();
    // 空字符串
    let s2 = String::new();
    let s3 = " world".to_string();
    // + 法 , 第二个变量 要是 字符串的借用
    let c = s1 + &s3; //s1 被move 了
    println!("{}", c);

    // format! 宏 来拼接字符串, 类似 println! ,只不过返回结果,不打印
    let s1 = String::from("tic");
    let s2 = String::from("tac");
    let s3 = String::from("toe");
    // let s = s1 + "-" + &s2 + "-" + &s3;
    let s = format!("{s1}-{s2}-{s3}");
    println!("{}-{}-{}", s1, s2, s3); // 都可用

    let mut x = String::from("abc");
    println!("{:p}", &x);
    println!("{}", mem::size_of_val(&x));
    x.push_str("我们");
    println!("{}", x);
    println!("{}", mem::size_of_val(&x));
    println!("{:?}-{}-{}", x.as_ptr(), x.len(), x.capacity());
    x.pop();
    println!("{:?}-{}-{}", x.as_ptr(), x.len(), x.capacity());
    x.push('x');
    println!("{:?}-{}-{}", x.as_ptr(), x.len(), x.capacity());
    // cap 这个显示的值和 内部实际预留空间,应该是不同, 参考go的设计
    x.push_str("hello world python golang rust c++ c# java php");
    println!("{:?}-{}-{}", x.as_ptr(), x.len(), x.capacity());
}

3.3.2 切片

fn main() {
    let s = String::from("hello");
    // [start_index..end_index]
    let slice1 = &s[0..2];
    let slice2 = &s[..2]; // 同上
    println!("{}-{}", slice1, slice2);

    let len = s.len();

    let slice3 = &s[3..len];
    let slice4 = &s[3..]; //同上
    println!("{}-{}", slice3, slice4);

    let slice5 = &s[0..len];
    let slice6 = &s[..]; // 同上
    println!("{}-{}", slice5, slice6);
}
通过可变借用的切片,对原String进行修改
fn main() {
    let mut a = "abc".to_string();
    let b = &mut a[..];
    // 我们无法通过b对原a String进行添加字符的操作
    // 因为我们只是可变借用了原String的一部分
    b.make_ascii_uppercase(); // 将 a 的内容转换为大写
    println!("{}", a);
}

3.3.3 索引操作?

String 类型无法进行 索引操作

fn main() {
    let s1 = String::from("hello");
    let h = s1[0]; // 报错
}
使用这种方式来
fn main() {
    let s1 = String::from("he我ll们o");
    // 这种查找 复杂度 O(n) , 每个"字" 占用空间不同, 需要先解析出一个个"字"
    let h = s1.chars().nth(0);
    let i = s1.chars().nth(2);
    println!("{:?}", h); // Some('h')
    println!("{:?}", i); // Some('我')
}

3.3.4 三种方式读取

String 类型是对 Vec[u8]的一种包装

fn main() {
    let s1 = String::from("नमस्ते");
    println!("{}", s1.len());
    // 1. 以字节(byte) 的形式
    for b in s1.bytes() {
        println!("{b}");
    }
    // 2. 以unicode 标量值(scalar values)的形式

    // 打印了 每一个字(或字母)
    for c in "hello我们".chars() {
        println!("{}", c);
    }
    // 但是 这个好像不对了,
    // .chars()  用unicode 编码的方式来读取,按理说应该能读取到想要的字才对
    // 毕竟 unicode 编码了全世界的字符(每个字符都有对应的unicode编码)
    // 原因是 梵文中的某些字符可能由多个Unicode标量值组成,这些标量值在视觉上被视为一个整体
    // 所以 这里只打印了 "真正字符"的一部分
    for c in s1.chars() {
        println!("{}", c);
    }
    println!("=============");
    // 3. 获取真正更接近 字母的 字形簇 (grapheme clusters)
    // 需要添加 第三方crate: cargo add unicode_segmentation
    use unicode_segmentation::UnicodeSegmentation;
    for grapheme in s1.graphemes(true) {
        println!("{}", grapheme);
    }
}

3.4 单字节字符字面量

Tip
  • 这个实际是单字节数组
  • char 是 字符字面量, 一个字符是4个字节
use std::any::type_name;

fn print_type_of<T>(_: &T) {
    println!("{}", type_name::<T>());
}
fn main() {
    let a = b"hello";
    // &[u8; 5]  结果发现是 [u8;5] 这种数组的引用
    print_type_of(&a);
    let b = a as &[u8]; // 转切片
}

3.5 通过切片计算实际指向的字符串占用的字节大小

fn main() {
    let s1 = String::from("hello我们");
    // 方法1:使用 std::mem::size_of_val()
    println!(
        "s1 实际指向的字符串内容占用的内存大小: {} 字节",
        // s1.as_str()  &String 转为 &str 字符串切片
        std::mem::size_of_val(s1.as_str()) //11  hello=5个字节, 我们=6个字节
    );
    // s1 字符串变量 占用空间大小 24
    println!("字符串变量s1占用内存大小: {}", std::mem::size_of_val(&s1));
    let a = [1, 2, 3];
    let b = &a; // b 数组 的指针, 就是 8个字节
    println!("数组指针的大小(b的大小): {}", std::mem::size_of_val(&b));
    let c = b as &[i32]; // 数组指针转换为 切片
    println!("数组切片占用空间(c的大小): {}", std::mem::size_of_val(&c)); // 16 胖指针 ,没毛病
    println!(
        "切片指向的内容(这里刚好是整个数组内容)实际占用的空间: {}",
        std::mem::size_of_val(c)
    ); // 12 ,3个i32 =12
    let d = &a[1..2];
    // size_of_val(指针) => 计算指针指向的 内容占用内存大小
    // &[T]或 &str 这种 切片, 指向的是 不定长的类型 [T] 或 str,所以是计算他们占用的空间
    println!("切片指向的内容占用的空间: {}", std::mem::size_of_val(d)); // 8 ,2个i32
}
Back to top