Rust入门(五):数据结构与泛型

rust提供了多种数据结构来帮助存储数据,他们都是可变长度的集合

Vector

vector 允许我们以可变长度存储一系列相同的值,类似于可变长度的数组,它在创建时需要指定存储的数据的类型,或者指定初始值使得编译器能够分析出其中存储的类型

//新建一个空的
let v: Vec<i32> = Vec::new();
//设置初始值
let v = vec![1, 2, 3];

你可以将 vector 申明为可变的,这样就可以使用push方法可以向其中新增元素

let mut v = Vec::new();
v.push(5);

以下是一些其他api的使用方式

vec.is_empty()  //判空,返回bool值
[1].repeat(5)   //[1,1,1,1,1],一个1重复5次的vector
v.len()         //长度
//追加一组数据
let mut vec5 = [1].repeat(2);
vec.append(&mut vec5);
//追加一组数据
vec.extend([1, 2, 3].iter().copied());
//删除
vec.remove(vec.len() - 1);
//排序
vec.sort();
//寻找是否存在某个数据,返回下标bool
let ss = vec.contains(&1);

再获取一个 vector 中的元素时,可以使用下标访问它,但是对于 String 等非标量的数据结构,直接访问它会发生所有权转移的问题

//这是可以的
let v = vec![1, 2, 3];
let third = v[2];
//这是报错的
let v = vec![String::from("a"), String::from("a"), String::from("a")];
let third = v[2];

为了解决这个问题,我们通过会引用 vector 中的元素在,引用 vector 的元素时,可以选择使用 &引用或者 get 的方式来引用它,区别是 &引用 是返回 vector中某个元素的引用,如果这个元素不存在,会发生越界的错误;而 get 函数返回一个 Option 指针 ,如果你访问的元素不存在不会产生问题:

let v = vec![1, 2, 3];
//引用
let third: &i32 = &v[2];
//get函数
match v.get(2) {
    	//这里的third是一个指针
        Some(third) => println!("The third element is {}", third),
        None => println!("There is no third element."),
}

但是如果我们获取了 vector 中一个数的引用,再去改变这个 vector 可能会引发问题,因为改变 vector 可能会引发内存的重新发配,而引用的地址的信息可能因此发生改变,从而产生异常。你可以通过之前处理String 相同的克隆一份数据的方式来获取数据:

//这样是报错的
let v = vec![1, 2, 3];
let third: &i32 = &v[2];
v.push(2);

//clone一份数据
let mut v = vec!["a".to_string(), "a".to_string(), "a".to_string()];
let mut first = v[0].clone();
v.push(String::from("aaa"));

使用for循环可以遍历 vector 中的元素,因为是先引用再遍历,每一个 i 的返回值都是引用,如果数组可变,也可以返回可变的引用,通过 * 解引用的方式可以直接改变每一个引用指向的内容的数值,这个在后续会提到:

let v = vec![100, 32, 57];
for i in &v {
    println!("{}", i);
}
//可变数组的遍历
let mut v = vec![100, 32, 57];
for i in &mut v {
    *i += 50;
}

String

在rust中有两种字符串:

  1. String就像是Vec一样是动态分配在堆heap上的字符串类型,它指向一串UTF8编码的字节序。使用一些 String 自带的api可以创建一个String。

  2. &str 是一个指向分配在某处的 String 的一个固定容量的视图,或者是一个使用 “ ” 定义的字符串字面量,不同的是前者指向String的内容,在堆上,后者指向静态内存,因为是引用,所以他没有字符串的所有权。

  3. 使用 to_string() 可以将str转换为 String

  4. 使用 [ a…b ] 可以将 String 转化成切片 , 同时在 rust 中 &String 可以直接转为 &str 传入各个函数 api 中

//创建空字符串
let mut s = String::new();
//新建一个字符串
let s = String::from("initial contents");
//创建一个字符串字面量 &str
let data = "initial contents";
//转换成String
let s = data.to_string();
//转化为 &str
let str = &s[0..2];
//可以直接转化
let str = &s;
let e_0 = String::from("hello world"); 
let e   = e_0.as_str();

String 的大小可以增加,其内容也可以改变,可以通过 push_str 方法来字符串常量,从而使 String 变长,这个方法也可以使用 + 符号来实现,但是 + 符号的第二个参数需要是一个 &str

let mut s = String::from("foo");
s.push_str("bar");
//上下的作用相同
let s1 = String::from("foo");
let s2 = String::from("bar");
let s3 = s1 + &s2; // 注意 s1 被移动了,不能继续使用

使用 format! 可以快速拼接多个字符串

let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let s = format!("{}-{}-{}", s1, s2, s3);

在rust中不能随意获取一个字符串中的一个字符串,因为 rust 使用 unicode 编码,所以有些语言一个字母不一定存一个字节,有时候是两个:

let hello = String::from("Здравствуйте");
hello.len(); // 24
//因为第一个字母占两个字节,所以这类代码是不可以通过的
let answer = &hello[0];
//这样的代码是可以通过的,因为刚好截取了正确的字母
let answer = &hello[0..2];
//这样的代码是不可以通过的,因为刚好截取一个字母的一半
let answer = &hello[0..1];

对于字符串可以使用不同的方式进行遍历,可以按照一个一个 unicode 字节或者一个一个字符进行遍历

for c in "नमस्ते".chars() {
    println!("{}", c);
}
//按照字符遍历न , म ....
for b in "नमस्ते".bytes() {
    println!("{}", b);
}
//按字节遍历 224 ,164.....

HashMap

HashMap<K, V> 类型储存了一个键类型 K 对应一个值类型 V 的映射,其中的每个元素都按照 k,v 对的形式存储,根据唯一的 k可以找对应的 v ,可以使用 new 创建一个空的 HashMap,并使用 insert 增加元素,在第一次新增元素之后,编译器就会将 HashMap 的类型固定,不能再插入其他类型的键值对

use std::collections::HashMap;
let mut a = HashMap::new();
a.insert(String::from("1"), 10);

另一个构建哈希 map 的方法是在一个元组的 vector 上使用迭代器(iterator)和 collect 方法,其中每个元组包含一个键值对,这将在后续介绍。

let t = vec![String::from("a"),String::from("b")];
let i = vec![10,20];
//必须有HashMap<_,_> ,collect可以返回多种数据结构
let hash : HashMap<_,_> = t.iter().zip(i.iter()).collect();

如果 insert 一个已经存在的 key 那么会直接更新它,所以可以使用 entry ( ) 和 or_insert( ) 函数进行检查 , 这样的组合只有再某个key值不存在的时候才会插入,存在则不会改变值,同时它可以返回指定 key 的 val ,它是一个引用,如果想要改变它的值需要解引用

let val = a.entry(String::from("1")).or_insert(50);
*val += 1;

可以通过 get 方法并提供对应的键来从哈希 map 中获取值,返回 Option<V>

let t = String::from("1");
let ta = a.get(&t);

可以使用与 vector 类似的方式来遍历哈希 map 中的每一个键值对

for (key, value) in &a {
    println!("{}: {}", key, value);
}

泛型

我们可以使用泛型为像函数签名或结构体这样的项创建定义,可以把它理解成是一个占位符,直到我们需要具体定义使用某个结构时,将具体的数据类型填充进去,所有使用泛型占位的地方都将成为该类型。使用泛型可以编写不重复的代码,而 Rust 将会为每一个实例编译其特定类型的代码。这意味着在使用泛型时没有运行时开销;当代码运行,它的执行效率就跟好像手写每个具体定义的重复代码一样。这个单态化过程正是 Rust 泛型在运行时极其高效的原因。

如以下的例子,T是那个泛型即占位符,传入一个 i32 的数组之后,T就改写为了 i32

//泛型定义
fn largest<T>(list: &[T]) -> T {
    let mut largest = list[0];
    for &item in list {
        if item > largest {
            largest = item;
        }
    }
    largest
}
let number_list = vec![34, 50, 25, 100, 65];
//传入一个数字
let result = largest(&number_list);

但是如果将数组变成字符串,则会产生报错,因为不能适用于 T 的所有可能的类型。因为在函数体需要比较 T 类型的值,不过它只能用于我们知道如何排序的类型。

在一个定义中,可以包含多个泛型,仅需要一一对应即可,在结构体、函数、枚举中都可以使用泛型

//单个泛型
struct Point<T> {
    x: T,
    y: T,
}
//多个泛型
struct Point<T, U> {
    x: T,
    y: U,
}
//枚举泛型
enum Result<T, E> {
    Ok(T),
    Err(E),
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

摸鱼老萌新

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值