Try   HackMD

社課額外補充教材 : 陣列 + 迴圈複習

如果你覺得社課還算輕鬆,這裡提供你一些額外的補充教材。
這些是我們在備課的時候覺得稍微進階,或是篇幅太多,而沒有放在正式社課的內容。


這邊有目錄,左邊也有,可以跳到自己想看的地方


建議必看的地方 :

  • 陣列的動態宣告
  • 陣列的遍歷
  • 常見的陣列
  • 一些有趣的用法
  • 陣列的陣列

陣列的動態宣告

我們知道陣列必須在宣告時就指定範圍,可是當不知道大小時就很頭痛。
這時候就要用到動態宣告。

使用方式

int size; int array[size]; //宣告時的大小可以換成變數

注意:

裡面的變數只能是整數(滿合理因為沒有"小數個東西"),
而且在舊的 C++ 中不支援這種使用方式。
當時只能使用像是 Array 或是 Vector(之後會教) 之類的資料結構實現。


陣列的遍歷

陣列的遍歷指的是針對陣列的每一項進行動作,也就是走過整個陣列。
雖然現在的陣列都能通過變數的形式得知長度,但之後可能會遇到能夠擴充的陣列,這時候就要使用這些方法遍歷陣列。

int l = sizeof(a); //a是目標陣列,不知道長度 int i=0; while(i<l){ std::cout<<a[l]<<' '; i++; } // //也等於 for(int i=0;i<sizeof(a);i++) std::cout<<a[i]<<' ';

不過,sizeof函數不能判斷當成資料傳入函數中的陣列,也就是說,它必須要和陣列的宣告位置處在同一函數中(例如:main函數。

所以,像是std::vector或std::array都有內建回傳陣列長度的函數,盡量用那些函數取得長度。

除此之外,c也有其他的方式遍歷陣列,不過都要求比較新的c版本(雖然也沒有多新。

for(auto i:a) //盡量用auto,因為它抓的資料型態很可能不是你想得那個 { std::cout<<i<<' '; //記住不能用a[i] }

到底什麼是陣列?

警告:以下提到的觀念牽扯到指標,非相關人員請立即撤離

你是否有想過為什麼 int,char,bool ,甚至連不是保留字的 std::string 都有陣列?
這是因為陣列不是一種資料型態,他是一種函式。
所謂陣列,是在宣告時替變數拉長他的空間,好放下更多同類型的資料。

也就是說 int a[2] 的大小會比 int a大兩倍

#include<iostream> int main(){ int a[2]; int b; std::cout << sizeof(a) << ' ' << sizeof(b); } //會輸出8 4

你可能有注意到我在 sizeof() 中的 a 後面並沒有中括號,很明顯不符合之前說的陣列使用。
其實不盡然,所以我們接下來就要談談中括號的意義是什麼,以及它為什麼從0開始。

先說說陣列長什麼樣子,
以上面的a[2]為例:

記憶體位置 0 1
對應到的變數 a[0] a[1]
指標的表示 a a+1

雖然實際上的記憶體位置不會那麼剛好從

0 開始,但他們確實會連在一起。

對不知道指標是什麼的人先簡單說明一下:
指標宣告時並不會像變數一樣被分配空間,但是他們可以被指定位置。

#include<iostream> int main(){ int *a; //宣告指標 int b = 0; //宣告變數b為0 a = &b; //&會回傳位置,這行的意思是將a的位置設成b的位置 //換句話說,就是將a指向b *a = 4; //*是指標專用的運算字,目的是設置該指標的值(而非位置) //而因為a,b在同一位置上,所以b也會變成4 std::cout << *a << ' ' << b; //因為我們要輸出a 的值所以也要加* //會輸出4 4而非4 0 }

也就是說,除了以變數的方式儲存資料,我們也可以用指標指向一塊地方後存資料。

但是,既然指標一開始不會被分配空間,那要怎麼使用?
這裡其實有兩種方式,但我們這邊只提到第二種,也就是陣列。

複習一下a[2]的模樣:

記憶體位置 0 1
對應到的變數 a[0] a[1]
指標的表示 a a+1

沒錯,陣列在宣告時會創建和變數同名的指標,之後給它分配空間。
而因為空間是連在一起的,所以沒有必要再創立一個指標,只要用原本的指標呼叫即可。

也就是說

#include<iostream> int main(){ int a[2]; //同時會創建叫做a的指標 a[0] = 1; //第一項,也就是指標本身 a[1] = 3; //第二項,也就是指標後一塊 std::cout << *a << ' '; //先輸出指標本身,也就是第一項 std::cout << *(a+1); //再輸出第二項,也就是指標後一塊 //一定要加括號,否則會變成第一項的值加一 } //輸出會是1 3

你可能會發現,a[1] 的呼叫方式剛好就是 *(a+1)。
沒錯,中括號內加的數字就是指標後幾格(宣告時例外,指的是要多少項)。
那你有沒有想過, a[-1] 會發生什麼?會是錯誤嗎?

試試這個:

#include<iostream> int main(){ int a[2]; std::cout << a[-1]; //或你要寫 //std::cout<<*(a-1); //也可以 } //輸出是一個奇怪的數字,通常會很大

你如果發現輸出是零,這是因為你的編譯器對你很好

當嘗試調出未初始化的記憶體空間時,它會回傳一個亂碼,
這也是為什麼會輸出亂碼的原因。

整理一下

  • 陣列在宣告時會創立一個指標
  • 之後會依據中括號的數值建立項數
  • 在使用時[]表示的是指標的偏移
a[0] == *a a[1] == *(a+1) a[-1] == *(a-1)

常見的陣列

這裡其實在之前的補充講義有稍微的提到,在const那邊

"hello world" 的資料型態是const char[12] //不知道為什麼是11+1的回去重看 "CRC" 的資料型態是const char[4]

一些有趣的用法

我現在要給你五個數字,請把他們依據除以3的餘數分類,
並輸出餘數分別為0,1,2的數量。
用if寫:

#include<iostream> int main(){ int zero = 0; int one = 0; int two = 0; int input; int times = 5; while(times--) { std::cin>>input; switch(input%3){ case 0: zero++; break; case 1: one++; break; case 2: two++; break; } } std::cout<<"餘數為零的有"<<zero<<"個"<<'\n'; std::cout<<"餘數為一的有"<<one<<"個"<<'\n'; std::cout<<"餘數為二的有"<<two<<"個"<<'\n'; }

雖然這題並不困難,但總覺得很麻煩不是嗎?
試試不用if寫寫看吧,你可能會想:這怎麼可能?
答案是可能的喔

解答
#include<iostream> int main(){ int input; int times = 5; int mod[3]; while(times--){ std::cin >> input; mod[input % 3] ++; } std::cout<<"餘數為零的有"<<mod[0]<<"個"<<'\n'; std::cout<<"餘數為一的有"<<mod[1]<<"個"<<'\n'; std::cout<<"餘數為二的有"<<mod[2]<<"個"<<'\n'; }

沒錯,中括號內的值不一定要是常數,所以可以用這種方式寫。


陣列的陣列

既然在之前有提到任何資料型態都有陣列,那有沒有陣列的陣列?
有,這被叫做二維陣列。
使用方式:

int a[2][2]; //宣告 a[0][0] = 1; //第一項的賦值 a[0][1] = 2; //第二項 a[1][0] = 3; //第三項 a[1][1] = 4; //第四項

為什麼會長這樣呢?
如果我們用大括號表示陣列

int a[2] = {1,2};

那這就是二維陣列的樣子

int a[2][2] = {{1,2},{3,4}}; int b[3][2] = {{1,2},{3,4},{5,6}};

也就是說,a的前面中括號指的是裡面有幾個一維陣列,
而第二個中括號則是每個一維陣列內有幾項。

所以也可以用這樣的方式表示(以a[2][2]當示範)

陣列\內容 第一項 第二項
第一個陣列 1 2
第二個陣列 3 4

所以,第二項是a[0][1],而第三項是a[1][0]

其實還有三維、四維等陣列,概念和二維陣列是相同的。

而至於他們的組成……
你有聽過…指標的指標嗎?