-- 簡書作者 謝恩銘 轉載請註明出處
第二部分第二課:進擊的指針,C語言的王牌!
終於來到了這一刻(課),是的,這一課我們就來看《C語言探索之旅》的重頭戲中的重頭戲:
指針!
-
如果把這個系列課程比做尋寶之旅的話,那麼指針就是最貴重的那個寶藏。
-
如果把C比做一棵佳美的葡萄樹,那麼指針就是那累累碩果;
-
如果把C比作太陽系,那麼指針就是我們美麗的地球;
-
如果把C比作人的一生,那麼指針就是令人神往的愛情;
-
如果一定要在這份愛上加一個期限,我希望是一萬年...
不好意思,又跑題了。
總而言之,言而總之,一起來享用這份精心烹製的指針大餐吧!
在開始這一課前深吸一口氣,因爲這一課可能不會像之前那些課一般“悠哉”了。
指針也許是C語言設計最出彩的地方了,也是精華部分。如果沒有了指針,C語言也會黯然失色。
可能你覺得我有點誇大其詞,但是在C語言的編程中,指針是隨處可見的,使用很廣泛,所以我們不得不來吃掉這個“燙手山芋”。
不少朋友學C語言的時候,指針那塊總是有點“蹣跚卻步”,在這一課裏我們會努力使你不再如此。你會發現,指針也沒那麼難麼。
好的開始是成功的一半,一起加油吧!
棘手的問題
對於初學C語言的朋友,除了覺得指針有點神祕之外,最大的問題之一可能是:
搞明白指針到底是用來幹什麼的。
對此,我會回答說:“指針絕對是必不可少的,我們會一直使用它,請相信我”。
你可能會丟我磚頭(說不定還有西紅柿和雞蛋) :P ,因爲我說了等於沒說。
好吧,我會給你一個問題,你會發現假如不用指針是不能解決的。這個問題有點類似我們這一課的引子。
在這一課的結尾我們還會重新來談這個問題,並嘗試用我們馬上要學到的知識來解決。
問題是這樣:
我要寫一個函數,它返回兩個值。
“這不可能!” 你會理直氣壯地說。
確實啊,之前我們學過:一個函數只能通過return返回一個值:
int function()
{
return value;
}
如上,我們將函數的返回值類型定爲int,那麼就用return返回一個int類型的值。
我們也可以不返回任何值,只要把函數的返回值類型定爲void:
void function()
{
}
你會說:“是啊,要一個函數一次返回兩個值,不可能啊,我們不能寫兩個return啊。臣妾做不到啊...”
假設我要寫的這個函數是這樣:
我們給它輸入的參數是一個分鐘數,它返回給我們兩個值:小時數和分鐘數。
例如:
傳給函數30,它返回0小時30分鐘
傳給函數60,它返回1小時0分鐘
傳給函數90,它返回1小時30分鐘
我們可以試着來寫一下這個函數:
#include <stdio.h>
/* 我把函數原型放在開頭了,並沒有用到.h頭文件,因爲程序實在太小了。當然在正常情況下,一般是用.h頭文件比較好 */
void transformMinutes(int hours, int minutes);
int main(int argc, char *argv[])
{
int hours = 0, minutes = 90;
/* 我們的分鐘數是 90。我想要在調用transformMinutes函數後,小時數變爲1,分鐘數變爲30 */
transformMinutes(hours, minutes);
printf("%d 小時 : %d 分鐘\n", hours, minutes);
return 0;
}
void transformMinutes(int hours, int minutes)
{
hours = minutes / 60; // 90 / 60 = 1
minutes = minutes % 60; // 90 % 60 = 30
}
看上去還不錯對麼?我們來運行一下這個程序。輸出:
0 hours and 90 minutes
不對,不對,這個函數沒有按照我們所想的來運行嘛!
到底這裏發生了什麼呢?
事實上,C語言的函數參數默認是傳值調用的,就是說當我們傳給函數的參數一個變量時,事實上傳遞的是這個變量的一份拷貝,並不是這個變量本身!
程序會先對這個要傳遞給函數的變量做一份拷貝,然後把這份拷貝傳給函數使用。
所以說:上面我們main函數裏的hours變量和實際傳給transformMinutes函數的hours,是不一樣的,傳給transformMinutes函數的只是hours變量的一個拷貝而已。就好比用複印機複印了一份紙張,內容是一樣,但是一個是原件,一個是複印件。
當然了,我們的函數transformMinutes很乖很呆萌,你叫它乾的活它肯定出色完成:在函數內部,它把參數hours和minutes的值通過計算轉換成了1和30。
但是,注意了,因爲transformMinutes拿到的兩個參數hours和minutes的值本身只是實際的變量hours和minutes的一份拷貝。
而且我們之前學過,在函數結束時,它裏面的非static變量都會銷燬,所以hours和minutes這兩個拷貝就都會被刪除了。
所以函數transformMinutes勤勤懇懇地工作之後,把程序交還給main函數繼續執行,但是hours和minutes這兩個main函數裏的變量的值並沒有被改變,還是0和90。可惜啊!
注意:函數的參數的名字和要傳給它的變量的名字不需要是一樣的,上例中我們爲了清楚表示含義,才把main函數裏的兩個變量和函數transformMinutes的兩個參數都命名爲hours和minutes。
其實你大可以把函數的參數寫成隨便什麼名字,例如:
void transformMinutes(int h, int m)
簡而言之,問題還是在那裏。
這裏我們需要用函數來改變兩個變量的值,我們不能用return,因爲一個函數只能return一個返回值;也不能用全局變量,雖然全局變量行得通,但是我們之前的課已經說了,儘量不用這種不安全的變量。
那麼指針到底能如何解決我們的難題呢?且聽我們慢慢道來。
內存,地址的問題
往事重提
“When I was young, I listened to the radio, waiting for my favorite song...”
不好意思,我搞錯了,不是《昨日重現》(Yesterday Once More)這首歌,我們說的是回顧一下之前“變量”的那一課。
不論你的回答如何,我都很建議你回去看一下《變量的世界》那一課的第一部分C語言探索之旅 | 第一部分第四課第一章:變量的世界之內存那檔事,因爲裏面有一張很重要的圖,我重新展示給你:
我們用上圖簡單地展示了我們的內存(RAM)。
我們應該一行一行地來“研究”這張圖:
第一行(地址爲0的那一行)展示了內存的第一個“區塊”(內存地址的最小單位是 1 個 byte,也就是一個字節,一個字節有8個比特位(bit))。
每一個“區塊”都有一個“門牌號碼”,就是它的地址。
好比我們有一排信箱,每個信箱上有不同的號碼,編號由小到大。
每個信箱裏儲存的東西就是信啦,就相當於內存地址上存放的數據。
但我們知道電腦數數是從0開始的(因爲二進制的關係),所以內存地址的第一位地址是0。
我們的內存一般有好多地址,從0一直到某一個比較大的數。一般來說,內存容量越大,可用地址就越多。比如4GB的內存的地址數就比1GB的多得多。
在每一個內存地址上,我們都可以存放一個數,也只能存放一個數,一個內存地址不能存放兩個數。
內存的主要功用就是存儲數值嘛。它不能存儲字母也不能存儲句子(因爲電腦只認識0和1組成的數字)。
爲了解決這個問題,計算機先驅們創立了一個表,這個表格建立了字符與數字的一一對應關係,比較常用的就是ASCII碼錶(更全面的是Unicode表)。
在這個表裏,字母“Y”對應的數字是89,字母“a”對應的數字是97,等等。大家可以Google或百度一下。
在之後的課程裏我們會再討論字符的處理,目前我們先把注意力集中在內存的功用上。
地址和值
當我們創建了一個int類型的變量,例如:
int age = 10;
實際上,你的程序首先請示一下操作系統(比如說Windows):“能否撥一點內存給我用用?”。
一般來說都是可以的,於是操作系統“告訴”你哪一小塊內存地址可以用來存放我們的age變量的值。
上面所說的其實也正是操作系統的一個主要任務:分配內存給程序。它好像一個大boss、大管家,控制每個程序,確認程序是否有使用某一塊內存區域的權利。
這其實也是我們的程序很多時候奔潰的原因:如果你的程序試圖操作一塊沒有使用權的內存,那麼操作系統就會“橫加干涉”,“粗暴”地停止你的程序(老大,就是這麼任性...)。
用戶就會看到一個“美麗”的窗口彈出來,裏面寫着“出現了一個問題,導致程序停止正常工作,Windows正在尋找問題的解決方案” (但其實一般是沒什麼解決方案的,比爾.蓋茨只會忽悠你)。
如下圖:
重新說回我們的變量age。因爲將它的值定爲10,所以在內存的某個地址上就儲存了10這個值,假設內存地址爲12345。
那我們的程序編譯的時候會發生什麼呢?對了,還記得我們的編譯器麼。它負責把我們的源代碼轉成電腦可以理解的二進制數。所以age這個變量在程序運行時就被12345這個地址所取代了。
這使得每次你在程序裏調用age這個變量時,電腦都會把其替換爲12345這個地址,並且去內存中的12345這個地址取它的值。
所以我們就知道變量的值是怎麼被取得的了。在程序中,我們只需要在想要使用變量的值的地方簡單地輸入變量的名字就可以了。例如我們要顯示age的值,我們可以這樣調用printf函數:
printf("變量age的值是 : %d\n", age);
運行程序會顯示:
變量age的值是 : 10
至此,並沒有太多新的知識點。但是...
稍微來點勁爆的
我們已經知道怎麼顯示變量的值了,但是你可知道我們也可以顯示變量在內存上的地址?
是的,你沒有聽錯。
爲了顯示變量的地址,我們需要使用符號組合 %p(p是pointer的首字母,pointer就是英語“指針”的意思!)。
而且我們這次不是把age傳給printf函數這麼簡單啦,我們是要傳遞age變量的地址。
爲了做到這一點,我們需要在age前面加上&這個符號(爲了取得地址也是蠻拼的)。
還記得我們之前的課裏面,說到scanf函數的用法時,就是用的&age這樣的形式,那時候沒有解釋爲什麼,現在你知道了吧。
因此,我們的代碼可以這麼寫:
printf("變量age的地址是 : %p", &age);
運行的結果是:
變量age的地址是 : 0034FFE6
你看到的 0034FFE6就是我運行程序時得到的age的地址的值。
是的, 0034FFE6是一個數,而且是用16進制來表示的。當然你可以將上述代碼中的 %p 改寫爲 %d,那就能看到以十進制來表示的age的地址值了。
當然了,你運行程序得到的地址值一般來說肯定與我的不一樣,這個值其實是電腦分配給age變量的一個可用地址值,所以每個人運行出來的結果不盡相同。
你也可以多次運行程序,可以看到這個值沒變,因爲這段時間裏,內存還是保持原樣,如果你重啓電腦,再次運行程序,那麼這個值一般會變得不一樣了。
上面的知識點,可以歸納如下:
-
age :表示變量的值
-
&age :表示變量的地址
不難吧~
指針的使用
到目前爲止,我們還只是創建了儲存數值的變量。
現在,我們一起來學習如何創建儲存地址的變量:也就是我們所說的指針。
但是,你又會問:“內存的地址也是數值啊?搞了半天都是存儲數值啊,那指針和一般變量到底有什麼區別呢?”
好問題!
爲什麼說指針特殊呢?因爲指針存儲的地址值,指明瞭內存中另一個變量的地址。
創建指針
爲了創建一個指針類型的變量,我們需要在變量名前再加一個*號。例如:
int *myPointer;
注意: 上面的代碼也可以寫成
int* myPointer;
效果是一樣的; 但是建議使用第一種寫法,因爲第二種寫法容易讓人搞混。
假如一行裏同時聲明好幾個指針變量,也許會寫成:
int* pointer1, pointer2, pointer3;
我們以爲pointer2和pointer3也是int 的指針變量,但其實不是,它們只是int變量,因爲號的結合優先級是從左到右,所以上面這行代碼的實際效果是:
創建了三個變量,pointer1是一個指向int類型的指針變量,pointer2和pointer3都只是int類型的普通變量。
所以正確的寫法應該是這樣:
int *pointer1, *pointer2, *pointer3;
之前的課裏,我們說過,在聲明變量的同時最好初始化,這樣可以保證變量的值不是任意的。
這個原則也適用於指針,而且對於指針來說初始化尤爲重要,以後會說爲什麼。
初始化一個指針變量,就是給它賦一個默認值,我們不用0,而是用NULL(注意在C語言中是大寫,其他語言如Java中null是小寫)。
int *myPointer = NULL;
這下,初始時你的指針裏面不包含有效地址。
實際上,這段代碼會在內存中佔用一塊區域,就和普通的變量定義沒什麼兩樣。但是,不同的是,這塊區域(內存地址)上存放的是指針的值,而這個值是一個地址值,一個其他變量的地址。
那我們何不來試試把變量age的地址賦給一個指針呢?如下:
int age = 10;
int *pointerOnAge = &age;
- 第一行的意思是:“創建一個int型的變量,名字是age,值爲10”;
- 第二行的意思是:“創建一個指針變量,名字是pointerOnAge,值爲變量age的地址”。
你肯定注意到了,我們雖然說“指針變量”,但是並沒有一種特定類型叫“指針”,就像int,douboe這樣的基礎類型(雖然有的書上說有“指針”這個類型),我們並沒有像下面這樣寫(只是打個比方,pointer是英語“指針”的意思):
pointer pointerOnAge; // 這種寫法不存在,因爲C語言沒有pointer這個類型
相反地,我們用了符號*來聲明一個指針變量,而類型我們還是給它int這樣的基本類型,這意味着什麼呢?
事實上,我們必須指明指針所要包含其地址的那個變量的類型(有點拗口)
比如上例中,我們的pointerOnAge指針需要包含age的地址,而age變量的類型是int,因此我們就必須將指針的類型定爲 int*
如果age變量的類型是double,
double age = 10;
那麼我就得這樣聲明我的指針:
double *pointerOnAge;
我們可以簡單地理解爲:
一個基本的數據類型(如int, double,包括結構體等自定義類型(關於自定義類型,我們馬上就可以學到了)加上*號就構成了一個指針類型的“模子”。
這個“模子”的大小是一定的,與*號前面的數據類型無關。
*號前面的數據類型只是說明指針所指向的內存裏存儲的數據類型。
所以,在32 位 系統下,不管什麼樣的指針類型,其大小都爲4個Byte(字節,8個二進制位)。可以測試一下sizeof(void *)
術語: “指針pointerOnAge指向age變量”
下圖給大家一個更直觀的展示,到底內存裏是怎麼一回事:
上圖中,age變量被存放在地址177450上,我們可以看到它的值是10;而我們可愛的指針變量pointerOnAge被存放在地址3上(這裏的地址值只是舉個例子)。
當我的指針變量pointerOnAge被創建時,操作系統就在內存上給它分配了一塊地址,就跟創建age變量一樣。
但是不同的是,變量pointerOnAge有點特別,仔細看圖,它的值正是age變量的地址:
177450
好了,親愛的讀者,其實你已經瞭解了所有C語言程序的絕對奧祕!
我們已經做到了,是的,我們剛剛跨入了指針的美妙世界!(當然了,要精通指針的使用還有不少路要走,但是難道不應該給自己一點鼓勵嗎?)
那你要問了:“這個機制有什麼用呢?”
當然了,這個偉大的機制並沒能讓你的電腦成爲一臺可以煮咖啡的機器...
目前我們還只是有了一個指針變量pointerOnAge,它的值是age這個變量的地址,僅此而已(讀者:我讀書少,你可不要騙我...)。
用printf函數來輸出pointerOnAge這個變量的值吧:
int age = 10;
int *pointerOnAge= &age;
printf("%d\n", pointerOnAge);
上面的代碼輸出如下:
177450
沒什麼可驚訝的,我們用printf輸出pointerOnAge的值,它的值正是age變量的地址:
177450
那我們怎麼能夠取得pointerOnAge這個指針變量中儲存的內存地址上的那個變量的值呢(好拗口)?
我們需要再一次用到*號,但這次它的作用和上一次聲明指針變量時不一樣,這一次它的作用是:
取得指針所指向的地址上的變量值。
例如:
int age = 10;
int *pointerOnAge= &age;
printf("%d\n", *pointerOnAge);
運行以上程序,輸出爲:
10
太棒了,我們只是使用了*號,把它放在指針變量名前,就取得了它所指向的變量的值。
假如上面的程序中,我們在pointerOnAge前面寫的不是*號,而是&號,那麼printf輸出的就是pointerOnAge的地址值(是3)了。
但是你又要問了:
“大費周章使用指針幹什麼呢?我們好像把事情複雜化了,不是麼?
本來我們要輸出變量age的值,只需要直接調用age就好了,現在還要把age的地址賦給指針變量pointerOnAge,然後再用*號來提取pointerOnAge裏的地址值所存的變量值,就是age。
搞了半天是同一個東西,爲什麼要繞這麼一大圈呢?”
這個問題是很合理的。畢竟,誰能說你沒什麼道理呢?
但是不要急,學下去,你就會發現指針的妙處了。
請暫時不理會這個問題,目前主要是學習指針的功用,這個問題稍後自會“守得雲開見月明”的。
畢竟,老爺子 Dennis Ritchie(C語言之父 丹尼斯.裏奇)不是傻子,他不會只爲了好玩或者把事情搞複雜而發明指針的。
你說是不,老爺子。
必須要牢記的
在這一課中要牢記幾點:
- 對於一個普通變量,例如age變量:
age: 意味着“age變量的值”
&age: 意味着“age變量所在的地址”
- 對於一個指針變量,例如pointerOnAge變量:
pointerOnAge: 意味着“pointerOnAge的值” (這個值是一個地址)
*pointerOnAge: 意味着“pointerOnAge的值所標明的地址上的變量值”
下圖可以幫助你加深理解:
注意: 不要混淆了*號的作用
在聲明一個指針變量時,*號的作用只是表示我要創建一個指針變量:
int *pointerOnAge;
而在之後的程序中,當我們寫:
printf("%d\n", *pointerOnAge);
這裏的*號的作用不是說“我要創建一個指針變量”,而是“取得指針變量pointerOnAge儲存的地址所指向的變量的值”。
上面的概念是“根基性”的,指針的基本概念是比較難理解,即使你現在雲裏霧裏,沒什麼好羞愧的,小編以前也是花了很久才搞清楚指針到底怎麼回事。
現在看來有點抽象是完全正常的,慢慢來,不要急,隨着多看代碼和多寫代碼,會慢慢入門的。
傳遞指針給函數
指針的一個優勢就是用來傳遞給函數,作爲函數的參數,使得在函數裏面修改指針所指向的變量的值,就直接在內存上修改了,而不是像之前看到的那樣,只是修改了一份拷貝,並沒有真正修改到實際的那個變量。
怎麼做到呢?有好幾種方法,先來看第一種:
#include <stdio.h>
void triplePointer(int *pointerOnNumber);
int main(int argc, char *argv[])
{
int number = 5;
triplePointer(&number); // 將number變量的地址傳給函數triplePointer
printf("%d\n", number); // 顯示number的值。上面函數已經直接修改了number的值,因爲函數知道number的內存地址
return 0;
}
void triplePointer(int *pointerOnNumber)
{
*pointerOnNumber *= 3; // 將pointerOnNumber的值乘以3
}
運行程序,顯示:
15
函數triplePointer接受一個int*類型的參數(就是說指向int類型的指針)。
我們從main函數的開始處分析究竟程序裏發生了什麼吧:
-
創建變量number,類型是int,值爲5。
-
調用函數triplePointer,給它的參數是變量number的地址。
-
函數triplePointer接受了這個參數(number的地址),並把它傳遞給pointerOnNumber儲存。現在triplePointer函數的內部,我們就有一個叫pointerOnNumber的指針,它的值是number的地址。
-
因爲我們已經有了一個指向number的指針,我們就可以在內存中直接修改number的值了,而不是像普通的傳值調用那樣修改拷貝的值。我們只需要用 *pointerOnNumber來表示number的值。
上例中,我們將*pointerOnNumber 乘以3,其實就是直接將number的值乘以3。
-
函數triplePointer執行完成,把控制權交給main函數繼續執行,這時number的值已經變成15了,因爲函數triplePointer藉着指針直接修改了number的值(大有“挾天子以令諸侯”之勢)。
所以,藉着指針,我們不需要用return,也可以修改好多個變量的值,直接在內存上修改!就好像我們可以返回多個值一樣。
有了指針,函數不再只能返回一個值了。
你的問題又來了:“既然指針這麼好,那我們還要return語句幹嘛呢?”
好問題。
答案是:
這取決於你和你的程序。由你決定。要知道的是,return語句在C語言裏很有用也很常用的。
有時候我們可以返回一個值,來表明程序是否正常運行。假如出錯,則返回0(表示false);假如正常結束,則返回不爲0的整數,一般爲1(表示true)。也有反過來用的。
將指針傳給函數的另一種方式
剛纔的那段代碼中,我們在main函數裏並沒有聲明指針,只有一個變量number,唯一看到指針的地方是在triplePointer函數的定義中
現在我們就來看一下第二種方式,在main函數裏用到指針的方式:
#include <stdio.h>
void triplePointer(int *pointerOnNumber);
int main(int argc, char *argv[])
{
int number = 5;
int *pointer = &number; // pointer裏面儲存的是number的值
triplePointer(pointer); // 將pointer(值是number的地址)傳給函數
printf("%d\n", *pointer); // 用 *pointer 來顯示number的值
return 0;
}
void triplePointer(int *pointerOnNumber)
{
*pointerOnNumber *= 3; // 將number的值乘以3
}
運行程序,輸出:
15
對比一下兩個程序,有細微的差別,結果卻是相同的。
這兩個程序最關鍵的地方就是:傳給函數triplePointer的參數是變量number的地址。
第二個程序中,pointer的值就是number的地址,所以正確。
在函數printf中,我們用 *pointer來代替number。因爲它們兩者在內存中是一回事。
在以前的課中,我們寫過一個C語言的遊戲,就是那個第一個C語言小遊戲:《或多或少》(C語言探索之旅 | 第一部分第八課:第一個C語言小遊戲)。
其實在那個程序裏你也在不知不覺中使用了指針。
有的讀者可能想到了。是的,就是scanf函數。
還記得麼,我們當時有這樣一小段程序:
int number = 0;
scanf("%d", &number);
類似的,我們也是傳遞了number的地址給scanf函數,所以scanf就可以使用用戶通過鍵盤輸入的值來直接修改number的值了。
當然我們也可以這樣寫:
int number = 0;
int *pointer = &number;
scanf("%d", pointer);
這兩段小程序的效果是一樣的。
誰說“棘手的問題”了?
這一課就要結束了,是時候來調出我們的引子:那個所謂“棘手”的問題。
如果你跟着這一課學下來,這個問題應該難不倒你了吧?不妨試試。
給出我的解法,可以做對比:
#include <stdio.h>
/* 我把函數原型放在開頭了,並沒有用到.h頭文件,因爲程序實在太小了。當然在正常情況下,一般是用.h頭文件比較好 */
void transformMinutes(int *hours, int *minutes);
int main(int argc, char *argv[])
{
int hours = 0, minutes = 90;
/* 這一次我們傳遞了hours和minutes的地址 */
transformMinutes(&hours, &minutes);
// 這一次,數值如我們所願改變了
printf("%d 小時 : %d 分鐘\n", hours, minutes);
return 0;
}
void transformMinutes(int *hours, int *minutes)
{
// 記得,不要忘了取值符號(*),這樣你纔可以改變變量的值,而不是它們的地址。
*hours = *minutes / 60; // 90 / 60 = 1
*minutes = *minutes % 60; // 90 % 60 = 30
}
運行以上程序,輸出:
1 小時 : 30 分鐘
看到了嗎?自從有了指針,天空飄來五個字:“那都不是事”~
總結
-
每一個變量都儲存在內存中的確定地址上。
-
指針是一種特殊的變量。與普通變量存儲值不一樣的是,指針存儲的是地址,而在這塊地址上,儲存着一個變量(或者是另一個指針)。
-
如果我們將符號&放在一個變量前面,那麼可以得到這個變量的儲存地址,例如 &age。
-
如果我們將符號*放在一個指針變量名前,那麼可以得到指針所儲存的地址上存放的那個變量。
-
指針是C語言的精華,也是強大所在,但是一開始會顯得比較難。需要我們好好花時間來理解指針的機制,因爲很多其他知識點是建基於其上的。
當然,今天只是爲指針這一個難題開了一個頭,因爲目前我們還有很多概念沒講。
之後會慢慢深入,指針的強(ke)大(pa)絕不僅於此。
而且,我們也不能在這一課裏丟給大家太多知識點,假如這裏就講
- 指向指針的指針
- 數組指針
- 指針數組
- 結構體指針
- 函數指針
等等知識點,那還能不能愉快地玩耍了...
第二部分第三課預告:
今天的課就到這裏,一起加油咯。
下一次我們學習第二部分第三課:C語言探索之旅 | 第二部分第三課:數組
數組是最常用的一種數據類型,也是C語言的一個重點。