Rust基本数据类型

[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
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,875评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,569评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,475评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,459评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,537评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,563评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,580评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,326评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,773评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,086评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,252评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,921评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,566评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,190评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,435评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,129评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,125评论 2 352

推荐阅读更多精彩内容