Rust:Rust的集合类型之String

1、引入

描述

打印圣诞颂歌 “The Twelve Days of Christmas” 的歌词,并利用歌曲中的重复部分

代码

fn main() {
    let order_day = ["first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth", "ninth", "tenth", "eleventh", "twelfth"];

    let  mut present = "A partridge in a pear tree.".to_string();
    for i in 0..12{
        if i == 1{
            present = "Two turtle doves, And ".to_string() + &present;
        }else if i == 2 {
            present = " Three French hens, ".to_string() + &present
        }else if i == 3{
            present = "Four calling birds, ".to_string() + &present
        }else if i == 4{
            present = "Five golden rings, ".to_string() + &present
        }else if i == 5{
            present = "Six geese a-laying, ".to_string() + &present
        }else if i == 6{
            present = "Seven swans a-swimming, ".to_string() + &present
        }else if i == 7{
            present = "Eight maids a-milking, ".to_string() + &present
        }else if i == 8{
            present = "Nine ladies dancing, ".to_string() + &present
        }else if i == 9{
            present = "Ten lords a-leaping, ".to_string() + &present
        }else if i == 10{
            present = "Eleven pipers piping, ".to_string() + &present
        }else{
            present = "Twelve drummers drumming, ".to_string() + &present
        }
        print!("On the {}  day of Christmas, my true love sent to me:{}\n", order_day[i], present);
    }
}

运行结果:
在这里插入图片描述

关键点:

  • 字符串拼接

2、String

1、Rust中的字符串通常指String和&str,
2、Rust内部的字符串默认是使用utf-8编码格式的【String和&str】,而内置的char类型是4字节长度的,存储的是Unicode字符,所以Rust里面的字符串不能视为char类型的数组,而更接近u8类型的数组。
使用utf-8编码字符串的优点:

  • 大小端无关
  • 跟ASCII码兼容,是互联网上的首选编码
  • 等等
    使用uft-8编码字符串的缺点
  • 不能支持O(1)时间复杂度的索引操作。换句话说:string与&str不允许使用索引访问字符串
    比如如果我们要找一个字符串s内部的第n个字符,不能直接通过s[n来找到]
fn main() {
    let hello = "hello world";
    println!("{}", &hello[0]); //rror[E0277]: the type `str` cannot be indexed by `{integer}`
    
    let hello = "Здhello world".to_string();
    println!("{}", &hello[0]); //error[E0277]: the type `std::string::String` cannot be indexed by `{integer}`
}

原因:因为字符串索引应该返回的类型是不明确的: 字节值、字符、字形簇或者字符串 slice; 使用索引获取 String 字符的原因是索引操作预期总是需要常数时间 (O(1))。但是对于 String 不可能保证这样的性能,因为 Rust 不得不检查从字符串的开头到索引位置的内容来确定这里有多少有效的字符。
方法改进:此方法的时间复杂度是o(n)。因为utf-8是变长编码,如果我们不从头开始过一遍,根本就不知道第n个字符的地址在什么地方

fn main() {
    let hello = "Здравстhello world";
    println!("{:?}", hello.chars().nth(0));

    let hello = "Здравстhello world".to_string();
    println!("{:?}", hello.chars().nth(0));
}

还可以使用切片索引,但是必须小心

fn main() {
     let hello = "Здравствуйте";
    println!("{}", &hello[0..4]);

    let hello = "Здравствуйте".to_string();
    println!("{}", &hello[0..4]);
}

&str与string的区别

什么是&str

  • &str
    {
        let s ="hello";   
     //变量s绑定到了一个字符串字面值,这个字符串值是硬编码进程序代码中的。这个变量从声明的点开始直到当前作用域结束时都是有效的
        println!("{}", s)
    }   //变量存储在栈上并且当离开作用域时被移出栈[就是s变量名不再和这个栈空间绑定了]

作用域是一个项在程序中有效的范围。
1、&str绑定一个字符串字面值,分配在栈上,当离开作用域时就看不到了
2、字符串字面值被硬编码进程序里,它们是不可变的。
3、不是所有字符串的值都能被编写进代码,比如当想要获取用户输入并存储时,文本的大小和内容是不确定的,因此不能被硬编码。此时应该用Rust的第二种字符串类型String。这个类型被分配在堆上,所以能够存储在编译时未知大小的文本。

什么是String

1、三种方法新建String

let s = String::from("hello"); 
let s = "السلام عليكم".to_string();
 let mut s = String::new();  //新建了一个叫做 s 的空的字符串,接着可以向其中装载数据
  let data = "initial string";
  s = data.to_string();

2、拼接字符串:通过 push_str或者push 方法,使用 + 运算符或 format! 宏拼接字符串

fn main() {
    //1:
    let mut s = String::new();  //新建了一个叫做 s 的空的字符串,接着可以向其中装载数据
    let data = "initial string";
    s = data.to_string();
    s.push_str(" world");
    s.push('l');
    println!("{}", s);

    //2:使用 to_string 方法从字符串字面值创建 String
    let s = "السلام عليكم".to_string();

    //3:使用 String::from 函数从字符串字面值创建 String
    let s = String::from("hello");
    let s1 = String::from("!!!");
    let s2 = s + &s1; //注意 s被移动了,不能继续使用
    println!("{}", s2);

    let s1 = String::from("anly");
    let s2 = String::from("bibi");
    let s3 = String::from("baidu");
    let s = format!("{}-{}-{}", s1, s2, s3); //不会获取任何参数的所有权。
    println!("{}", s) //format! 与 println! 的工作原理相同,不过不同于将输出打印到屏幕上,它返回一个带有结果内容的 String
}

如果使用+拼接字符串,必须是String + n个&str,&str不能+String

3、String不允许使用索引访问字符串

4、遍历String

fn main() {
    for c in "नमस्ते".chars() { //单独的 Unicode 标量值,有效的 Unicode 标量值可能会由不止一个字节组成
        print!("{}\t", c);
    }
    println!();
    
    for b in "नमस्ते".bytes(){   //bytes 方法返回每一个原始字节
        print!("{}\t", b);
    }
}

在这里插入图片描述

fn main() {
   let s = "&str  String";
   for i in s.chars(){
       print!("{}\t", i)
   }
    println!();
    for i in s.bytes(){
        print!("{}\t", i)
    }
    println!();
    for i in s.as_bytes(){
        print!("{}\t", i)
    }
    println!();
    let s = "String &str".to_string();
    for i in s.chars(){
        print!("{}\t", i)
    }
    println!();
    for i in s.bytes(){    
        print!("{}\t", i)
    }
    println!();
    for i in s.bytes().into_iter(){
        print!("{}\t", i)
    }
    println!();
    for i in s.as_bytes().into_iter(){
        print!("{}\t", i)
    }
    println!();
    for i in s.as_bytes(){
        print!("{}\t", i)
    }
    println!();
}

在这里插入图片描述

  • as_bytes 方法将 String 转化为字节数组
  • into_iter方法在字节数组上创建一个迭代器,返回集合中的每一个元素

5、String由3部分组成,一个指向存放字符串内容内存的指针,一个长度,一个容量。这一组数据存放在栈上。右侧是堆上存放内容的内存部分
长度表示String的内容当前使用了多少字节的内存。容量是String从操作系统总共获取了多少字节的内存。
在这里插入图片描述
s1是一个堆上内存的引用。

  • String与&str的最主要的区别是:string有管理内存空间的权利
    &str类型是一块字符串区间的借用,它对所指向的空间没有所有权,&mut str也没有
fn main(){
    let x = "hello";  //作用域:从声明的点开始直到下一个x重新定义结束
    //x = " xdwe";   //error
    let x = "world";  //覆盖,重新生成了一个x变量
}

我们无法修改&str所借用的范围,在它后面增加内容。但是String类型可以。

fn main() {
    let mut s = String::from("hello");  //作用域:从声明的点开始直到下一个}结束
    s.push(' ');
    s.push_str("world!");   //s是可以改变的
    println!("{}", s);

    s = "你好".to_string();
    println!("{}", s);
}//当可变变量s离开作用域的时候,也就是结尾的}处,Rust自动调用drop函数将内存返回给操作系统

这是因为String类型在堆上动态申请了一块内存空间,它有权对这块内存空间进行扩容或者修改。内部的实现类似std::Vec,因此我们可以将String类型看成是一个容纳字符串的容器。

&mut str的疑问:

fn capitalize(s:&mut str){
    s.make_ascii_uppercase();
}
fn main() {
    let mut s = String::from("hello world");
    capitalize(&mut s);
    println!("{}", s)  //HELLO WORLD
}

capitalize函数调用的时候,形式参数要求是&mut
str类型,而实际参数是&mut String类型,这里编译器给我们做了自动类型转换。在capitalize函数内部,它有权修改&mut str所指向的内容,但是无权给这个字符串扩容或者释放内存。
String实现了Deref<Target=str>的trait。所以在很多情况下,&String类型可以被编译器自动转换为&str类型

$str不可变

&str是一个指向二进制程序内部特定位置的slice。

  • "hello"是一个字符串字面量, x的类型是&str。x存储在栈上,当x离开作用域时就会由所有权系统从栈上移除【这和其他语言不一样】
  • 字符串字面值是不可变的,因为它被写死在二进制编译程序中
  • &str可以看成一个固定大小固定内容的数组,不能拼接,不能更改;
  • 程序编译成二进制文件之后,这个字符串会被保存在文件内部,所以s是特定位置字符串的引用&str
  • &str由于保存在二进制文件内,所以&str类型不存在生命周期的概念,它是整个程序生命周期static内都能访问的

为什么String是可变的

1、字符串字面值在编译时就知道其内容,所以文本被直接硬编码进最终的可执行文件中。因此字符串字面值是不可变的。
但是我们不能为了一个在编译时大小未知并且的文本而将一块内存放入二进制文本中,并且文本大小还可能随着程序运行而改变:而堆内存的大小可以动态变化,String就是存储在堆上的
2、对于String类型,为了支持一个可变、可增长的文本字段,需要在堆上分配一块在编译时未知大小的内存来存放内容。这意味着:

  • 必须在运行时向操作系统请求内存
  • 需要一个当我们处理完String时将内存返回给操作系统的方法

第一部分由程序员完成:当调用String::from时,它的实现请求其所需的内存
第二部分由编程语言完成:

  • 在垃圾回收GC语言中,GC记录并清除不再使用的内存;
  • 在没有GC的语言中,程序员手动释放,且需要精确的为一个 allocate 配对一个 free;
  • 对于Rust而言,内存在拥有它的变量离开作用域之后就被自动释放

Rust标准库中还包含一系列其他提供所有权和可借用的字符串变体:OsString,OsStr,CString和CStr等。

不太理解

Rust官方的解释: 
 String是一个被拥有的在堆上分配的UTF-8的字节缓冲区。可变String可以被修改,根据需要增加其内容
 &str是一个指向分配在某处的String的一个固定容量的[视图]。如果切片是在从String解引而来的,则通常是指向在堆上,如果是字符串字面值,则指向静态内存
 &str是一个由Rust语言实现的原生类型,而String则是由标准库实现的。
 对于Rust而言,"字符串"是Unicode标量值的序列编码为utf-8字节的流。所有字符串必须保证为有效的utf-8编码序列

参考:https://kaisery.github.io/trpl-zh-cn/ch04-01-what-is-ownership.html
<深入浅出Rust>

总结:

Rust标准库中包含一系列被称为集合的数据结构。集合中可以包含多个值,不同于内建的数组和元组类型,这些集合指向的数据是存储在堆上的,这意味着数据的数量不必再编译时就已经并且可以随着程序的运行增长或缩小

  • vector允许我们一个挨着一个地存储一系列数量可变的值
  • 字符串string是一个字符的集合外加一些方法实现的。
  • 哈希map[hash map]是键值对:将值与一个特定的键相关联
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值