掘金 后端 ( ) • 2024-04-24 14:16

在 Rust 中,标准库并没有内建的 getchar() 函数,如果我们想要读取单个字符,必须借助标准库中的输入/输出功能,尤其是 std::io 模块。

常规操作

使用 Rust 进行安全地读取单个字符涉及到以下基本步骤:

引入 std::io 模块。

处理可能的输入错误。

读取并返回一个字符(可选择地限定为 Unicode 标量值)。

下面是一个 Rust 程序的例子,它会安全地从标准输入读取单个字符并输出它:

use std::io;
use std::io::Read;

fn main() -> io::Result<()> {
    let stdin = io::stdin(); // 获取标准输入的句柄
    let mut handle = stdin.lock(); // 锁定标准输入,提升性能

    let mut buffer = [0; 1]; // 创建一个缓冲区,用来存放读取的字节

    println!("Press any key:");

    // 读取一个字节到缓冲区
    handle.read_exact(&mut buffer)?;

    // 将字节转换为字符
    // 注意:这里没有考虑UTF-8,只是简单地将字节解释为ASCII字符
    let c = buffer[0] as char;

    // 确认它是有效的 ASCII 字符
    if c.is_ascii() {
        println!("You pressed: {}", c);
    } else {
        println!("Non-ASCII input is not supported.");
    }

    Ok(())
}

上面的代码片段使用了 read_exact 方法,该方法尝试从标准输入中读取足够的字节来填充整个缓冲区。

如果用户输入了多于一个字符的内容,这个方法只会读取第一个字节,剩余的输入将留在缓冲区中。请注意,上面的代码并不处理输入的字符是否为有效 UTF-8 字符,它只是简单地将输入的第一个字节转换为 char 类型,这只对 ASCII 输入有效。

如果需要处理非 ASCII 或完整的 Unicode 输入,需要采取不同的策略来正确地解码 UTF-8 序列。

为了解码可能的多字节 Unicode 字符,可以使用标准库中的 chars() 方法,它会处理 UTF-8 序列。不过这样做通常会读取更多的输入,而不只是一个字符。如果只需要安全且简单地读取一个标量值,可以使用外部的 crate,如 crossterm 或 termion,它们提供了跨平台的终端操作方式。

跨平台适配

Rust的std::io模块提供了跨平台的输入输出功能,这意味着你可以在不同的操作系统上使用相同的代码。

实验操作

Rust 可以通过 libc crate 调用 C 语言库的函数,包括 getchar。这个 crate 提供了 C 标准库的绑定,使得 Rust 代码可以与 C 代码交互。当你通过 libc crate 使用 getchar 时,实际上调用的是 C 语言的运行时库的这一函数。

使用 libc crate 中的 getchar 方法与 Rust 原生读取输入方法的主要区别如下:

安全性:

Rust 的标准库通常以安全性为优先。Rust 标准库的 I/O 方法会进行必要的错误处理和类型检查。 使用 libc 中的 getchar 则相当于提供了一个不安全的接口,因为 C 标准库的函数通常不做 Rust 风格的安全检查,这可能使得你的程序易于出错。

错误处理:

Rust 的原生方法通过返回 Result 类型的错误处理,明确地要求你处理可能出现的错误情况,这符合 Rust 的错误处理哲学。 而 getchar 函数的错误处理比较原始,通常只通过返回特殊值(比如 EOF)来指示错误或文件结束状态。

可移植性:

Rust 标准库设计用于跨平台工作,不管你的程序在哪个操作系统上运行,都可以使用相同的 Rust 代码进行标准输入/输出操作。 C 函数的行为可能因操作系统和环境的不同而异,尽管 getchar 函数在大多数环境下表现一致,但是在非 POSIX 兼容平台上可能需要特殊处理。

易用性和抽象级别:

Rust 的 IO 方法提供了许多方便的特性,如缓冲读取、字符串处理等。 直接调用 getchar 更为底层,不提供 Rust 中那些高级特性,并且需要你自己管理缓冲和错误处理。

以下是基于 libc crate 使用 getchar 的一个简单例子

extern crate libc;
use libc::getchar;

fn main() {
    println!("Press any key...");

    // 因为 getchar() 可能返回 EOF,通常需要把返回值存到一个更大的整数类型中
    let c: i32 = unsafe { getchar() }; // 用 unsafe 包裹因为调用了外部 C 函数

    if c != libc::EOF {
        // 输出这个字符,需要将其转换为 u8,假设输入都是 ASCII
        println!("You pressed: {}", c as u8 as char);
    } else {
        println!("End of file or error encountered.");
    }
}

在这个例子中,调用了 libc crate 提供的 getchar() 函数来从标准输入读取单个字符。unsafe 块是必要的,因为我们正在调用一个外部的 C 函数,而 Rust 不能保证这种操作的安全性。

在考虑使用 libc 版本的 getchar 与 Rust 原生方法时,需要基于应用的需求和对安全性的重视程度,决定是否接受 Rust 提供的额外安全性和便利性,或者接受调用 C 函数带来的底层访问与额外的风险。

疑问

在上述的 Rust 代码中,使用 libc::getchar 调用时返回的 c 是一个 i32 类型的整数。这个整数通常对应于标准输入中读取的下一个字符的 ASCII 码,如果输入是基于 ASCII 的。但是 getchar 也能够读取非 ASCII 字符,并返回相应的值,因为 ASCII 只占用了 0 到 127 的范围,而 getchar 的返回类型 i32 能够表示更广泛的字符集。

简而言之,当输入字符是 ASCII 字符时,getchar 返回的值确实是该字符的 ASCII 码。如果输入是扩展 ASCII 或其他编码(如 UTF-8 编码的 Unicode 字符),那么返回的码点会表示相对应的字符,但对于多字节字符可能并不适用。

另外,getchar 可以返回特殊的 EOF 值(在 C 和许多类 Unix 系统中通常是 -1),表示已达到输入流的末尾或发生读取错误。由于 EOF 的值在不同系统上可能有变化,并且 EOF 通常用它的负值来与正常的字符码点区分开,所以必须用一个比标准字符宽度更宽的类型(比如 i32 )来存储返回值。

当解释这个返回值时,如果需要将其作为字符处理,可以将其转换到 Rust 的 char 类型,但在此过程中需要确保它不是 EOF,并且如果对非 ASCII 字符集或 Unicode 进行操作,要考虑到字符编码问题。