likes
comments
collection
share

笔记-Rust的基本类型2: 指针类型、数组、向量及切片

作者站长头像
站长
· 阅读数 12

三种指针类型:引用、Box 和 原始指针

Rust 的设计目标就是保持内存占用最小化,因此 Rust 中的值默认是嵌套的, 例如 ((0,0), (1440, 900)) 会被保存为 4 个相邻的整数。

引用类型

  • 引用可以看作 Rust 的基本指针类型。
  • 引用可以指向 栈上堆上 的任何值。
  • 表达式 &x 会产生一个对 x 的引用。(借用了一个对 x 的引用)
  • &x 是不可修改引用,&mut x 是可以修改引用。 样例:
  • &String 读作 "引用字符串" 或 字符串引用。
  • 如果拿到了一个引用 r,那么 *r 则是 r指向的值。

Box类型

在堆上分配一个值的最简单方式是使用 Box::new ,例如:

let t: (&str, i32) = ("aa", 10);
let b = Box::new(t);           // b 的类型是 Box<(&str, i32)>
let c = Box::new("the_str");   // c 的类型是 Box<&str>
let d = Box::new(10);          // d 的类型是 Box<i32>

原始指针

  • 原始指针类型 *const T 和 *mut T。
  • 原始指针的解引用一般只能在 unsafe 块中进行。

数组、向量和切片

Rust有三种在内存中表示一系列值的类型: 数组、向量(Vec<T>) 和 切片(&[T]、&mut[T])

数组

  • 类型 [T;N] 表示一个 N 个值的数组,每个值的类型都是 T。

  • 数组的大小是在编译时确定的常量,因此类型必须是 Sized,若是复合类型,则类型的每个属性都得满足 Sized 要求。

  • 数组三要素:元素类型相同,元素大小明确,数组长度(元素个数)不可变。

  • 常见的初始化方法:(1) 直接赋值;(1) 赋初始值,然后填充。

let arr_01: [i32; 6] = [1, 3, 5, 7, 9, 11];
let arr_02: [&str; 3] = ["aa", "bb", "cc"];
let mut arr_03 = ["aa".to_string(), "bb".to_string(), "cc".to_string()];
arr_03[0].push_str("bb");
println!("arr_03 = {:?}", arr_03); // arr_03 = ["aabb", "bb", "cc"]

let arr_04 = [0; 100];  // 初始化100个元素的数组,初始值都是0
  • 可以在数组上调用切片的任何方法(调用切片方法时,Rust会隐式地把对数组的引用转换为对切片的引用)

向量

  • 类型 Vec<T> 叫做 T类型的向量,是一种动态分配、可扩展的类型 T 的序列值。
  • 向量的元素保存在堆上,因此可随意缩放(推入新元素,追加其他向量、或删除元素等)。

向量的创建方式

  1. 使用 vec! 宏。
let mut v = vec![2,3,5,7];
assert_eq!(v.iter().fold(1, |a, b| a * b), 210);
  1. 使用 vector::new 创建向量,然后推入元素。
let mut v = Vec::new();
v.push("do");
v.push("not");
v.push("do");
v.push("that");
assert_eq!(vec!["do", "not", "do", "that"], v);
  1. 基于迭代器产生的值来构建向量。
let v:Vec<i32> = (0..5).collect();  // 使用 collect 的时候,要写明类型。
println!("v = {:?}", v); // v = [0, 1, 2, 3, 4]
let v: Vec<i32> = (0..=5).collect();
println!("v = {:?}", v);  // v = [0, 1, 2, 3, 4, 5]
  1. 推荐使用 Vec::with_capacity 方法,在初始化的时候指明容量。 (减少容量扩展开销,一次性分配合适的空间。)
    let mut v = Vec::<i32>::with_capacity(2);
    // let mut v: Vec<i32> = Vec::with_capacity(10);
    println!("v.len() = {},  v.capacity() = {}", v.len(), v.capacity()); // v.len() = 0,  v.capacity() = 2
    v.push(1);
    v.push(2);
    v.push(3);
    println!("v.len() = {},  v.capacity() = {}", v.len(), v.capacity()); // v.len() = 3,  v.capacity() = 4

切片(切片引用)

  • 切片 slice 写作不指定长度的[T],表示数组或者向量的一个范围
  • 切片只能按引用传递。
  • 切片的引用是一个胖指针,双字宽度,保存着指向切片第一个元素的指针和切片中元素的个数。
  • 遍历切片样例:
fn print_slice(s: &[f64]) {
    for item in s {
        print!("{} ", item);
    }
}
let vec: Vec<f64> = vec![0.1, 0.732, -3.9, 0.301];
let arr: [f64; 4] = [0.3, -12.56, 1.7, 3.1415926];

let vec_slice = &vec; // &Vec[64] 类型
let arr_slice = &arr; // &[f64;4] 类型

print_slice(vec_slice); // 0.1 0.732 -3.9 0.301
println!(""); 
print_slice(arr_slice); // 0.3 -12.56 1.7 3.1415926 
println!(""); 
  • 在索引中 使用范围 可以获取 对数组或向量切片 的引用
// 接上面代码
print_slice(&vec[0..=1]); // 0.1 0.732
println!();
print_slice(&arr[0..=1]); // 0.3 -12.56
println!();
let new_slice: &[f64] = &vec_slice[0..=1];
print_slice(new_slice); // 0.1 0.732
println!();

字符串类型

字符串字面量(&str)

  • 字符串字面量放在一对双引号中。(所以,双引号需要转义)。
  • 如果字符串有换行,即多行字符串,则换行信息也被保留。
  • 如果多行字符串中的一行以反斜杠 \ 结尾,那么该行的换行符和下一行开头的空白符会被删除。
  • 原始字符串语法:在字符串前面加上 r 标记。若原始字符串中包含双引号(转义不可行),则需要在字符串前面加 r###,后面加 ###。
let aa = "This is &str";
println!("{}", aa);   // This is &str
let aa = "This is 
    &str"; // This is 
           //         &str
println!("{}", aa);
let aa = "This is \
    &str";  
println!("{}", aa); // This is &str
let aa = r"This is \t str";
println!("{}", aa); // This is \t str
let aa = r###"This is \t str and "haha"."###;
println!("{}", aa); // This is \t str and "haha".

字节字符串(byte string)

  • 字节字符串(byte string) 就是带有前缀 b 的字符串字面量。
  • 字节字符串是 u8 值(即字节) 的切片,不是 Unicode 文本的切片。
  • 字节字符串可以不能包含任意 Unicode 字符,只能是 ASCII 和 \xHH 转义序列。 // 因为 u8 的限制。
let method = b"GET"; // 类型是 &[u8;3],及对包含3个字节的数组的引用
assert_eq!(method, &[b'G', b'E', b'T']);

字符串(String)在内存中的表示

  • Rust 字符串就是 Unicode 字符序列,但是它在内存中不是以 char 数组的形式存储的。
  • 字符串在内存中使用 UTF-8 可变宽度编码存储。字符串中的每个 ASCII 字符用 1个字节存储,其他字符串则占用多个字节
  • 字符串存储在可伸缩缓存区中(堆上),因此可按要求或请求来调整大小。事实上,是一个能存储格式完好的 UTF-8 的 Vec<u8>。
  • &str (字符串切片) 是对其他变量拥有的一串 UTF-8 文本的引用。&str 也是一个胖指针,包含实际数据的地址及其长度。 &str 类似 &[T]。
  • String 或 &str 的 .len() 方法返回的是它们的字节长度,而非字符个数。
  • &str 不可变(不可修改),&mut str (&mut String 的变种,需要传递进去一个 &mut String 类型的变量)目前仅有两个(原地)改变自身内容的方法: make_ascii_lowercase 和 make_ascii_uppercase。
let mut s = String::from("AdBcE");
test_mutstr(&mut s);
// --------------------------
fn test_mutstr(ins: &mut str) {
    // ins.push_str("KdjAp"); // this is error
    ins.make_ascii_lowercase(); // adbce
    println!("{}",ins);
    ins.make_ascii_uppercase(); // ADBCE
    println!("{}",ins);
}

字符串的创建方式

类似Vec,每个 String 都有独立分配在堆上的缓冲区,可以被转移所有权 或者 释放(变量超出作用域)。

  • .to_string() 方法可以把 &str 转换为 String。 实际上是复制字符串。
  • format!() 宏。和 println!() 类似,但是返回字符串,而不是把文本写入标准输出。
  • 字符串的数组 和 向量 有两个方法 .concat() 和 .join(sep),可将多个字符串拼接成一个新 String 。
let arr1: [&str; 3] = ["a1", "b2", "c3"];
println!("{}", arr1.concat()); // a1b2c3

let arr2: [String; 3] = ["a11".to_string(), "b22".to_string(), "c33".to_string()];
println!("{}", arr2.concat()); // a11b22c33

let vec1: Vec<&str> = vec!["aa", "dd", "cc"];
println!("{}", vec1.concat()); // aaddcc

let vec2: [[i32; 2]; 2] = [[1, 2], [3, 4]];
println!("{:?}", vec2.concat()); // [1, 2, 3, 4]

字符串相等的比较

  • 字符串支持 == 和 != 操作符。
  • 只要两个字符串包含的字符相同,顺序也相同,那么他们就是相等的。(无论它们是否指向内存中同一个地址)