Rust基础[part6]_数组与切片_字符串

Rust基础[part6]_数组与切片、字符串

数组

数组的类型格式:[T; N]

  • 固定长度:必须在编译时指定长度 N,且无法扩容
  • 可变:如果声明为 mut,可以修改元素值,但不能改变长度
  • 存储在栈上(除非被装箱到堆上,如 Box<[T; N]>)。

定义

let arr = [1, 2, 3, 4, 5];
   println!("Array: {:?}", arr);

默认初始值

// 默认初始值
   let arr1: [i32; 4] = [10; 4];
   println!("Array with default value: {:?}", arr1);

数组长度

// 数组长度
   println!("Array length: {}", arr1.len());

遍历

for index in 0..arr.len() {
        // 遍历索引
        println!("Element at index {}: {}", index, arr[index]);
    }
    for element in arr1.iter() {
        // 使用迭代器遍历
        println!("Element: {}", element)
    }
    arr1.iter().for_each(|x: &i32| println!("Element: {}", x)); // 使用闭包遍历

数组的值传递

    // 数组值传递
    println!("Before update: {:?}", arr2);
    update(arr2);
    println!("After update: {:?}", arr2); // 注意这里的 arr2 仍然是原来的值,因为数组是值传递
fn update(mut arr: [i32; 5]) {
    println!("Updating array: {:?}", arr);
    for index in 0..arr.len() {
        arr[index] = 100; // 修改每个元素
    }
    println!("Updated array inside function: {:?}", arr);
}

输出:主函数中的数组并未改变,因为是值传递

Before update: [10, 2, 3, 4, 5]
Updating array: [10, 2, 3, 4, 5]
Updated array inside function: [100, 100, 100, 100, 100]
After update: [10, 2, 3, 4, 5]

数组的引用传递

 // 数组引用传递
    println!("Before update_mut: {:?}", arr2);
    update_mut(&mut arr2);
    println!("After update_mut: {:?}", arr2); // 这里的 arr2 已经被修改,因为传递的是可变引用

fn update_mut(arr: &mut [i32; 5]) {
    println!("Updating array with mutable reference: {:?}", arr);
    for index in 0..arr.len() {
        arr[index] = 100; // 修改每个元素
    }
    println!("Updated array with mutable reference: {:?}", arr);
}

输出:

Before update_mut: [10, 2, 3, 4, 5]
Updating array with mutable reference: [10, 2, 3, 4, 5]
Updated array with mutable reference: [100, 100, 100, 100, 100]
After update_mut: [100, 100, 100, 100, 100]

数组的切片

也就是slice类型,他表示从包含多个元素的容器中取得局部数据,这个过程称为切片操作。 不同语言对切片的支持有所不同。 Rust可以支持Slice操作,Rust中的切片操作只允许获取一段连续的局部数据。支持的有Array、String、Vec。

⚠️ Slice切片 是单独的类型

切片的类型是 &[T](不可变切片)或 &mut [T](可变切片),它不包含长度信息。

切片也是一种引用,不包含所有权

内存结构,切片由两部分组成:

  1. 指向数据起始位置的指针(指向数组或 Vec 的内存)。
  2. 长度字段len),表示切片包含的元素个数。

这里以Array的切片示例:

fn slice_example() {
    let arr = [1, 2, 3, 4, 5];
    let slice1: &[i32] = &arr[1..3]; // 包含索引1和2的元素
    println!("Slice from index 1 to 3: {:?}", slice1);

    let slice2: &[i32] = &arr[..3]; // 包含前3个元素
    println!("Slice of first three elements: {:?}", slice2);

    let slice3: &[i32] = &arr[2..]; // 从索引2开始到末尾
    println!("Slice from index 2 to end: {:?}", slice3);
}

常用的函数

  • len ():取 slice 元素个数

  • is_empty ():判断 slice 是否为空

  • contains ():判断是否包含某个元素

  • repeat ():重复 slice 指定次数

  • reverse ():反转 slice

  • join ():将各元素压平 (flatten) 并通过指定的分隔符连接起来

  • swap ():交换两个索引处的元素,如 s.swap (1,3)

  • windows ():以指定大小的窗口进行滚动迭代

    for i in arr.windows(3) {
            // 遍历数组的窗口
            println!("Window of size 3: {:?}", i);
        }
    Window of size 3: [1, 2, 3]
    Window of size 3: [2, 3, 4]
    Window of size 3: [3, 4, 5]
  • starts_with ():判断 slice 是否以某个 slice 开头

练习 ⚠️

给定一个整数数组 nums,返回一个数组 answer ,使得 answer[i] 等于 nums 除 nums[i] 之外的所有元素的乘积。 任何前缀或后缀的乘积 nums 都保证适合 32 位整数。 您必须编写一个能够及时运行 O(n) 且无需使用除法运算的算法。

示例 1: 输入:nums = [1,2,3,4] 输出:[24,12,8,6]

示例 2: 输入:nums = [-1,1,0,-3,3] 输出:[0,0,9,0,0] 限制: 2 <= nums.length <= 10^5 -30 <= nums[i] <= 30 任何前缀或后缀的乘积 nums 都保证适合 32 位整数。

进阶:你能以 O(1) 额外空间复杂度解决这个问题吗?(输出数组不算作空间复杂度分析的额外空间。)

#[test]
fn feature_array_practice() {
    let nums: [i32; 4] = [1, 2, 3, 4];
    let answer: Vec<i32> = arr_practice(&nums[..]);
    println!("Result of arr_practice: {:?}", answer);
}

fn arr_practice(nums: &[i32]) -> Vec<i32> {
    let n: usize = nums.len();
    let mut answer = vec![1; n]; // 初始化为全1

    for i in 1..n {
        answer[i] = answer[i - 1] * nums[i - 1];
    }
    let mut suffix = 1;
    for i in (0..n).rev() {
        answer[i] *= suffix;
        suffix *= nums[i];
    }

    answer
}

字符串

是字符组成的连续集合

Rust字符是Unicode类型,每个字符占四个字节,但是在字符串里面不一样,字符串是UTF-8编码。也就是字符串中的字符所占的字节数的变化的四分之一。

Rust 的字符串主要分为两种类型:

  1. str(字符串切片):它是不可变的引用类型,长度固定,通常以借用的形式存在,即&str
  2. String:这是一个在堆上分配内存的可增长类型,具备所有权。

创建字符串

// 创建String类型
let s1 = String::new(); // 空字符串
let s2 = "初始内容".to_string(); // 从&str转换
let s3 = String::from("直接创建"); // 使用from函数

// 创建&str类型(字符串字面量)
let s4: &str = "这是一个字符串切片";

String ->&str

三种

pub fn string_example() {
    let s3 = String::from("Rust");
     say_hello(&s3); // Rust自动解引用s3,&String会自动转换为&str
    say_hello(s3.as_str()); // 显式转换为&str
    say_hello(&s3[..]); // 通过切片转换为&str
}

fn say_hello(s: &str) {
    println!("Hello, {}!", s);
}

各种转换方式

image-20250713193730227

字符串拼接

都是原有的字符串添加,需要可变

let mut s = String::from("Hello");
s.push(' '); // 添加单个字符
s.push_str("world!"); // 添加字符串切片

let s2 = format!("{} {}", s, "Rust"); // 格式化拼接

字符串查找与替换

是返回的新的字符串,不需要可变

let s = "Hello, world!";
let contains = s.contains("world"); // true
let replaced = s.replace("world", "Rust"); // "Hello, Rust!"

其他常见操作

let mut s = String::from("Hello");
s.pop(); // 删除最后一个字符
   println!("After pop: {}", s);

   s.remove(0); // 删除第一个字符
   println!("After remove: {}", s);

   s.truncate(3); // 截断字符串到指定长度
   println!("After truncate: {}", s);

   s.clear(); // 清空字符串
   println!("After clear: {}", s); 

   // 字符串连接
   let s4 = String::from("Hello");
   let s5 = String::from(", Rust!");
   let s6 = s4 + &s5; // 使用 + 运算符连接字符串,使用 &s5 传递引用
   println!("After concat: {}", s6);
 //format
   let s7 = format!("{}{}", s5, " is awesome!"); // 使用 format! 宏连接字符串

	// 转义



性能考量

  • 拼接字符串时,format!宏比+运算符更高效。
  • 频繁修改字符串建议使用String

练习


// 修复所有错误,并且不要新增代码行
fn main() {
    let mut s = String::from("hello");
    s.push(',');
    s.push_str(" world");
    s += "!";//字面量 不能用to_string()

    println!("{}", s)
}