rust的复合类型,类似于TS中的对象类型,主要有String,Struct,元组,数组,枚举,HasMap。
字符串
之前提到过,字符串String和&str是两种不同的类型。这里说的字符串,是指String。 我们可以将&str转换为String
ini
let name:String = String::form("Hello");
let name2:String = "Hello".to_string();
我们可以利用切片,将String转换为&str
ini
let s = String::from("hello,world!");
let hello = &s[0..5];//这是切片语法
索引操作的陷阱
在对字符串使用索引相关的时需要格外小心,下标索引必须落在字符之间的边界位置,也就是 UTF-8 字符的边界,例如中文在 UTF-8 中占用三个字节,下面的代码就会崩溃:
let
let a = &s[0..2];
println!("{}",a); '
String 类型具有很多操作
rust
fn main() {
let mut s = String::from("Hello ");
s.push_str("World"); // 追加字符
println!("push: {}", s);
s.insert(5, ','); // 插入字符
println!("insert: {}", s);
let new_string_replace = s.replace("Hello", "HELLO"); //字符串替换,返回一个新的字符串
println!("new_string_replace: {} s: {}", new_string_replace, s);
let new_string_replacen = s.replacen("World", "WORLD", 1); //与replace相似,第三个参数表示替换几次
println!("new_string_replace: {} s: {}", new_string_replacen, s);
let p1 = s.pop(); //删除并返回字符串的最后一个字符。返回值是下文会讲述的Option类型
println!("pop: {:?} s: {}", p1, s);
let remove = s.remove(0); //删除并返回某个位置的字符,注意可能会发生索引异常
println!("remove: {:?} s: {}", remove, s);
let truncate = s.truncate(3); //删除字符串中从指定位置开始到结尾的全部字符
println!("truncate: {:?} s: {}", truncate, s);
s.clear(); //清空字符串
println!("s: {}", s);
}
字符串相加
- 和JS一样,可以使用+进行相加,但是对其类型有要求。相加的第一个参数必须是String类型,后面的参数则是&str类型或者&String类型。
ini
fn main() {
let hello = String::from("Hello");
let world = "World";
let rust = ",rust";
let pointer: &String = & String::from("WS");
let result = hello+ &world+rust+pointer;
let mut result = result + "!"; // `result + "!"` 中的 `result` 是不可变的
result += "!!!";
println!("拼接结果: {}", result);
}
- 使用format进行格式化,format的用法有点类似于println
ini
let s1 = "Hello";
let s2 = String::from("World");
let s3 = String::from("And Rust");
let s = format!("{} {} {}", s1, s2,s3);
println!("{}", s);
Struct
Struct,也叫结构体,这个概念类似于TS中的class,用来定义一个对象。 我们看一下声明
rust
//rust
struct Person {
age: u32,
username: String,
isBoy:bool,
}
和TS的语法非常类似,但是struct没有可选属性,所有的属性在初始化时必填
typescript
//ts
class User {
age: number,
username: string,
isBoy:true,
constructor(age:number, username:string) {
this.age = age;
this.username = username;
}
}
两者的实例化方式也有所不同:
csharp
// rust
let user1 = User {
username: String::from("shengjiang"),
age: 18,
isBoy:true,
};
//TS
let user1 = new User(18,'shengjiang')
两者在访问和属性赋值上是一致的,甚至rust也提供了解构语法
ini
//rust,不要忘了mut
let mut user1 = User {
username: String::from("shengjiang"),
age: 18,
};
user1.age =20;
let user2 = User {
age: 30,
..user1 //注意,这里只有两个点
};
//TS
const user1 = new User(18,'shengjiang')
user1.age=20;
const user2:User = {
username: String::from("natie"),
...user1// 这里有三个点
};
这里,需要注意,我们将user1解构赋值给了user2,这时发生了所有权转移,如第四章所说,这里只有String类型的username属性发生了所有权转移,其他属性因为是基础类型,因此不涉及,这时候直接访问user1会报错,但是访问未发生所有权转移的字段却依然可行
rust
let mut user1 = User {
username: String::from("shengsuannatie"),
age: 18,
};
let user2 = User {
age: 30,
..user1 //注意,这里只有两个点
};
println!("{}", user1.age); // 正常
println!("{:?}", user1);// 报错
方法
在TS中,使用Class,可以在里面定义方法,struct却不行。但是rust通过impl
关键字,来达到这种效果:
rust
struct Person {
first_name: String, //注意,属性命名必须使用蛇形命名,既'***_**_'
second_name: String,
}
impl Person {
fn say_my_name(&self) -> String { //注意,方法名也必须使用蛇形命名
format!("{}{}", self.first_name, self.second_name) //&slef的概念就是JS中的this,但是它的作用域比JS的更加清晰,更加容易理解
}
}
fn main() {
let person = Person {
first_name: String::from("生姜"),
second_name: String::from("拿铁"),
};
println!("Say My Name {}", person.say_my_name());
}
枚举
概念上与TS的枚举一致,写法也类似:
ini
enum Sex {
Boy,
Girl,
}
let boy = Sex::Boy;
在TS中我们还可以这么写:
ini
enum Sex {
Boy=1,
Girl='girl',
}
let boy = Sex.Boy;
rust中也有类似的实现
rust
enum Sex {
Boy:u32,
Girl:&str,
}
let boy = Sex::Boy(1);
let girl = Sex::Girl('gril');
对于强类型语言来说,rust的枚举是一个非常神奇的存在,它的每一个子项都可以对应一个不同的数据结构,在处理某些场景下,几乎像动态语言一样强大。
特殊的枚举
Rust中有一个特殊的枚举,叫Option,前文中String的remove
的返回值就是一个Option。
scss
enum Option<T> {
Some(T),
None,
}
其中None表示没有,Rust中没有Null,使用枚举None表达同等的含义。 在使用时,可以省略Option::,直接使用Some或者None。
ini
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;
在rust的api中,如果一个方法不能确保一定有值返回,就会返回一个Option枚举,便于我们做异常捕捉。一般会搭配match关键字使用。我们后面会介绍到。
元组
rust元组的概念与TS的元祖也是一致的。 看一个简单的示例:
ini
let x: (u32, u32, u32) = (30, 1, 1);
let age = x.0;
let grade = x.1;
元组和struct
元组可以和strcuct结合
rust
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
静态数组
rust的数组有两类,一种是静态数组,一种是动态数组。 静态数组是存放在栈上的,要求比如类型一致,且需要在初始化的时候确定长度,且元素要保持相同的类型。 动态数据是存放在堆上的,在初始化的时候不需要确定长度。
less
let a = [1, 2, 3, 4, 5];//直接初始化,rust可以推断出类型
let a: [i32; 5] = [1, 2, 3, 4, 5]; //手动声明类型
let a = [3; 5];//表示初始长度为3,且每个元素都是5
静态数组的存取效率比较高,因为存放在栈上,但是静态数组是不允许越界访问的:
less
let a = [1, 2, 3, 4, 5];
let b = a[6] //报错
数组同样支持切片,之前说到字符串的切片是危险的,因为不同文字所占的长度不一样。而数组是安全的,因为它的每一个元素所占长度一样:
ini
let a: [i32; 5] = [1, 2, 3, 4, 5];
let slice: &[i32] = &a[1..3];
因为是静态的数组,所以没有push,pop之类会改变数组长度的操作。
动态数组
动态数组类型用 Vec<T>
表示。动态数组也只能存储相同类型的元素,如果你想存储不同类型的元素,可以使用之前讲过的枚举类型或者特征对象。(特征类似于TS中interface,后面章节会讲) 我们看一下动态数组的常用API
rust
let mut v: Vec<i32> = Vec::new(); //实例化,此时要声明类型
let v = vec![1, 2, 3]; //实例化的同时进行初始化,此时可以省略类型声明
v.push(1);// 添加
let third: &i32 = &v[2]; //下标访问
除了下标访问,还可以使用get来访问,但是get返回的结果是一个Option枚举:
rust
let v = vec![1, 2, 3, 4, 5];
match v.get(6) {
Some(num) => println!("第6个是 {num}"),
None => println!("没有第6")
}
直接使用下标访问,可能会越界引起运行时错误。但是使用get则不会有这种问题,当你访问一个越界下标时,只会返回一个None。
遍历
rust
let mut v = vec![1, 2, 3];
for i in &mut v {
*i += 1 //注意这里的指针用法
}
排序
动态数组自带了两种排序,一种是稳定排序,一种是非稳定排序。在 稳定
排序算法里,对相等的元素,不会对其进行重新排序。而非稳定排序则不会,因此非稳定排序的速度会比较快,同时占用更少的内存。
ini
let mut vec = vec![1, 3, 3, 2, 5];
vec.sort_unstable();
像JS数组的sort一样,原生的sort只能支持基础类型的比较,如果是复合类型的元素,如何排序呢? Rust提供了了JS一样的方式,使用sort_by和sort_unstable_by传入函数:
css
let mut vec = vec![1, 3, 3, 2, 5];
vec.sort_unstable_by(|a, b| a - b);
这里用到了匿名函数,也就是闭包了。
HashMap
HashMap与ES6的Map类似,用法也相似
rust
let mut keys = HashMap::new();
keys.insert("key", 1);
keys.insert("key2", 2);
let score: Option<&i32> = keys.get("key");
HashMap和所有权
rust
fn main() {
use std::collections::HashMap;
let name = String::from("拿铁");
let age = 18;
let mut handsome_boys = HashMap::new();
handsome_boys.insert(name, age);//发生了所有权转移
println!("{}", name);//这里调用name就错了
println!("{}岁", age);
}