自学内容网 自学内容网

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)

有三个相关方法:

  1. replace:

    该方法可适用于 String&str 类型。replace() 方法接收两个参数,第一个参数是要被替换的字符串,第二个参数是新的字符串。该方法会替换所有匹配到的字符串。该方法是返回一个新的字符串,而不是操作原来的字符串

  2. replacen:

    该方法可适用于 String&str 类型。replacen() 方法接收三个参数,前两个参数与 replace() 方法一样,第三个参数则表示替换的个数。该方法是返回一个新的字符串,而不是操作原来的字符串

  3. replace_range:

    该方法仅适用于 String 类型。replace_range 接收两个参数,第一个参数是要替换字符串的范围(Range),第二个参数是新的字符串。该方法是直接操作原来的字符串,不会返回新的字符串。该方法需要使用 mut 关键字修饰

删除(delete)

与字符串相关的方法有4个,仅适用于 String 类型

  1. 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!()这个宏可以快速打印出数据,并且给出详细位置。
    }

  2. remove:

    删除并返回字符串中指定位置的字符,该方法是直接操作原来的字符串。但是存在返回值,其返回值是删除位置的字符串,只接收一个参数,表示该字符起始索引位置。remove() 方法是按照字节来处理字符串的,如果参数所给的位置不是合法的字符边界,则会发生错误。

  3. truncate:

    删除字符串中从指定位置开始到结尾的全部字符,该方法是直接操作原来的字符串。无返回值。该方法 truncate() 方法是按照字节来处理字符串的,如果参数所给的位置不是合法的字符边界,则会发生错误。

  4. 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)!