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

Rust基础[part6]_数组与切片_字符串
SoniaChenRust基础[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]
(可变切片),它不包含长度信息。切片也是一种引用,不包含所有权
内存结构,切片由两部分组成:
- 指向数据起始位置的指针(指向数组或
Vec
的内存)。 - 长度字段(
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 的字符串主要分为两种类型:
str
(字符串切片):它是不可变的引用类型,长度固定,通常以借用的形式存在,即&str
。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);
}
各种转换方式
字符串拼接
都是原有的字符串添加,需要可变
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)
}