【C++ 觀念理解 #3】C++ 理解 for loop (迴圈) 為什麼有時候會有 * ,為什麼有時候會有 &

前言

這邊要來整理長期我在 C++ 感到困惑的一個問題,
就是為什麼有時候 for 迴圈 會有 &,有時候 for 迴圈 又沒有 &,
然後有時候沒有 & 也跑得過,有時候看範例卻又不是這樣寫。

直接看程式碼再來解釋

下面這一段程式碼是一個簡化後的例子,它可以用來解釋我目前困惑的所有問題。

我們單純的讓程式印出從 0 到 9,
也宣告了另外一個 arr 從 9 一路到 0。

#include <iostream>

using namespace std;

int main()
{

    for (int i = 0; i < 10; ++i)
    {
        cout << i;
    }
    cout << endl << "---------------------" << endl;

    int arr[10] = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
    for (int i = 0; i < 10; ++i)
    {
        cout << arr[i];
    }
    cout << endl << "---------------------" << endl;

    for (int* it = std::begin(arr); it != std::end(arr); ++it){
        cout << it << ", "; // it: address      
        cout << *it << endl; // *it get value       
    }
    cout << endl << "---------------------" << endl;


    for (int &ele : arr)
    {
        cout << ele;
    }
    cout << endl << "---------------------" << endl;

    return 0;
}

結果

解釋區

上面的程式碼大致分為四段,讓我理解了非常多的事情,還有觀念混淆的地方

第一段 for loop 的例子,最單純的數值增加

for (int i = 0; i < 10; ++i)
{
    cout << i;
}

第一段例子是每一位學 c++ 新手都一定最早學到的例子,
他就是一個單純的數值增加,
但大部分開始混亂,都是從下面那幾個例子才開始出現的,例如我XD。

第二段 for loop 的例子,我們是在用 for 增加變數,透過變數去拿取物件

int arr[10] = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
for (int i = 0; i < 10; ++i)
{
    cout << arr[i];
}

到這裡應該也還不算太混亂,就只是一個透過 array 的 index 去拿取對應的 arr 數值。

第三段 for loop 的例子 (重要),其實,我們是直接去地址拿值!

for (int* it = std::begin(arr); it != std::end(arr); ++it){
    cout << it << ", "; // it: address      
    cout << *it << endl; // *it get value       
}

讓我觀念開始混亂,就是從這個例子開始的,
碰到 array 數值的拿取,第二種寫法或是第三種寫法都是主流,

  • 第二種寫法是透過「index」去拿取 arr 裡面的值
  • 第三種寫法是,「我們直接去對應的記憶體位置,把值拿出來

    所以這邊我們看到 for 開頭宣告是指標的 int
    因為我們是透過這個 int
    ,作為 iterator 去遍歷我們的 array 的內容。

    而這裡還有一個重要的觀念,注意輸出結果的部分,我們發現儲存的記憶體位置,
    每一次都是移動 4 個 bytes,並且連續。

    我們唯有透過正確的一次移動 4 bytes 的記憶體位置,才能夠拿到正確的值。
    (而我們怎麼知道「記憶體位置正確的移動」是 4 bytes? 注意到我們 iterator 定義的是 int* 了嗎! )

  • 而這個例子,經常有的延伸應用

    通常這樣子的寫法會被我們拿來遍歷各種更複雜的資料結構,例如像是 vector…
    這也是為什麼我們在 for 的後面,必須要宣告一個指標的原因,
    實際上我們就是在不斷地移動記憶體位置,好讓我們去拿取對應的值。

    第四段 for loop 的例子 (重要),其實,我們也是直接去地址拿值!

    第四種寫法出現後,更是讓我跟前面的第二、三個例子,整個大爆炸!!!
    這也是為什麼這次整理,一開始就在強調,
    現在就是要整理 for 迴圈後面出現的東西,
    為什麼有時候有 * ? 有 & ?
    他們一起出現的時候,我的觀念就爆了XDDD,
    開始懷疑人生,跟我是不是不適合寫程式XDDD

    for (int &ele : arr)
    {
        cout << ele;
    }
    

    這個寫法其實還蠻新鮮,是在 C++ 11之後才出現,
    因此如果使用 C++ 98 的編譯器是沒有辦法成功執行的。

    關於這個寫法的關鍵字,可以搜尋「Range-based for loop」

    他一開始出現的用意,是為了幫助我們簡化寫 for loop,
    但面對像我這種觀念不穩的人,這個東西出現之後整個就讓我觀念大爆炸XDDD,

    總之, 他做的事情非常簡單,就是去指定物件中去拿每一個地址的值。
    像上面的表示方法,因為我知道 arr 是 Int array,我就透過 int & 去拿每一個值。

    題外話,那不加 & 可以嗎? 為什麼有時候看到不加也可以?

    沒錯,不加也可以!
    但這也是我們另外今天要另外討論的另外一個主題!

    拿參考? 拿真實地址?

    為了解決這個問題,我們另外加了一點點程式碼,
    這邊我們應該直接看結果就知道差別了。
    (先跟人家說,沒錯,印出結果會是一樣的,但注意細節!)

    for (int ele : arr)
    {
        cout << &ele << ", " << ele << endl;
    }
    cout << endl << "---------------------" << endl;
    
    for (int &ele : arr)
    {
        cout << &ele << ", " << ele << endl;
    }
    cout << endl << "---------------------" << endl;
    
    • 與他對應的結果

    有注意到差別了嗎?仔細看上面「沒有 &」的例子,雖然記憶體位置好像感覺都一樣,
    但這並不好!!!
    這表示實際上我們多使用了一個 int ele 的記憶體空間。

    而且,再仔細觀察一下記憶體位置,
    你會發現下面的寫法跟第三個例子印出來的記憶體位址,結果完全一模一樣。

    因此上面的寫法實際上我們在 int ele 多跟記憶體要了一個 int 空間,
    但實際上這是完全沒有必要的,我們只是單純的要做指標的移動,
    不需要新的記憶體空間, 所以這邊使用「int &」的方法會更好。

    Reference