前言
這邊要來整理長期我在 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」
* Range-based for loop
* Range-based for loop in C++
他一開始出現的用意,是為了幫助我們簡化寫 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 &」的方法會更好。