天天看點

Rust變量與資料類型

變量與資料類型

常用的三大資料結構:

  • 動态數組
  • 映射
  • 字元串

Rust标準庫std::collections提供了4種通用的容器類型,其中包含8種資料結構。

動态數組可細分為普通動态數組Vec和雙端隊列VecDeque

映射包括HashMap

字元串包括String等類型

變量和可變性

Rust的變量不同于其他程式設計語言的變量,其本質上是一種綁定語義,即将一個變量名與一個值綁定在一起。變量名和值建立關聯關系。

變量預設是不可改變的

變量聲明

使用let關鍵字聲明變量,先聲明後使用

let x :i8 =1
let x = 1 ; // 等價于 let x: i32 = 1;      

變量聲明以let關鍵字開頭,x為變量名,變量名後緊跟冒号和資料類型

Rust編譯器具有變量類型的自動推導功能

在可以根據指派類型或上下文資訊推導出變量類型的情況下,冒号和資料類型可以省略。

變量命名

  • 由字母、數字、下劃線組成
  • 字母區分大小
  • 不能以數字開頭,也不能隻有下劃線
Rust中下劃線是一種特殊的辨別符,其含義是“忽略這個變量”

變量的可變性

let聲明的變量預設是不可變的,在第一次指派後不能通過再次指派來改變它的值,即聲明的變量是隻讀狀态

在變量名的前面加上mut關鍵字就是告訴編譯器這個變量是可以重新指派的

let mut x = 3;
x = 5;
println!("x: {}",x);      

Rust編譯器保證了如果一個變量聲明為不可變變量,那它就真的不會變

變量遮蔽

Rust允許在同一個代碼塊中聲明一個與之前已聲明變量同名的新變量,新變量會遮蔽之前的變量,即無法再去通路前一個同名的變量,這樣就實作了變量遮蔽

fn main(){
    let x = 3;
    let x = x+2;
    let x = x*2;
    let x = "Hello, World";
}      

變量遮蔽的實質是通過let關鍵字聲明了一個新的變量,隻是名稱恰巧與前一個變量名相同而已,但它們是兩個完全不同的變量,處于不同的記憶體空間,值可以不同,值的類型也可以不同。

常量

常量是指綁定到一個辨別符且不允許改變的值,一旦定義後就沒有任何方法能改變其值了

const MAX_NUM : u32 = 1024;      

使用const關鍵字來聲明常量

常量名通常是大寫字母,且必須指定常量的資料類型

常量與不可變變量的差別主要在于:

  • 常量聲明使用const關鍵字,且必須注明值的類型
  • 通過變量遮蔽的方式可以讓不可變變量的值改變(本質上是新的變量,隻是同名而已)。但是,常量不能遮蔽,不能重複定義。

    不存在内層或後面作用域定義的常量去遮蔽外層或前面定義的同名常量的情況。常量一旦定義後就永遠不可變更和重新指派。

  • 常量可以在任何作用域中聲明,包括全局作用域。在聲明它的作用域中,常量在整個程式生命周期内都有效
  • 常量隻能被指派為常量表達式或數學表達式,不能是函數傳回值,或是其他在運作時才能确定的值。

    在編譯階段就要确定其值

基本資料類型

強類型的靜态編譯語言

Rust的基本資料類型有整數類型、浮點數類型、布爾類型、字元類型、範圍類型等。

整數類型

整數可以分為有符号整型和無符号整型

按照存儲大小,整數類型可以進一步分為1位元組、2位元組、4位元組、8位元組、16位元組

Rust預設的整數類型是i32

isize和usize主要作為數組或集合的索引類型使用,其長度依賴于運作程式的計算機系統。在64位計算機系統上,其長度是64位;在32位計算機系統上,其長度是32位。

長度 有符号 無符号
8 bit i8 u8
16 bit i16 u16
32 bit i32 u32
64 bit i64 u64
128 bit i128 u128
arch isize usize
  • 數字字面量後使用類型字尾
  • 字首0b、0o和0x表示二進制、八進制和十六進制的數字
let integer1 : u32 = 17 ; // 類型聲明
let integer2 = 17u32;     // 類型字尾聲明
let integer3 = 17 ;       // 預設i32
let integer4 : u32 = 0b1001; // 二進制
let integer5 : u32 = 0o21;   // 八進制
let integer6 : u32 = 0x11 ;  // 十六進制
let integer7 = 50_000;   // 資料可讀性分隔符_      

Rust允許使用下劃線“_”作為虛拟分隔符來對數字進行可讀性分隔

Rust在編譯時會自動移除數字可讀性分隔符“_”。

如果某個變量的值超出了給定的數值範圍,将會發生整型溢出。編譯器将其視為一種錯誤。

浮點數類型

浮點數分為f32和f64兩類。Rust預設的浮點數類型是f64

  • f32: 單精度, 小數點後至少6位有效數字
  • f64 : 雙精度, 小數點後至少15位有效數字

浮點數支援使用數字可讀性分隔符“_”

let float1 : f32 = 1.1;  // 類型聲明
let float2  = 2.2f32;  // 類型字尾聲明
let float3 = 3.3  ;  // 預設f64類型
let float4 = 11_00.555_01;  // 數字可讀性分隔符      

布爾類型

使用bool來聲明布爾類型的變量

let t : bool = true;  //顯式類型聲明
let f = false ;       // 隐式類型聲明      

字元類型

Rust使用UTF-8作為底層的編碼。

字元類型代表的是一個Unicode标量值(Unicode Scalar Value),包括數字、字母、Unicode和其他特殊字元。

每個字元占4個位元組。

字元類型char由單引号來定義

let z = 'z' ;  // 使用單引号
let hz = '中';      

範圍類型

範圍類型常用來生成從一個整數開始到另一個整數結束的整數序列,有左閉右開和全閉兩種形式

  • (1..5)是左閉右開區間,表示生成1、2、3、4這4個數字
  • (1..=5)是全閉區間,表示生成1、2、3、4、5這5個數字

範圍類型自帶一些方法

  • rev方法可以将範圍内的數字順序反轉
  • sum方法可以對範圍内的數字進行求和
fn main(){
    print!("(1..5):")
    for i in 1..5 {
        print!("{} ",i);
    }
    println!();

    print!("(1..=5).rev:");
       for i in (1..=5).rev() {
        print!("{} ",i);
    }
    println!();

    let sum :i32 = (1..=5).sum();
    println!("sum(1..=5)={}",sum);
}

//(1..5):1 2 3 4 
//(1..=5).rev:5 4 3 2 1 
//sum(1..=5)=15      
複合資料類型

複合資料類型是由其他類型組合而成的類型

Rust的複合資料類型有元組、數組、結構體、枚舉等。

元組類型

元組類型是由一個或多個類型的元素組合成的複合類型,使用小括号“()”把所有元素放在一起。元素之間使用逗号“,”分隔

元組中的每個元素都有各自的類型,且這些元素的類型可以不同。

元組的長度固定,一旦定義就不能再增長或縮短。

如果顯式指定了元組的資料類型,那麼元素的個數必須和資料類型的個數相同。

可以使用元組名.索引的方式來通路元組中相應索引位置的元素。

當元組中隻包含一個元素時,應該在元素後面添加逗号來區分是元素,而不是括号表達式

let tup1 :(i8,f32,bool) = (-10,7.7,false);
let tup2 = (7.7,(false,10));
let tup3 = (100,);
println!("{},{}",tup1.0,tup2.1)
// 解構指派
let (x,y,z) = tup1;      

數組類型

由相同類型的元素組合成的複合類型

使用[T;n] 表示,T代表元素類型,n代表長度即元素個數

// 指定數組類型,為每一個元素指派
let arr:[i32;5] = [1,2,3,4,5];
// 省略數組類型 --> 編譯器可以從初始值推斷出數組類型
let arr = [1,2,3,4];
// 省略數組類型,為所有元素使用預設值初始化
let arr = [1;5]; // -> 等價let arr = [1,1,1,1,1]      

可以使用"數組名[索引]"來通路數組中相應索引位置的元素,元素的索引從0開始計數。

動态數組Vec, Vec是允許增長和縮短長度的容器類型,其提供的get方法在通路元素時可以有效避免索引越界

結構體類型

結構體類型是一個自定義資料類型,通過struct關鍵字加自定義命名,可以把多個類型組合在一起成為新的類型。

結構體中以"name: type"格式定義字段,name是字段名稱,type是字段類型。

字段預設不可變,并要求明确指定資料類型,不能使用自動類型推導功能。

struct ListNode {
    var : i32,
    next : Option<Box<ListNode>>,  // next類型時指向ListNode的智能指針
}      

使用"執行個體名.字段名"形式更改和通路結構體執行個體某個字段的值。

結構體執行個體預設是不可變的,且不允許隻将某個字段标記為可變,如果要修改結構體執行個體必須在執行個體建立時就聲明其為可變的。

struct Student {
    name : &'static str,
    score: i32,
}

fn main(){
    let score = 59;
    let username = "wkk";

    let mut student = Student{
        score, // 變量和字段同名,可以簡寫
        name : username,
    };

    student.score = 60;
    //結構體更新文法,對除字段name外未顯式設定值的字段以student執行個體對應字段的值來指派。
    let student2 = Student {
        name : "yyr",
        ..student
    };
}      

特殊結構體:

  • 元組結構體

    字段隻有類型,沒有名稱

struct Color(i32,i32,i32);      
  • 單元結構體

    沒有任何字段的結構體

struct Solution;      

枚舉類型

使用enum關鍵字加自定義命名來定義

含若幹枚舉值,可以使用“枚舉名::枚舉值”通路枚舉值。

變量的值限于枚舉值範圍内

根據枚舉值是否帶有類型參數,枚舉類型還可以分成無參數枚舉類型和帶參數枚舉類型。

無參枚舉類型

// #[derive(Debug)] 讓ColorNoParam自動實作Debug trait
// 隻有實作了Debug trait的類型才擁有使用{:?}格式化列印的行為
#[derive(Debug)]
enum ColorNoParam {
    Red,
    Yellow,
    Blue,
}

fn main(){
    let color_no_param = COlorNoParam::Red;
    match color_no_param{
        ColorNoParam :: Red => println!("{:?}",ColorNoParam::Red),
        ColorNoParam :: Yellow => println!("{:?}",ColorNoParam::Yellow),
        ColorNoParam :: Blue => println!("{:?}",ColorNoParam::Blue),
    }
}      

帶參枚舉類型

#[derive(Debug)]
enum ColorParam{
    Red(String), //帶有String類型參數
    Yellow(String),
    Blue(String),
}

fn main(){
    //使用這種枚舉值需要傳入實參
    println!("{:?}",ColorParam::Blue(String::from("blue")));
}      
容器類型

Rust标準庫std::collections提供了4種通用的容器類型,包含8種資料結構

Rust變量與資料類型

Vec

動态可變長數組, 在運作時可以增長或者縮短數組的長度

動态數組在記憶體中開辟了一段連續記憶體塊用于存儲元素,且隻能存儲相同類型的元素。

新加入的元素每次都會被添加到動态數組的尾部

  • 建立
// 建立空的動态數組
let mut v: Vec<i32> = Vec::new();
// 建立指定容量的動态數組
let mut v: Vec<i32> = Vec::with_capacity(10);
// 使用vec!宏在建立動态數組的同時進行初始化,并且根據初始值自動推斷動态數組的元素類型
let mut v: Vec<i32> = vec![]; // 沒有初始值,需要聲明元素類型
let mut v = vec![1,2,3]; // 自動推斷元素個數
let mut v = vec![0;10]; // 10個元素,元素的初始值都是0      
應該盡可能根據初始元素個數以及增長情況來指定合理的容量。
  • 修改
// 使用push方法在動态數組尾部添加新元素
v.push(1);
// 使用數組名[索引] 擷取元素
v[1] = 5;
// 使用pop方法删除并傳回動态數組的最後一個元素,如果數組為空傳回None
v.pop();
// remove方法删除并傳回動态數組指定索引的元素,同時後面的所有元素向前移動一位
// 索引越界将導緻程式錯誤
v.remove(1);      
  • 通路
//使用索引通路
v[2];
// 使用get方法以索引作為參數通路
v.get(1);
// 周遊
for i in v {
    print!("{}",i);
}
// 可變引用
for i in &mut v {
    *i += 50;
    print!("{}",i);
}      

VecDeque

雙端隊列是一種同時具有棧(先進後出)和隊列(先進先出)特征的資料結構,适用于隻能在隊列兩端進行添加或删除元素操作的應用場景

定義在标準庫的std::colllections::VecDeque中,使用前需要顯式導入std::collections::VecDeque;

  • 建立
// 建立空的VecDeque
let mut v : VecDeque<u32> = VecDeque::new();
// 建立指定容量的VecDeque
let mut v : VecDeque<u32> = VecDeque :: with_capacity(10);      
  • 修改
// push_front 在隊列頭部添加新的元素
v.push_front(1);
// push_back 在尾部添加新元素
v.push_back(2);      
// 使用索引修改元素
v[1] = 5;      
  • 删除
// pop_front 删除并傳回隊列的頭部元素
v.pop_back();
// pop_back 删除并傳回尾部元素
v.pop_front();      
// remove 删除并傳回隊列指定索引的元素,同時後面的所有元素向左移動一位
// 索引越界傳回None
v.remove(1);      
  • 通路
// 使用索引通路
v[0];
// 使用get方法以索引作為參數通路元素
v.get(0);      

HashMap

哈希表(HashMap)是基于雜湊演算法來存儲鍵-值對的集合,其中所有的鍵必須是同一類型,所有的值也必須是同一類型,不允許有重複的鍵

定義在标準庫std::collections 子產品中,使用前要顯式導入std::collection::HashMap

  • 建立
// 建立空的HashMap
let mut map: HashMap<&str,i32> = HashMap::new();
// 建立指定容量
let mut map: HashMap<&str,i32> = HashMap::with_capacity(10);      
  • 修改
// insert 執行插入或者更新
// 鍵不存在,執行插入并傳回None
// 鍵存在,執行更新,并傳回舊值
let zhangsan = map.insert("zhangsan",16);      
// 使用entry 和 or_insert 方法檢查是否有對應值,沒有對應值就插入,有對應值不執行操作
// entry方法以鍵為參數,傳回值是一個枚舉類型Entry
// Entry類型的or_insert 方法以值為參數,在鍵有對應值時不執行任何操作,沒有對應值時,将鍵與值組成鍵值對插入
map.entry("zhangsan").or_insert(23);      
// iter_mut 方法會傳回由所有鍵值對的可變引用組成的疊代器
for( _, val ) in map.iter_mut() {
    *var += 2; // 所有的值都加2
}      
// remove 删除并傳回指定鍵值對的值,不存在傳回None
let result = map.remove("wkk");      
  • 通路
// 使用執行個體名[鍵] 通路指定鍵值對, 鍵不存在會導緻程式錯誤
map["wkk"]      
// 使用get 方法,以鍵作為參數通路指定的鍵值對,存在傳回值,不存在傳回None
map.get("wkk");      
字元串

字元串的本質是一種特殊的容器類型,是由零個或多個字元組成的有限序列。

字元串常被作為一個整體來關注和使用

常用的字元串有兩種:

  • 固定長度的字元串字面量
  • 可變長度的字元串對象String

建立

  1. &str的建立

    内置的字元串類型是str, 通常以引用的形式&str出現。

    字元串字面量&str是字元的集合,代表的是不可變的UTF-8 編碼的字元串的引用,建立後無法再追加内容或者更改内容

// 使用雙引号建立字元串字面量
let s1 = "hello,wkk";      
// 使用as_str方法将字元串對象轉換為字元串字面量
let str = String::from("hello,wkk");
let s2 = str.as_str();      
  1. String 的建立

    字元串對象String 由Rust 标準庫提供。

    建立後可以為其追加内容或者更改内容。

    本質是一個字段為Vec<u8> 類型的結構體,把字元内容放在堆上,由指向堆上位元組序列的指針(as_ptr方法)、記錄堆上位元組序列的長度(len方法)和堆配置設定容量(capacity) 3部分組成。

// 建立空的字元串對象
let mut s = String::new();      
// 根據指定的字元串字面量建立字元串對象
let s = String::from("wkk");      
// 使用to_string 方法将字元串字面值轉換為字元串對象
let str = "wkk";
let s = str.to_string();      

修改

  1. 使用push方法在字元串後面追加字元,使用push_str方法在字元串後追加字元串字面量

    都是在原字元串上追加,不會傳回新的字元串

let mut s = String::from("wkk");
s.push('R');
s.push_str("111");      
要追加,字元串必須是可變的,使用mut關鍵字
  1. 使用insert方法在字元串中插入字元,使用insert_str方法在字元串中插入字元串字面量

    第1個參數是插入位置的索引,第2個參數是插入字元或者字元串字面量

    都是在原字元串上插入,并不是傳回新的字元串

索引非法會導緻程式錯誤
s.insert(5,',');
s.insert_str(7,"Rust ");      
  1. 使用 "+" 或者"+="運算符将兩個字元串連接配接成一個新的字元串,要求運算符的右邊必須是字元串字面量

    不能對兩個String 類型的字元串使用

    連接配接與追加的差別在于,連接配接會傳回新的字元串,而不是在原字元串上的追加

let s = "hello " + "wkk";      
  1. 對于較為複雜的帶有格式的字元串連接配接,可以使用格式化宏format!

    對于String 類型 和 &str類型的字元串都适用

let s = format!("{}-{}-{}",s1,s2,s3);      
  1. replace 和 replacen() 方法将字元串中指定的子串替換為另一個字元串。

    replace 接收兩個參數,第1個參數為要被替換的子串,第2個參數為新的字元串,會替換所有比對的子串。

    replacen 方法除了上述兩個參數外,還接受第3個參數來指定替換的個數

let s1 = s.replace("aa","77");
let s2 = s.replace("aa","77",1);      
  1. 适用pop , remove , truncate 和 clear 方法删除字元串中的字元
  • pop

    删除并傳回字元串的最後一個字元,傳回類型為Option<char>, 如果字元串為空,傳回None

  • remove

删除并傳回字元串中指定位置的字元,參數是該字元的起始索引位置。

remove方法是按位元組處理字元串的,如果給定的索引位置不是合法的字元邊界,将會導緻程式錯誤。

  • truncate

    删除字元串中從指定位置開始到結尾的全部字元,參數是起始索引位置。

    truncate 也是按照位元組處理字元串,如果給定的索引位置不是合法的字元邊界,會導緻程式錯誤。

  • clear

    删除字元串中所有字元

s.pop();
s.remove(9);
s.truncate(9);
s.clear();      

字元串的通路

  • 字元串是UTF-8 編碼的位元組序列,不能直接使用索引來通路字元
  • 字元串操作可以分為按位元組處理和按字元處理兩種方式,按位元組處理使用bytes方法傳回位元組疊代的疊代器,按字元處理使用chars方法傳回按字元疊代的疊代器。
len 方法擷取以位元組為機關的字元串長度
  • UTF-8 中字母1位元組,特殊字元2位元組,漢字3位元組,不同字元的長度是不一樣的。
s.len();      
// 按位元組周遊
let bytes = s.bytes();
for b in bytes {
    print!("{} |",b);
}
// 按字元周遊
let chars = s.chars();
for c in chars{
    print!("{} |",c);
}      
字面量和運算符

字面量

由文字,數字或者符号構成的值

可以在字面量後面追加類型字尾進行類型說明:

  • 1u8 : 8位無符号整數
  • 1.2f32 32位浮點數

單元類型,單元類型的值叫做單元值,以()表示,一個函數無傳回值,實際上是以單元值作為函數的傳回值了。

運算符

支援算術運算符、關系運算符、邏輯運算符、位運算符

算術運算符

+ - * / %      
不支援 ++ 和 --

關系運算符

比較兩個值之間的關系,并傳回一個布爾類型的值

> < >= <= == !=      

邏輯運算符

組合兩個或者多個條件表達式,傳回一個布爾類型的邏輯運算結果

&& || !      
& | ^ ! << >>      

繼續閱讀