美文网首页
Rust基本数据类型

Rust基本数据类型

作者: 端碗吹水 | 来源:发表于2022-06-09 09:47 被阅读0次

    [TOC]

    Rust基本数据类型


    类型系统概述

    什么是类型?类型是对二进制数据的一种约束行为。类型比起直接使用二进制数据,有许多优势:

    • 减少开发者心智负担
    • 安全
    • 容易优化

    常见的类型分类:

    • 静态类型:在编译期对类型进行检查
    • 动态类型:在运行期对类型进行检查
    • 强类型:不允许隐式类型转换
    • 弱类型:允许进行隐式类型转换

    C 语言由于允许隐式类型转换因此是静态弱类型语言,许多人易将 C 语言误认为静态强类型,需要特别注意:

    int main() {
        long a = 10;
        return a;
    }
    
    • Rust 是静态强类型语言

    变量和可变性

    创建和使用变量

    在 Rust 代码中,可以使用 let 关键字将值绑定到变量:

    fn main() {
        let x = 5;
        println!("The value of x is: {}", x);
    }
    

    println 是一个宏,它是最常用的将数据打印在屏幕上的方法。目前,我们可以简单地将它视为一个拥有可变参数数量的函数,在后面的章节中我们会对宏进行详细的讨论。

    可变性

    在 Rust 中,变量默认是不可变的,一旦一个值绑定到一个名称,就不能更改该值:

    fn main() {
        let x = 5;
        println!("The value of x is: {}", x);
        x = 6;  // cannot assign twice to immutable variable `x`
        println!("The value of x is: {}", x);
    }
    

    但有时候允许变量可变是非常有用的。通过在变量名前面添加 mut 来使它们可变:

    fn main() {
        let mut x = 5;
        println!("The value of x is: {}", x);
        x = 6;
        println!("The value of x is: {}", x);
    }
    

    常量和变量

    不可变变量容易让你联想到另一个概念:常量。在 Rust 中,常量使用 const 定义,而变量使用 let 定义:

    • 不允许对常量使用修饰词 mut,常量始终是不可变的
    • 必须显示标注常量的类型
    • 常量可以在任何作用域中声明,包括全局作用域
    • 常量只能设置为常量表达式,而不能设置为函数调用的结果或只能在运行时计算的任何其他值
    const A_CONST: i32 = 1;
    

    隐藏(Shadowing)

    可以声明一个与前一个变量同名的新变量,并且新变量会隐藏前一个变量,这种操作被称为隐藏(Shadowing)。

    fn main() {
        let x = 5;
    
        let x = x + 1;
    
        let x = x * 2;
    
        println!("The value of x is: {}", x);
    }
    

    基础数据类型

    Rust 是一门静态编程语言,所有变量的类型必须在编译期就被明确固定。

    整数

    Rust 中有 12 种不同的整数类型:

    长度 有符号 无符号
    8-bit i8 u8
    16-bit i16 u16
    32-bit i32 u32
    64-bit i64 u64
    128-bit i128 u128
    arch isize usize
    • 对于未明确标注类型的整数,Rust 默认采用 i32.
    • isize 和 usize 根据系统的不同而有不同的长度.

    浮点数

    Rust 有两种浮点数类型,为 f32f64,后者精度更高。对于未明确标注类型的小数,Rust 默认采用 f64.

    fn main() {
        let x = 2.0; // f64
    
        let y: f32 = 3.0; // f32
    }
    

    布尔值

    与大多数其他编程语言一样,Rust 中的布尔类型有两个可能的值:truefalse。布尔值的大小是一个字节。

    fn main() {
        let t = true;
    
        let f: bool = false;
    }
    

    字符

    Rust 支持单个字符,字符使用单引号包装。

    fn main() {
        let c = 'z';
        let z = 'ℤ';
        let heart_eyed_cat = '😻';
    }
    

    作业: 求两个无符号数的平均数

    编写一个函数,它接收两个 u32 类型参数并返回它们的平均数。

    fn avg(a: u32, b: u32) -> u32 {
        // 补充你的代码
        (a & b) + ((a ^ b) >> 1)
    }
    
    • 提示:必须考虑整数溢出问题。

    一些有用的测试用例:

    fn main() {
        assert_eq!(avg(4294967295, 4294967295), 4294967295);
        assert_eq!(avg(0, 0), 0);
        assert_eq!(avg(10, 20), 15);
        assert_eq!(avg(4294967295, 1), 2147483648);
        println!("passed")
    }
    

    整数溢出

    在电脑领域里所发生的溢出条件是,运行单项数值计算时,当计算产生出来的结果是非常大的,大于寄存器或存储器所能存储或表示的能力限制就会发生溢出。

    在不同的编程语言中,对待溢出通常有以下几种不同的做法:

    • 崩溃:当溢出被侦测到时,程序立即退出运行
    • 忽略:这是最普遍的作法,忽略任何算数溢出

    对于溢出的处理方法,Rust 在 debug 与 release 模式下是不同的。在 debug 模式下编译时,Rust 会检查整数溢出,如果发生这种行为,会导致程序在运行时终止并报出运行时错误。而如果在 release 模式下编译时,Rust 不会对整数溢出进行检查。

    要显式处理溢出,可以使用标准库提供的一些 .overflowing_* 方法:

    fn main() {
        let a: u32 = 4294967295;
        let b: u32 = 1;
    
        let (r, is_overflow) = a.overflowing_add(b);
        println!("r={} is_overflow={}", r, is_overflow);
    }
    

    元组

    元组是将多个具有各种类型的值组合成一个复合类型的通用方法。元组有固定的长度:一旦声明,它们的大小就不能增长或收缩。

    我们通过在括号内写一个逗号分隔的值列表来创建一个元组。元组中的每个位置都有一个类型,元组中不同值的类型不必相同。

    fn main() {
        let a: i32 = 10;
        let b: char = 'A';
    
        // 创建一个元组
        let mytuple: (i32, char) = (a, b);
    
        // 从元组中读取一个值
        println!(".0={:?}", mytuple.0);
        println!(".1={:?}", mytuple.1);
    
        // 解封装
        let (c, d) = mytuple;
        println!("c={} d={}", c, d);
    }
    

    数组

    另一种拥有多个数据集合的方法是使用数组。与元组不同,数组中的每个元素都必须具有相同的类型。Rust 中的数组不同于其他一些语言中的数组,Rust 中的数组具有固定长度。

    数组下标以 0 开始,同时 Rust 存在越界检查:

    fn main() {
        // 创建数组, [i32; 3] 是数组的类型提示, 表示元素的类型是 i32, 共有 3 个元素
        let myarray: [i32; 3] = [1, 2, 3];
    
        // 根据索引获取一个值, 数组下标从 0 开始
        println!("{:?}", myarray[1]);
    
        // 索引不能越界
        println!("{:?}", myarray[3]);
    
        // 如果数组的每个元素都有相同的值, 我们还可以简化数组的初始化
        let myarray: [i32; 3] = [0; 3];
        println!("{:?}", myarray[1]);
    }
    

    切片类型

    切片类型是对一个数组(包括固定大小数组和动态数组)的引用片段,有利于安全有效地访问数组的一部分,而不需要拷贝数组或数组中的内容。切片在编译的时候其长度是未知的,在底层实现上,一个切片保存着两个 uszie 成员,第一个 usize 成员指向切片起始位置的指针,第二个 usize 成员表示切片长度:

    fn main() {
        let arr: [i32; 5] = [1, 2, 3, 4, 5];
        let slice = &arr[0..3]; // 取前 3 个元素,.. 是 Rust Range 语法,& 是引用符号
        println!("slice[0]={}, len={}", slice[0], slice.len());
    }
    

    结构体

    结构体是多种不同数据类型的组合。它与元组类似,但区别在于我们可以为每个成员命名。可以使用 struct 关键字创建三种类型的结构:

    • 元组结构
    • 经典的 C 结构
    • 无字段的单元结构

    结构体使用驼峰命名:

    // 元组结构
    struct Pair(i32, f32);
    
    // 经典的 C 结构
    struct Person {
        name: String,
        age: u8,
    }
    
    // 无字段的单元结构, 在泛型中较为常用
    struct Unit;
    
    fn main() {
        // 结构体的实例化
        let pair = Pair(10, 4.2);
        let person = Persion {
            name: String::from("jack"),
            age: 21,
        };
        let unit = Unit;
    
        // 从结构体中获取成员
        println!("{}", pari.0);
        println!("{}", persion.name);
    }
    

    枚举

    enum 关键字可创建枚举类型。枚举类型包含了取值的全部可能的情况。在 Rust 中,有多种不同形式的枚举写法。

    无参数的枚举

    enum Planet {
        Mars,
        Earth,
    }
    

    上面的代码定义了枚举 Planet,包含了两个值 Mars 和 Earth。

    带枚举值的枚举

    enum Color {
        Red = OxffOOOO,
        Green = OxOOffOO,
        Blue = OxOOOOff,
    }
    

    带参数的枚举

    Rust 还支持携带类型参数的枚举:

    enum IpAddr {
        IPv4(u8, u8, u8, u8),
        IPv6(u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8),
    }
    

    模式匹配

    枚举通常与 match 模式匹配一起使用:

    enum IpAddr {
        IPv4(u8, u8, u8, u8),
        IPv6(u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8),
    }
    
    fn main() {
        let localhost: IpAddr = IpAddr::IPv4(127, 0, 0, 1);
        match localhost {
            IpAddr::IPv4(a, b, c, d) => {
                println!("{} {} {} {}", a, b, c, d);
            }
            _ => {} // 任何非 IPv4 类型走这条分支
        }
    }
    

    各种注释类型

    与许多现代语言一样,Rust 也支持丰富的注释种类,我们可以通过注释来了解一段代码干了什么工作,甚至可以直接通过注释生成文档。

    普通的注释

    // 使用 // 注释单行
    
    /*
    也可以使用 /* */ 注释多行, 这一点与 C 语言是一样的
    */
    

    文档注释

    文档注释是一种 Markdown 格式的注释,用于对文档中的代码生成文档。可以使用 cargo doc 工具生成 HTML 文挡。

    //! 这是模块级别的文档注释, 一般用于模块文件的头部
    
    /// 这是文档注释, 一般用于函数或结构体的说明, 置于说明对象的上方.
    struct Person;
    

    例子

    下面的代码演示了斐波那契函数及其注释,使用 cargo doc 构建 HTML 文档:

    //! A main project provides fibonacci function
    
    /// In mathematics, the Fibonacci numbers, commonly denoted Fn form a sequence, called the Fibonacci sequence, such that
    /// each number is the sum of the two preceding ones, starting from 0 and 1. That is
    /// ```
    /// F(0) = 0
    /// F(1) = 1
    /// F(n) = F(n − 1) + F(n − 2)
    /// ```
    fn fibo(n: u32) -> u32 {
        if n== 0 || n == 1 {
            n
        } else {
            fibo(n - 1) + fibo(n - 2)
        }
    }
    
    fn main() {
        // Calculate fibo(10)
        println!("fibo(10) = {}", fibo(10));
        /*
        The result should be 55
        */
    }
    
    image.png

    println函数

    println! 用于将数据打印到标准输出,且在数据末尾自动带上换行符。在所有平台上,换行符都是换行符(没有额外的回车符)。

    使用 println! 用于程序的正常输出,使用 eprintln! 打印错误或者进度条。前者数据被写入 stdout,后者则是 stderrprintln! 宏常用的格式化语法如下所示:

    fn main() {
        // `{}` 会被变量内容替换, 这是最常见的一种用法
        println!("{}", 42);
    
        // 可以使用额外的位置参数.
        println!("{0}{1}{0}", 4, 2);
    
        // 使用命名参数.
        println!("name={name} age={age}", name="jack", age=6);
    
        // 可以在 `:` 后面指定特殊的格式.
        println!("{} of {:b} people know binary, the other half don't", 1, 2);
    
        // 可以按指定宽度来右对齐文本.
        println!("{number:>width$}", number=1, width=6);
    
        // 在数字左边补 0.下面语句输出 "000001".
        println!("{number:>0width$}", number=1, width=6);
    
        // println! 会检查使用到的参数数量是否正确.
        println!("My name is {0}, {1} {0}", "Bond");
        // 编译将会报错, 请补上漏掉的参数:"James"
    }
    

    在不同类型之间转换

    Rust 是一门强类型语言,因此不支持隐式类型转换。Rust 为了实现类型之间的转换提供了几种不同的方法。

    as 语法

    as 语法是 Rust 最基础的一种类型转换方法,它通常用于整数,浮点数和字符数据之间的类型转换:

    fn main() {
        let a: i8 = -10;
        let b = a as u8;
        println!("a={} b={}", a, b);
    }
    

    数值转换的语义是:

    • 两个相同大小的整型之间(例如:i32 -> u32)的转换是一个 no-op
    • 从一个大的整型转换为一个小的整型(例如:u32 -> u8)会截断
    • 从一个小的整型转换为一个大的整型(例如:u8 -> u32)会
      • 如果源类型是无符号的会补零(zero-extend)
      • 如果源类型是有符号的会符号(sign-extend)
    • 从一个浮点转换为一个整型会向 0 舍入
    • 从一个整型转换为一个浮点会产生整型的浮点表示,如有必要会舍入(未指定舍入策略)
    • 从 f32 转换为 f64 是完美无缺的
    • 从 f64 转换为 f32 会产生最接近的可能值(未指定舍入策略)

    transmute

    as 只允许安全的转换,例如会拒绝例如尝试将 4 个字节转换为一个 u32

    let a = [0u8, 0u8, 0u8, 0u8];
    let b = a as u32; // Four u8s makes a u32.
    

    但是我们知道 u32 在内存中表示为 4 个连续的 u8,因此我们可以使用一种危险的方法:告诉编译器直接以另一种数据类型对待内存中的数据。编译器会无条件信任你,但是,除非你知道自己在干什么,不然并不推荐使用 transmute。要使用 transmute,需要将代码写入 unsafe 块中:

    fn main() {
        unsafe {
            let a = [0u8, 1u8, 0u8, 0u8];
            let b = mem::transmute::<[u8; 4], u32>(a);
            println!("{}", b); // 256
            // Or, more concisely:
            let c: u32 = mem::transmute(a);
            println!("{}", c); // 256
        }
    }
    

    相关文章

      网友评论

          本文标题:Rust基本数据类型

          本文链接:https://www.haomeiwen.com/subject/tfmumrtx.html