做者 謝恩銘,公衆號「程序員聯盟」(微信號:coderhub)。 轉載請註明出處。 原文:www.jianshu.com/p/e5e685b67…程序員
《C語言探索之旅》全系列編程
上一課是 C語言探索之旅 | 第二部分第一課:模塊化編程 。小程序
終於來到了這一刻(課)。是的,這一課咱們就來學習《C語言探索之旅》的重頭戲中的重頭戲:指針!數組
- 若是把這個系列課程比做尋寶之旅的話,那麼指針就是最貴重的那個寶藏。
- 若是把 C語言比做一棵佳美的葡萄樹,那麼指針就是那累累碩果;
- 若是把 C語言比做太陽系,那麼指針就是咱們美麗的地球;
- 若是把 C語言比做人的一輩子,那麼指針就是使人神往的愛情;
- 若是必定要在這份愛上加一個期限,我但願是一萬年...
「很差意思,又跑題了~」安全
總而言之,言而總之,一塊兒來享用這份精心烹製的指針大餐吧!bash
在開始這一課前,請深吸一口氣,由於這一課可能不會像以前那些課通常「悠哉」了。微信
指針也許是 C語言設計最出彩的地方了,也是精華部分。若是沒有了指針,C語言也會黯然失色。模塊化
可能你以爲我有點誇大其詞,可是在 C語言的編程中,指針是隨處可見的,使用很普遍,因此咱們不得不來吃掉這個「燙手山芋」。函數
很多朋友學 C語言的時候,指針那塊老是有點「蹣跚卻步」。在這一課裏咱們會努力使你再也不如此。你會發現,指針也沒那麼難。學習
好的開始是成功的一半,一塊兒加油吧!
對於初學 C語言的朋友,除了以爲指針有點神祕以外,最大的問題之一多是:搞明白指針究竟是用來幹什麼的。
對此,我會回答說:「指針絕對是必不可少的,咱們會一直使用它,請相信我!」
你可能會丟我磚頭(說不定還有西紅柿、雞蛋,等物品),由於我說了等於沒說。
好吧,我會給你一個問題,你會發現假如不用指針是不能解決的。這個問題有點相似咱們這一課的引子。
在這一課的結尾咱們還會從新來談這個問題,並嘗試用咱們立刻要學到的知識來解決。
問題是這樣:我要寫一個函數,它返回兩個值。
「這不可能!」,你會義正詞嚴地說。
確實,以前咱們學過:一個函數只能經過 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 songs...」
很差意思,我搞錯了,不是《昨日重現》(Yesterday Once More)這首歌,咱們說的是回顧一下以前「變量」的那一課。
咱們以前在 C語言探索之旅 | 第一部分第四課:變量的世界(一),內存那檔事 那一課中展現過一張很重要的圖,以下:
咱們用上圖簡單地展現了內存(RAM)。
咱們應該一行一行地來「研究」這張圖:
第一行(地址爲 0 的那一行)展現了內存的第一個「區塊」(內存地址的最小單位是 1 個 Byte,也就是一個字節,一個字節有 8 個比特位(bit))。
每個「區塊」都有一個「門牌號碼」,就是它的地址。比如咱們有一排信箱,每一個信箱上有不一樣的號碼,編號由小到大。每一個信箱裏儲存的東西就是信啦,就至關於內存地址上存放的數據。
但咱們知道電腦數數是從 0 開始的(由於二進制的關係),因此內存地址的第一位地址是 0。
咱們的內存通常有好多地址,從 0 一直到某一個比較大的數。通常來講,內存容量越大,可用地址就越多。好比 4GB 的內存的地址數就比 1GB 的多得多。
在每個內存地址上,咱們均可以存放一個數,也只能存放一個數,一個內存地址不能存放兩個數。
內存的主要功用就是存儲數值嘛。它不能存儲字母也不能存儲句子(由於電腦只認識 0 和 1 組成的數字)。
爲了解決這個問題,計算機先驅們創立了一個表,這個表格創建了字符與數字的一一對應關係,比較經常使用的就是 ASCII 碼錶(更全面的是 Unicode 表)。
在這個表裏,大寫字母 Y 對應的數字是 89,小寫字母 a 對應的數字是97,等等。你能夠去網上搜索 ASCII 表或 Unicode 表。
在以後的課程裏咱們會再討論字符的處理,目前咱們先把注意力集中在內存的功用上。
當咱們建立了一個 int 類型的變量,例如:
int age = 10;
複製代碼
實際上,你的程序首先會「請示」一下操做系統(Operating System,簡稱 OS。Windows、Linux、macOS、Unix,等等都是經常使用的操做系統):「可否撥一點內存給我用用?」。
通常來講都是能夠的,因而操做系統「告訴」你哪一小塊內存地址能夠用來存放 age 變量的值。
上面所說的其實也正是操做系統的一個主要任務:分配內存給程序。它好像一個大 boss、大管家,控制每一個程序,確認程序是否有使用某一塊內存區域的權利。
這其實也是咱們的程序不少時候奔潰的緣由:若是你的程序試圖操做一塊沒有使用權的內存,那麼操做系統就會「橫加干涉」,「粗暴」地中止你的程序(老大,就是這麼任性...)。
用戶就會看到一個「美麗」的窗口彈出來,裏面寫着「出現了一個問題,致使程序中止正常工做,Windows 正在尋找問題的解決方案」 (但其實通常是沒什麼解決方案的,微軟只會忽悠你...)。
以下圖:
好了,從新說回咱們的變量 age。由於咱們將 age 的值定爲 10,因此在內存的某個地址上就儲存了 10 這個值,假設內存地址爲 123456。
那咱們的程序編譯的時候會發生什麼呢?對了,還記得咱們的編譯器麼。它負責把咱們的源代碼轉成電腦能夠理解的二進制數。因此 age 這個變量在程序運行時就被 123456 這個地址所取代了。
這樣,每次你在程序裏調用 age 這個變量時,電腦都會把其替換爲 123456 這個地址,而且去內存中的 123456 這個地址取它的值。
因此,咱們就知道變量的值是怎麼被取得的了。在程序中,咱們只須要在想要使用變量的值的地方簡單地輸入變量的名字就能夠了。例如咱們要顯示 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 變量的一個可用地址值,因此每一個人運行出來的結果不盡相同。
你也能夠屢次運行程序,會看到這個值沒變,由於這段時間裏,內存仍是保持原樣。不過,若是你重啓電腦,再次運行程序,那麼這個值通常會變得不同。
上面的知識點,能夠概括以下:
不難吧~
到目前爲止,咱們還只是建立了儲存數值的變量。
如今,咱們一塊兒來學習如何建立儲存地址的變量,也就是咱們所說的指針。
可是,你又會問:「內存的地址也是數值啊。搞了半天都是存儲數值,那指針和通常變量到底有什麼區別呢?」
好問題!
爲何說指針特殊呢?由於指針存儲的地址值,指明瞭內存中另外一個變量的地址。
指針的英語是 pointer。point 在英語中表示「指向」,是動詞。pointer 在英語中表示「指針,指示器」,是名詞。
顧名思義,指針就是表示指向某些東西的一個概念。
爲了建立一個指針類型的變量,咱們須要在變量名前再加一個 * 號。例如:
int *myPointer;
複製代碼
注意,上面的代碼也能夠寫成
int* myPointer;
複製代碼
效果是同樣的。可是建議使用第一種寫法,由於第二種寫法容易引發誤解。
假如一行裏同時聲明好幾個指針變量,也許會寫成:
int* pointer1, pointer2, pointer3;
複製代碼
你可能覺得 pointer2 和 pointer3 也是 int* 的指針變量,但其實不是,它們只是 int 變量,由於 * 號的結合優先級是從左到右,因此上面這行代碼的實際效果是:
建立了三個變量,pointer1 是一個指向 int 類型的指針變量,pointer2 和 pointer3 都只是 int 類型的普通變量。
因此正確的寫法應該是這樣:
int *pointer1, *pointer2, *pointer3;
複製代碼
咱們在以前的課程裏說過,在聲明變量的同時最好初始化,這樣能夠保證變量的值不是任意的。
這個原則也適用於指針,並且對於指針來講初始化尤其重要,之後會說爲何。
初始化一個指針變量,就是給它賦一個默認值。咱們不用 0,而是用 NULL(null 表示「空,無效的,無價值的」。注意,在 C語言中 NULL 是大寫,其餘語言如 Java 中 null 是小寫)。
int *myPointer = NULL;
複製代碼
這樣,初始時你的指針裏面就不包含有效地址。
實際上,這段代碼會在內存中佔用一塊區域,就和普通的變量定義沒什麼兩樣。可是,不一樣的是,這塊區域(內存地址)上存放的是指針的值,而這個值是一個地址值,一個其餘變量的地址。
那咱們何不來試試把變量 age 的地址賦給一個指針呢?以下:
int age = 10;
int *pointerOnAge = &age;
複製代碼
你確定注意到了,咱們雖然用了「指針變量」這個詞,但其實並無一種特定的類型叫「指針」,就像 int,double 這樣的基礎類型(雖然有的書上說有「指針」這個類型)。咱們並無像下面這樣寫(只是打個比方,pointer 是英語「指針」的意思):
pointer pointerOnAge; // 這種寫法不存在!由於 C語言沒有 pointer 這個類型
複製代碼
相反地,咱們用符號 * 來聲明一個指針變量,至於類型咱們仍是給它 int 這樣的基本類型,這意味着什麼呢?
事實上,咱們必須指明指針所要包含其地址的那個變量的類型(有點拗口...)。
好比上例中,咱們的 pointerOnAge 指針須要包含 age 的地址,而 age 變量的類型是 int,所以咱們就必須將指針的類型定爲 int* 。
若是 age 變量的類型是 double,
double age = 10;
複製代碼
那麼就得這樣聲明咱們的指針:
double *pointerOnAge;
複製代碼
咱們能夠簡單地理解爲:
一個基本的數據類型(如 int, double,包括結構體等自定義類型(關於自定義類型,咱們立刻就能夠學到了)加上 * 號就構成了一個指針類型的「模子」。
這個「模子」的大小是必定的,與 * 號前面的數據類型無關。
因此,在 32 位系統下,無論什麼樣的指針類型,其大小都爲 4 個 Byte(字節,等於 8 個二進制位,也就是 8 個比特位)。在 64 位系統下,無論什麼樣的指針類型,其大小都爲 8 個 Byte。
咱們能夠用下面的語句測試一下:
printf("指針的大小是 %lu 字節\n", sizeof(void *));
複製代碼
你能夠把上面語句中的 void *
改成 int *
或者 double *
,等等,輸出都是同樣的。
爲了讓你知道到底內存裏是怎麼一回事,咱們用下圖給出一個更直觀的展現:
上圖中,age 變量被存放在地址 123456 上,咱們能夠看到它的值是 10;而咱們可愛的指針變量 pointerOnAge 被存放在地址 3 上(這裏的地址值只是舉個例子)。
當個人指針變量 pointerOnAge 被建立時,操做系統就在內存上給它分配了一塊地址,就跟建立 age 變量同樣。
可是不一樣的是,變量 pointerOnAge 有點特別。仔細看圖,它的值正是 age 變量的地址 123456 。
好了,親愛的讀者,其實你已經瞭解了全部 C語言程序的絕對奧祕!
咱們已經作到了!是的,咱們剛剛跨入了指針的美妙世界!(固然了,要精通指針的使用還有很多路要走,可是難道不該該給本身一點鼓勵嗎?)
那你要問了:「這個機制有什麼用呢?」
固然了,這個偉大的機制並沒能讓你的電腦成爲一臺能夠煮咖啡的機器...
目前咱們還只是有了一個指針變量 pointerOnAge,它的值是 age 這個變量的地址,僅此而已(「我讀書少,你可不要騙我...」)。
用 printf 函數來輸出 pointerOnAge 這個變量的值吧:
int age = 10;
int *pointerOnAge= &age;
printf("%d\n", pointerOnAge);
複製代碼
假設上面的代碼輸出以下:
123456
複製代碼
沒什麼可驚訝的,咱們用 printf 輸出 pointerOnAge 的值,它的值正是 age 變量的地址 123456。
那咱們怎麼可以取得 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 變量所在的地址」。
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語言小遊戲 )。
其實在那個程序裏你也在不知不覺中使用了指針。
聰明如你可能已經想到了。是的,就是 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語言的一個重點。
我是 謝恩銘,公衆號「程序員聯盟」(微信號:coderhub)運營者,慕課網精英講師 Oscar 老師,終生學習者。 熱愛生活,喜歡游泳,略懂烹飪。 人生格言:「向着標杆直跑」