Rust中字符串类型&str和String的使用

 更新时间:2024年03月14日 09:50:19   作者:希望_睿智  
在Rust中,字符串是一种非常重要的数据类型,&str和String是Rust中两种主要的字符串类型,本文主要介绍了Rust中字符串类型&str和String的使用,感兴趣的可以了解一下

概述

在Rust中,字符串是一种非常重要的数据类型,用于处理文本数据。Rust的字符串是以UTF-8编码的字节序列,主要有两种类型:&str和String。其中,&str是一个对字符数据的不可变引用,更像是对现有字符串数据的“视图”,而String则是一个独立、可变更的字符串实体。

&str和String

&str和String是Rust中两种主要的字符串类型,它们在以下6个方面存在比较明显的区别。

所有权和可变性

  • &str:是Rust核心语言中唯一的字符串类型,它是一个不可变的字符串切片,是对字符串数据的引用,并不拥有数据的所有权。&str可以安全地使用,但它的内容是不可变的,也就是说,不能改变它指向的字符串的内容。&str可以指向String的内容,也可以指向静态字符串字面量。
  • String:这是一个在堆上分配的、可变的字符串类型。String类型由Rust标准库提供,而不是编入核心语言。它拥有其内容的所有权,这意味着String可以被修改。String本质上是一个封装了动态大小数组(Vec<u8>)的结构体,该数组存储了UTF-8编码的字节。

生命周期

  • &str:生命周期取决于它的来源。如果是字符串字面量,则生命周期为'static。如果来自某个作用域内的String或其他类型,则其生命周期与该作用域相同。
  • String:没有明确的生命周期限制,只要String实例存在,它就可以被使用。

存储位置

  • &str:可能是指向静态内存中的字符串字面量(&'static str),比如:编译时确定的常量字符串。也可能是指向堆上分配的String的一部分,或者任何其他类型的UTF-8编码数据的区域。
  • String:始终在堆上动态分配。

性能

  • &str:由于它只是一个引用,没有额外的内存分配成本,因此在某些情况下可能更高效。
  • String:由于它在堆上分配,因此会有额外的内存分配和复制成本,尤其是在字符串拼接时。

使用场景

  • &str:当只需要读取字符串内容,或者想要避免额外的内存分配时,使用&str。此外,在函数参数中,使用&str可以允许函数接受不同类型的字符串参数,包括:String和静态字符串字面量。
  • String:当需要一个可变的字符串,或者不关心字符串的具体来源时,使用String。

与C/C++语言的比较

  • &str:类似于C语言中的const char *,它只是一个指向字符串数据的指针,并不拥有数据。在Rust中,&str比C语言中的裸指针更安全,因为它有一个生命周期参数来确保引用的有效性。
  • String:类似于C++中的std::string,是一个字符的容器,并且拥有其内容。

字符串的创建

在Rust中,创建字符串有多种方法。根据具体需求,我们可以选择不同的方法。如果需要一个可变的字符串并且打算在程序运行时修改它,那么String类型是最佳选择。如果只是需要一个对静态文本的引用,那么&str就足够了。

使用字符串字面量创建&str

字符串字面量是在代码中直接写入的文本,它们被存储在程序的只读数据段中,并且是不可变的。字符串字面量隐式地具有&str类型。在下面的示例代码中,text是一个指向字符串字面量的引用,其类型为&str。

let text: &str = "Hello, CSDN";

使用String::new创建空的String

如果我们想要一个可变的、可以增长的字符串,应该使用String类型。在下面的示例代码中,empty_str是一个空的String变量,我们可以向其中添加内容。

fn main() {
    let mut empty_str = String::new();
    empty_str.push_str("Hello");
    println!("{}", empty_str);
}

使用字符串字面量初始化String

可以直接将字符串字面量转换为String,这是通过调用to_string方法或to_owned方法来实现的。

fn main() {
    let text1 = "Hello, CSDN".to_string();
    let str_slice: &str = "Hello, Rust";
    let text2 = str_slice.to_owned();
    println!("{}", text1);
    println!("{}", text2);
}

使用format!宏创建String

format!宏是Rust中创建格式化字符串的强大工具,它可以根据提供的格式字符串和参数生成一个 String。

fn main() {
    let name: &str = "CSDN";
    let info = format!("Hello, {}", name);
    println!("{}", info);
}

使用String::from创建String

String::from是一个便利的方法,用于从实现了Into<String>特征的任何类型创建String。因为字符串字面量隐式地实现了这个特征,故可以直接使用。

let text = String::from("Hello, CSDN");

字符串的拼接

Rust提供了强大的字符串拼接功能,可以让字符串操作变得更加灵活和高效。

使用+运算符或+=运算符

如果想要将两个String类型进行拼接,可以使用+运算法。

fn main() {
    let str1 = String::from("Hello");
    let str2 = String::from(" CSDN");
    // 不能直接使用str1 + str2
    let str = str1 + &str2;
    println!("{}", str);
    // 编译错误:value borrowed here after move
    println!("{}", str1);
}

在上面的示例代码中,我们将str1和str2进行了拼接,并得到了str。拼接时,我们使用了&str2,而没有直接使用str2。拼接完成后,str1不再有效。之所以会这样,与使用+运算符时调用的函数签名有关。Rust的+运算符使用了add函数,其签名与下面的函数声明类似。

fn add(self, s: &str) -> String

首先,str2使用了&,意味着我们使用第二个字符串的引用与第一个字符串相加。这是因为add函数只能将&str和String相加,而不能将两个String值相加。在Rust中,可以通过Deref强制转换将&String强转成&str,相当于自动把&str2变成了&str2[..]。其次,add函数直接获取了self的所有权,因为self没有使用&。这意味着,str1的所有权被移动到add函数后,str1将不再有效。

若要对可变的String进行拼接操作,还可以使用+=操作符。但实际上,这并不是简单的连接,而是创建了一个新的String实例,并丢弃了原String分配的内存。

fn main() {
    let mut str1 = String::from("Hello");
    let str2 = " CSDN";
    str1 += str2;
    println!("{}", str1);
}

注意:使用+=运算符,或者连续使用+运算符进行多次拼接,会导致多次内存分配,效率较低,尤其是在处理大量数据时。如果需要高效地拼接多个字符串,建议使用下面的format!宏。

使用format!宏

format!宏是一种更灵活且高效的字符串拼接方法,尤其适用于包含变量和格式化文本的情况。format!宏可以处理各种复杂的格式化需求,并且它的性能通常优于简单的+拼接。

fn main() {
    let name: &str = "CSDN";
    let info = format!("Hello, {}", name);
    println!("{}", info);
}

使用push_str方法或push方法

如果已经有了一个String变量,并且想要将另一个字符串或字符追加到它后面,可以使用push_str方法或push方法。注意:push系列方法不会创建新的String实例,而是直接在原有的String缓冲区上追加内容,这通常比使用+运算符更高效。

fn main() {
    let mut text = String::from("Hello ");
    text.push_str("Rust");
    println!("{}", text);

    text.push(' ');
    text.push('C');
    text.push('S');
    text.push('D');
    text.push('N');
    println!("{}", text);
}

字符串的搜索与替换

在Rust中,我们可以使用find、rfind、contains、replace等方法来进行字符串的搜索与替换。在下面的示例代码中,我们首先调用find方法查找子串"CSDN",并返回一个Option类型的值。接下来,我们调用contains方法来检查text字符串是否包含了子串"Hello",若包含,返回true,否则返回false。最后,我们调用replace方法来替换字符串中的子串。

replace方法接收两个参数:第一个参数是要被替换的子串,第二个参数是替换后的新子串。该方法会返回一个新的字符串,其中所有与给定模式匹配的子串都被替换为指定的替换字符串。注意:第一个参数中的原始字符串不会被修改。

fn main() {
    let text = "Hello CSDN";

    // 搜索子串
    let index = text.find("CSDN");
    if let Some(value) = index {
        println!("found: {}", value);
    } else {
        println!("not found");
    }

    // 包含子串
    let contain_hello = text.contains("Hello");
    println!("contain hello: {}", contain_hello);
    
    // 替换子串
    let replaced: String = text.replace("CSDN", "GitHub");
    println!("{}", replaced);
}

字符串的长度

在Rust中,获取字符串的长度是一个常见的操作。Rust的String类型提供了一个len方法,可以用来获取字符串中字节的数量。需要特别注意的是:这个长度是以字节为单位的,对于ASCII字符串来说,每个字符占用一个字节;但是,对于包含多字节字符(比如:UTF-8编码的Unicode字符)的字符串,len方法返回的是字节的总数,而不是字符的总数。

如果想要获取字符串中Unicode字符的数量,我们应该使用chars方法,然后计算迭代器中元素的数量。chars方法会返回一个迭代器,该迭代器逐个产生字符串中的Unicode字符。

fn main() {
    let text = "Hello 霸都";

    // 获取字节长度
    let byte_len = text.len();
    // 输出:12
    println!("{}", byte_len);

    // 获取字符长度
    let char_len = text.chars().count();
    // 输出:8
    println!("{}", char_len);
}

另外,Rust字符串不支持直接通过索引来访问单个字符。这是因为,UTF-8编码格式下,单个字符可能占用1到4个字节,索引操作会带来潜在的非确定性和不一致性问题。如果确实需要通过索引访问字符,可以使用chars()方法。它会返回一个迭代器,产生字符串中的每个Unicode字符。然后,我们可以使用nth方法或者其他集合方法来获取特定位置的字符。

fn main() {
    let text = "Hello 霸都";
    // 注意:索引从0开始计数
    let index = 6;
    let cur_char = text.chars().nth(index);
    // 输出:index 6: 霸
    match cur_char {
        Some(c) => println!("index {}: {}", index, c),
        None => println!("index out of bounds"),
    }
}

字符串与字节的转换

Rust中的字符串和字节之间可以方便地进行转换,这在处理二进制数据和编解码时非常有用。

fn main() {
    let text = "Hello CSDN";

    // 字符串转字节
    let bytes = text.as_bytes();
    // 输出:[72, 101, 108, 108, 111, 32, 67, 83, 68, 78]
    println!("{:?}", bytes);
    
    // 字节转字符串
    let bytes2 = [72, 101, 108, 108, 111];
    let text2 = std::str::from_utf8(&bytes2).unwrap();
    // 输出:Hello
    println!("{}", text2);
}

总结

由于Rust强调安全性与内存管理,它的字符串设计也体现出了这一点:不可变的&str确保了引用安全,而String则通过所有权系统保证了内存的有效管理,避免了悬垂引用和其他常见的内存错误。

到此这篇关于Rust中字符串类型&str和String的使用的文章就介绍到这了,更多相关Rust &str String内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Rust中引用的具体使用

    Rust中引用的具体使用

    在Rust语言中,引用机制是其所有权系统的重要组成部分,ust提供了两种类型的引用,不可变引用和可变引用,本文就来详细的介绍一下这两种的用法,感兴趣的可以了解一下
    2024-03-03
  • Rust实现一个表达式Parser小结

    Rust实现一个表达式Parser小结

    这篇文章主要为大家介绍了Rust实现一个表达式Parser小结,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-11-11
  • Rust声明宏在不同K线bar类型中的应用小结

    Rust声明宏在不同K线bar类型中的应用小结

    在K线bar中,往往有很多不同分时k线图,比如1,2,3,5,,,,,60,120,250,300…,,不同分钟类型,如果不用宏,那么手写会比较麻烦,下面就试用一下宏来实现不同类型的bar,感兴趣的朋友一起看看吧
    2024-05-05
  • Rust语言实现图像编码转换

    Rust语言实现图像编码转换

    image-rs库是 Rust 社区中广泛使用的一个开源库,它提供了丰富的图像编解码功能,本文主要介绍了Rust语言实现图像编码转换,具有一定的参考价值,感兴趣的可以了解一下
    2024-05-05
  • 深入探究在Rust中函数、方法和关联函数有什么区别

    深入探究在Rust中函数、方法和关联函数有什么区别

    在 Rust 中,函数、方法和关联函数都是用来封装行为的,它们之间的区别主要在于它们的定义和调用方式,本文将通过一个简单的rust代码示例来给大家讲讲Rust中函数、方法和关联函数区别,需要的朋友可以参考下
    2023-08-08
  • C++的替代:微软如何使用rust?

    C++的替代:微软如何使用rust?

    这篇文章主要介绍了微软如何使用rust的,帮助大家了解c++和rust这两门编程语言的联系与区别,感兴趣的朋友可以了解下
    2020-09-09
  • 详解Rust中的方法

    详解Rust中的方法

    方法其实就是结构体的成员函数,在C语言中的结构体是没有成员函数的,但是Rust毕竟也是一门面向对象的编程语言,所以给结构体加上方法的特性很符合面向对象的特点,这篇文章主要介绍了Rust中的方法,需要的朋友可以参考下
    2022-10-10
  • Rust中字符串类型&str和String的使用

    Rust中字符串类型&str和String的使用

    在Rust中,字符串是一种非常重要的数据类型,&str和String是Rust中两种主要的字符串类型,本文主要介绍了Rust中字符串类型&str和String的使用,感兴趣的可以了解一下
    2024-03-03
  • 教你使用RustDesk 搭建一个自己的远程桌面中继服务器

    教你使用RustDesk 搭建一个自己的远程桌面中继服务器

    这篇文章主要介绍了RustDesk 搭建一个自己的远程桌面中继服务器,主要包括服务端安装和客户端配置方法,配置好相关操作输入控制码即可发起远程或文件传输,本文通过图文给大家讲解的非常详细,需要的朋友可以参考下
    2022-08-08
  • rust标准库std::env环境相关的常量

    rust标准库std::env环境相关的常量

    在本章节中, 我们探讨了Rust处理命令行参数的常见的两种方式和处理环境变量的两种常见方式, 抛开Rust的语法, 实际上在命令行参数的处理方式上, 与其它语言大同小异, 可能影响我们习惯的也就只剩下语法,本文介绍rust标准库std::env的相关知识,感兴趣的朋友一起看看吧
    2024-03-03

最新评论