Rust快速入门(三)
字符串
切片创建方法
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];
创建切片的语法,使用方括号包括的一个序列:[开始索引..终止索引](左闭右开)
和golang类似,可以使用[..5]
表示[0..5]
, [6..]
表示 [6..11]
在对字符串使用切片语法时需要非常小心,切片的索引需要落在字符之间的边界位置(UTF-8边界),不然会导致崩溃发生。
注意:字符串字面量时切片,意味着我们可以这样处理:
let s = "Hello, world!";
//等效于
let s: &str = "Hello, world!";
为什么字符串遍历字面量是不可变的?—— &str是一个不可变引用
对所有权转移和借用的探讨:
-
所有权借用
fn main() { let s = String::from("hello"); let n = change( &s); println!("{}", n); println!("{}", s) } fn change(str: &str)->usize{ str.len() }
上述代码中,change函数对s字符串的所有权进行了借用,不取得其所有权,借用结束后,s所有权依旧被main持有,所以当我们输出s时,可以被println!()调用。
-
所有权转移
fn main() { let s = String::from("hello"); let n = change( s); println!("{}", n); println!("{}", s) // error: value borrowed here after move!!! } fn change(str: String)->usize{ str.len() }
上述代码中,change函数直接获取的s的所有权,这意味着s所有权从main转移到了change函数中,当change函数结束,s内存被自动释放,返回一个n。所以我们如果希望再次打印s,就会报错了。
什么是字符串
-
Rust 中的字符是 Unicode 类型,因此每个字符占据 4 个字节内存空间,但是在字符串中不一样,字符串是 UTF-8 编码,也就是字符串中的字符所占的字节数是变化的(1 - 4)
-
str
类型是硬编码进可执行文件,也无法被修改,但是String
则是一个可增长、可改变且具有所有权的 UTF-8 编码字符串当 Rust 用户提到字符串时,往往指的就是String
类型和&str
字符串切片类型,这两个类型都是 UTF-8 编码。
转换:
&str ==> String:
-
String::from("hello,world")
-
"hello,world".to_string()
String ==> &str:
-
我们只需要对String类型取引用就可以了。如:
fn main() { let s = String::from("hello,world!"); say_hello(&s); say_hello(&s[..]); say_hello(s.as_str()); } fn say_hello(s: &str) { println!("{}",s); }
字符串索引
在golang中,我们习惯使用索引来访问字符串某个字符或者子串,但是在rust中就会报错。归起缘由,我们还是要想到rust字符串组成格式——[u8],一个字节数组。对于一个英文串来讲 "aaa" 长度是3,但是对于一个中文串来讲 "中国人"长度是9,那么此时取所以0连一个完整的中字都取不了。
同时由于rust一个字符串字符所占字节数为 1~4 所以我们并不能像golang中一样以O(1)的时间复杂度来获取到字符,而是需要遍历O(n)才行。
字符串切片
上文提到过了,使用时需非常注意索引字节刚好落在字符边界上。
字符串操作
追加(push)
-
在字符串的尾部,可以使用
push()
方法追加字符串char,也可以使用push_str()
方法追加字符串字面量。两个方法都是 在原有的字符串上面追加,并不会返回新的字符串。 -
当然,我们既然要进行追加,那么这个字符串一定是 mut 可变字符串。
fn main() {
let mut s = String::from("hello\t");
s.push_str("Victor\t");
s.push('c');
s.push('o');
s.push('m');
s.push('e');
s.push_str("\t");
s.push('o');
s.push('n');
s.push('!');
print!("{}", s)
}
插入(insert)
-
和追加类似,可以使用
insert()
方法插入单个字符char
,也可以使用insert_str()
方法插入字符串字面量。但是这两个方法需要传入两个参数,第一个参数是字符(串)插入位置的索引,第二个参数是插入的字符(串)。 -
同样的,要求是mut可变字符串
fn main() {
let mut s = String::from("hello");
s.insert_str(1, "aaa");
print!("{}\n", s);
s.insert(1, '!');
print!("{}", s)
}
替换(replace)
有三个相关方法:
-
replace:
该方法可适用于
String
和&str
类型。replace()
方法接收两个参数,第一个参数是要被替换的字符串,第二个参数是新的字符串。该方法会替换所有匹配到的字符串。该方法是返回一个新的字符串,而不是操作原来的字符串。 -
replacen:
该方法可适用于
String
和&str
类型。replacen()
方法接收三个参数,前两个参数与replace()
方法一样,第三个参数则表示替换的个数。该方法是返回一个新的字符串,而不是操作原来的字符串。 -
replace_range:
该方法仅适用于
String
类型。replace_range
接收两个参数,第一个参数是要替换字符串的范围(Range),第二个参数是新的字符串。该方法是直接操作原来的字符串,不会返回新的字符串。该方法需要使用mut
关键字修饰。
删除(delete)
与字符串相关的方法有4个,仅适用于 String
类型
-
pop:
删除并返回字符串的最后一个字符, 该方法是直接操作原来的字符串。但是存在返回值,其返回值是一个
Option
类型,如果字符串为空,则返回None
。fn main() { let mut string_pop = String::from("rust pop 中文!"); let p1 = string_pop.pop(); let p2 = string_pop.pop(); dbg!(p1); // dbg!()这个宏可以快速打印出数据,并且给出详细位置。 }
-
remove:
删除并返回字符串中指定位置的字符,该方法是直接操作原来的字符串。但是存在返回值,其返回值是删除位置的字符串,只接收一个参数,表示该字符起始索引位置。
remove()
方法是按照字节来处理字符串的,如果参数所给的位置不是合法的字符边界,则会发生错误。 -
truncate:
删除字符串中从指定位置开始到结尾的全部字符,该方法是直接操作原来的字符串。无返回值。该方法
truncate()
方法是按照字节来处理字符串的,如果参数所给的位置不是合法的字符边界,则会发生错误。 -
clear:
清空字符串
链接
-
使用
+
或者+=
连接字符串,要求右边的参数必须为字符串的切片引用(Slice)类型。究其底层:本质上是调用了std::string
标准库中的 add() 方法,这里add()
方法的第二个参数是一个引用的类型。 -
+
是返回一个新的字符串,所以变量声明可以不需要mut
关键字修饰。
元组
元组可以存储不同类型的值,我们可以使用 .
来访问元组中某特定位置的值,索引从0开始。在函数返回时很常用,类似于golang的多值返回:
fn main() {
let s1 = String::from("hello");
let (s2, len) = calculate_length(s1);
println!("The length of '{}' is {}.", s2, len);
}
fn calculate_length(s: String) -> (String, usize) {
let length = s.len(); // len() 返回字符串的长度
(s, length)
}
结构体
定义结构体:
-
通过关键字
struct
定义 -
一个清晰明确的结构体
名称
-
几个有名字的结构体
字段
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
结构体实例
为了使用上述结构体,我们需要创建其实例,
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
注意:
-
初始化实例时,每个字段都需要初始化。
-
初始化时的字段顺序不需要和结构体定义时一致
fn main(){
// 结构体创建
struct User {
active: bool,
username: String,
email : String,
}
let user = User {
email: String::from("1234@mail.com"),
active: true,
username: String::from("Victor"),
};
let name = user.username;
print!("{}", name);
}
如果需要修改字段,则需将结构体改为可变:
fn main(){
// 结构体创建
struct User {
active: bool,
username: String,
email : String,
}
let mut user = User {
email: String::from("1234@mail.com"),
active: true,
username: String::from("Victor"),
};
let name = user.username;
println!("{}", name);
user.username = String::from("YDY");
print!("{}", &user.username)
}
我这里使用的借用引用。
构造函数方式创建结构体:
fn build_user(email: String, username: String) -> User {
User {
email: email,
username: username,
active: true,
sign_in_count: 1,
}
}
结构体更新
在实际应用中,存在一个应用场景——根据已有的结构体实例,创建新的结构体实例。
举个栗子,我们更加上述user创建user1:
let user2 = User {
active: user1.active,
username: user1.username,
email: String::from("another@example.com"),
sign_in_count: user1.sign_in_count,
};
但是这样太麻烦了,于是rust提供了简化语法:
let user1 = User {
email: String::from("another@example.com"),
..user
};
因为只有email不同,就直接修改email即可。但是需要注意 ..user
必须在结构体的尾部使用。
由于这种二次创建是基于绑定而来的,所以当我们在基于user创建user1的时候,user里面的引用字段会将所有权转移给user1,所以当user1建立完成,user里面的非 Copy 类型字段都不能够再被使用了。
元组结构体:
结构体必须要有名称,但结构体的字段可以没有名称,这种结构体长得像元组,因此被称为元组结构体。
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
结构体信息打印:
使用 #[derive(Debug)]
对结构体进行了标记,这样才能使用 println!("{:?}", s);
打印结构体信息
// 结构体创建
#[derive(Debug)]
struct User {
active: bool,
username: String,
email : String,
}
fn main(){
let user = User {
email: String::from("1234@mail.com"),
active: true,
username: String::from("Victor"),
};
println!("userinfor is {:?}", user)
}
枚举
列举可能的成员来定义一个 枚举类型。
enum PokerSuit {
Clubs,
Spades,
Diamonds,
Hearts,
}
枚举类型是一个类型,它会包含所有可能的枚举成员,而枚举值是该类型中的具体某个成员的实例。
我们可以创建枚举类型的成员变量,如:
let heart = PokerSuit::Hearts;
let diamond = PokerSuit::Diamonds;
数组
-
固定长度 (array)
-
定义:
let a = [1,2,3,4];
数组是存储在栈上的——因为其长度固定,元素类型大小固定。
-
需要类型声明:
let a: [i32: 5] = [1, 2, 3, 4, 5];
-
初始化长度为n,其中元素全为m的数组:
let a = [m; n];
-
-
访问:
由于数组元素是连续存放的,因此可以通过索引的方式来访问其中的元素
fn main() { let a = [9, 8, 7, 6, 5]; let first = a[0]; let second = a[1]; }
-
非基础类型(无法Copy)的数组:
fn main(){ let arr: [String; 8] = std::array::from_fn(|_i| String::from("rust is good")); println!("{:#?}", arr) }
-
-
可动态增长 (Vector)
数组切片:
fn main(){
let arr: [i32; 5] = [1, 2, 3, 4, 5];
let sli: &[i32] = &arr[1..3];
assert_eq!(sli, &[2,3])
}
-
切片的长度可以与数组不同,并不是固定的,而是取决于你使用时指定的起始和结束位置
-
创建切片的代价非常小,因为切片只是针对底层数组的一个引用
-
切片类型 [T] 拥有不固定的大小,而切片引用类型 &[T] 则具有固定的大小,因为 Rust 很多时候都需要固定大小数据类型,因此 &[T] 更有用,
&str
字符串切片也同理
原文地址:https://blog.csdn.net/weixin_74783792/article/details/144327720
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!