Rust學(xué)習(xí)筆記

Rust 編程語言入門教程

第一章: Rust簡(jiǎn)介

為什么要用Rust

Rust是一種令人興奮的新編程語言, 它可以讓每一個(gè)人編寫可靠且高效的軟件.
它可以用來替換C/C++, Rust和他們具有同樣的性能, 但是很多常見的bug在編譯時(shí)就可以被消滅.
Rust是一種通用的編程語言, 但是它更善于以下場(chǎng)景:
需要運(yùn)行時(shí)的速度
需要內(nèi)存安全
更好的利用多處理器

與其他語言比較

C/C++ 性能非常好, 但類型系統(tǒng)和內(nèi)存都不太安全.
JAVA/C#, 擁有GC, 能保證內(nèi)存安全, 也有很多優(yōu)秀特性, 但是性能不行
RUST:
安全
無需GC
易于維護(hù), 調(diào)試, 代碼安全高效

Rust特別擅長(zhǎng)的領(lǐng)域

高性能webservice
webassembly
命令行工具
網(wǎng)絡(luò)編程
嵌入式設(shè)備
系統(tǒng)編程

Rust與Firefox

Rust最初是Mozilla公司的一個(gè)研究性項(xiàng)目, firefox是Rust產(chǎn)品應(yīng)用的一個(gè)重要例子.
Mozilla 一直以來都在用rust創(chuàng)建一個(gè)名為servo的實(shí)驗(yàn)性瀏覽器引擎, 其中的所以內(nèi)容都是并行執(zhí)行的.
目前servo的部分功能已經(jīng)被集成到firefox里面了
firefox原來的量子版就包含了servo的css渲染引擎

  • rust使得firefox在這方便得到了巨大的性能改進(jìn)

Rust的用戶和案例

  • google: 新操作系統(tǒng)Fuschia, 其中Rust代碼量大約30%
  • Amazon: 基于Linux開發(fā)的直接可以在裸機(jī), 虛擬機(jī)上運(yùn)行容器的操作系統(tǒng).
  • System76: 純Rust開發(fā)了下一代安全操作系統(tǒng)Redox
  • 螞蟻金服: 庫操作系統(tǒng)Occlum
  • 斯坦福和密歇根大學(xué): 嵌入式實(shí)時(shí)操作系統(tǒng), 應(yīng)用于google的加密產(chǎn)品.
  • 微軟: 正在使用Rust重寫windows系統(tǒng)中的一些低級(jí)組件.
  • 微軟: winRT/Rust項(xiàng)目
  • Dropbox, yelp, Coursera, LINE, Cloudflare, Atlassian, npm, Ceph, 百度, 華為, Sentry, Deno

Rust的優(yōu)點(diǎn)

  • 性能
  • 安全性
  • 無所畏懼的并發(fā)

Rust的缺點(diǎn)

  • 難學(xué)

注意

Rust有很多獨(dú)有的概念, 它們和現(xiàn)在大多主流語言都不同.

  • 所以學(xué)習(xí)Rust必須從基礎(chǔ)概念一步一步學(xué), 否則會(huì)懵.

參考教材:

The Rust programming language
Rust權(quán)威指南

安裝Rust

安裝rust

更新Rust

 rustup update

卸載Rust

   rustup self uninstall

驗(yàn)證安裝是否成功

  rustc --version
  • 結(jié)果格式: rustc x.y. z (abcabcabc yyyy-mm-dd)
  • 會(huì)顯示最新穩(wěn)定版的: 版本號(hào), commit hash, commit 日期

第二章:第一個(gè)rust程序

hello world

fn main() {
    println!("hello world");
}

編譯:

rustc main.rs

編譯完成后將會(huì)輸出 可執(zhí)行文件main
運(yùn)行程序使用

./main

Rust 程序解析

  • 定義函數(shù) fn main() {}
    沒有參數(shù), 沒有返回值
  • main 函數(shù)很特別 : 他是每一個(gè)Rust 可執(zhí)行程序 最先運(yùn)行的代碼
  • 打印文本: println!("hello, world!")
    • rust 的縮進(jìn)是4個(gè)空格而不是tab
    • println! 是一個(gè)Rust macro(宏)
      • 如果是函數(shù)的話就沒有!
    • "hello world" 是字符串, 它是println!的參數(shù)
    • 這行代碼以;結(jié)尾

編譯和運(yùn)行是單獨(dú)的兩步

  • 運(yùn)行rust程序之前需要先編譯, 命令為: rustc 源文件名
      rustc main.rs
    
  • 編譯成功后, 會(huì)生成一個(gè)二進(jìn)制文件
    • 在wendow上還會(huì)生成一個(gè).pdb文件, 里面包含調(diào)試信息
  • Rust是 ahead-of-time 預(yù)編譯的語言
    • 可以先編譯程序, 然后把可執(zhí)行文件交給別人運(yùn)行(無需安裝Rust)
  • rustc只適合簡(jiǎn)單的Rust程序
    • 當(dāng)文件比較多, 項(xiàng)目比較多是需要使用Cargo

hello cargo

cargo 介紹

  • Cargo 是Rust的構(gòu)建系統(tǒng)和包管理工具
    • 構(gòu)建代碼非洲, 下載依賴的庫装处, 構(gòu)建這些庫
  • 安裝Rust的時(shí)候會(huì)安裝Cargo
    • cargo --version
    $cargo --version
    cargo 1.55.0 (32da73ab1 2021-08-23)
    

使用Cargo創(chuàng)建項(xiàng)目

  • 創(chuàng)建項(xiàng)目: cargo new hello_cargo

  • 創(chuàng)建完成后項(xiàng)目的目錄結(jié)構(gòu)

    $tree -a .      
    hello_cargo
    
    ├── Cargo.toml
    ├── .git
    │   ├── config
    │   ├── description
    │   ├── HEAD
    ...................
    ├── .gitignore
    └── src
          └── main.rs
    

    目錄結(jié)構(gòu)說明:
    Cargo.toml:

    • TOML(Tom's obvious, Minimal Language)格式妖混,是Cargo的配置格式
      [package]
      name = "hello_cargo"
      version = "0.1.0"
      edition = "2018"
    
      # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
    
      [dependencies]
    
  • [package], 是一個(gè)區(qū)域標(biāo)題, 表示下方內(nèi)容是用來配置包(package)的.

    • name: 項(xiàng)目名
    • version: 項(xiàng)目版本
    • authors: 項(xiàng)目作者
  • [dependencies], 另一個(gè)區(qū)域的開始萌庆,它會(huì)列出項(xiàng)目的依賴項(xiàng).

  • 在Rust里面,代碼的包稱作crate(板條箱;簍子;)

    src/main.js

    • cargo生成的main.rs在src目錄下
    • 而Cargo.toml在項(xiàng)目頂層下
    • 源代碼都應(yīng)該在src目錄下
    • 頂層目錄可以放置:README, 許可信息猜惋, 配置文件和其它與程序源代碼無關(guān)的文件
    • 如果創(chuàng)建項(xiàng)目時(shí)沒有使用cargo, 也可以把項(xiàng)目轉(zhuǎn)化為使用cargo:
    • 把源代碼文件移動(dòng)到src下
    • 創(chuàng)建Cargo.toml并填寫相應(yīng)的配置

構(gòu)建Cargo項(xiàng)目

  • cargo build
    創(chuàng)建可執(zhí)行文件:target/debug/hello_cargo (linux/macos)或者 target\debug\hello_cargo.exe (windows)
  • 第一次運(yùn)行cargo build會(huì)在頂層目錄生成cargo.lock文件
    • 該文件負(fù)責(zé)追蹤項(xiàng)目依賴的精確版本
    • 不需要手動(dòng)修改該文件
  • 構(gòu)建和運(yùn)行cargo項(xiàng)目
    • cargo run
    • cargo run, 編譯代碼+執(zhí)行結(jié)果
       - 如果之前編譯成功過, 并且源代碼沒有改變音诫, 那么就會(huì)直接運(yùn)行二進(jìn)制文件
  • cargo check
    • cargo check, 檢查代碼惨奕, 確保能通過編譯雪位, 但是不產(chǎn)生任何可執(zhí)行文件
  • cargo buid -release
    • 編譯時(shí)會(huì)進(jìn)行優(yōu)化
       - 代碼會(huì)運(yùn)行的更快竭钝, 但是編譯時(shí)間更長(zhǎng)
       - 會(huì)在target/release 而不是target/debug生成可執(zhí)行文件
       - 兩種配置, 一種是開發(fā)用的雹洗, 一種是發(fā)布用的

猜數(shù)字游戲

聲明變量

main.rs

println!("猜數(shù)香罐!");
    println!("猜測(cè)一個(gè)數(shù)!");
    //let聲明一個(gè)變量
    //mut 聲明變量為可變變量时肿, 
    // 默認(rèn)情況下變量是不可變的庇茫, 除非顯示使用mut指明變量為可變變量
    // = 賦值操作
    //注意申明時(shí)沒有指定變量類型, 變量類型是根據(jù)賦初始值時(shí)進(jìn)行推導(dǎo)的
    //String是由rust 的標(biāo)準(zhǔn)庫所提供的類型螃成,內(nèi)部是使用utf-8編碼
    //:: 符號(hào)表明new是String類型的關(guān)聯(lián)函數(shù)旦签,關(guān)聯(lián)函數(shù)相當(dāng)與其他語言中的靜態(tài)方法
    let mut guess = String::new();
    //io是rust標(biāo)準(zhǔn)庫中的一個(gè)包名
    //stdin()方法會(huì)返回一個(gè)Stdin對(duì)象, 標(biāo)準(zhǔn)輸入對(duì)象
    //read_line 是標(biāo)準(zhǔn)輸入對(duì)象的一個(gè)方法,調(diào)用該方法時(shí)寸宏,需要提供一個(gè)可變字符串變量宁炫,用于接收用戶輸入
    //& 取地址符號(hào),表示傳遞引用氮凝, 表示這個(gè)參數(shù)是一個(gè)引用reference羔巢,通過引用我們就可以在不同地方,訪問程序的統(tǒng)一塊內(nèi)存區(qū)域
    //&mut表示這個(gè)引用是可變的, 如果不加mut竿秆, 表明這個(gè)引用也是不可變的
    //read_line函數(shù)返回的是一個(gè)i0:Result<usize>對(duì)象启摄,expect是result對(duì)象的一個(gè)方法
    // result是一個(gè)枚舉類型, 其有兩種類型的返回結(jié)果幽钢, 一種是err, 一種ok
    // 如果返回的result為err, 該expect就會(huì)將錯(cuò)誤信息輸出到終端
    //如果返回結(jié)果是ok類型歉备, expect就會(huì)提取出result中附加的值并將這個(gè)值作為結(jié)果返回給用戶
    //如果不調(diào)用expect方法, 編譯時(shí)將會(huì)收到搅吁, rusult未被使用的警告
    io::stdin().read_line(&mut guess).expect("無法讀取行");
    //{} 是一個(gè)占位符威创,輸出時(shí)將會(huì)替換成相對(duì)應(yīng)的變量的值
    println!("你猜測(cè)的數(shù)時(shí){}", guess);

引入依賴

  • rust中的依賴被稱為crate
    crate分為兩種, 一種是二進(jìn)制格式的可執(zhí)行文件谎懦, 一種是源文件肚豺, 這種crate被稱為library crate

  • rust的crate倉庫為crates.io, 可以訪問該網(wǎng)站獲得相應(yīng)的crate

  • cargo 引入依賴的方式

#Cargo.toml
[package]
name = "hello_cargo"
version = "0.1.0"
edition = "2018"

[dependencies]
rand = "^0.7.0"

在dependencies 區(qū)域添加依賴的crate名稱和版本如上所示.

  • 示例代碼
use std::io;
use rand::Rng; //trait
use std::cmp::Ordering;
fn main() {
    println!("猜數(shù)!");
   
    let secret_num = rand::thread_rng().gen_range(1, 101);
    //rust循環(huán)
    loop {
        println!("猜測(cè)一個(gè)數(shù)界拦!");
        let mut guess = String::new();
        io::stdin().read_line(&mut guess).expect("無法讀取行");
        println!("你猜測(cè)的數(shù)是: {}", guess);
        //shadow
        let guess:u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(ex) => {
                //rust異常處理
                println!("解析錯(cuò)誤 {} {}", guess, ex);
                continue;
            }
        };
    // rust條件運(yùn)算
        match guess.cmp(&secret_num) {
            Ordering::Less => println!("Too small!"), //arm
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");
                break;
            }
        }
    }
   
}

第三章 通用的編程概念

變量和可變性

  • 聲明變量使用let 關(guān)鍵字

  • 默認(rèn)情況下吸申,變量是不可變的(immutable)
    例如

       let x =5;  // x 為不可變變量
       println!("the value of x is {}", x);
       x = 6; // 注意: 這里會(huì)有編譯錯(cuò)誤
       println!("the value of x is {}", x);
    
    
  • 聲明變量時(shí), 在變量前面加上mut, 就可以使變量可變.

       let mut x =5;
       println!("the value of x is {}", x);
       x = 6;
       println!("the value of x is {}", x);
    
    

    變量和常量

    • 常量(constant), 常量在綁定值以后也是不可變的享甸, 但是它與不可變的變量有很多區(qū)別:
       - 不可以使用mut, 常量永遠(yuǎn)都是不可變的
       - 聲明常量使用const關(guān)鍵字截碴, 它的類型必須被標(biāo)注
       - 常量可以在任何作用域內(nèi)進(jìn)行聲明, 包括全局作用域
       - 常量只可以綁定到常量表達(dá)式蛉威, 無法綁定到函數(shù)的調(diào)用結(jié)果或只能在運(yùn)行時(shí)才能計(jì)算出的值
       - 在程序運(yùn)行期間日丹, 常量在其聲明的作用域內(nèi)一直有效
       - 命名規(guī)范: Rust里常量使用全大寫字母, 每個(gè)單詞之間用下劃線分開蚯嫌, 例如:
       const MAX_POINTS: u32 = 100_000;
    

shadowing (隱藏)

  • 可以使用相同的名字聲明新的變量哲虾, 新的變量就會(huì)shadow(隱藏)之前聲明的同名變量
    • 在后續(xù)代碼中這個(gè)變量名代表的就是新的變量
    • shadow和把變量標(biāo)記為mut是不一樣的
      例如
        fn main() {
          //定義不可變變量x
          let x = 5;
          println!("the value of x is {}", x);
          // x = 6 // 如果這里給x賦值會(huì)報(bào)錯(cuò)
          // 但是如果我們聲明一個(gè)同名的新的變量,就可以編譯通過
          // 我們甚至可以改變x的數(shù)據(jù)類型, 甚至可以定義新的同名但是不同的可變性的變量
          
          //新的同名變量 
          let x = 6;
          println!("the value of x is {}", x);
          //不同可變性的同名變量
          let mut x = "my love";
          println!("the value of x is {}", x);
          x = "hello kitty";
          println!("the value of x is {}", x);
        }
      

Rust數(shù)據(jù)類型

  • 標(biāo)量和復(fù)合類型

  • Rust 是靜態(tài)編譯語言择示, 在編譯時(shí)必須知道所有變量的類型

    • 基于使用的值束凑, 編譯器通常能夠推斷出它的具體類型
    • 但如果可能的類型比較多(例如把String 轉(zhuǎn)為整數(shù)的parse方法),就必須添加類型的標(biāo)注栅盲,否則編會(huì)報(bào)錯(cuò).
      例如
       let guess: u32 = "42".parse().expect("not a number")
        println!("{}", guess)
    

    標(biāo)量類型

    • 一個(gè)標(biāo)量類型代表一個(gè)單個(gè)的值
    • Rust有四個(gè)主要的標(biāo)量類型:
      • 整數(shù)類型
      • 浮點(diǎn)類型
      • 布爾類型
      • 字符類型
    整數(shù)類型

    - 整數(shù)類型分為無符合整數(shù)類型汪诉, 
    - 無符合整數(shù)類型以u開頭
    - 有符號(hào)整數(shù)類型以i開頭

    • Rust 的整數(shù)類型列表如圖:

      • 每種長(zhǎng)度都有對(duì)應(yīng)的有符號(hào)型和無符號(hào)型.

      • 有符號(hào)范圍
           -(2的n-1次方-1) 到(2的n-1次方-1)

      • 無符號(hào)范圍
           - 0 到2的n次方 -1
           - isize 和 usize類型

        • isize和usize類型的位數(shù)由程序運(yùn)行的計(jì)算機(jī)的架構(gòu)所決定
        • 如果是64位的計(jì)算機(jī),那就是64位的
        • 如果是32位的計(jì)算機(jī)谈秫,那就是32位的
        • 使用isize或者usize的場(chǎng)景是對(duì)某種集合進(jìn)行索引操作
        length signed unsigned
        8-bit i8 u8
        16-bit i16 u16
        32-bit i32 u32
        64-bit i64 i64
        128-bit i128 u128
        arch isize usize
    • 整數(shù)的字面值
        - 除了byte類型外扒寄, 所有的數(shù)值字面值都允許使用類型后綴
         例如: 57u8: 值為57 類型為u8

      • 整數(shù)的默認(rèn)類型就是i32:
        • 總體來說速度很快, 即使在64位系統(tǒng)中
            | NumberLiteral | Example |
            |:----|:----|
            | Decimal | Oxff |
            | Hex | Oo77 |
            | Binary | Ob1111_0000 |
            | Byte (u8 only) | b'A' |

- 整數(shù)溢出
  例如:u8的范圍是0-255, 如果你把一個(gè)u8變量的值設(shè)為256, 那么:
  - 調(diào)試模式下編譯:rust會(huì)檢查整數(shù)溢出拟烫, 如果發(fā)生溢出该编, 程序運(yùn)行時(shí)就會(huì)panic
  - 在發(fā)布模式下(--release)編譯:rust不會(huì)檢查可能導(dǎo)致panic的整數(shù)溢出
   - 在這種模式下如果發(fā)生溢出:rust會(huì)執(zhí)行環(huán)繞操作
    256變成0, 257變成1....
- 但是不會(huì)導(dǎo)致panic

浮點(diǎn)類型

  • rust 有兩種基礎(chǔ)的浮點(diǎn)類型,也就是含有小數(shù)部分的類型
    • f32, 32位, 單精度
    • f64, 64位构灸,雙精度
  • rust的浮點(diǎn)類型使用了IEEE-754標(biāo)準(zhǔn)來表述
  • f64是默認(rèn)類型上渴,因?yàn)樵诂F(xiàn)代cpu上f64和f32的數(shù)度差不多岸梨,而且精度更高
  • 例子
      let x = 2.0 //默認(rèn)為f64
      let y: f32 = 3.0; //f32
    

數(shù)值操作

```rust
 let sum = 5+10;
 let difference = 95.5-4.3; //f64
 let product = 4*30;
 let quotient = 56.7/32.2
 let reminder = 54%5 
```

布爾類型

字符類型

rust語言中char類型被用來描述語言中最基礎(chǔ)的單個(gè)字符.
字符類型的字面值使用單引號(hào)
占用4字節(jié)大小
是Unicode 標(biāo)量值, 可以表示比ASCII多得多的字符內(nèi)容:拼音稠氮, 中日文曹阔, 零長(zhǎng)度空白字符,emoji表情等.

  • 其范圍為
    • U+0000到U+D7FF
    • U+E000到U+10FFFF
  • 但是unicode中并沒有字符的概念隔披, 所以自覺上認(rèn)為的字符也許與Rust中的概念并不相符

復(fù)合類型

  • 復(fù)合類型可以將多個(gè)值放在一個(gè)類型里
  • Rust提供了兩種基礎(chǔ)的復(fù)合類型: 元組(Tuple), 數(shù)組

Tuple

  • Tuple可以將多個(gè)類型的多個(gè)值放在一個(gè)類型里
  • Tuple的長(zhǎng)度是固定的: 一旦聲明就無法改變

創(chuàng)建tuple

  • 在小括號(hào)里赃份, 將值用逗號(hào)分開
  • Tuple中的每個(gè)位置都對(duì)應(yīng)一個(gè)類型,tuple中各元素的類型不必相同
  • 實(shí)例
       let tup: (i32, f64, u8) = (500, 6.4, 1)
       println!("{},{},{}", tup.0, tup.1, tup.2)
    

獲取tuple的元素值

  • 可以使用模式匹配來解構(gòu)(destructure)一個(gè)Tuple來獲取元素的值
  • 例子
       let tup: (i32, f64, u8) = (500, 6.4, 1);
       let (x, y, z) = tup; //這里使用模式匹配解構(gòu)tup的值
       println!("{},{},{}", x, y, z)
    

訪問tuple的元素

  • 在tuple變量使用點(diǎn)標(biāo)記法奢米,后接元素的索引號(hào)
  • 實(shí)例
   let tup: (i32, f64, u8) = (500, 6.4, 1)
   println!("{},{},{}", tup.0, tup.1, tup.2)

數(shù)組

  • 數(shù)組也可以將多個(gè)值放在一個(gè)類型里
  • 數(shù)組中每個(gè)元素的類型必須相同
  • 數(shù)組的長(zhǎng)度也是固定的

聲明一個(gè)數(shù)組

  • 在中括號(hào)里抓韩, 各值用逗號(hào)分開
  • 例子
        fn main() {
          let a = [1, 2, 3, 4, 5]
        }
    

數(shù)組的用處

  • 如果想讓你的數(shù)據(jù)存放在stack上, 而不是heap上鬓长,或者想保證有固定數(shù)量的元素谒拴, 這時(shí)使用數(shù)組更有好處.

  • 數(shù)組沒有Vector靈活(以后再講)

    • Vector和數(shù)組類似,它是由標(biāo)準(zhǔn)庫提供的
    • Vector的長(zhǎng)度是可以改變的
    • 如果你不確定應(yīng)該使用數(shù)組還是vector, 那么估計(jì)你應(yīng)該用vector.
  • 例子

      fn main() {
        let months = ["January",
                  "Fabruary",
                  ......
                  "December"
                  ]
      }
    

    數(shù)組的類型

    • 數(shù)組的類型以這種形式來表示: [類型; 長(zhǎng)度]
      • 例如: let a: [i32; 5] = [1, 2, 3, 4, 5];
    • 另外一種聲明數(shù)組的方法
       如果數(shù)組的沒一個(gè)元素值都相同涉波, 那么可以在:
      在中括號(hào)里指定初始值
      然后是一個(gè);
      最后是數(shù)組的長(zhǎng)度
      例如: let a = [3; 5]; 它相當(dāng)于:let a = [3, 3, 3, 3, 3]

訪問數(shù)組的元素

  • 數(shù)組是stack上分配的單個(gè)塊的內(nèi)存

  • 可以使用索引來訪問數(shù)組的元素

  • 例子

       let first = months[0];
       let second = months[1];
    
  • 如果訪問的索引超出了數(shù)組的范圍英上, 那么

    • 編譯會(huì)通過
    • 運(yùn)行會(huì)報(bào)錯(cuò)(runtime時(shí)會(huì)panic)
      • Rust不會(huì)允許其繼續(xù)訪問相應(yīng)地址的內(nèi)存
        說明:
        在簡(jiǎn)單的情況下,編譯會(huì)報(bào)錯(cuò)啤覆,但是在復(fù)雜情況下苍日,編譯不會(huì)報(bào)錯(cuò)
        例如
       let index = 15;
       let month = months[index]; //此時(shí)編譯會(huì)報(bào)錯(cuò)
    
       let index = [15, 1, 2 , 3];
       let month = months[index[0]]; //此時(shí)編譯不會(huì)報(bào)錯(cuò), 運(yùn)行時(shí)會(huì)發(fā)生panic
    

    3.4 函數(shù)

    • 聲明函數(shù)使用fn關(guān)鍵字
    • 依照慣例窗声,針對(duì)函數(shù)和變量名相恃,rust使用snake case命名規(guī)范:
      • 所有的字母都是小寫的, 單詞之間使用下劃線分開
      • 例子
        fn main() {
          println!("hello world");
          another_function();
        }
        
        fn another_function() {
          println!("another function")
        }
        

函數(shù)的參數(shù)

  • parameters, arguments
  • 例子
    fn main() {
      println!("hello world");
      another_function(5);
    }
    
    fn another_function(x: i32) {
      println!("the value of x is {}", x)
    }
    

函數(shù)中的語句(statement)和表達(dá)式(expression)

  • 函數(shù)體由一系列語句組成笨觅, 可選的由一個(gè)表達(dá)式結(jié)束
  • Rust是一個(gè)基于表達(dá)式的語言
  • 語句是執(zhí)行一些動(dòng)作的指令
  • 表達(dá)式會(huì)計(jì)算產(chǎn)生一個(gè)值
  • 函數(shù)的定義也是語句
  • 語句不返回值拦耐,所以不可以使用let將一個(gè)語句-賦給一個(gè)變量

例子

  fn main() {
    let x = 5; 
    let y = {
      let x = 1;
      //x+3; //注意這里有個(gè)分號(hào),它為語句而不是表達(dá)式屋摇, 但是這個(gè)語句有些特殊 它的值等于一個(gè)空的tuple 即()
      x+3 //這里沒有分號(hào)揩魂, 它是一個(gè)表達(dá)式幽邓, 表達(dá)式的值為5 它是整個(gè)block的返回值炮温, 該值將會(huì)被賦給變量y
    }
  }

函數(shù)的返回值

  • 在->符號(hào)后邊聲明函數(shù)返回值的類型, 但是不可以為返回值命名
  • 在Rust里面牵舵, 返回值就是函數(shù)體里面最后一個(gè)表達(dá)式的值
  • 若想提前返回柒啤, 需使用return 關(guān)鍵字, 并指定一個(gè)值
    • 大多數(shù)函數(shù)都是默認(rèn)使用最后一個(gè)表達(dá)式為返回值
      例子:
  fn five() -> i32 {
    5
  }
  fn main() {
    let x = five();
    println("the value of x is: {}", x);
  }

注釋

單行注釋
多行注釋
文檔注釋

控制流

if 表達(dá)式

  • if 表達(dá)式允許你根據(jù)條件來執(zhí)行不同的代碼分支
    • 這個(gè)條件必須是bool類型
  • if 表達(dá)式中畸颅, 與條件相關(guān)聯(lián)的代碼塊就叫做分支(arm)
  • 可選的担巩, 在后面可以加上一個(gè)else表達(dá)式
  • 但是如果使用了多余一個(gè)else if, 那么最好使用match來重構(gòu)代碼
  • 例子
       fn main() {
         let number = 3;
         if number < 5 {
           println!("condition was false");
         } else {
           println!("condition was false");
         }
       }
    
  • 例子:else if
   fn main() {
     let number = 3;
     if number % 4 == 0 {
       println!("number is divisaible by 4");
     } else if number % 3 == 0 {
       println!("number is divisaible by 3");
     } else {
       println!("number is not divisaible by 3 or 4");
     }
   }

在let語句中使用if

  • 因?yàn)閕f是一個(gè)表達(dá)式, 所以可以將它放在let語句中等號(hào)的右邊(例子)
 fn main() {
   let condition = ture;
   let muber = if condition {5} else {6};
   println!("The value of number is {}", number);
 }

Rust的循環(huán)

  • Rust提供了3中循環(huán):loop, while 和 for.

loop 循環(huán)

loop關(guān)鍵字告訴Rust反復(fù)地執(zhí)行一塊代碼没炒,直到你喊停位置
可以在loop循環(huán)中使用break關(guān)鍵字來告訴程序何時(shí)停止循環(huán)
例子

   fn main() {
   let mut counter = 0;
      let resut = loop {
        counter += 1;
        if counter == 10 {
          break counter * 2;
        }
      }

      println!("The result is: {}", result);
   }

while條件循環(huán)

  • 另外一種常見的循環(huán)模式是每次執(zhí)行循環(huán)體之前都判斷一次條件.

  • while條件循環(huán)就是為這種模式而生的

  • 例子

       fn main() {
         let mut number = 3;
         while number != 0 {
           println!("{}!", number);
           number = number -1;
         }
         println!("LIFTOFF!!!");
       }
    
    

    for 循環(huán)遍歷集合

    • 可以使用while 或 loop 來遍歷機(jī)會(huì)涛癌, 但是易出錯(cuò)且低效.

    • 使用for循環(huán)更簡(jiǎn)潔緊湊, 它可以針對(duì)集合中的每一個(gè)元素來執(zhí)行一些代碼

    • 例子

      fn main() {
        let a = [10, 20, 30, 40, 50]
        for element in a.iter() {
          println!("the value is: {}", element);
        }
      }
      
    • 由于for循環(huán)的安全,簡(jiǎn)潔性拳话,所以它在Rust里用的最多

    Range

    • 標(biāo)準(zhǔn)庫提供
    • 指定一個(gè)開始數(shù)字和一個(gè)結(jié)束數(shù)字先匪,Range可以生成它們之間的數(shù)字(不包含介紹)
    • rev方法可以反轉(zhuǎn)range
    • 例子
         fn main() {
           for number in (1..4).rev() {
             println!("{}!", number);
           }
           println!("LIFTOFF!");
         }
      

第四章: 所有權(quán)

4.1 什么是所有權(quán)

  • Rust的核心特性就是所有權(quán)
  • 所有程序在運(yùn)行時(shí)都必須管理它們使用計(jì)算機(jī)內(nèi)存的方式
      - 有些語言有垃圾收集機(jī)制,在程序運(yùn)行時(shí)弃衍, 它們不斷地尋找不再使用的內(nèi)存
      - 在其他語言中呀非,程序員必須顯示地分配和釋放內(nèi)存
      - Rust采用了第三種方式
      - 內(nèi)存是通過一個(gè)所有權(quán)系統(tǒng)來管理的,其中包含一組編譯器在編譯時(shí)檢查的規(guī)則.
      - 當(dāng)程序運(yùn)行時(shí)镜盯,所有權(quán)特性不會(huì)減慢程序運(yùn)行速度

Stack vs Heap

  • Stack按值的接收順序來存儲(chǔ)岸裙,按相反的順序?qū)⑺鼈円瞥?后進(jìn)先出, LIFO)
  • 添加數(shù)據(jù)叫做壓入棧
  • 移除數(shù)據(jù)叫做彈出棧
  • 所有存儲(chǔ)在stack上的數(shù)據(jù)必須擁有已知的固定的大小
    • 編譯時(shí)大小未知的數(shù)據(jù)或運(yùn)行時(shí)大小可能變化的數(shù)據(jù)必須存放在heap上
  • Heap內(nèi)存組織性差一些
    • 當(dāng)你把數(shù)據(jù)放入heap時(shí), 你會(huì)請(qǐng)求一定數(shù)量的空間
    • 操作系統(tǒng)在heap里找到一塊足夠大的空間速缆,把它標(biāo)記為在用降允,并返回一個(gè)指針,也就是這個(gè)空間的地址
    • 這個(gè)過程叫做在heap上進(jìn)行分配艺糜, 有時(shí)僅僅稱為分配
    • 把值壓到stack上不叫分配
    • 因?yàn)橹羔樖且阎潭ù笮〉哪飧猓】梢园阎羔槾娣旁趕tack上.
       但如果想要實(shí)際數(shù)據(jù),你必須使用指針來定位
    • 把數(shù)據(jù)壓倒stack上要比在heap上分配快得多:
       - 因?yàn)椴僮飨到y(tǒng)不需要尋找用來存儲(chǔ)新數(shù)據(jù)的空間倦踢,那個(gè)位置永遠(yuǎn)在stack的頂端
    • 在heap上分配空間需要做更多的工作:
       - 操作系統(tǒng)首先需要找到一個(gè)足夠大的空間來存放數(shù)據(jù)送滞,然后要做好記錄方便下次分配
       - 訪問heap中的數(shù)據(jù)要比訪問stack中的數(shù)據(jù)慢,因?yàn)樾枰ㄟ^指針才能找到heap中的數(shù)據(jù)
        - 對(duì)于現(xiàn)代的處理器來說辱挥, 由于緩存的緣故犁嗅,如果指令在內(nèi)存中跳轉(zhuǎn)的次數(shù)越少,那么速度就越快.
        - 如果數(shù)據(jù)存放的距離比較近晤碘,那么處理器的處理速度就會(huì)更快一些(stack上)
        - 如果數(shù)據(jù)之間的距離比較遠(yuǎn)褂微,那么處理速度就會(huì)慢一些(heap上)
        - 在heap上分配大量的空間也是需要時(shí)間的
        - 當(dāng)你的代碼調(diào)用函數(shù)時(shí),值被傳入到函數(shù)(也包括指向heap的指針).函數(shù)本地的變量被壓到stack上园爷,當(dāng)函數(shù)結(jié)束后宠蚂,這些值會(huì)從stack上彈出.

所有權(quán)存在的原因

  • 所有權(quán)解決的問題
    • 跟蹤代碼的哪些部分正在使用heap的哪些數(shù)據(jù)
    • 最小化heap上的重復(fù)數(shù)據(jù)量
    • 清理heap上未使用的數(shù)據(jù)以避免空間不足
  • 一旦你懂得了所有權(quán),那么就不需要經(jīng)常去想stack或heap了
  • 但是知道管理heap數(shù)據(jù)是所有權(quán)存在的原因童社,這有助于解釋它為什么會(huì)這樣工作.

所有權(quán)求厕,內(nèi)存與分配

所有權(quán)規(guī)則

  • 每個(gè)值都有一個(gè)變量,這個(gè)變量是該值的所有者
  • 每個(gè)值同時(shí)只能有一個(gè)所有者
  • 當(dāng)所有者超出作用域的時(shí)候扰楼,該值將被刪除.

變量作用域

  • scope就是程序中一個(gè)項(xiàng)目的有效范圍

  • 例子

      fn main() {
        //s 不可以
        let s = "hello"; // s可用
                 //可以對(duì)s進(jìn)行相關(guān)操作
      } //s 作用域到此結(jié)束呀癣,s不再可用
    

    string 類型

    • String 比那些基礎(chǔ)標(biāo)量數(shù)據(jù)類型更復(fù)雜
    • 字符串字面值: 程序里手寫的那些字符串值.它們是不可變的
    • Rust還有第二種字符串類型: String
      • 在heap上分配,能夠存儲(chǔ)在編譯時(shí)未知數(shù)量的文本
    創(chuàng)建String類型的值
    • 可以使用from函數(shù)從字符串字面值創(chuàng)建出String類型
       let s = String::from("hello");// :: 表示from是String類型下的函數(shù)
    

    這類字符串是可以被修改的

       fn main() {
         let mut s = String::from("Hello");
         s.push_str(", world");
         pringln!(s);
       }
      
    
  • 字符串字面值弦赖,在編譯時(shí)就知道它的內(nèi)容项栏, 其文本內(nèi)容直接被編碼到最終的可執(zhí)行文件里

    • 速度快,高效蹬竖,得益于其不可變性
  • String類型沼沈,為了支持可變性流酬, 需要在heap上分配內(nèi)存來保存編譯時(shí)未知的文本內(nèi)容:

    • 操作系統(tǒng)必須在運(yùn)行時(shí)來請(qǐng)求內(nèi)存
      • 這步通過調(diào)用String::from來實(shí)現(xiàn)
    • 當(dāng)用完String之后,需要使用某種方式將內(nèi)存返回給操作系統(tǒng)
      • 這步列另,在擁有GC的語言中康吵,GC會(huì)跟蹤并清理不再使用的內(nèi)存
      • 沒有GC的語言中,就需要我們?nèi)プR(shí)別內(nèi)存何時(shí)不再使用访递,并調(diào)用代碼將它返回
        • 如果忘了晦嵌,那就浪費(fèi)內(nèi)存.
        • 如果提前做了,變量就會(huì)非法
        • 如果做了兩次拷姿,也是bug. 必須一次分配對(duì)應(yīng)一次釋放
      • rust采用了不同的方式: 對(duì)于某個(gè)值來說惭载,當(dāng)擁有它的變量走出作用范圍時(shí),內(nèi)存會(huì)立即自動(dòng)的交換給操作系統(tǒng).
      • drop函數(shù)

變量和數(shù)據(jù)交互的方式: 移動(dòng) move

  • 多個(gè)變量可以與同一個(gè)數(shù)據(jù)使用一種獨(dú)特的方式來交互

    let x = 5;
    let y = x;
    

    整數(shù)是已知且固定大小的簡(jiǎn)單的值响巢, 這兩個(gè)5被壓到了stack中

      let s1 = String::from("hello");
      let s2 = s1;
    

    一個(gè)String 由3部分組成:

    • 一個(gè)指向存放字符串內(nèi)容的內(nèi)存的指針
    • 一個(gè)長(zhǎng)度
    • 一個(gè)容量
  • 上面這些東西被放在stack上.

  • 存放字符串內(nèi)容的部分在heap上

  • 長(zhǎng)度len, 就是存放字符串內(nèi)容所需的字節(jié)數(shù)

  • 當(dāng)把s1 賦值給S2, String 的數(shù)據(jù)被復(fù)制了一份

  • 在stck上復(fù)制了一份指針描滔, 長(zhǎng)度, 容量

  • 并沒有復(fù)雜指針?biāo)赶虻膆eap上的數(shù)據(jù)

  • 當(dāng)變量離開作用域時(shí)踪古,Rust會(huì)自動(dòng)調(diào)用Drop函數(shù)含长,并將變量使用的heap內(nèi)存釋放.
      當(dāng)S1, S2離開作用域時(shí), 它們都會(huì)嘗試釋放相同的內(nèi)存

  • 二次釋放(double free) bug
    為了保證內(nèi)存安全:

  • Rust沒有嘗試復(fù)制被分配的內(nèi)存

  • Rust讓s1失效

    • 當(dāng)s1離開作用域的時(shí)候伏穆, rust不需要釋放任何東西

    • 試試看當(dāng)s2創(chuàng)建后拘泞,再使用s1是什么效果

          fn main() {
            let s1 = String::from("hello");
            let s2 = s1;
            println!("{}", s1); //這里會(huì)有編譯錯(cuò)誤,這里s1已經(jīng)失效了
          }
      
      • 淺拷貝(shallow copy)
      • 深拷貝(deep copy)
      • 你也許會(huì)將復(fù)制枕扫, 指針陪腌, 長(zhǎng)度, 容量視為淺拷貝烟瞧, 但是由于Rust讓s1失效了诗鸭, 所以我們用一個(gè)新的術(shù)語:移動(dòng)move
      • 隱藏了一個(gè)設(shè)計(jì)原則,Rust不會(huì)自動(dòng)創(chuàng)建數(shù)據(jù)的深拷貝
          - 就運(yùn)行時(shí)性能而言参滴, 任何自動(dòng)賦值的操作都是廉價(jià)的

    變量和數(shù)據(jù)交互的方式: 克隆(Clone)

  • 如果真想對(duì)heap上面的String 數(shù)據(jù)進(jìn)行深拷貝强岸, 而不僅僅是stack上的數(shù)據(jù),可以使用clone方法

        fn main() {
          let s1 = String::from("hello");
          let s2 = s1.clone();
          println!("{}", s1); 
        }

Stack上的數(shù)據(jù): 復(fù)制

  • Copy trait, 可以勇于想整數(shù)這樣完全放在stack上面的類型
    如果一個(gè)類型實(shí)現(xiàn)了Copy這個(gè)trait,那么舊的變量在賦值后仍然可用
  • 如果一個(gè)類型或者該類型的一部分實(shí)現(xiàn)了Drop trait,那么Rust不允許讓它再去實(shí)現(xiàn)Copy trait了

一些擁有copy trait的類型

  • 任何簡(jiǎn)單標(biāo)量的組合類型都可以是copy的
  • 任何需要分配內(nèi)存或某種資源的都不是copy的
  • 一些擁有Copy trait的類型
      - 所有的整數(shù)類型, 例如u32
    • bool
    • char
    • 所有的浮點(diǎn)類型, 例如f64
    • tuple 元組砾赔, 如果其所有的字段都是Copy的
       (i32, i32) 是
      (i32, String) 不是

所有權(quán)與函數(shù)

  • 在語義上蝌箍, 將值傳遞給函數(shù)和把值賦給變量是類似的:
  • 將值傳遞給函數(shù)將發(fā)生移動(dòng)或復(fù)制

返回值與作用域

  • 函數(shù)在返回值的過程中同樣也會(huì)發(fā)生所有權(quán)的轉(zhuǎn)移
  fn main() {
    let s1 = gives_ownership();
    let s2 = String::from("hello");
    let s3 = take_and_gives_back(s2);
  }

  fn gives_ownership() -> String {
    let some_string = String::from("hello");
    some_string
  }
  fn takes_and_gives_back(a_string: String) -> String {
    a_string
  }
  • 一個(gè)變量的所有權(quán)總是遵循同樣的模式:
  • 把一個(gè)值賦給其它變量時(shí)就會(huì)發(fā)生移動(dòng)
  • 當(dāng)一個(gè)包含heap數(shù)據(jù)的變量離開作用域時(shí),它的值就會(huì)被Drop函數(shù)清理过蹂,除非數(shù)據(jù)的所有權(quán)移動(dòng)到另一個(gè)變量上了

如何讓函數(shù)使用某個(gè)值十绑,但是不獲得其所有權(quán)聚至?

fn main() {
  let s1 = String::from("hello");
  let(s2, len)= calculate_length(s1);
  println!("the length of '{}' is {}", s2, len);
}

fn calculate_length(s: String) -> (String, usize) {
  let length = s.len();
  (s酷勺,length)
}

引用和借用

  • 一下例子中參數(shù)的類型是&String而不是String
  • &符號(hào)就表示引用:允許你引用某些值而不取得所有權(quán)
fn main() {
  let s1 = String::from("hello");
  let(s2, len)= calculate_length(s1);
  println!("the length of '{}' is {}", s2, len);
}

fn calculate_length(s: &String) -> usize {
  s.len()
}

我們把引用作為函數(shù)參數(shù)這個(gè)行為叫做借用
是否可以修改借用的東西?不行
和變量一樣扳躬,引用默認(rèn)也是不可變的

可變引用

可變引用有一個(gè)重要的限制: 在特定作用域內(nèi)脆诉,對(duì)某一塊數(shù)據(jù)甚亭, 只能有一個(gè)可變引用

  • 這樣做的好處是可在編譯時(shí)防止數(shù)據(jù)競(jìng)爭(zhēng)
    以下三種行為下會(huì)發(fā)生數(shù)據(jù)競(jìng)爭(zhēng):
  • 兩個(gè)或多個(gè)指針同時(shí)訪問同一個(gè)數(shù)據(jù)
  • 至少有一個(gè)指針用于寫入數(shù)據(jù)
  • 沒有使用任何機(jī)制來同步對(duì)數(shù)據(jù)的訪問
  • 可以通過創(chuàng)建新的作用域, 來允許非同時(shí)的創(chuàng)建多個(gè)可變引用(例子)
  • 不可以同時(shí)擁有一個(gè)可變引用和一個(gè)不變的引用
fn main() {
  let mut s1 = String::from("hello");
  let len = calculate_length(&mut s1);
  println!("Then length of '{}' is {}.", s1, len);
}

fn calculate_length(s: &mut String) -> usize {
  s.push_str(", world")
  s.len()
}

懸空引用Dangling References

  • 懸空指針(Dangling Pointer): 一個(gè)指針引用了內(nèi)存中的某個(gè)地址击胜, 而這塊內(nèi)存可能已經(jīng)釋放并分配給其它人使用了.
  • 在Rust里亏狰, 編譯器可保證引用永遠(yuǎn)都不是懸空引用:
    • 如果你引用了某些數(shù)據(jù), 編譯器將保證在引用離開作用域之前數(shù)據(jù)不會(huì)離開作用域
     fn main() {
       let r = dangle();
     }
     fn dangle() -> &String { // 這里編譯器會(huì)報(bào)錯(cuò)偶摔,因?yàn)閟出了此作用域?qū)?huì)被是放暇唾,而返回值是一個(gè)指向已經(jīng)被釋放區(qū)域的指針,這會(huì)導(dǎo)致問題辰斋,而rust在編譯期就杜絕了這種錯(cuò)誤.
       let s = String::from("hello");
       &s
     }
    
    
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末策州,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子宫仗,更是在濱河造成了極大的恐慌够挂,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件藕夫,死亡現(xiàn)場(chǎng)離奇詭異孽糖,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)毅贮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門办悟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人滩褥,你說我怎么就攤上這事誉尖。” “怎么了铸题?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵铡恕,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我丢间,道長(zhǎng)探熔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任烘挫,我火速辦了婚禮诀艰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘饮六。我一直安慰自己其垄,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布卤橄。 她就那樣靜靜地躺著绿满,像睡著了一般。 火紅的嫁衣襯著肌膚如雪窟扑。 梳的紋絲不亂的頭發(fā)上喇颁,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天漏健,我揣著相機(jī)與錄音,去河邊找鬼橘霎。 笑死蔫浆,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的姐叁。 我是一名探鬼主播瓦盛,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼外潜!你這毒婦竟也來了谭溉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤橡卤,失蹤者是張志新(化名)和其女友劉穎扮念,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體碧库,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡柜与,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了嵌灰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片弄匕。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖沽瞭,靈堂內(nèi)的尸體忽然破棺而出迁匠,到底是詐尸還是另有隱情,我是刑警寧澤驹溃,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布城丧,位于F島的核電站,受9級(jí)特大地震影響豌鹤,放射性物質(zhì)發(fā)生泄漏亡哄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一布疙、第九天 我趴在偏房一處隱蔽的房頂上張望蚊惯。 院中可真熱鬧,春花似錦灵临、人聲如沸截型。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宦焦。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間赶诊,已是汗流浹背笼平。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來泰國打工园骆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留舔痪,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓锌唾,卻偏偏與公主長(zhǎng)得像锄码,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子晌涕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容