当前位置:   article > 正文

Rust语言基础教程学习【一】(Rust基础语法、数据类型)_rust教程

rust教程

安装vscode环境

首先,需要安装最新版的 Rust 编译工具和 Visual Studio Code。

Rust 编译工具:安装 Rust - Rust 程序设计语言

Visual Studio Code:Download Visual Studio Code - Mac, Linux, Windows

初次编译时

遇到报错 error: linker `link.exe` not found

rustup toolchain install  stable-x86_64-pc-windows-gnu
rustup default stable-x86_64-pc-windows-gnu

执行上面两个命令。解决了

一、Rust基础语法

1.变量

        用let关键字声明变量,Rust具有自动判断变量类型的能力。

  1. let a = 111;
  2. a = "abc"; //错误,当声明 a 是111以后,a 就被确定为整型数字,不能把字符串类型的值赋给它。
  3. a = 4.56; //错误,自动转换数字精度有损失,Rust 语言不允许精度有损失的自动数据类型转换。

         Rust 语言为了高并发安全而做的设计:在语言层面尽量少的让变量的值可以改变。所以 a 的值不可变。但这不意味着 a 不是"变量"(英文中的 variable),官方文档称 a 这种变量为"不可变变量"。如果我们编写的程序的一部分在假设值永远不会改变的情况下运行,而我们代码的另一部分在改变该值,那么代码的第一部分可能就不会按照设计的意图去运转。由于这种原因造成的错误很难在事后找到。这是 Rust 语言设计这种机制的原因。

        Rust 编译器保证,如果声明一个值不会变,它就真的不会变,所以你不必自己跟踪它。这意味着你的代码更易于推导。

  1. /* 接上边代码 */
  2. a = 222; //错误,a不是一个可变变量

        如果想使变量可变(mutable)只需一个 mut 关键字。

  1. let mut a = 111;
  2. a = 222; // 正确,利用mut关键字后,可以改变变量的值

        是否让变量可变的最终决定权仍然在你,取决于在某个特定情况下,你是否认为变量可变会让代码更加清晰明了。

2.常量

        类似于不可变变量,常量 (constants) 是绑定到一个名称的不允许改变的值,不过常量与变量还是有一些区别。

        首先,不允许对常量使用 mut。常量不光默认不可变,它总是不可变。声明常量使用 const 关键字而不是 let,并且 必须 注明值的类型。

        在 Rust 中,以下程序是合法的:

  1. let a = 111;
  2. let a = 222;

        但是如果 a 是常量就不合法:

  1. const A_CONST: i32 = 111;
  2. let A_CONST = 222; //错误,常量的值不能改变
  3. A_CONST = 333; //错误,常量的值不能改变
  4. const b = 222; //错误,定义常量必须声明类型

        Rust 对常量的命名约定是在单词之间使用全大写加下划线。

        在声明它的作用域之中,常量在整个程序生命周期中都有效,此属性使得常量可以作为多处代码使用的全局范围的值,例如一个游戏中所有玩家可以获取的最高分或者光速。

        将遍布于应用程序中的硬编码值声明为常量,能帮助后来的代码维护人员了解值的意图。如果将来需要修改硬编码值,也只需修改汇聚于一处的硬编码值。

3.重影/隐藏(Shadowing

        我们可以定义一个与之前变量同名的新变量,称之为第一个变量被第二个变量隐藏了(shadowing),或者说重影了。这意味着当使用变量的名称时,编译器将看到第二个变量。实际上,第二个变量“遮蔽”了第一个变量,此时任何使用该变量名的行为中都会视为是在使用第二个变量,直到第二个变量自己也被隐藏或第二个变量的作用域结束。可以用相同变量名称来隐藏一个变量,以及重复使用 let 关键字来多次隐藏。

  1. fn main()
  2. {
  3. let x = 5;
  4. let x = x * 2;
  5. println!("The value of x is: {x}");
  6. let x = x * 2;
  7. println!("The value of x in the inner scope is: {x}");
  8. }

此段代码输出如下图:

         当使用 let 时,实际上创建了一个新变量,我们可以改变值的类型,并且复用这个名字。

  1. let s = "abcd";
  2. let s = s.len();

        以上代码是正确的,第一个变量s是字符串类型,第二个变量是数字类型,可以获得字符串的长度。

然而,如果尝试使用 mut,将会得到一个编译时错误,这个错误说明,我们不能改变变量的类型,如下所示:

  1. let mut s = "abcd";
  2. s = s.len(); //错误,我们不能改变变量的类型

 二、Rust数据类型

        在 Rust 中,每一个值都属于某一个 数据类型data type),这告诉 Rust 它被指定为何种数据,以便明确数据处理方式。我们将看到两类数据类型子集:标量(scalar)和复合(compound)。

        记住,Rust 是 静态类型statically typed)语言,也就是说在编译时就必须知道所有变量的类型。根据值及其使用方式,编译器通常可以推断出我们想要用的类型。当多种类型均有可能时,必须增加类型注解。比如当进行数据类型转换时,必须增加数据类型注解,如:

  1. /* 将整型数据类型转换成字符型 */
  2. let a: u32 = "42".parse().expect("Not a number!"); //正确
  3. let c = "42".parse().expect("Not a number!"); //错误,没用给出c的数据类型注解

 1. 标量类型

        标量scalar)类型代表一个单独的值。Rust 有四种基本的标量类型:整型、浮点型、布尔类型和字符类型。

1.1 整型

        整数 是一个没有小数部分的数字。表格 2-1 展示了 Rust 内建的整数类型。我们可以使用其中的任一个来声明一个整数值的类型。

                                                        表格 2-1: Rust 中的整型

长度有符号无符号
8-biti8        u8
16-biti16u16
32-biti32u32
64-biti64u64
128-biti128u128
archisizeusize

        每一个变量都可以是有符号或无符号的,并有一个明确的大小。有符号 和 无符号代表数字能否为负值,换句话说,这个数字是否有可能是负数(有符号数),或者永远为正而不需要符号(无符号数),有符号数以补码形式存储。

        每一个有符号的变量可以储存包含从 -(2^{n-1}- 1) 到 2^{n-1} - 1 在内的数字,这里 n 是变量使用的位数。所以 i8 可以储存从 -(2^{7}) 到 2^{7}- 1 在内的数字,也就是从 -128 到 127。无符号的变量可以储存从 0 到 2^{n} - 1 的数字,所以 u8 可以储存从 0 到 2^{8} - 1 的数字,也就是从 0 到 255。

         另外,isize 和 usize 类型依赖运行程序的计算机架构:64 位架构上它们是 64 位的,32 位架构上它们是 32 位的。

         可以使用表格 2-2 中的任何一种形式编写数字字面值。请注意可以是多种数字类型的数字字面值允许使用类型后缀,例如 57u8 来指定类型,指定该数值的数据类型为u8,值为57。同时也允许使用 _ 做为分隔符以方便读数,例如1_000,它的值与你指定的 1000 相同。

                                         表格 2-2: Rust 中的整型字面值

         

        那么该使用哪种类型的数字呢?如果拿不定主意,Rust 的默认类型通常是个不错的起点,数字类型默认是 i32isize 或 usize 主要作为某些集合的索引。

 整型溢出

        假如有一个u8类型的变量,它的值的范围为0~255,如果为其赋予一个超过这个范围的数值,将会发生整型溢出(“integer overflow” ),这会导致以下两种行为之一的发生。

        当在 debug 模式编译时,Rust 检查这类问题并使程序 panic,这个术语被 Rust 用来表明程序因错误而退出。

        使用 --release flag 在 release 模式中构建时,Rust不会检测会导致 panic 的整型溢出。相反发生整型溢出时,Rust 会进行一种被称为二进制补码 wrapping(two’s complement wrapping)的操作。简而言之,比此类型能容纳最大值还大的值会回绕到最小值,值 256 变成 0,值 257 变成 1,依此类推。程序不会 panic,不过变量可能也不会是你所期望的值。依赖整型溢出 wrapping 的行为被认为是一种错误。

    为了显式地处理溢出的可能性,可以使用这几类标准库提供的原始数字类型方法:

  • 所有模式下都可以使用 wrapping_* 方法进行 wrapping,如 wrapping_add
  • 如果 checked_* 方法出现溢出,则返回 None
  • 用 overflowing_* 方法返回值和一个布尔值,表示是否出现溢出
  • 用 saturating_* 方法在值的最小值或最大值处进行饱和处理

 1.2 浮点型

        Rust 也有两个原生的 浮点数floating-point numbers)类型,它们是带小数点的数字。Rust 的浮点数类型是 f32 和 f64,分别占 32 位和 64 位。默认类型是 f64,因为在现代 CPU 中,它与 f32 速度几乎一样,不过精度更高。所有的浮点型都是有符号的。

 1.3 布尔型

        正如其他大部分编程语言一样,Rust 中的布尔类型有两个可能的值:true 和 false。使用布尔值的主要场景是条件表达式。Rust 中的布尔类型使用 bool 表示。例如:

  1. let t = true;
  2. let f: bool = false;

 1.4 字符型

Rust 的 char 类型是语言中最原生的字母类型。char字符型用单引号表示,字符串用双引号表示。

  1. let c : char = 'a';
  2. let s : &str = "aaaa";

        Rust 的 char 类型的大小为四个字节 (four bytes),并代表了一个 Unicode 标量值(Unicode Scalar Value),这意味着它可以比 ASCII 表示更多内容。在 Rust 中,带变音符号的字母(Accented letters),中文、日文、韩文等字符,emoji(绘文字)以及零长度的空白字符都是有效的 char 值。Unicode 标量值包含从 U+0000 到 U+D7FF 和 U+E000 到 U+10FFFF 在内的值。不过,“字符” 并不是一个 Unicode 中的概念,所以人直觉上的 “字符” 可能与 Rust 中的 char 并不符合。

注意:由于中文文字编码有两种(GBK 和 UTF-8),所以编程中使用中文字符串有可能导致乱码的出现,这是因为源程序与命令行的文字编码不一致,所以在 Rust 中字符串和字符都必须使用 UTF-8 编码,否则编译器会报错。

1.5 数值运算

        Rust 中的所有数字类型都支持基本数学运算:加法、减法、乘法、除法和取余。整数除法会向下舍入到最接近的整数。

  1. fn main() {
  2. // addition
  3. let sum = 5 + 10; //结果为15
  4. // subtraction
  5. let difference = 95.5 - 4.3; //结果为91.2
  6. // multiplication
  7. let product = 4 * 30; //结果为120
  8. // division
  9. let quotient = 56.7 / 32.2; //结果为1.7608695652173911
  10. let truncated = -5 / 3; // 结果为 -1
  11. // remainder
  12. let remainder = 43 % 5; //结果为3
  13. }

        输出结果如下:

许多运算符号之后加上 = 号是自运算的意思,例如:

sum += 1 等同于 sum = sum + 1。

sum *= 1 等同于 sum = sum * 1。

注意:Rust 不支持 ++ 和 --,因为这两个运算符出现在变量的前后会影响代码可读性,减弱了开发者对变量改变的意识能力。

 2. 复合类型

        复合类型Compound types)可以将多个值组合成一个类型。Rust 有两个原生的复合类型:元组(tuple)和数组(array)。

2.1 元组类型

        元组是一个将多个其他类型的值组合进一个复合类型的主要方式。元组长度固定:一旦声明,其长度不会增大或缩小。

        我们使用包含在圆括号中的逗号分隔的值列表来创建一个元组,元组中的每一个位置都有一个类型,而且这些不同值的类型也不必是相同的。例如:

  1. let tup: (i32, f64, u8,&str) = (500, 6.4, 1,"ssss");
  2. let tup_a= (500, 6.4, 1,"ssss");//也是可以的

  tup 变量绑定到整个元组上,因为元组是一个单独的复合元素。为了从元组中获取单个值,可以使用模式匹配(pattern matching)来解构(destructure)元组值,像这样:

  1. let tup = (500, 6.4, 1, "ssss");
  2. let (x, y, z, w) = tup;
  3. println!("The value of x is: {x}");
  4. println!("The value of y is: {y}");
  5. println!("The value of z is: {z}");
  6. println!("The value of w is: {w}");

输出如下:

         程序首先创建了一个元组并绑定到 tup 变量上。接着使用了 let 和一个模式将 tup 分成了四个不同的变量,xy 、 z、w。这叫做 解构destructuring),因为它将一个元组拆成了四个部分。

        我们也可以使用点号(.)后跟值的索引来直接访问它们。例如:

  1. let tup: (i32, f64, u8,&str) = (500, 6.4, 1,"ssss");
  2. let x = tup.0;
  3. let y = tup.1;
  4. let z = tup.2;
  5. let w = tup.3;

输出结果和上边相同。

        这个程序创建了一个元组,x,然后使用其各自的索引访问元组中的每个元素。跟大多数编程语言一样,元组的第一个索引值是 0。

        不带任何值的元组有个特殊的名称,叫做 单元(unit) 元组。这种值以及对应的类型都写作 (),表示空值或空的返回类型。如果表达式不返回任何其他值,则会隐式返回单元值。

2.2 数组类型

        另一个包含多个值的方式是 数组array)。与元组不同,数组中的每个元素的类型必须相同。Rust 中的数组与一些其他语言中的数组不同,Rust 中的数组长度是固定的。

我们将数组的值写成在方括号内,用逗号分隔:

  1. let a = [1, 2, 3, 4, 5];
  2. let a: [i32; 5] = [1, 2, 3, 4, 5]; //这里,i32 是每个元素的类型。分号之后,数字 5 表明该数组包含五个元素。

当你想要在栈(stack)而不是在堆(heap)上为数据分配空间,或者是想要确保总是有固定数量的元素时,数组非常有用。但是数组并不如 vector 类型灵活。vector 类型是标准库提供的一个 允许 增长和缩小长度的类似数组的集合类型。当不确定是应该使用数组还是 vector 的时候,那么很可能应该使用 vector(在后续章节会学习到vector)。

        然而,当你确定元素个数不会改变时,数组会更有用。例如,当你在一个程序中使用月份名字时,你更应趋向于使用数组而不是 vector,因为你确定只会有 12 个元素。

  1. let months = ["January", "February", "March", "April", "May", "June", "July",
  2. "August", "September", "October", "November", "December"];

        还可以通过在方括号中指定初始值加分号再加元素个数的方式来创建一个每个元素都为相同值的数组:

let a = [3; 5];

        变量名为 a 的数组将包含 5 个元素,这些元素的值最初都将被设置为 3。这种写法与 let a = [3, 3, 3, 3, 3]; 效果相同,但更简洁。

        数组是可以在栈 (stack) 上分配的已知固定大小的单个内存块。可以使用索引来访问数组的元素:

  1. let a = [1, 2, 3, 4, 5];
  2. let first = a[0];
  3. let second = a[1];
  1. let a = [1, 2, 3, 4, 5];
  2. a[0] = 123; // 错误:数组 a 不可变
  3. let mut a = [1, 2, 3];
  4. a[0] = 4; // 正确

        当尝试用索引访问一个元素时,Rust 会检查指定的索引是否小于数组的长度。如果索引超出了数组长度,Rust 会 panic,这是 Rust 术语,它用于程序因为错误而退出的情况。这种检查必须在运行时进行,特别是在这种情况下,因为编译器不可能知道用户在以后运行代码时将输入什么值。这样当提供了一个不正确的索引时,就会访问无效的内存。通过立即退出而不是允许内存访问并继续执行,Rust 让你避开此类错误。

Rust语言基础教程学习其他章节【未完待续】:

Rust语言基础教程学习【二】:Rust语言基础教程学习【二】(函数、条件语句、循环)_LLLL、的博客-CSDN博客

Rust语言基础教程学习【三】: Rust语言基础教程学习【三】(所有权)_LLLL、的博客-CSDN博客

Rust语言基础教程学习【四】:Rust语言基础教程学习【四】(结构体)_LLLL、的博客-CSDN博客

Rust语言基础教程学习【五】: Rust语言基础教程学习【五】(枚举和模式匹配)_LLLL、的博客-CSDN博客

Rust语言基础教程学习【六】:Rust语言基础教程学习【六】(包、Crate、模块)_LLLL、的博客-CSDN博客

Rust语言基础教程学习【七】: Rust语言基础教程学习【七】(常见集合)_LLLL、的博客-CSDN博客

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/465722
推荐阅读
相关标签
  

闽ICP备14008679号