从零学Rust - 变量与数据类型

学习任何一门语言,都需要了解一些基本编程概念在该语言中的特点,首先来学习下变量和一些基础的数据类型。

变量与常量

程序中通过变量对状态进行维护,变量一般分为可变的变量,以及不可变的变量,后者我们一般称为常量。可变的变量为程序带来灵活性,常量为编程提供安全性。

变量 let & mut

使用 let 关键字声明和初始化一个变量 x,这个过程称为:变量绑定。

在 Rust 中通常用 绑定 而不是 赋值 来描述这个过程,与 Rust 的一个核心原则“所有权”有关:任何内存对象都是有主人的,而且一般情况下完全属于它的主人,绑定就是把这个对象绑定给一个变量,让这个变量成为它的主人。

fn main {
  // Rust 的变量在默认情况下是不可变的, 使用 let 关键字声明
  let x = 5;
  println!("The value of x is: {x}");
  // 二次赋值,会报错 cannot assign twice to immutable variable
  x = 10;
  println!("The value of x is: {x}");
}

Rust 中声明可变的变量要加个 mut 关键字。

fn main {
  let mut x = 5;
  println!("The value of x is: {x}");
  // 或 println!("The value of x is: {}", x);
  // 有多个值 println!("The value of x is: {}、{}", x, y);
  x = 10;
  println!("The value of x is: {x}");
}
// The value of x is: 5
// The value of x is: 10

未使用的变量会有警告,可以在前面加上一个下划线去掉报警。

fn main() {
    let _x = 5;
    let y = 10;
}
// help: if this is intentional, prefix it with an underscore: `_y`

常量 const

使用 const 关键字声明一个常量:

const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;

常量与 let 的区别

  • const 必须显式注明值的类型,const x = 100 报错:provide a type for the constant
  • 没有 const mut x = 1mut 不能用于 const,常量始终不可变
  • 常量可以在任何作用域中声明,包括全局作用域
  • 常量只能被设置为常量表达式,而不可以是其他任何只能在运行时计算出的值
  • 命名规范:常量 SCREAMING_SNAKE_CASE,局部变量 snake_case

隐藏 Shadowing

变量会隐藏(或者说遮蔽)前面声明的同名变量。

fn main() {
    // 用相同变量名称来隐藏一个变量,以及重复使用 let 关键字来多次隐藏
    let x = 5;
    let x = x + 1;
    {
        let x = x * 2;
        println!("The value of x in the inner scope is: {x}");
    }
    println!("The value of x is: {x}");
}
// The value of x in the inner scope is: 12
// The value of x is: 6// 在这里用花括号 { } 创建了一个局部作用域
// 在声明变量时也能通过 { } 初始化
let a = 100;
let b = {
    let c = 20;
    a + c // 这里没有分号 ;
}; // 这里有分号 ;

用处在于:如果你在某个作用域内无需再使用之前的变量,就可以重复的使用变量名字

隐藏与将变量标记为 mut 是有区别的:不能改变可变变量的类型

// ok
let spaces = "   ";
let spaces = spaces.len();
// 编译时错误
let mut spaces = "   ";
spaces = spaces.len(); // expected `&str`, found `usize`

数据类型

Rust 是 静态类型statically typed)语言,也就是说在编译时就必须知道所有变量的类型。

// 能够直接通过值和上下文中的使用方式推导出类型
let a = 34;
let b = a + 10;
// 编译器还不够智能!当类型可能有多种时,不能直接推导出,必须显式的增加类型注解
// “字符串”.parse() 将字符串解析为数字
let guess: u32 = "42".parse().expect("Not a number!");
// 不加则报错 error[E0282]: type annotations needed// 类型标注
let twenty_one: i32 = 21;
// 通过类型后缀的方式进行类型标注:22是i32类型
let twenty_two = 22i32;

Rust 中的数据类型可分为:标量类型(scalar)、复合类型(compound)

  • 标量类型(代表一个单独的值):数值类型(整型、浮点型)、布尔类型、字符类型
  • 两个原生的复合类型:元组、数组

还有一些其他的复合类型、集合类型等,这些类型在之后进行了解。

整型 integer

分为有符号(i)和无符号(u)两种,长度包括 8/16/32/64/128 比特。

例如:u8 代表0到 255,i8 代表 -128到127。

另外还有两种类型:isizeusize,依赖运行程序的计算机架构:64 位架构上它们是 64 位的,32 位架构上它们是 32 位的。数字类型默认是 i32isizeusize 主要作为某些集合的索引。

整型字面量的写法包括:

  • Decimal (十进制):98_222
  • Hex (十六进制):0xff
  • Octal (八进制):0o77
  • Binary (二进制):0b1111_0000
  • Byte (单字节字符)(仅限于u8):b'A'

其中数字里面可以加下划线方便读数,比如 121_000 其实就是 121000

当将变量修改为超出它定义的范围时,会出现整型溢出(integer overflow)

let mut a: u8 = 34;
a = 256;
// error: literal out of range for `u8`
// the literal `256` does not fit into the type `u8` whose range is `0..=255`

整数除法会向零舍入到最接近的整数:

 let truncated = -5 / 3; // 结果为 -1

浮点型 float

带小数点的数字,即浮点数的类型,包括 f32f64(默认),所有的浮点型都是有符号的。

let a: f32 = 23.23232323;
// 只有同样类型,才能进行加、减、乘、除、取余的数值运算
let b = a + 10; // cannot add `{integer}` to `f32`
let b = a + 10.0; // ok

浮点数采用 IEEE-754 标准表示。f32 是单精度浮点数,f64 是双精度浮点数。

布尔型 bool

truefalse,用 bool 表示,主要用在条件表达式内。

let t = true;
let f: bool = false; // 显式声明

字符类型 char

Rust 的 char 类型是语言中最原生的字母类型。

let c = 'z';
let z: char = 'ℤ'; // 显式声明
let heart_eyed_cat = '😻';

用单引号声明 char 字面量,使用双引号声明字符串字面量。

char 类型的大小为4个字节,并代表了一个 Unicode 标量值(包含从 U+0000U+D7FFU+E000U+10FFFF 在内的值)。有效的 char 值包括:带变音符号的字母、中文、日文、韩文等字符、emoji以及零长度的空白字符。

元组 tuple

元组是将多个其他类型的值组合进一个复合类型的主要方式,长度固定不可变。

// 用括号()包含多个类型进行定义,绑定值也是对应的结构
let tup: (i32, f64, u8) = (500, 6.4, 1); // ok// 必须一一对应,error[E0308]: mismatched types
// expected a tuple with 3 elements, found one with 2 elements
let tup: (i32, f64, u8) = (500, 6.4); 
​
// 可以嵌套
let tup: (i32, f64, u8, (u8, u8)) = (500, 6.4, 1, (2, 2));
​
// 使用模式匹配(pattern matching)来解构(destructure)元组值
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
​
// 可以解构为可变变量
let (x, mut y) = (true, false);
y = true;
assert_eq!(x, y);
​
// 使用 . 点 + 索引值来访问单个元素
let val = tup.0; // 500// error[E0609]: no field `3` on type `({integer}, {float}, {integer})`
let val = tup.3; 
​
// 不能通过数组下标的形式
// error[E0608]: cannot index into a value of type `({integer}, {float}, {integer})`
let val = tup[0]; 

数组 array

  • 每个元素的类型必须相同
  • 数组长度是固定的
  • 数组在栈(stack)而不是在堆(heap)上为数据分配空间
// 写在方括号内,逗号分隔
let a = [1, 2, 3];
// 显式的定义类型
let a: [i32, i32, i32] = [1, 2, 3]; // 错误 error[E0423]: expected value, found builtin type `i32`
// 正确的方式: [元素类型; 元素数量]
let a: [i32; 3] = [1, 2, 3];
// [初始值; 元素个数] 创建相同元素的数组
let a = [3; 5]; // 元素的长度为5,每个元素的值都是3// error[E0308]: mismatched types
// expected integer, found floating-point number
let a = [1, 2, 3.2]; // 每个元素的类型必须相同// error[E0277]: `[{integer}; 3]` doesn't implement `std::fmt::Display`
// `[{integer}; 3]` cannot be formatted with the default formatter
print!("{}\n", a); // 不能直接打印
print!("{:?}\n", a); // 正确方式 加上 :?

其他

单元类型

单元类型就是 () ,唯一的值也是 () 。它作为一个值用来占位,但是完全不占用任何内存。 比如常用的入口函数 fn main() 没有返回值。没有返回值的函数在 Rust 中是有单独的定义的:发散函数(diverge function),即无法收敛的函数,它返回的其实就是这个单元类型。

参考

全部评论

相关推荐

本科生是不是只能去送外卖了
有气魄的海豚在喝茶:外卖这个版本被保安克制
点赞 评论 收藏
分享
04-13 18:10
门头沟学院 Java
想熬夜的小飞象在秋招:被腾讯挂了后爸妈以为我失联了
点赞 评论 收藏
分享
牛客583549203号:腾讯还好,况且实习而已,实习生流动性很大,属于正常现象,记得和HR委婉解释
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务