[Rust入门]Rust 基本数据类型

本教程环境:
系统:MacOS
Rust 版本:1.77.2

Rust 的类型系统是它语言设计中最核心的部分之一。通过在编译时执行严格的类型检查来提供安全保障,帮助程序员避免常见的错误,例如空指针、解引用或类型不匹配等。以下是 Rust 类型系统的一些核心概念:

  • 强类型 意味着每个值都必须具有一个确切的类型,而且这个类型是在编译时已经确定的。如果代码试图对两个不同类型的值进行操作(除非明确使用了类型转换),编译器将报错。
  • 静态类型 类型检查在编译时进行,一切类型错误都会编译时捕。

同时,Rust 有以下两个特性可以让上述的这些工作变得轻松。

  • 类型推断
  • 泛型。

下表是 Rust 类型的总结。

类型说明
i8i16i32i64i128u8u16u32u64u128给定位宽的有符号整数和无符号整数4-5i80x400u160o100i1620_922_789_888_000u64b'*'(u8 字节字面量)
isizeusize与机器字(32位或64位)一样大的有符号整数和无符号整数
f32f64单精度 IEEE 浮点数、双精度 IEEE 浮点数
bool布尔值truefalse
charUnicode 字符,32 位宽(4字节)'x' '\\n'
(char, i8, bool)元组,允许混合类型
()单元元组、空元组
struct S { x: f32, y: f32 }具名字段型结构体
struct T(i32, char)元组结构体
struct E;单元结构体
enum Attend { OnTime, Late(u32) }枚举
Box<Attend>Box:指向堆中值的拥有型指针
&i32&mut i32共享引用和可变引用;非拥有型指针,其声明周期不能超出引用目标
StringUTF-8 字符串,动态分配大小
&strstr 的引用:指向 UTF-8 文本的非拥有型指针
[f64; 8]数组,固定长度,其元素类型都相同
Vec<f64>向量,可变长度,其元素类型都相同vec![1, 2, 3, 4]
&[u8]&mut [u8]对切片(数组或向量的某一部分)的引用,包含指针和长度&v[10..20]&mut a[..]
Result<u64, Error>可能失败的操作结果:成功值 Ok(v);失败值 Err(e)
Option<&str>可选值:没有值,取值为 None;有值,取值为 Some(v)
&dyn any&mut dyn Read特型对象:对任何实现了一组给定方法的值的引用value as &dyn Any&mut file as &mut dyn Read
fn(&str) -> bool函数指针str::is_empty
(闭包没有显式的书写形式)闭包&#124; a, b &#124; a * a + b * b

整型

无符号整型 - u8u16u32u64u128isizeu8 的取值范围 0 ~ 2^8 - 1;其他的采用相同的方法计算。
有符号整型 - i8i16i32i64i128usizei8 的取值范围 -2^7 ~ 2^7 -1; 其他的采用相同的方法计算。
isizeusize 依赖与计算机架构,64 位架构是 64 位,32 位架构是 32 位的。
要获取整型类型的取值范围可以使用标准库中的 MINMAX 常量。

// 返回 u8 类型的取值范围
println!("u8 range: {} ~ {}", u8::MIN, u8::MAX); 
// u8 range: 0 ~ 255

u8 常作为**字节值。**例如,从文件或网路中读取的二进制数据是 u8 构成的流。
Rust 中数组索引是 usize类型的值。表示数组或向量大小,或者某些数据结构中元素数量,通常也使用 usize

整型字面量

116i80xcafu320b0010_10100o106 都是整型字面量。
前缀 0x0o0b分别表示十六进制、八进制、二进制。

字节字面量

在 Rust 中,字节字面量表示单个字节的值。可以用它们更加方便的表示 u8 类型的值。
字节字面量写作 b'x'x 可以是任何 ASCII 字符或转义字符。
例如,A 的 ASCII 码为 65,字面量 b'A'65u8 完全等效。

// 字节字面量
#[allow(non_snake_case)] // 去除警告
let A = b'A';
println!("A is {}", A); // A is 65

对于难以书写或阅读的字符,可将其编码改为十六进制。形如 b'\xHH',其中 HH 是任意两位十六进制数。可以将 ASCII 控制字符 escape 的字节字面量写成 b'\x1b',因为 escape 的 ASCII 码为 27,即十六进制的 1b

整型之间转换

在 Rust 中,整型之间不能进行隐式的转化,必须进行显式的转换

  1. 使用 as 进行转换。超出取值范围时,转换会导致数据丢失。
// 整型之间的转换
let a: i32 = 300;
let b: u8 = a as u8;
let c: i64 = a as i64;

println!("i32: {}", a); // 300
println!("u8: {}", b);  // 44 转换为 u8 类型,只有值在 u8 的范围内才安全
println!("i64: {}", c); // 300
  1. 使用 try_from()try_into() 方法进行尝试,转换失败时,返回错误。
    • try_from() 定义在 std::convert::TryFrom trait 中**,尝试将一个类型转化为另一个类型。**
    • try_into() 定义在 std::convert::TryInto trait 中,尝试将一个类型转化为另一个类型。
let d: i32 = 300;
// try_from
let f = u32::try_from(d).expect("数值超出了 u8 的范围");
println!("f: {}", f); // f: 300
let e = u8::try_from(d).expect("数值超出了 u8 的范围");
println!("e: {}", e); // 数值超出了 u8 的范围: TryFromIntError(())

// try_into 
let g: u32 = d.try_into().expect("数值超出了 u8 的范围");
println!("g: {}", g); // g: 300
let h = u8::try_from(d).expect("数值超出了 u8 的范围");
println!("h: {}", h); // 数值超出了 u8 的范围: TryFromIntError(())

整型的一些常用方法

标准库提供了一些计算方法:

assert_eq!(2_u16.pow(4), 16);            // 求幂
assert_eq!((-4_i32).abs(), 4);           // 求绝对值
assert_eq!(0b101101_u8.count_ones(), 4); // 求二进制1的个数”

整型溢出处理

在调试时,整型运算溢出时会出现 panic
发布构建中,运算溢出默认会回绕。 如果这种默认行为不是你想要的,可以使用一些其他方法。
主要分为如下四大类。

检查运算

返回结果的 Option 值。方法调用添加 checked_ 前缀。

// 10与20之和可以表示为u8
assert_eq!(10_u8.checked_add(20), Some(30));

// 很遗憾,100与200之和不能表示为u8
assert_eq!(100_u8.checked_add(200), None);

// 做加法。如果溢出,则会出现panic
let sum = x.checked_add(y).unwrap();

回绕运算

默认行为。 调用的方法添加 wrapping_ 前缀。

饱和运算

饱和运算的 结果“紧贴着”该类型可表达的最大值和最小值 。调用的方法添加 saturating_ 前缀。

溢出运算

会返回一个元组 (result, overflowed),其中 result 是函数的回绕版本所返回的内容,而 overflowed 是一个布尔值,指示是否发生过溢出。调用的方法添加 overflowing_ 前缀。

浮点类型

Rust 提供了 IEEE 单精度浮点类型(f32)和双精度浮点类型(f64)。默认选择 f64
f32 类型和 f64 类型具有一些特殊值的关联常量:正无穷大(INFINITY)、负无穷大(NEG_INFINITY)、非数值(NAN)、最小有限值(MIN)、最大有限值(MAX)。
std::f32::consts 模块和 std::f64::consts 模块提供了各种常用的数学常量,比如 EPI 和 2 的平方根。

类型转换

可使用 as 关键字转换。或 try_fromtry_intointo 等。

let f1: f64 = 1.03;
let f2: f32 = 1.03;
assert_eq!(f1 as f32, f2); // 通过
assert_eq!(f1, f2.into()); // assertion `left == right` failed

一些处理函数

  • round() - 四舍五入到最接近的整数;
  • ceil() - 将上取整,得到不小于浮点数的最小整数。
  • floor() - 将下取整,得到不大于浮点数的最大整数。
  • trunc() - 仅保留整数部分。
  • fract() - 仅保留小数部分。

布尔类型 - bool

Rust 的布尔类型(bool),具有两个值 truefalse
Rust 中不能将其他类型隐式的转换为布尔类型。所以,条件或循环语句中的条件必须是 bool 表达式。
as 运算符可将布尔类型转换为整型;反之不行。

字符 - char

字符是 char 类型,会以 32 位值(4字节)表示单个 Unicode 字符。使用单引号创建 char 字面量。

// char
#[allow(unused_variables)]
fn main() {
    let letter: char = 'a';   // 英文字母
    let number: char = '1';   // 数字字符
    let symbol: char = '$';   // 符号
    let space: char = ' ';    // 空格
    let emoji: char = '😂';  // 表情符号
    let chinese: char = '中'; // 中文字符

    // 一些 char 类型的操作
    println!("{} is alphabetic: {}", letter, letter.is_alphabetic()); // a is alphabetic: true
    println!("{} is digit: {}", number, number.is_digit(10));         // 1 is digit: true
    println!("{} to uppercase: {}", letter, letter.to_uppercase()); // a to uppercase: A
}

可以使用 aschar 转换为整型;如果转换的数值类型小于 32 位,高位会截断。
u8 是唯一能通过 as 运算符转换为 char 的类型。
标准库函数 std::char::from_u32 可以接受任何 u32 值并返回一个 Option<char>:如果此 u32 不是允许的 Unicode 码点,那么 from_u32 就会返回 None,否则,它会返回 Some(c),其中 c 是转换成 char 后的结果。

元组

元组是各种类型的值对,例如二元组、三元组等等。所以也叫做 n 元组。不同长度的元组类型不同。
元组的每个元素类型可不同。只允许通过常量下标来获取,例如元组 a,要获取第三个元素需要通过 a.2 获取。
元组通常用来从一个函数返回多个值。 例如,字符串切片的 split_at 方法将字符串分成两半,并返回一个元组。可以使用模式匹配的方法将返回值的每个元素赋值给不同的变量。

// 元组
#[allow(unused_variables)]
fn main() {
    let temp = (); // 零元组
    let one = (100, );
    let two = (100, 200);
    println!("{}", two.0); // 100

    let text = "Hello World";
    // 元组解构
    let (hello, world) = text.split_at(5);
    println!("hello: {}, world: {}", hello, world); // hello: Hello, world:  World
}

元组类型的零元组是 ()。不返回值的函数的返回类型是 ()
如果元组只有一个元素,那么必须在后面添加逗号,例如 (100, )

指针类型

Rust 有多种表示内存地址的类型。

引用

&String 是对 String 值的引用。&i32 是对 i32 值的引用。以此类推。
表达式 &x 会生成一个对 x 的引用。给定一个引用 r,表达式 *r 会引用 r 指向的值。
Rust 的引用有两种形式:

  • &T 不可变共享引用。只读。可以同时拥有多个给定值的共享引用。
  • &mut T 可变的独占引用。可以读取和修改它指向的值。如果该引用存在,就不能对该值有任何其他类型的引用。
// 引用
fn main() {
    let mut a = 10;
    let b = &a;
    println!("b: {}", b); // 10
    println!("a: {}", a); // 10

    let c = &mut a;
    *c = 11; 
    println!("c: {}", a); // 11
}

智能指针

智能指针是一个数据结构,它除了提供对数据的访问之外,还包含了额外的元数据和功能。在 Rust 中,智能指针通常通过实现 DerefDrop trait 来提供这些额外的能力。智能指针的核心特性是它能自动管理内存,确保代码安全和高效地运行。
Rust 标准库中几种常用的智能指针如下。

  • Box<T> 一个在堆上分配内存的智能指针。它拥有它指向的数据,在离开作用域时自动清理。
  • Rc<T> 一个引用计数的智能指针,允许数据有多个所有者。它主要用于单线程场景下的数据共享。
  • Arc<T>Rc<T> 的线程安全版本,适用于多线程场景。
  • Cell<T>RefCell<T> 提供内部可变性,即使在拥有不可变引用的情况下也可以改变所包含的值。

裸指针

  • 不可变原始指针 (*const T): 可以多次复制,并且可以并发读取指向的数据,但无法保证指向的内存是有效的。
  • 可变原始指针 (*mut T): 它提供了一个可改变的内存地址,不过使用这种类型需要特别注意,因为存在潜在的数据竞争问题。

原始指针不在 Rust 的安全抽象之内,使用它们需要 unsafe 代码块,因为编译器无法保证其安全性。它们通常用于与 C 代码的交互,或者执行一些底层的内存操作。

数组、向量、切片

数组

数组的类型为 [T; N],每个值都是 T 类型,数组的长度是 N。数组是编译期确定的常量。数组的长度是类型的一部分。数组不能追加新元素或缩小。
编写数组的方法:

let a: [i32; 3] = [1, 2, 3];
let b = ["a", "b", "c"];
a[0] // 1
a.len() // 3

数组本身没有提供诸如遍历、搜索、排序等方法,这些都是切片提供的方法。 但是在写的时候,数组可以调用这些方法,是因为 Rust 会隐式的创建一个引用整个数组的切片,然后使用这个切片进行这些操作。例如:

let mut chaos = [3, 5, 4, 1, 2];
chaos.sort();
assert_eq!(chaos, [1, 2, 3, 4, 5]);

向量

Vec<T> 类型可称为 T 的向量。是一个动态分配且可增长的 T 类型的值序列。向量的元素存在于堆中,所以可以随意调整向量的大小。
向量有一下几个关键信息:

  • 有一个指向堆上数组开始位置的指针;
  • 向量的长度,即当前向量持有的元素数量;
  • 向量的容量,即堆上分配的内存能容纳的元素数量。

当堆上的缓冲区达到其最大容量时,往向量中添加另一个元素需要分配一个更大的缓冲区,将当前内容复制到其中,更新向量的指针和容量以指向新缓冲区,最后释放旧缓冲区。
创建向量方式很多。最简单使用 vec! 宏来创建向量。

let v = vec![2, 3, 5, 7];
v.push(8); // 添加元素

let repeat_v = vec![0; 4] // [0, 0, 0, 0]  

let vec_v = Vec::new(); // 创建空向量。
vec_v.push('a');

// 从迭代器生成的值构建一个向量
let v: Vec<i32> = (0..5).collect(); // 使用 collect 时候,通常要指定类型

和数组一样,可以对向量使用切片的方法。

let mut v = vec![1, 2, 3, 4];
v.reverse();

如果事先知道向量所需的元素数量,可使用 Vec::with_capacity 来创建。这样创建的缓冲区足够大,一开始就可容纳所有元素。vec! 宏就使用了这个技巧。
len() 方法返回向量包含的元素数。
capacity() 方法返回不重新分配下的课容纳的元素数量。
可以在向量的任意位置删除或插入元素。这个操作会影响之后的元素,它们需要向前或向后移动。所以,如果向量越长,操作越慢。

let mut v = vec!(1, 2, 3, 5, 6);
v.insert(3, 4); // 插入元素
v.remove(1); // 删除索引为1的元素
v.pop(); // 移除最后一个元素并返回

for item in v {
    println!("{}", item);
}

切片

切片([T]),是数组或向量中的一个区域。
类型 &[T]&mut [T] 可称为 T 的共享切片T 的可变切片。切片是对数组或向量的一部分值的引用。
对切片的引用是一个胖指针:一个双字值,包括指向切片第一个元素的指针和切片中元素的数量。

let v: Vec<f64> = vec![0.0, 0.707, 1.0, 0.707];
let a: [f64; 4] = [0.0, -0.707, -1.0, -0.707];

let sv: &[f64] = &v;
let sa: &[f64] = &a;

截屏2023-12-04 11.37.47.png

普通引用是指向单个值的非拥有型指针。对切片的引用是指向内存中一系列连续值的非拥有型指针
如果要对数组或向量进行操作,使用切片是不错的选择。

fn print(n: &[f64]) {
	for elt in n {
        println!("{}", elt);
    }
}

可以使用范围值对数组或向量进行索引,来获取一个切片的引用。

print(&v[0..2]);
print(&v[2..]);
print(&sv[1..3]);

字符串

str

在 Rust 中 str 是不定长字符串切片类型,它是一种不拥有所有权的类型。不能直接声明。因为 Rust 在编译期间需要确定每个类型的长度。
所以一般使用的是**字符串切片的引用 **&str它是不可变的。

字符串字面量

用双引号包裹,如果里面有 " 用反斜杠转译。它的类型就是 &str

let speech = "\"Ouch!\" said the well.";

Rust 提供了**原始字符串。**用小写字母 r 进行标记。原始字符串中所有反斜杠和空白字符都会逐字包含在字符串中。

let default_win_install_path = r"C:\Parogram Files\Gorillas";

字节串

带有 b 前缀的字符串字面量都是**字节串。**这样的字符串是 u8 值(字节)的切片而不是 Unicode 文本。

let method = b"GET";

method 的类型是 &[u8; 3],它是对 3 字节数组的引用。
原始字节串要以 br" 开头。

String

在 Rust 中,String 类型是一个标准库提供的动态、可增长、可变的、拥有所有权的 UTF-8 编码的字符串类型。它是用来存储和操作可变长的文本数据的。
String 类似 Vec<T>。它会在堆上分配自己的缓冲区。
String 创建的几个方法:

  • .to_string()&str 转换为 String。会进行复制。
  • format!() 宏会返回一个新的 String
  • 字符串的数组、切片和向量都有 concat()join() 方法,能从多个字符串中形成一个新的 String
let mut s = String::new(); // 空的 String
let hello = String::from("Hello, world!"); // 从字符串字面量创建 String

let mut s = String::from("foo");
s.push_str("bar"); // 追加字符串切片
s.push('!'); // 追加单个字符
println!("{}", s); // 输出:"foobar!"

let s = String::from("example");
let slice: &str = &s; // 借用 String 为 &str

let slice = "example";
let s = slice.to_string(); // 或 String::from(slice)

其他类似字符串的类型

  • 对于 Unicode 文本,坚持使用 String&str
  • 使用文件名时,改用 std::path::PathBuf&Path
  • 当处理根本不是 UTF-8 编码的二进制数据时,请使用 Vec<u8>&[u8]
  • 当使用操作系统提供的原生形式的环境变量名和命令行参数时,使用 OsString&OsStr
  • 当和使用 null 结尾字符串的 C 语言库进行互操作时,请使用 std::ffi::CString&CStr

类型别名

使用 type 关键字来为现有类型声明一个新名称:

type Bytes = Vec<u8>;

本教程代码仓库:https://github.com/zcfsmile/RustLearning/tree/main/basic-data-type

参考链接:

🌟🌟 🙏🙏感谢您的阅读,如果对你有帮助,欢迎关注、点赞 🌟🌟

  • 24
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值