鸿博教育 發表於 2024-4-19 11:32:03

Rust中字符串String集合的具有使用

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>Rust开发者经常被字符串困扰的原因</li><li>字符串是什么</li><li>创建字符串</li><li>更新String</li><ul class="second_class_ul"><li>使用push_str和push附加字符串</li><li>如何拼接字符串</li></ul><li>索引字符串</li><ul class="second_class_ul"><li>内部表示</li><li>字节、标量值、字形簇</li></ul><li>字符串切割slice</li><ul class="second_class_ul"></ul><li>遍历string</li><ul class="second_class_ul"></ul><li>字符串不简单</li><ul class="second_class_ul"></ul></ul></div><p class="maodian"></p><h2>Rust开发者经常被字符串困扰的原因</h2>
<ul><li>倾向于确保暴露出可能的错误。</li><li>字符串是比很多程序员所想象的要更为复杂的数据结构。</li><li>UTF-8。</li></ul>
<p class="maodian"></p><h2>字符串是什么</h2>
<p>Rust 的核心语言中只有一种字符串类型:字符串 slice&nbsp;<code>str</code>,它通常以被借用的形式出现,<code>&amp;str</code>。</p>
<p>字符串(<code>String</code>)类型由 Rust 标准库提供,而不是编入核心语言,它是一种可增长、可变、可拥有、UTF-8 编码的字符串类型。当 Rustaceans 提及 Rust 中的 &quot;字符串 &quot;时,他们可能指的是&nbsp;<code>String</code>&nbsp;或 string slice&nbsp;<code>&amp;str</code>&nbsp;类型,而不仅仅是其中一种类型。</p>
<p class="maodian"></p><h2>创建字符串</h2>
<p>很多&nbsp;<code>Vec</code>&nbsp;可用的操作在&nbsp;<code>String</code>&nbsp;中同样可用,事实上&nbsp;<code>String</code>&nbsp;被实现为一个带有一些额外保证、限制和功能的字节 vector 的封装。其中一个同样作用于&nbsp;<code>Vec&lt;T&gt;</code>&nbsp;和&nbsp;<code>String</code>&nbsp;函数的例子是用来新建一个实例的&nbsp;<code>new</code>&nbsp;函数</p>
<div class="jb51code"><pre class="brush:plain;">let mut s = String::new();
</pre></div>
<p>这新建了一个叫做&nbsp;<code>s</code>&nbsp;的空的字符串,接着我们可以向其中装载数据。通常字符串会有初始数据,因为我们希望一开始就有这个字符串。为此,可以使用&nbsp;<code>to_string</code>&nbsp;方法,它能用于任何实现了&nbsp;<code>Display</code>&nbsp;trait 的类型,比如字符串字面值。</p>
<div class="jb51code"><pre class="brush:plain;">let data = "initial contents";
let s = data.to_string();
    // 该方法也可直接用于字符串字面值:
    let s = "initial contents".to_string();</pre></div>
<p>因为字符串应用广泛,这里有很多不同的用于字符串的通用 API 可供选择。其中一些可能看起来多余,不过都有其用武之地!在这个例子中,<code>String::from</code>&nbsp;和&nbsp;<code>.to_string</code>&nbsp;最终做了完全相同的工作,所以如何选择就是代码风格与可读性的问题了。</p>
<div class="jb51code"><pre class="brush:plain;">let s = String::from("initial contents");
</pre></div>
<p>记住字符串是 UTF-8 编码的,所以可以包含任何可以正确编码的数据</p>
<div class="jb51code"><pre class="brush:plain;">    let hello = String::from("السلام عليكم");
    let hello = String::from("Dobrý den");
    let hello = String::from("Hello");
    let hello = String::from("שָׁלוֹם");
    let hello = String::from("नमस्ते");
    let hello = String::from("こんにちは");
    let hello = String::from("안녕하세요");
    let hello = String::from("你好");
    let hello = String::from("Olá");
    let hello = String::from("Здравствуйте");
    let hello = String::from("Hola");
</pre></div>
<p class="maodian"></p><h2>更新String</h2>
<p><code>String</code>&nbsp;的大小可以增加,其内容也可以改变,就像可以放入更多数据来改变&nbsp;<code>Vec</code>&nbsp;的内容一样。另外,可以方便的使用&nbsp;<code>+</code>&nbsp;运算符或&nbsp;<code>format!</code>&nbsp;宏来拼接&nbsp;<code>String</code>&nbsp;值。</p>
<p class="maodian"></p><h3>使用push_str和push附加字符串</h3>
<p>可以通过&nbsp;<code>push_str</code>&nbsp;方法来附加字符串 slice,从而使&nbsp;<code>String</code>&nbsp;变长:</p>
<div class="jb51code"><pre class="brush:plain;">    let mut s = String::from("foo");
    s.push_str("bar");
</pre></div>
<p>执行这两行代码之后,<code>s</code>&nbsp;将会包含&nbsp;<code>foobar</code>。<code>push_str</code>&nbsp;方法采用字符串 slice,因为我们并不需要获取参数的所有权。</p>
<div class="jb51code"><pre class="brush:plain;">    let mut s1 = String::from("foo");
    let s2 = "bar";
    s1.push_str(s2);
    println!("s2 is {s2}");
</pre></div>
<p><code>push</code>&nbsp;方法被定义为获取一个单独的字符作为参数,并附加到&nbsp;<code>String</code>&nbsp;中。</p>
<div class="jb51code"><pre class="brush:plain;">    let mut s = String::from("lo");
    s.push('l');
</pre></div>
<p class="maodian"></p><h3>如何拼接字符串</h3>
<div class="jb51code"><pre class="brush:plain;">    let s1 = String::from("Hello, ");
    let s2 = String::from("world!");
    let s3 = s1 + &amp;s2; // 注意 s1 被移动了,不能继续使用
</pre></div>
<p>执行完这些代码之后,字符串&nbsp;<code>s3</code>&nbsp;将会包含&nbsp;<code>Hello, world!</code>。<code>s1</code>&nbsp;在相加后不再有效的原因,和使用&nbsp;<code>s2</code>&nbsp;的引用的原因,与使用&nbsp;<code>+</code>&nbsp;运算符时调用的函数签名有关。<code>+</code>&nbsp;运算符使用了&nbsp;<code>add</code>&nbsp;函数,这个函数签名看起来像这样:</p>
<div class="jb51code"><pre class="brush:plain;">fn add(self, s: &amp;str) -&gt; String {</pre></div>
<p><code>解释:s2</code>&nbsp;使用了&nbsp;<code>&amp;</code>,意味着我们使用第二个字符串的&nbsp;<strong>引用</strong>&nbsp;与第一个字符串相加。这是因为&nbsp;<code>add</code>&nbsp;函数的&nbsp;<code>s</code>&nbsp;参数:只能将&nbsp;<code>&amp;str</code>&nbsp;和&nbsp;<code>String</code>&nbsp;相加,不能将两个&nbsp;<code>String</code>&nbsp;值相加。不过等一下 &mdash;&mdash;&nbsp;<code>&amp;s2</code>&nbsp;的类型是&nbsp;<code>&amp;String</code>, 而不是&nbsp;<code>add</code>&nbsp;第二个参数所指定的&nbsp;<code>&amp;str</code>。</p>
<p>如果想要级联多个字符串,<code>+</code>&nbsp;的行为就显得笨重了:</p>
<div class="jb51code"><pre class="brush:plain;">    let s1 = String::from("tic");
    let s2 = String::from("tac");
    let s3 = String::from("toe");

    let s = s1 + "-" + &amp;s2 + "-" + &amp;s3;
</pre></div>
<p>这时&nbsp;<code>s</code>&nbsp;的内容会是 &ldquo;tic-tac-toe&rdquo;。在有这么多&nbsp;<code>+</code>&nbsp;和&nbsp;<code>&quot;</code>&nbsp;字符的情况下,很难理解具体发生了什么。对于更为复杂的字符串链接,可以使用&nbsp;<code>format!</code>&nbsp;宏:</p>
<div class="jb51code"><pre class="brush:plain;">    let s1 = String::from("tic");
    let s2 = String::from("tac");
    let s3 = String::from("toe");

    let s = format!("{s1}-{s2}-{s3}");
</pre></div>
<p class="maodian"></p><h2>索引字符串</h2>
<p>在很多语言中,通过索引来引用字符串中的单独字符是有效且常见的操作。然而在 Rust 中,如果你尝试使用索引语法访问&nbsp;<code>String</code>&nbsp;的一部分,会出现一个错误。</p>
<div class="jb51code"><pre class="brush:plain;">    let s1 = String::from("hello");
    let h = s1;
</pre></div>
<p>错误和提示说明了全部问题:Rust 的字符串不支持索引。那么接下来的问题是,为什么不支持呢?为了回答这个问题,我们必须先聊一聊 Rust 是如何在内存中储存字符串的。</p>
<p class="maodian"></p><h3>内部表示</h3>
<p><code>String</code>&nbsp;是一个&nbsp;<code>Vec&lt;u8&gt;</code>&nbsp;的封装。</p>
<div class="jb51code"><pre class="brush:plain;">let hello = String::from("Hola");
</pre></div>
<p>在这里,<code>len</code>&nbsp;的值是 4,这意味着储存字符串 &ldquo;Hola&rdquo; 的&nbsp;<code>Vec</code>&nbsp;的长度是四个字节:这里每一个字母的 UTF-8 编码都占用一个字节。那下面这个例子又如何呢?(<strong>注意这个字符串中的首字母是西里尔字母的 Ze 而不是数字 3。</strong>)</p>
<div class="jb51code"><pre class="brush:plain;">let hello = String::from("Здравствуйте");
</pre></div>
<p>我们已经知道&nbsp;<code>answer</code>&nbsp;不是第一个字符&nbsp;<code>3</code>。当使用 UTF-8 编码时,(西里尔字母的 Ze)<code>З</code>&nbsp;的第一个字节是&nbsp;<code>208</code>,第二个是&nbsp;<code>151</code>,所以&nbsp;<code>answer</code>&nbsp;实际上应该是&nbsp;<code>208</code>,不过&nbsp;<code>208</code>&nbsp;自身并不是一个有效的字母。返回&nbsp;<code>208</code>&nbsp;可不是一个请求字符串第一个字母的人所希望看到的,不过它是 Rust 在字节索引 0 位置所能提供的唯一数据。用户通常不会想要一个字节值被返回。即使这个字符串只有拉丁字母,如果&nbsp;<code>&amp;&quot;hello&quot;</code>&nbsp;是返回字节值的有效代码,它也会返回&nbsp;<code>104</code>&nbsp;而不是&nbsp;<code>h</code>。</p>
<p class="maodian"></p><h3>字节、标量值、字形簇</h3>
<p>比如这个用梵文书写的印度语单词 &ldquo;नमस्ते&rdquo;,最终它储存在 vector 中的&nbsp;<code>u8</code>&nbsp;值看起来像这样:</p>
<div class="jb51code"><pre class="brush:plain;">[224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164,
224, 165, 135]
</pre></div>
<p>这里有 18 个字节,也就是计算机最终会储存的数据。如果从 Unicode 标量值的角度理解它们,也就像 Rust 的&nbsp;<code>char</code>&nbsp;类型那样,这些字节看起来像这样:</p>
<div class="jb51code"><pre class="brush:plain;">['न', 'म', 'स', '्', 'त', 'े']
</pre></div>
<p>这里有六个&nbsp;<code>char</code>,不过第四个和第六个都不是字母,它们是发音符号本身并没有任何意义。最后,如果以字形簇的角度理解,就会得到人们所说的构成这个单词的四个字母:</p>
<div class="jb51code"><pre class="brush:plain;">["न", "म", "स्", "ते"]
</pre></div>
<p>Rust 提供了多种不同的方式来解释计算机储存的原始字符串数据,这样程序就可以选择它需要的表现方式,而无所谓是何种人类语言。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<strong>最后一个 Rust 不允许使用索引获取&nbsp;<code>String</code>&nbsp;字符的原因是,索引操作预期总是需要常数时间(O(1))。但是对于&nbsp;<code>String</code>&nbsp;不可能保证这样的性能,因为 Rust 必须从开头到索引位置遍历来确定有多少有效的字符</strong>。</p>
<p class="maodian"></p><h2>字符串切割slice</h2>
<p>索引字符串通常是一个坏点子,因为字符串索引应该返回的类型是不明确的:字节值、字符、字形簇或者字符串 slice。因此,如果你真的希望使用索引创建字符串 slice 时,Rust 会要求你更明确一些。为了更明确索引并表明你需要一个字符串 slice,相比使用&nbsp;<code>[]</code>&nbsp;和单个值的索引,可以使用&nbsp;<code>[]</code>&nbsp;和一个 range 来创建含特定字节的字符串 slice:</p>
<div class="jb51code"><pre class="brush:plain;">let hello = "Здравствуйте";

let s = &amp;hello;
</pre></div>
<p>这里,<code>s</code>&nbsp;会是一个&nbsp;<code>&amp;str</code>,它包含字符串的头四个字节。早些时候,我们提到了这些字母都是两个字节长的,所以这意味着&nbsp;<code>s</code>&nbsp;将会是 &ldquo;Зд&rdquo;。</p>
<p>如果获取&nbsp;<code>&amp;hello</code>&nbsp;会发生什么呢?答案是:Rust 在运行时会 panic,就跟访问 vector 中的无效索引时一样。<strong>你应该小心谨慎地使用这个操作,因为这么做可能会使你的程序崩溃。</strong></p>
<p class="maodian"></p><h2>遍历string</h2>
<p>操作字符串每一部分的最好的方法是明确表示需要字符还是字节。对于单独的 Unicode 标量值使用&nbsp;<code>chars</code>&nbsp;方法。对 &ldquo;Зд&rdquo; 调用&nbsp;<code>chars</code>&nbsp;方法会将其分开并返回两个&nbsp;<code>char</code>&nbsp;类型的值,接着就可以遍历其结果来访问每一个元素了:</p>
<div class="jb51code"><pre class="brush:plain;">for c in "Зд".chars() {
    println!("{c}");
}
</pre></div>
<p>另外&nbsp;<code>bytes</code>&nbsp;方法返回每一个原始字节,这可能会适合你的使用场景:</p>
<div class="jb51code"><pre class="brush:plain;">for b in "Зд".bytes() {
    println!("{b}");
}
</pre></div>
<p><strong>不过请记住有效的 Unicode 标量值可能会由不止一个字节组成。</strong></p>
<p class="maodian"></p><h2>字符串不简单</h2>
<p>总而言之,字符串还是很复杂的。不同的语言选择了不同的向程序员展示其复杂性的方式。Rust 选择了以准确的方式处理&nbsp;<code>String</code>&nbsp;数据作为所有 Rust 程序的默认行为,这意味着程序员们必须更多的思考如何预先处理 UTF-8 数据。这种权衡取舍相比其他语言更多的暴露出了字符串的复杂性,不过也使你在开发周期后期免于处理涉及非 ASCII 字符的错误。</p>
<p>到此这篇关于Rust中字符串String集合的具有使用的文章就介绍到这了,更多相关Rust 字符串String内容请搜索琼殿技术社区以前的文章或继续浏览下面的相关文章希望大家以后多多支持琼殿技术社区!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>Rust&nbsp;&nbsp;利用&nbsp;chrono&nbsp;库实现日期和字符串互相转换的示例</li><li>Rust字符串类型全解析(最新推荐)</li><li>Rust中字符串类型&str和String的使用</li><li>Rust中字符串类型String的46种常用方法分享</li><li>Rust动态调用字符串定义的Rhai函数方式</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: Rust中字符串String集合的具有使用