首頁 > 軟體

詳解Rust中三種迴圈(loop,while,for)的使用

2022-10-02 14:01:06

楔子

我們常常需要重複執行同一段程式碼,針對這種場景,Rust 提供了多種迴圈(loop)工具。一個迴圈會執行迴圈體中的程式碼直到結尾,並緊接著回到開頭繼續執行。

而 Rust 提供了 3 種迴圈:loop、while 和 for,下面逐一講解。

loop 迴圈

我們可以使用 loop 關鍵字來指示 Rust 反覆執行某一段程式碼,直到我們顯式地宣告退出為止。

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

這段程式碼會不停地在終端中列印 hello world,我們只能使用 Ctrl + C 來終止這種陷入無限迴圈的程式。當然,Rust 提供了另外一種更加可靠的迴圈退出方式,可以在迴圈中使用 break 關鍵字來通知程式退出迴圈。

fn main() {
    let mut x = 1;  // x 可變
    loop {
        println!("hello world");
        if x == 5 {
            break;
        }
        // 注意 x 必須是可變的
        // 否則此處報錯
        x += 1;
    }
    /*
    hello world
    hello world
    hello world
    hello world
    hello world
     */
}

列印了五遍就停止了,沒什麼好說的。但 loop 迴圈還支援返回值,我們舉個例子:

fn main() {
    let mut x = 1;
    let y = loop {
        if x == 5 {
            // break 之後的值就是整個 loop 的返回值
            break x * 2;
        }
        x += 1;
    };
    println!("y = {}", y);  // y = 10
}

如果 break 後面沒有值,那麼整個 loop 返回的就是空元組:

fn main() {
    let mut x = 1;
    let y = loop {
        if x == 5 {
            break;
        }
        x += 1;
    };
    println!("y = {:?}", y);  // y = ()
}

需要說明的是,無論 break 後面有沒有分號,它都是整個 loop 迴圈的返回值。

既然是 loop 迴圈是一個表示式,那麼除了賦值給一個變數之外,肯定也可以作為函數的返回值:

fn f() -> i32 {
    let mut x = 1;
    loop {
        if x == 5 {
            break x * 2;
        }
        x += 1;
    } // 此處結尾不可以有分號
}

fn main() {
    println!("{}", f());  // 10
}

注意 loop 迴圈的最後一定不能加分號,因為加了就會變成語句,而語句不會返回任何內容。所以在 if 表示式的時候我們囉嗦了那麼多關於表示式、分號的內容,就是因為這些概念在迴圈中同樣會體現。

下面的做法是錯誤的:

fn f() -> i32 {
    let mut x = 1;
    loop {
        if x == 5 {
            break x * 2;
        }
        x += 1;
    };  // 這裡加上了分號
}

我們一定不能這麼做,因為這會讓 loop 迴圈變成語句,而下面又沒有內容了,因此函數 f 會預設返回空元組。而函數的返回值簽名是 i32,於是出現矛盾,造成編譯錯誤。那麼下面這個例子可以嗎?

fn f() -> i32 {
    let mut x = 1;
    loop {
        if x == 5 {
            // break 語句結尾有沒有分號
            // 並不重要
            break x * 2;
        }
        x += 1;
    }
    33
}

答案是依舊不行,因為 loop 迴圈是一個表示式,而它下面還有表示式,違反了我們之前說的函數末尾只能有一個表示式的原則。但是有一個例外,相信你已經猜到了,就是當 loop 表示式返回元組的時候,那麼會忽略掉。

fn f() -> i32 {
    let mut x = 1;
    loop {
        if x == 5 {
            // 等價於 break;
            break ();  
        }
        x += 1;
    }
    33
}

此時是沒有問題的,以上就是 loop 迴圈。

while 迴圈

另外一種常見的迴圈模式是在每次執行迴圈體之前都判斷一次條件,如果條件為真,則執行程式碼片段,如果條件為假、或在執行過程中碰到 break 就退出當前迴圈。

這種模式可以通過 loop、if、else 及 break 關鍵字的組合使用來實現,有興趣的話可以試著完成這一功能。不過由於這種模式太過於常見,所以 Rust 為此提供了一個內建的語言結構:while 條件迴圈。

fn main() {
    let mut x = 1;
    while x <= 5 {
        println!("hello world");
        x += 1;
    }
}

執行完之後會列印 5 次 hello world,然後是返回值的問題,while 迴圈不可以像 loop 一樣 break 一個值,也就是說它只能預設返回空元組。

fn f() -> i32 {
    let mut x = 1;
    while x <= 5 {
        if x == 3 {
            break;
        }
        x += 1
    }
    // 沒有下面這個 33,那麼該函數就是非法的
    33
}

fn main() {
    println!("{:?}", f());  // 33

    // 當然 while 迴圈也可以賦值給一個變數
    // 因為只可能返回空元組,所以這麼做沒有什麼意義
    let x = while 1 <= 2 {
        break;
    };
    println!("{:?}", x);  // ()
}

而當 break 後面有值的時候,會編譯錯誤,假設我們 break 123。

告訴我們帶有值的 break 只能出現在 loop 迴圈中,而 while 迴圈是不支援的。另外即便 break 一個空元組也是不允許的,儘管 while 迴圈會預設返回空元組。

for 迴圈

我們遍歷一個陣列可以選擇 loop 迴圈、while 迴圈,但是這樣容易因為使用了不正確的索引長度而使程式崩潰。

fn traverse1() {
    let arr = [1, 2, 3, 4, 5];
    let mut sum: i32 = 0;
    let mut index: usize = 0;
    loop {
        if index < 5 {
            // 通過索引獲取元素
            // 索引必須是 usize 型別
            sum += arr[index];
        } else {
            break;
        }
        index += 1;
    }
    println!("sum([1, 2, 3, 4, 5]) = {}", sum);
}

fn traverse2() {
    let arr = [1, 2, 3, 4, 5];
    let mut sum: i32 = 0;
    let mut index: usize = 0;
    while index < 5 {
        sum += arr[index];
        index += 1;
    }
    println!("sum([1, 2, 3, 4, 5]) = {}", sum);
}

fn main() {
    traverse1();  
    // sum([1, 2, 3, 4, 5]) = 15
    traverse2();  
    // sum([1, 2, 3, 4, 5]) = 15
}

雖然成功遍歷了,但如果索引越界的話就會發生錯誤,因此可以使用 for 迴圈這種更簡明的方法來遍歷集合中的每一個元素。

fn traverse() {
    let arr = [1, 2, 3, 4, 5];
    let mut sum: i32 = 0;
    for element in arr {
        sum += element;
    }
    println!("sum([1, 2, 3, 4, 5]) = {}", sum);
}

fn main() {
    traverse();  
    // sum([1, 2, 3, 4, 5]) = 15
}

結果是一樣的,但我們增強了程式碼的安全性,不會出現諸如越界存取或漏掉某些元素之類的問題。

假如後期修改程式碼,我們從 arr 陣列中移除了某個元素,卻忘記將回圈中的條件更新為 while index < 4,那麼再次執行程式碼就會發生崩潰。而使用 for 迴圈的話,就不需要時常惦記著在更新陣列元素數量時,還要去修改程式碼的其他部分。

for 迴圈的安全性和簡捷性使它成為了 Rust 中最為常用的迴圈結構,即便是為了實現迴圈特定次數的任務,大部分的 Rust 開發者也會選擇使用 for 迴圈。我們可以配合標準庫中提供的 Range 來實現這一目的,它被用來生成從一個數位開始到另一個數位結束之前的所有數位序列。

fn main() {
    for number in 1..4 {
        println!("number = {}", number);
    }
    /*
    number = 1
    number = 2
    number = 3
     */

    // 還可以逆序輸出
    for number in (1..4).rev() {
        println!("number = {}", number);
    }
    /*
    number = 3
    number = 2
    number = 1
     */
}

程式碼是不是更加精煉了呢。

到此這篇關於詳解Rust中三種迴圈(loop,while,for)的使用的文章就介紹到這了,更多相關Rust迴圈內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com