Rust的集合

我爱海鲸 2024-01-30 12:54:04 rust学习

简介Vector、String、HashMap

1、Vector

使用Vector存储多个值

   vec<T>,叫做vector

   由标准库提供

   可存储多个值

   只能存储相同类型的数据值

   在内存中连续存放

创建Vector

   Vec:new函数

fn main() {
    let v: Vec<i32> = Vec::new();
}

使用初始值创建Vec<T>,使用vec!宏

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

更新Vector

向Vector添加元素,使用push方法

fn main() {
    let mut v = Vec::new();

    v.push(1);
}

删除Vector

与任何其它struct一样,当Vector离开作用域后

   它就被清理掉了

读取Vector的元素

两种方式可以引用Vector里的值;

fn main() {
    let v = vec![1,2,3,4,5];
    let third:&i32 = &v[2];
    println!("The third element is {}",third);

    match v.get(2) {
        Some(third) => println!("The third element is {}",third),
        None => println!("There is no third element"),
    }
}

索引vs get处理访问越界

所有权和借用规则

不能在同一作用域内同时拥有可变和不可变引用

fn main() {
    let mut v = vec![1,2,3,4,5];
    let first:&i32 = &v[0];
    v.push(6);

    println!("The third element is {}",first);
}

这里就会报错,因为它已经进行了一个不可变的引用,现在又进行可变的引用,所以就会报错。

遍历Vector 中的值

for循环

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

    for i in &v {
        println!("{}",i)
    }
}

修改里面的值:

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

    for i in &mut v {
        *i += 50;
    }

    for i in &v {
        println!("{}",i)
    }
}

2、Vector+enum

使用enum来存储多钟数据类型

   Enum的变体可以附加不同类型的数据

   Enum的变体定义在同一个enum类型下

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),
    ];
    
}

3、String

创建和更新

Rust开发者经常会被字符串困扰的原因

   Rust倾向于暴露可能的错误

   字符串数据结构复杂

   UTF-8

字符串是什么?

   Byte 的集合

   一些方法能将byte解析为文本

Rust的核心语言层面,只有一个字符串类型:字符串切片str(或&str)

字符串切片:对存储在其它地方、UTF-8编码的字符串的引用

字符串字面值:存储在二进制文件中,也是字符串切片

String类型

   来自标准库而不是核心语言

   可增长、可修改、可拥有

   UTF-8编码

通常说的字符串是指?

   String和 &str

   标准库里用的多

   UTF-8编码

其它类型的字符串

   Rust的标准库还包含了很多其它的字符串类型,例如:OsString、OsStr、CStrng、CStr

   String vs Str后缀:拥有或借用的变体

   可存储不同编码的文本或在内存中以不同的形式展现

   Library crate针对存储字符串可提供更多的选项

创建一个新的字符串(String)

   很多Vec<T>的操作都可用于String。

   String::new()函数

fn main() {
    let mut s = String::new();
}

使用初始值来创建String:

   to_string()方法,可用于实现了Display trait的类型,包括字符串字面值

fn main() {
    // 字符串字面值(字符串切片)
    let data = "init";
    // 转化为字符串
    let s = data.to_string();
    // 直接转化为字符串
    let s1 = "init".to_string();
}

   String::from()函数,从字面值创建String

fn main() {
    let s = String::from("init");
}

更新String

   push_str()方法:把一个字符串切片附加到String

fn main() {
    let mut s = String::from("foo");

    s.push_str("bar");

    println!("{}",s)
}

   push()方法:把单个字符附加到String

fn main() {
    let mut s = String::from("lo");

    s.push('l');

    println!("{}",s)
}

+:连接字符串

fn main() {
    let  s1 = String::from("Hello");

    let  s2 = String::from(" World!");

    let s3 = s1 + &s2;

    println!("{}",s3);

    println!("{}",s1);

    println!("{}",s2);
}

s1是String类型的,但是s2是字符串的切片或者是字符串的引用

s2在拼接时候还可以继续使用,但是s1在拼接之后就不能够使用了

这是因为:

   +使用了类似这个签名的方法 fn add(self, s: &str) -> String { ... }

      标准库中的add方法使用了泛型

      只能把&str添加到String

      解引用强制转换( deref coercion)

format!:连接多个字符串

fn main() {
    let  s1 = String::from("tic");

    let  s2 = String::from(" tac!");

    let  s3 = String::from(" toe!");

    // let s3 = s1 + "_" + &s2 + "_" + &s3;

    // println!("{}",s3);

    let s3 = format!("{}-{}-{}",s1,s2,s3);

    println!("{}",s3); 

    println!("{}",s1); 

    println!("{}",s2); 

}

上面我们看到 s1,s2也同样能够输出 表示format!不会获得他们的所有权

对String按索引的形式进行访问

   按索引语法访问String的某部分,会报错

fn main() {
  let s1 = String::from("hello");
  let h = s1[0];

}

Rust的字符串不支持索引语法访问

内部表示

String是对Vec<u8>的包装

len方法

fn main() {
  let s1 = String::from("我").len();
  // UniCode 标量值
  println!("{}",s1);

}

字节、标量值、字形簇
Bytes, Scalar Values, Grapheme Clusters

Rust有三种看待字符串的方式:

字节

fn main() {
  let s1 = String::from("我");
  
  for b in s1.bytes() {
    println!("{}",b)
  }
  

}

标量值

fn main() {
  let s1 = String::from("我");
  
  for b in s1.chars() {
    println!("{}",b)
  }
  

}

字形簇(最接近所谓的“字母”) 这个官方的标准库没有支持,可以自行去第三方库实现

Rust不允许对String进行索引的最后一个原因:

   索引操作应消耗一个常量时间(O(1))

   而String无法保证:需要遍历所有内容,来确定有多少个合法的字符。

切割String

可以使用和一个范围来创建字符串的切片

fn main() {
  let s1 = String::from("我");
  
    let s1 = &s1[0..3];

    println!("{}",s1)

}

这里中文是占用三个,所以上面我们能输出”我“这个文中,但是如果把它修改为0..2就会painc

必须谨慎使用

如果切割时跨越了字符边界,程序就会 panic。

(b1,b2).(b3,b3),(b4,b5),(b7,b7) 如果这样切割就会发生painc

遍历String的方法

fn main() {
  let s1 = String::from("我");
  
  for b in s1.chars() {
    println!("{}",b)
  }
  

}

String不简单

Rust选择将正确处理String 数据作为所有Rust程序的默认行为一程序员必须在处理UTF-8数据之前投入更多的精力

可防止在开发后期处理涉及非ASCIl字符的错误。

3、HashMap

HashMap<K,V>

键值对的形式存储数据,一个键(Key)对应一个值(Value)

Hash函数:决定如何在内存中存放K和V

适用场景:通过K(任何类型)来寻找数据,而不是通过索引

创建HashMap

创建空HashMap: new()函数

添加数据: insert()方法

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();

    map.insert(String::from("key"), 56);
}

HashMap 用的较少,不在 Prelude 中

标准库对其支持较少,没有内置的宏来创建HashMap

数据存储在heap 上

同构的。一个HashMap 中:

   所有的K必须是同一种类型

   所有的V必须是同一种类型

另一种创建HashMap 的方式: collect方法

在元素类型为Tuple 的Vector上使用collect方法,可以组建一个HashMap:

   要求Tuple有两个值:一个作为K,一个作为V

   collect方法可以把数据整合成很多种集合类型,包括HashMap

   返回值需要显式指明类型

use std::collections::HashMap;

fn main() {
    let teams = vec![String::from("Blue"),String::from("Yellow")];
    let intial_scores = vec![10,50];

    let scores :HashMap<_,_> =
        teams.iter().zip(intial_scores.iter()).collect();
}

HashMap和所有权

对于实现了Copy trait的类型(例如 i32),值会被复制到HashMap 中。

对于拥有所有权的值(例如String),值会被移动,所有权会转移给HashMap-(例子)

如果将值的引用插入到HashMap,值本身不会移动

use std::collections::HashMap;

fn main() {
    let field_name = String::from("Favoirte color");

    let field_value = String::from("Blue");

    let mut map = HashMap::new();

    // map.insert(field_name, field_value);
    // println!("{},{}",field_name,field_value);

    map.insert(&field_name, &field_value);
    println!("{},{}",field_name,field_value)
}

在HashMap有效的期间,被引用的值必须保持有效

访问HashMap中的值

get 方法

―参数:K

一返回:Option<& V>

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();

    map.insert(String::from("Blue"), 10);

    map.insert(String::from("Yellow"), 50);

    let tem_name = String::from("Blue");

    let score = map.get(&tem_name);

    match score {
        Some(s)=> println!("{}",s),
        None => println!("tem not exist")
    }

}

遍历HashMap

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();

    map.insert(String::from("Blue"), 10);

    map.insert(String::from("Yellow"), 50);

    for (k,v) in map{
        println!("{},{}",k,v)
    }

}

更新HashMap<K,V>

   HashMap大小可变

   每个K同时只能对应一个V

   更新HashMap 中的数据:

K已经存在,对应一个V

   替换现有的V

   保留现有的V,忽略新的V。

   合并现有的V和新的V

K不存在
   添加一对K,V

替换现有的V

   如果向 HashMap插入一对 KV,然后再插入同样的K,但是不同的V,那么原来的V会被替换掉

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();

    map.insert(String::from("Blue"), 10);

    map.insert(String::from("Blue"), 50);

    println!("{:?}",map);

}

   只在K不对应任何值的情况下,才插入V

      entry方法:检查指定的K是否对应一个V

         参数为K

         返回enum Entry:代表值是否存在

Entry 的 or_insert()方法:―返回:

   如果K存在,返回到对应的V的一个可变引用

   如果K不存在,将方法参数作为K的新值插进去,返回到这个值的可变引用

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();

    map.insert(String::from("Blue"), 10);

    // map.entry(String::from("Yellow")).or_insert(50);
    let e = map.entry(String::from("Yellow"));
    println!("{:?}",e);
    e.or_insert(50);

    map.entry(String::from("Blue")).or_insert(50);

    println!("{:?}",map);

}

基于现有V来更新V

use std::collections::HashMap;

fn main() {
    let text = "hello world winderful world";
    let mut map = HashMap::new();

    for word in text.split_ascii_whitespace()  {
        let count = map.entry(word).or_insert(0);
        *count += 1;
    }

    println!("{:?}",map);

}

Hash函数

默认情况下,HashMap使用加密功能强大的Hash 函数,可以抵抗拒绝服务(DoS)攻击。

   不是可用的最快的Hash

   算法但具有更好安全性。

可以指定不同的hasher来切换到另一个函数。

   hasher是实现BuildHasher trait的类型

 

你好:我的2025