rust字符串

news2025/1/16 16:49:02

字符串类型

诸位在入门rust的时候,要认真,因为字符串类型在rust中有好几种,一不小心就搞混了类型,导致代码编译报错。好在有强大的rust-analyzer和vscode帮助我们。我们直接通过一段代码来开始认识rust的字符串类型。

fn main() {
    let s = "Hello, Rust string!";      // s是&str类型
    println!("{s}");

    let s = "Hello, Rust string!".to_string();  // s是String类型
    println!("{s}");
    
    let s1 = &s;                    // &String类型
    println!("{s1}");
    
    let s2 = &s[0..5];              // &str类型
    println!("{s2}");

    // let s1 = s[0..5];       error 这应该是什么类型?是str吗?那么如何使用str类型?
}

字符串切片引用类型(&str)

首先,我们还是从字符串字面值来谈起,在rust中,字符串字面值常量的类型是&str,这个类型称之为“字符串切片引用”。字符串字面值是特殊的,它实际上存储在可执行程序的只读内存段中(rodata)。通过&str可以引用rodata中的字符串。同样,对于在堆上存放的字符串String类型,也可以通过&str来引用其中的部分。就和python的切片类似。但是如果想要直接使用str类型,是不行的,只能通过Box<str>来使用。例如上面通过切片引用&s[0..5]来使用s的第1个字节到第5个字节的内容。索引下标是从0开始的,范围是区间[0, 5),注意这个区间是左闭右开的。例如:

let s = String::from("hello");
let slice = &s[2..5];
println!("{slice}");

这段代码中的slice是&str类型,切片引用了s的第3个字节到第5个字节的内容。即输出llo,在rust的切片中,下标也不能超过字符串长度的边界,否则会导致运行时错误。例如:

let s = String::from("hello");
let slice = &s[2..8];
println!("{slice}");

这段代码在执行的时候会直接panic,异常信息如下所示:

thread 'main' panicked at 'byte index 8 is out of bounds of `hello`', src/main.rs:23:18
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

它告诉我们索引8超出了"hello"的界限。我们再次尝试让起始索引越界。

let s = String::from("hello");
let slice = &s[-1..5];
println!("{slice}");

这次将无法通过编译,编译时报错如下。

rust要求索引必须是usize类型的,这意味着索引不能是负数。另外,如果起始索引是0,可以简写为&s[..3];同样如果终止索引是String的最后一个字节,那么可以简写为&s[1..];因此如果要引用整个String,那么可以简写为&s[..]。下面是一个简单的演示,它们每组都是等价的。

let slice = &s[0..3];
println!("{slice}");
let slice = &s[..3];
println!("{slice}");

let len = s.len();
let slice = &s[1..len];
println!("{slice}");
let slice = &s[1..];
println!("{slice}");

let slice = &s[0..len];
println!("{slice}");
let slice = &s[..];
println!("{slice}");

最后,字符串切片引用的索引必须落在字符之间的边界位置,但是由于rust的字符串是UTF-8编码的,因此必须要小心。后文会讲述如何取出UTF-8编码的一个字符。切片类型是对集合的部分引用,不仅有字符串切片引用,其它的集合类型也有。

字符串类型(String)

Rust 中的字符是 Unicode 类型,因此每个字符占据 4 个字节内存空间,但是在字符串中不一样,字符串是 UTF-8 编码,也就是字符串中的字符所占的字节数是变化的(1 - 4)

Rust 在语言级别,只有一种字符串类型: str,它通常是以引用类型出现 &str,也就是上文提到的字符串切片引用。虽然语言级别只有上述的 str 类型,但是在标准库里,还有多种不同用途的字符串类型,其中使用最广的即是 String 类型。

前面说个,str类型被存放在了rodata区域,无法被修改。而String是一个可增长,可变且具有所有权的utf-8编码的字符串。

String和&str的相互转换

前文已经看到了如何将&str转为String,例如:

let s = String::from("hello");
let s = "hello".to_string();

将String转为&str前面也演示了,例如:

let s = String::from("hello");
let slice = &s[..2];
println!("{slice}");    // 直接打印,没有解引用。

其中实际上还有一个问题,可能有部分读者已经注意到了,那就是我们直接打印了slice这个切片引用,而没有解引用。实际上这是因为deref 隐式强制转换,这是由编译器帮我们完成的。而且你不能直接使用str类型。如果手动解引用,会引起编译错误。

不能使用字符串索引

由于rust的字符串类型是utf-8编码的,如果允许使用索引来取出字符串中的某个字符,那么这将牺牲一部分性能,而rust期望索引操作的时间复杂度是O(1)。因此,你不能像python那样使用索引去访问第n个字符。当然rust提供了其它方式来获取其中某个字符。例如chars方法。

操作字符串

操作字符串,主要是针对String类型来进行的。无非就是涉及到增删改查。

创建String字符串

通过官方文档可以得知,我们可以有下面三种方式从字符串字面值来创建一个新的String字符串。

let s = "Hello".to_string();
let s = String::from("world");
let s: String = "also this".into();

追加

在字符串尾部可以使用 push() 方法追加字符 char,也可以使用 push_str() 方法追加字符串字面量。这两个方法都是在原有的字符串上追加,并不会返回新的字符串。由于字符串追加操作要修改原来的字符串,则该字符串必须是可变的,即字符串变量必须由 mut 关键字修饰。例如:

fn main() {
    let mut s = String::from("Hello ");
    s.push('r');
    println!("追加字符 push() -> {}", s);

    s.push_str("ust!");
    println!("追加字符串 push_str() -> {}", s);
}

插入

可以使用 insert() 方法插入单个字符 char,也可以使用 insert_str() 方法插入字符串字面量。例如:

fn main() {
    let mut s = String::from("Hello rust!");
    s.insert(5, ',');
    println!("插入字符 insert() -> {}", s);
    s.insert_str(6, " I like");
    println!("插入字符串 insert_str() -> {}", s);
}

这俩方法需要传入两个参数,第一个参数是字符(串)插入位置的索引,第二个参数是要插入的字符(串),索引从 0 开始计数,如果越界则会发生错误。它们都是在原字符串上做修改,因此原字符串必须可变。

替换

  1. replace

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

    fn main() {
        let string_replace = String::from("I like rust. Learning rust is my favorite!");
        let new_string_replace = string_replace.replace("rust", "RUST");
        dbg!(new_string_replace);
        let s = "12345";
        let new_s = s.replace("3", "t");
        dbg!(new_s); 
    }
    

    dbg!是rust提供的调试使用的宏,方便rust使用者进行打印输出。它会打印出其所在的文件,代码行,变量名。非常便于调试。

  2. replacen

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

    fn main() {
        let string_replace = "I like rust. Learning rust is my favorite!";
        let new_string_replacen = string_replace.replacen("rust", "RUST", 1);
        dbg!(new_string_replacen);
    }
    
  3. replace_range

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

    fn main() {
        let mut string_replace_range = String::from("I like rust!");
        string_replace_range.replace_range(7..8, "R");
        dbg!(string_replace_range);
    }
    

    这个方法在字符串中的字符都是由ASCII组成的时候,是非常有用的。但是如果存在非ASCII编码的字符时,就需要计算出正确的utf-8字符的起始位置和结束位置,否则会造成运行时错误

删除

与字符串删除相关的方法有 4 个,他们分别是 pop(),remove(),truncate(),clear()。这四个方法仅适用于 String 类型。

  1. pop

    删除并返回字符串的最后一个字符(按字符处理,不是字节),该方法是直接操作原来的字符串。但是存在返回值,其返回值是一个 Option 类型,如果字符串为空,则返回 None。我们在这里都是用dbg!这个宏来打印的,他能够将Option<char>类型打印出来,后面我们再来介绍Option类型。

    fn main() {
        let mut string_pop = String::from("rust pop 中文!");
        let p1 = string_pop.pop();
        let p2 = string_pop.pop();
        dbg!(p1);
        dbg!(p2);
        dbg!(string_pop);
    }
    
  2. remove

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

    fn main() {
        let mut string_remove = String::from("测试remove方法");
        println!(
            "string_remove 占 {} 个字节",
            std::mem::size_of_val(string_remove.as_str())
        );
        // 删除第一个汉字
        string_remove.remove(0);
        // 下面代码会发生错误
        // string_remove.remove(1);
        // 直接删除第二个汉字
        // string_remove.remove(3);
        dbg!(string_remove);
    }
    
  3. truncate

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

    fn main() {
        let mut string_truncate = String::from("测试truncate");
        string_truncate.truncate(3);
        dbg!(string_truncate);
    }
    
  4. clear

    清空字符串,该方法是直接操作原来的字符串。调用后,删除字符串中的所有字符,相当于 truncate() 方法参数为 0 的时候。例如:

    fn main() {
        let mut string_clear = String::from("string clear");
        string_clear.clear();
        dbg!(string_clear);
    }
    

连接

  1. 使用 + 或者 += 连接字符串

    使用 + 或者 += 连接字符串,要求右边的参数必须为字符串的切片引用(Slice)类型。其实当调用 + 的操作符时,相当于调用了 std::string 标准库中的 add() 方法,这里 add() 方法的第二个参数是一个引用的类型。因此我们在使用 +, 必须传递切片引用类型。不能直接传递 String 类型。(类似于C++的运算符重载)+ 和 += 都是返回一个新的字符串。所以变量声明可以不需要 mut 关键字修饰。例如:

    fn main() {
        let string_append = String::from("hello ");
        let string_rust = String::from("rust");
        // // &string_rust会自动解引用为&str,这是因为deref coercing特性。这个特性能够允许把传进来的&String,在API执行之前转成&str。
        let result = string_append + &string_rust;
        let mut result = result + "!";
        result += "!!!";
    
        println!("连接字符串 + -> {}", result);
    }
    

    这里出现了deref coercing这个特性,这是为了使用起来更方便,但是个人认为deref coercing是一个不一致性设计。另外一点是add函数的函数原型是fn add(self, s: &str) -> String,self 是 String 类型的字符串,因此string_append在经过连接运算(+)之后,转移了所有权,导致string_append被释放。

  2. 使用 format! 连接字符串

    format! 这种方式适用于 String 和 &str,和C/C++提供的sprintf函数类似。例如:

    fn main() {
        let s1 = "hello";
        let s2 = String::from("rust");
        let s = format!("{} {}!", s1, s2);
        println!("{}", s);
    }
    

    format宏使用起来更加方便,而且你可以自由的连接多个字符串,并且由于宏使用的是引用,因此不会发送所有权的转移。

标准库中String和&str有非常多的方法,可以在rust官方文档中进行查看。rust的官方文档编写的算是非常Nice的,几乎每个函数都有例子。
在这里插入图片描述
在这里,你能几乎能找到关于rust的一切。最常用的可能就是标准库,cargo书册和编译错误索引表。

关于字符串的其他部分

  1. 我们可以通过转义的方式 \ 输出 ASCII 和 Unicode 字符

    fn main() {
        // 通过 \ + 字符的十六进制表示,转义输出一个字符
        let byte_escape = "I'm writing \x52\x75\x73\x74!";
        println!("What are you doing\x3F (\\x3F means ?) {}", byte_escape);
    
        // \u 可以输出一个 unicode 字符
        let unicode_codepoint = "\u{211D}";
        let character_name = "\"DOUBLE-STRUCK CAPITAL R\"";
    
        println!(
            "Unicode character {} (U+211D) is called {}",
            unicode_codepoint, character_name
        );
    }
    
  2. 将一个物理行字符串拆分到多行

    使用\连接两行(多行)为一个物理行。

    fn main() {
        let long_string = "String literals
                        can span multiple lines.
                        The linebreak and indentation here \	
                        can be escaped too!";
        println!("{}", long_string);
    }
    

    这段代码输出如下所示:
    在这里插入图片描述
    String literals和can span multiple lines.是分开的两行,而The linebreak and indentation here和can be escaped too!由\连接为一个物理行。

  3. 原始字符串

    现在的大多数语言中都提供了原始字符串的语法,rust也不例外,在字符串前面加上小写字母r,那么字符串在被使用的时候将不会发生转义。

    fn main() {
        println!("{}", "hello \x52\x75\x73\x74");           // 输出hello Rust
        let raw_str = r"Escapes don't work here: \x3F \u{211D}";    // 原始字符串
        println!("{}", raw_str);        // 输出Escapes don't work here: \x3F \u{211D}
    }
    
  4. 字符中出现双引号

    在C/C++中,字符串中出现双引号的时候,需要进行转义;rust提供了r#这种方式来解决这个问题,当然你也可以使用转义的方式。

    fn main() {
        // 如果字符串包含双引号,可以在开头和结尾加 #
        let quotes = r#"And then I said: "There is no escape!""#;
        println!("{}", quotes);
    
        // 如果还是有歧义,可以继续增加#,没有限制
        let longer_delimiter = r###"A string with "# in it. And even "##!"###;
        println!("{}", longer_delimiter);
    }
    

字符串和字符数组

由于rust的字符串是uft-8编码的,而String类型不允许以字符为单位进行索引。有时候会不方便。String提供了chars()方法遍历字符以及bytes()方法遍历字节。另外就是,如果需要从String中获取子串是比较困难的,标准库没有提供相关的方法。 你需要在 crates.io 上搜索 utf8 来寻找想要的功能。可以使用utf8_slice这个库。当然了另一种解决方案就是使用字符数组,这样就可以非常方便的进行操作了。

参考资料

  1. rust圣经
  2. rust标准库String
  3. rust标准库str

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/158896.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

nginx学习笔记5(小d课堂)

全局异常兜底数据返回 我们如果访问的路径不存在&#xff0c;会给我们返回一个404.那么我们就可以通过nginx配置去返回兜底数据&#xff1a; 修改完配置&#xff0c;重启nginx。 这样我们就返回我们的兜底数据了。 nginx封禁恶意ip 我们现在随便选择一个ip进行封禁测试。 我们现…

.Net Core 6.0 WebApi配置跨域

ASP.Net Core 6.0 WebApi配置跨域 Program.cs文件代码如下&#xff1a; //配置跨域 builder.Services.AddCors(cor > {var cors configuration.GetSection("CorsUrls").GetChildren().Select(p > p.Value);cor.AddPolicy("Cors", policy >{poli…

92、【树与二叉树】leetcode ——111. 二叉树的最小深度:层次遍历+先序DFS+后序DFS[子问题分解](C++版本)

题目描述 原题链接&#xff1a;111. 二叉树的最小深度 解题思路 1、迭代法&#xff1a;层序遍历BFS 最小深度的特点是第一次遍历到结点的左右指针为NULL&#xff0c;此时该路径为最短路径。 /*** Definition for a binary tree node.* struct TreeNode {* int val;* …

探索SpringMVC-组件之HandlerExceptionResolver

前言 在介绍完Handler、HandlerAdapter、HandlerMapping之后&#xff0c;剩下的比较关键的组件就是HandlerExceptionResolver、ViewResolver。其他的像国际化、主题、文件上传、重定向&#xff0c;这些锦上添花的组件都是一个框架需要关心的。但不是我们平常使用的核心功能&am…

蓝桥杯省赛习题练习(三)

题目来源&#xff1a;2022年第十三届省赛(B组)真题 目录1.九进制转十进制运行结果2. 顺子日期运行结果3. 刷题统计运行结果4. 积木画1.九进制转十进制 问题描述&#xff1a;九进制正整数 (2022)9 转换成十进制等于多少&#xff1f; #include<stdio.h> #include<math.h…

一文带你深入了解线程池

目录一. 什么是线程池二. 为什么要使用线程池三. 线程池的参数四. 线程池的工作流程五. 使用Executors 创建常见的功能线程池一. 什么是线程池 简单来说&#xff0c;线程池就是提前创建好一批线程&#xff0c;当有任务的时候&#xff0c;从池子中取出一个线程去执行该任务&…

再说多线程(二)——细说Monitor类

在上一节我们已经讨论了使用Lock来保证并发程序的一致性&#xff0c;Lock锁是借助了Monitor类的功能。本节将详细的介绍Monitor类&#xff0c;以及如何通过Monitor类的成员函数实现并行程序的一致性。1.Monitor类介绍根据微软的说法&#xff0c;C#中的监视器类提供了一种同步对…

Microsoft Visual SourceSafe的使用

1、介绍 Microsoft Visual SourceSafe&#xff0c;简称vss。是一款早期微软推出的版本管理工具。跟据官方的定义&#xff0c;vss有两种控制模式&#xff1a;独占&#xff08;Lock-Modify-Unlock Model&#xff09;和并行&#xff08;Copy-Modify-Merge Model&#xff09;。独占…

程序的安装——软件安装包的制作、软件源的使用

读书笔记 —— 《嵌入式C语言自我修养》 软件安装 linux 安装包的制作 编译 软件安装包路径 使用dpkg命令来制作安装包 及 安装包的卸载 软件仓库 更新源 查看具体需要更新的软件包 更新软件包 软件安装 软件安装的过程其实就是将一个可执行文件安装到ROM的过…

安全狗云原生安全从1.X到2.X的演变之路(1)

随着云计算技术的蓬勃发展&#xff0c;传统上云实践中的应用升级缓慢、架构臃肿、无法快速迭代等“痛点”日益明显。能够有效解决这些“痛点”的云原生技术正蓬勃发展&#xff0c;成为赋能业务创新的重要推动力&#xff0c;并已经应用到企业核心业务。然而&#xff0c;云原生技…

大型数据中心分层分布式谐波治理方案设计与效果分析

摘要&#xff1a;数据中心行业在国民经济中起到了不可替代的作用,但其繁多的非线性电力负载,如通讯系统、大型计算机、网络控制设备、变频空调、各种数码办公设备、灯光调控系统、UPS、监控系统等给其供电系统带来了严重的谐波干扰,对大型数据中心的运行安全造成了较大的威胁,为…

200:vue+openlayers 添加删除多边形,modify feature,双向互动颜色显示

第200个 点击查看专栏目录 本示例的目的是介绍如何在vue+openlayers项目中添加删除多边形,每绘制一个,左侧列表出一个信息。 hover左侧文字,右边地图上红色显示图形,点击选中右侧地图上某feature,变成蓝色高亮,同时左侧也会蓝色显示,做到双向互动。 高亮显示某feature,…

如何删除图片数据中的重复数据

我们在工作中经常这种情况&#xff0c;leader给你一堆数据&#xff0c;让你用这些没有清洗过的数据完成项目。痛苦的是&#xff0c;这批数据居然存在很多重复的样本。那如何删除这些冗余数据呢&#xff1f;imagehash库非常好用。 github地址&#xff1a;https://github.com/ch…

络达开发----如何开启DMIC

芯片型号&#xff1a;AB1565 功能模块&#xff1a;数字MIC接口的使用 AB1656评估板上支持两路数字MIC&#xff0c;分别为DMIC0和DMIC1&#xff0c;如果图1所示&#xff0c;分别 可以由GPIO_2/3/4/5/13/14/15/16来当数字MIC的接口。 图1&#xff1a;支持DMIC的IO口但是评估板上…

MAC M1使用Rosetta安装python3.6

在使用网上提到的brew和pyenv安装的时候&#xff0c;我的电脑总会报BUILD FAILED错误。 找了一天才找到解决办法&#xff0c;真的十分痛苦&#xff0c;特此记录一下&#xff0c;让别的小伙伴也不再迷茫。 解决办法参考网址&#xff1a;click here&#xff08;需要VPN&#xff…

html跑马灯走马灯效果

演示 <marquee width"100%" scrollamount"5"> <a href"http://www.taobaojp5.tk"><font face"楷体_GB2312" color"#ff0000" size"3"></font><strong>带有超链接的跑马灯!点我试试&…

uwsgi 快速入门

文章目录uwsgi 快速入门一、 概述1、 简单介绍2、 环境配置二、 第一个 WSGI 应用1、 运行2、 添加并发三、 结合 Web 服务器使用1、 Flask2、 Django3、 Nginx配置uwsgi 快速入门 一、 概述 1、 简单介绍 WSGI&#xff08;Web Server Gateway Interface&#xff09;&#x…

FPGA知识汇集-ASIC移植中的FPGA芯片划分

通常&#xff0c;FPGA单芯片难以容纳下整个ASIC设计&#xff0c;因此需要将整个系统划分到多颗FPGA芯片中运行&#xff08;见图1&#xff09;&#xff0c;工程师往往需要借助原型验证平台来实现这样的目标。多芯片的划分绝不是简单的将不同的模板放置到不同的FPGA中那么简单&am…

算法训练营 day16 二叉树 二叉树的最大深度 二叉树的最小深度 完全二叉树的节点个数

算法训练营 day16 二叉树 二叉树的最大深度 二叉树的最小深度 完全二叉树的节点个数 二叉树的最大深度 104. 二叉树的最大深度 - 力扣&#xff08;LeetCode&#xff09; 给定一个二叉树&#xff0c;找出其最大深度。 二叉树的深度为根节点到最远叶子节点的最长路径上的节点…

(JVM) 沙箱安全机制

沙箱安全机制 沙箱安全机制 保证程序安全 保护Java原生的JDK代码 Java安全模型的核心就是Java沙箱&#xff08;sandbox&#xff09;。什么是沙箱&#xff1f;沙箱是一个限制程序运行的环境。 沙箱机制就是将Java代码限定在虚拟机&#xff08;JVM&#xff09;特定的运行范围…