【问题标题】:Modifying chars in a String by index按索引修改字符串中的字符
【发布时间】:2014-10-24 08:52:31
【问题描述】:

我写了一个函数来对借用的字符串进行标题大小写(第一个字母大写,所有其他字母小写),但它最终变得比想象的要麻烦。

fn titlecase_word(word: &mut String) {

    unsafe {
        let buffer = word.as_mut_vec().as_mut_slice();
        buffer[0] = std::char::to_uppercase(buffer[0] as char) as u8;

        for i in range(1, buffer.len()) {
            buffer[i] = std::char::to_lowercase(buffer[i] as char) as u8;
        }
    }
}

不安全的块是特别不受欢迎的。有没有更好的方法来通过索引修改字符串内容?

【问题讨论】:

    标签: rust


    【解决方案1】:

    更新:为最新的 Rust 更新。从 Rust 1.0.0-alpha 开始,to_lowercase()/to_uppercase() 现在是 CharExt 特征中的方法,并且不再有单独的 Ascii 类型:ASCII 操作现在聚集在两个特征中,AsciiExt 和 @987654323 @。它们被标记为不稳定,因此它们可能会在整个 Rust 测试版期间发生变化。


    您的代码不正确,因为它访问单个字节以执行基于字符的操作,但在 UTF-8 中字符不是字节。对于非 ASCII 的任何内容,它都无法正常工作。

    事实上,没有办法正确地就地执行此操作,因为任何字符转换都可能改变字符占用的字节数,这需要完整的字符串重新分配。您应该遍历字符并将它们收集到一个新字符串中:

    fn titlecase_word(word: &mut String) {
        if word.is_empty() { return; }
    
        let mut result = String::with_capacity(word.len());
    
        {
            let mut chars = word.chars();
            result.push(chars.next().unwrap().to_uppercase());
    
            for c in chars {
                result.push(c.to_lowercase());
            }
        }
    
        *word = result;
    }
    

    (试试看here

    因为无论如何您都需要生成一个新字符串,所以最好直接返回它,而不用替换旧字符串。在这种情况下,最好将切片传递给函数:

    fn titlecase_word(word: &str) -> String {
        let mut result = String::with_capacity(word.len());
    
        if !word.is_empty() {
            let mut chars = word.chars();
            result.push(chars.next().unwrap().to_uppercase());
    
            for c in chars {
                result.push(c.to_lowercase());
            }
        }
    
        result
    }
    

    (试试看here

    另外,String 具有来自 Extend 特征的 extend() 方法,它提供了一种比 for 循环更惯用的方法:

    fn titlecase_word(word: &str) -> String {
        let mut result = String::with_capacity(word.len());
    
        if !word.is_empty() {
            let mut chars = word.chars();
            result.push(chars.next().unwrap().to_uppercase());
            result.extend(chars.map(|c| c.to_lowercase()));
        }
    
        result
    }
    

    (试试看here

    事实上,使用迭代器可以进一步缩短它:

    fn titlecase_word(word: &str) -> String {
        word.chars().enumerate()
            .map(|(i, c)| if i == 0 { c.to_uppercase() } else { c.to_lowercase() })
            .collect()
    }
    

    (试试看here

    如果您事先知道您正在使用 ASCII,那么您可以使用 std::ascii 模块提供的特征:

    fn titlecase_word(word: String) -> String {
        use std::ascii::{AsciiExt, OwnedAsciiExt};
        assert!(word.is_ascii());
    
        let mut result = word.into_bytes().into_ascii_lowercase();
        result[0] = result[0].to_ascii_uppercase();
    
        String::from_utf8(result).unwrap()
    }
    

    (试试看here

    如果输入字符串包含任何非 ASCII 字符,此函数将失败。

    此函数不会分配任何内容,而是会就地修改字符串内容。但是,如果没有不安全的,您不能使用单个&mut String 参数编写这样的函数,因为它需要从&mut 移出,这是不允许的。

    您可以使用std::mem::swap() 和一个带有空字符串的临时变量 - 它不需要不安全,但它可能需要分配空字符串。我不记得它是否真的需要分配;如果没有,那么您可以编写这样的函数,尽管代码会有些麻烦。无论如何,&mut-arguments 对于 Rust 来说并不是真正地道。

    【讨论】:

    • 感谢您的详尽介绍。
    • @Vladimir:空的String 由空的Vec 支持,doesn't allocate storage on the heap
    • result.push(c.to_lowercase()); 不能与 Rust 1.9 一起编译,因为 to_lowercase() 现在返回 std::char::ToLowercase
    猜你喜欢
    • 2019-08-27
    • 2014-10-01
    • 1970-01-01
    • 2012-06-29
    • 1970-01-01
    • 2014-07-02
    • 1970-01-01
    • 2014-11-22
    • 1970-01-01
    相关资源
    最近更新 更多