好多同學都說:“老師,我看到指針就暈!”,說實話,見過暈血的,見過暈車的,暈指針的到是第一次聽說!
我們先來分析一下暈車的原理,再來對比一下為什麼暈指針。
暈車,是因為耳朵裡的一個器官對外界的振動太敏感,導致身體調節功能紊亂,系統不能正常工作,輕則,暈點,中則,吐點,重則,“重啟”(倒地)。時間長了,知道自己暈車,於是,看到公交車,TAXI,火車,自行車,都暈!這樣的病就大了,上升到心理疾病了。這可得治。我們都明白上面的道理,怎麼克服暈車呢?你不可能避免做車,你要去面對,怎麼克服暈車也很簡單,先是功心,“口服不如心服”,所以先治心病:建立信心。經常試著去做晃動不大的車,最好聽著MP3,這樣能減緩心理對暈車的敏感度。時間長了,慢慢的克服了心理作用,然後就要上升到治根的階段了,要去試著挑戰做公交,TAXI,船。
上面都是個人的一些見解,上大學時,要是不學計算機就去學醫了!白衣王子嗎(天使算不上,爺們嗎,王子就行了)。
回來我們開始治“暈指針”這號病,其實很多同學暈指針,也是因為自己基礎不好(身體素質不好),剛開始學的時候,不用心,還沒有弄的很明白,一編程(有的同學4年都沒編過一個程序)就錯,形成了恐懼心理,其實C指針非常靈活,它對學生的要求也比較高,說白了,它要求有計算機的組成原理一些基礎,如果有的話,你回頭看指針,So easy!
我們先開始“攻心”。
1. 暈指針,唐式偏方一:“投石問路”
#include <stdio.h>
int main(void)
{
char* str = "ABCDEFGHIJKL";
int* pInt = (int*)str;
printf("%c\n%c\n",*(str+1),*(char *)(pInt+1));
return 0;
}
上面的程序如果你要是灰常EASY的回答出來,那你的功底已經不錯了!心病是估計沒有了,可以進入吃藥保養階段了(做題)。
如果上面的題看起來就特別費勁,看來你暈的不輕,得治。要不問題會很嚴重。
基於上面的小測試,進入唐式第二方:“理氣靜心”
我們先看把C指針的基礎打好。在這之前先來復習下變量和常量,有的同學會問,這是為什麼,我只能告訴你,你暈C指針,是因為你從剛開始有問題就沒有重視,或者你忽略了問題的嚴重性,導致現在的情況,這也沒有辦法,中國的教育就不重視這一塊,說實話,大學裡講C語言的,很多都沒有太多的講到變量和常量,這是很XXX的。我們開始治療。
常量又分為:直接常量和符號常量。
直接常量又叫做:字面常量。如12,0,4.6,‘a‘,“abcd”
符號常量如宏定義的:#define PI 3.14
特點:常量的值在其作用域內不會發生改變,也不能再被賦值。其在出現時就被當作一個立即數來使用。也就是說,只能被訪問,被讀,而不能被寫,被賦值。
變量名是在,變量的聲明的時候,該名字就和內存中一塊地址綁定在一起了。可以通過變量名直接找到對應的內存區域,也可以通過地址找到其內存區域。因此有了引入指針的依據。
變量的值是變量所對應的內存區域內存放的二進制序列。當該變量被聲明成整形時,內存區域的二進制序列被以整形的形式翻譯出來。比如:int a = 97; 其在內存中是以97的二進制形式存放的,當使用時,他會被以10進制形式表現出來。同樣的char a = ‘a’; a的ASIIC碼是97,也是以97的二進制存放的,使用時,會被以字符a的形式表現出來。
如果變量是一個指針變量,那麼指針變量裡的二進制序列被翻譯成一個地址,
比如:
int a = 10;
int * p;
p = &a;
這裡的指針變量p的值是a的地址(p = &a),它是什麼啊?看下圖:
(說實話哥們圖畫的不錯,大學自學過PS,還TMD的拿過獎)
聲明了一個變量a,它是整型,被賦值為10(它的值被翻譯成整數),要形成這種思維,時間長了你就知道這樣做的好處了,又聲明了一個指針變量p,它是Int類型的(它指向的地址裡面要裝Int),然後將a變量的地址(ox2c406b24)給了p,這兒注意下。現在訪問a裡面的值有了兩種方式(其實本來也有這兩種),一個是通過變量名a(綁定的),一個是通過地址ox2c406b24,地址ox2c406b24給了p了,p指針變量(指針變量是變量,這個思維很重要)裡面存放的是ox2c406b24(a的地址),那麼現在訪問a可以通過:
printf("%d\n", a); //通過變量名
printf("%d\n", *p); //通過指針變量
如果你現在上面的都很明白了,那你有了暈指針好轉的跡象,只是跡象,(跡象だけです^_^)。現在驗證下是不是真的有好轉:
1、
char ch = 'a';
int a = (int)ch;
printf("%d %c\n", a, ch);
ch是什麼? ch 裡面是什麼? a是什麼? a裡面又是什麼?打印什麼?
2、
int add = 0x123456;
int * p = (int*)add;
add是什麼?add裡是什麼? P是什麼? P裡面是什麼? *p 又是什麼?
嘿嘿,暈不?別慌,再來。。。
3、
#define PI 3.14
int a = PI;
printf("%d\n", a);
上面的程序有沒有問題?
4、
#define PI 3.14
printf("%d\n", PI);
程序有沒有問題?
5、
#define PI 3.14
int a = PI;
PI = 1.85;
int b = PI;
printf("%d%d\n", a, b);
程序有沒有問題?
差不多頭疼的不行了吧,沒事,這是藥勁,好藥都這樣。
最後一個:
6、
char *str = "abcdef";
printf("%s\n", str);
*str = "fedcba";
printf("%s\n", str);
str[2] = 'C'; //修改第三個字符為大寫
printf("%s\n", str);
程序有沒有問題?
好,第一方到此為止,老中醫要休息會。
上一方藥勁比較重,年輕人嗎,口味要重點。現在給你們點解藥:
1、主要是測試類型轉換,還有對變量的理解是否到位。
2、對變量的值的理解是否到位
3、對常量,宏替換的理解是否到位
4、對宏替換的理解是否到位
5、能否修改常量的值?
6、能否修改字符串常量的值?
通過上面我們可以學到以下內容,重點,記下,考試要考(這話在學校裡很耳熟,但是XXX的這樣很不負責任):
1、不管什麼常量,其值是編譯是固定好的,不能再被改變
2、變量裡的值,和其數據類型沒有關系,它只是一個二進制序列,不要將電腦想的多聰明,它只認識0, 1,只不過,這一堆01被其類型限定了其代表的意義,類型為整形就是其值,指針地址類型,就是內存裡的一個地址,字符型,就是其無符號整形代表的ASCII碼。
3、字符串,是常量(字符指針指向一字符串,不是數組,數組和指針的區別在後面),其值不能再被改變,char * str = "abcdef";這行代碼的意思是告訴編譯器:老編啊,我這兒有個抽屜(指針變量str),你給我找個房間(存放字符串的內存空間),找到後,把鑰匙給我放到這個抽屜裡。老編去找客房經理,找到後問:有沒有空房間啊,有的話給我一間,一哥們要開房,客房經理去查房間入住情況(內存管理),最後說,這兒有一間,它是老總的房間,你要不先用著,你用沒事,你可別亂動裡面的東西,你要是動的話,估計你那哥們就被KILL掉,老板是黑社會的,不過,你用沒事。於是,老編將老總房間鑰匙給我放到了抽屜(指針變量str)裡,這樣,我打開抽屜(指針變量),拿出房間(內存空間)鑰匙(指針),去開房了,老編告訴的,別亂動,用沒事(讀取),一定不能破壞裡面的結構(修改數據內容),要是亂動(修改數據),會出亂子的(段錯誤),然後你會被KILL的(異常結束程序)。如果有兩個人,就會Double kill,老板就GOD LIKE了。
明白了常量和變量的區別後,再進行後面的學習就輕松點了!下面我們來看下指針的雙胞胎哥們,數組。一般暈車人裡面有很多也暈飛機,暈船,所以呢,暈指針的,也一般都會暈數組。
數組有幾個特點一定要注意,看到數組就要想到:
1、 數組裡面的數據類型是相同的,小組裡面的成員肯定要一樣的啊,驢堆裡站一馬,驢馬不分,雞窩裡蹲一丹頂鶴,鶴立雞群,這都是不合群的東西,計算機裡面也講“和諧”。我們順便看下數據的基本類型:int float double char 這些是基本類型,所以它們可以存在以下類似數組:int a[10], float f[10], doule d[10], char str[10]; 它們每一個都有10個元素,每一個元素的類型都是其前面聲明的類型。我們是學嵌入式的,我們不能光看到表面的東西,我們要看到底,好東西是走了光才叫好,光有好東西,別人不知道也不行。其實數組在內存中是連續分配的,如下圖:
定義了一char型數組a,它有6個元素,分別是'A', 'B', 'C', 'D', 'E', 'F',它們在內存裡面是連續存放的,每個元素占用一個字節。 強烈譴責那些將'A' 當成"A"的人,前者是單個字符,後者是字符串,鄙視那些,問“字符和字符串有什麼區別?”的人,不知道字符和字符串的區別,那你吃過羊肉串嗎?單個羊肉塊能叫串嗎,多個羊肉塊串起來才叫串,所以字符是單個,字符串可以是多個字符組成的數組(最後有一個結束符號\0),羊肉塊串起來一烤就叫烤羊肉串,字符串一拷,叫拷貝串,學計算機哪有那麼簡單,要是我開個學校,入學前要體檢,測視力,量身高體重,....,三圍什麼的,不行的PASS。
仔細看上面的圖,每一個字符都有一個地址,它們的跨度是1(字節),數組的每個元素都可以通過下標來訪問,下標(index, for循環變量經常用i就是因為這個東西)其實就是他在數組中的位置,也就是他的號,拉10個人過來,報數,1,2,3.....,只不過,C語言裡數組的下標是從0開始的,在計算機裡面能訪問的最小單位就是字節了,也就是地址只能找到以字節為單位,不能再精確了。數組名a和變量名道理上是一樣的,在編譯時就和數組的首地址綁定上了,a就是數組的首地址,變量名和數組名其實都是方便人們記憶而取的代號,它在代碼反匯編後,其實不存在變量名的,回想下,訪問數據有兩種方式,既然反匯編後的代碼不存在變量名,只能通過那種方式訪問數據了,那就是地址。數組中的每一個元素,可以被看成一個變量(回想下變量的特點),因此其可以被讀,寫,修改,愛怎麼得瑟怎麼得瑟,你只要不把房間拆了(內存空間),怎麼折騰都行。每一個元素的地址都可以通過首地址的偏移量(offset這個詞記住,四級裡沒有)來算出來,這個偏移量說白了就是下標了。比如上面的圖中:'C'所在地址0x28c5,相對首地址0x28c3的偏移量是2,那a[2]也可以訪問'C'了,注意一點,a代表數組,代表數組的首地址,代表數組第一個元素的地址,這“三個代表”一定要記住。那a+1呢,a是三個代表,那它是第一代表還是第二個代表,還是第三個代表呢?這兒的a應該是第一個元素地址的意思表示的意思應該是&a[0],a+1是個地址的算術運算,而數組是個一維數組,數組中每個元素都是一個字符,a+1就是a當前地址0x28c3的下一個元素的地址0x28c4(結合圖來看),也就是'B'的地址,如果a是一個二維數組名的話,那麼二維數組被編譯器理解為一個一維數組,一維數組裡的每一個元素是一個一維數組,有點亂,別慌,屢一下,看下圖:
a是個二維數組,它有3X4=12個字符元素,而編譯器將它認為是一個一維數組,它有三個元素,分別是a[0], a[1], a[2], 每一個元素是一個含有4個字符的數組,那麼a+1的話,是&a[0]的地址,a[0]是一個一給數組,取了一個數組的地址,再加1 ,肯定就是下一個數組的地址了,就是a[1]上圖,那麼這個時候它的地址增加可不再是1了,而是列數4,也就是說是a這個二維數組中元素(一維數組)的長度。
www.2cto.com 回到前面那個圖:a[5] - a[3] = ? 地址的運算,這裡算的也是元素的相對偏移量,結果當然是2,雖然0x28c8- 0x28c6 = 2結果也是2,不過意義不一樣,如果數組類型換成int a[10], 那麼a[5] - a[3] 還是2,不是8,這個直接用下標相減就對了。
因此我們可以總結一下,指針相加減時,要看類型,打狗看主人,指針加減看類型,其運算的值n*sizeof(類型),比如:字符型地址加1 ,其實地址加也是sizeof(char) = 1,整形地址加1,地址加sizeof(int) = 4,結構體數組中,地址加1,地址加sizeof(結構體)。
通過上面的分析可以看出,數組有很多地方很相似,其實,編譯器這哥們處理數組的時候就是將其看成指針來處理的,沒有辦法,編譯器只認地址,變量名一直都是被編譯器藐視的。
看一個程序圖:
程序圖這個名詞是我自創的,有版權的,因為好多東西說不清,道不明,一個圖全搞定。
上面的執行結果是什麼?已經很清楚了。休息會。
上面的例子結果是:B和E
摘自 mr_raptor的專欄