当前位置:   article > 正文

c++ string 赋值文本内容_【翻译】 Rust中的String和&str

rust中的string 传递到c++中

db0c764f50226eb40a6a31d3b8d8919f.png
原文标题: String vs &str in Rust
原文链接: https:// blog.thoughtram.io/stri ng-vs-str-in-rust/
公众号: Rust碎碎念

当你开始Rust的学习之旅后,很可能遇到需要使用字符串的场景,但是编译器却无法让你的代码通过编译,因为有一部分代码,看起来像字符串,事实上却又不是。
例如,让我们看看下面这个简单的函数greet(name: String),这个函数接收一个String类型的参数,然后使用println!()这个宏将它打印到屏幕上:

  1. fn main() {
  2. let my_name = "Pascal";
  3. greet(my_name);
  4. }
  5. fn greet(name: String) {
  6. println!("Hello, {}!", name);
  7. }

编译这段代码会产生下面的编译错误:

  1. error[E0308]: mismatched types
  2. --> src/main.rs:3:11
  3. |
  4. 3 | greet(my_name);
  5. | ^^^^^^^
  6. | |
  7. | expected struct `std::string::String`, found `&str`
  8. | help: try using a conversion method: `my_name.to_string()`
  9. error: aborting due to previous error
  10. For more information about this error, try `rustc --explain E0308`.

你可以在这里运行代码。只要点击Run按钮就可以看到编译输出。
幸运地是, Rust编译器很友好地告诉了我们问题所在。很明显,这里我们使用了两个不同的类型: std::string::String,简写为String,和&str。但是greet() 期望传入一个String, 很显然,我们传给函数的类型是&str。 编译器甚至已经提示我们如何修正这个错误。 把第3行改为let my_name= "Pascal".to_string();即可修正这个问题。
这里发生了什么? &str是什么? 为什么我们不得不使用to_string()执行一个显式的转换?

理解字符串类型(Understanding the String type)

要回答这些问题,需要我们很好地理解Rust是如何在内存中存储数据的。如果你还没有阅读我们的文章 Taking a closer look at Ownership in Rust, 我强烈推荐你先去阅读一下。(译者注: 这篇文已经翻译,译文地址: https://zhuanlan.zhihu.com/p/115651233)
让我们以上面的代码为例,看看my_name是如何在内存中存储的,先假定它是String类型(我们已经按照编译器提示使用了 .to_string()):

  1. buffer
  2. / capacity
  3. / / length
  4. / / /
  5. +–––+–––+–––+
  6. stack frame │ • │ 86<- my_name: String
  7. +–│–+–––+–––+
  8. [–│–––––––– capacity –––––––––––]
  9. +–V–+–––+–––+–––+–––+–––+–––+–––+
  10. heap │ P │ a │ s │ c │ a │ l │ │ │
  11. +–––+–––+–––+–––+–––+–––+–––+–––+
  12. [––––––– length ––––––––]

Rust会在栈上存储String对象。这个对象里包含以下三个信息: 一个指针指向一块分配在堆上的缓冲区,这也是数据真正存储的地方,数据的容量长度。因此,String对象本身长度总是固定的三个字(word)。String之所以为String的一个原因在于它能够根据需要调整缓冲区的容量。例如,我们能够使用push_str()方法追加更多的文本,这种追加操作可能会引起缓冲区的增长。(注意,my_name需要是可变(mutable)的):

  1. let mut my_name = "Pascal".to_string();
  2. my_name.push_str( " Precht");

事实上, 如果你熟悉Rust的Vec<T>类型,你就可以理解String是什么样子的了。因为它们的行为和特性在本质上是相同的,唯一不同地是,String保证内部只保存标准的UTF-8文本。

理解字符串切片(Understanding string slices)

当我们需要引用一个被拥有的UTF-8文本的区间(range),或者当我们使用字符串字面量(string literals)时,我们就需要使用字符串切片(也就是 str)。
如果我们只是对存储在my_name中的last name感兴趣,我们可以像下面这样来获取一个针对字符串中的特定部分的引用:

  1. let mut my_name = "Pascal".to_string();
  2. my_name.push_str( " Precht");
  3. let last_name = &my_name[7..];

通过指定从第7个字节(因为有空格)开始一直到缓冲区的结尾(".."),last_name现在是一个引用自my_name拥有的文本的字符串切片(string slice)。它借用了这个文本。这里是它在内存中的样子:

  1. my_name: String last_name: &str
  2. [––––––––––––] [–––––––]
  3. +–––+––––+––––+–––+–––+–––+
  4. stack frame │ • │ 1613 │ │ • │ 6
  5. +–│–+––––+––––+–––+–│–+–––+
  6. │ │
  7. +–––––––––+
  8. │ │
  9. │ │
  10. │ [–│––––––– str –––––––––]
  11. +–V–+–––+–––+–––+–––+–––+–––+–V–+–––+–––+–––+–––+–––+–––+–––+–––+
  12. heap │ P │ a │ s │ c │ a │ l │ │ P │ r │ e │ c │ h │ t │ │ │ │
  13. +–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+–––+

注意last_name没有在栈上存储容量信息。这是因为它只是对一个字符串切片的引用,而该字符串管理它的容量。这个字符串切片,即str本身,是不确定大小(unsized)的。 而且,在实际使用中,字符串切片总是以引用的形式出现,也就是它们的类型总是&str而不是str
上面已经解释了String,&String,和str以及&str的区别,但是我们还没有在最开始的示例中创建过这样的引用,不是吗?

理解字符串字面量(Understanding string literals)

正如前面所提到的,有两种情况我们需要使用字符串切片:要么创建一个对子字符串的引用,或者我们使用字符串字面量(string literals)。
一个字符串字面量由一串被双引号包含的文本创建,就像我们之前写的:

let my_name = "Pascal Precht"; // This is a `&str` not a `String`

下一个问题是,如果&str是一个引用了被(某人)拥有的String的切片,假定这个文本在适当的地方被创建,那么这么String的所有者是谁?
很显然,字符串字面量有点特殊。他们是引用自“预分配文本(preallocated text)”的字符串切片,这个预分配文本存储在可执行程序的只读内存中。换句话说,这是装载我们程序的内存并且不依赖于在堆上分配的缓冲区。
也就是说,栈上还有一个入口,指向当程序执行时预分配的内存。

  1. my_name: &str
  2. [–––––––––––]
  3. +–––+–––+
  4. stack frame │ • │ 6
  5. +–│–+–––+
  6. +––+
  7. preallocated +–V–+–––+–––+–––+–––+–––+
  8. read-only │ P │ a │ s │ c │ a │ l │
  9. memory +–––+–––+–––+–––+–––+–––+

当我们对String&str的区别有了更好的理解之后,另一个问题也就随之而来了。

应该使用哪一个?(Which one should be used?)

显然,这取决于很多因素,但是一般地,保守来讲,如果我们正在构建的API不需要拥有或者修改使用的文本,那么应该使用&str而不是String。这意味着,我们可以改进一下最原始的greet()函数:

  1. fn greet(name: &str) {
  2. println!("Hello, {}!", name);
  3. }

等一下,但是如果这个API的调用者真的有一个String并且出于某些未知原因无法将其转换成&str呢?完全没有问题。Rust有一个超级强大的特性叫做deref coercing,这个特性能够允许把传进来的带有借用操作符的String引用,也就是&String,在API执行之前转成&str。我们会在另一篇文章里介绍更多地相关细节。
因此,我们的greet()函数在下面代码中也可以正常工作:

  1. fn main() {
  2. let first_name = "Pascal";
  3. let last_name = "Precht".to_string();
  4. greet(first_name);
  5. greet(&last_name); // `last_name` is passed by reference
  6. }
  7. fn greet(name: &str) {
  8. println!("Hello, {}!", name);
  9. }

这里可以运行代码。

这就是本文全部内容,希望这篇文章对你有用。关于这部分内容,Reddit上有一个很有意思的讨论。

欢迎关注我的微信公众号: Rust碎碎念

80ee3f34ddea4b7b5232988caac2935c.png
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/121364?site
推荐阅读
相关标签
  

闽ICP备14008679号