<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                **肆** ***數組與指針(二)*** **數組與指針的糾葛** ***以指針的形式訪問數組:*** 下標表達式: 后綴表達式[表達式] 在C語言中,根據定義,**表達式e1[e2]準確地對應于表達式*((e1)+(e2))。**因此,要求表達式e1[e2]的其中一個操作數是指針,另一個操作數是整數。且這兩個操作數的順序可以顛倒。 故: a[4] 等同于 4[a] 等同于 *(a+4) 編譯器把所有的e1[e2]表達式轉換成*((e1)+(e2))。 所以,以下標的形式訪問在本質上與以指針的形式訪問沒有區別,只是寫法上不同罷了! **多維數組** 二維數組a[i][j] **編譯器總是將二維數組看成是一個一維數組,而一維數組的每個元素又都是一個數組。** 多維數組定義的下標從前到后可以看做是 最宏觀的維到最微觀的維。例:三維數組a[i][j][k] 可理解為 共有i個大組,每個大組里有j個小組,每個小組里有k個元素。 故: a 表示為整個三維數組,其值為&a[0][0][0], &a+1為整個三維數組后面的第一個位置。(偏移整個三維數組的長度) a+1? 為第二個大組的首位置處(偏移一個大組的長度)【數組名a代表的是數組首元素的首地址,即:第一個大組的首地址】 a[0]表示為三維數組的i個大組中的第一個大組【可看做一個二維數組】,其值為&a[0][0][0], &a[0]+1為第二個大組的首位置處(偏移一個大組的長度) a[0]+1為第一個大組中第二個小組的首位置處(a[0]可看做是一個二維數組名,故其代表的是第一個小組的首地址)(偏移一個小組的長度) a[0][0]表示為第一個大組中的第一個小組【可看做一個一維數組】其值為&a[0][0][0], &a[0][0]+1為第一個大組中第二個小組的首位置處(偏移一個小組的長度) a[0][0]+1為第一個大組中第一個小組的第二個元素位置處(偏移一個元素的長度) a[0][0][0]表示為第一個大組中的第一個小組中的第一個元素。其值為&a[0][0][0],a[0][0][0]+1為首元素值加1。(因為a[0][0][0]為元素值而不是地址) 數組的數組(即:二維數組名)退化為數組的(常量)指針,而不是指針的指針。 同理,**n維數組名退化為n-1維數組的(常量)指針。** 【**總結:指針代表的是誰的首地址 就以誰的長度為偏移單位。**】 【**規律:與定義比較,缺少幾對方括號,就是幾維數組的數組名**,如上例:a缺少3對方括號,即為3維數組的數組名(代表的是2維數組的地址);a[0]缺少2對方括號,即為2維數組的數組名(代表的是1維數組的地址);a[0][0]缺少1對方括號,即為1維數組的數組名(代表的是數組元素的地址)】 【數組名與整數相加,首先要轉換成數組的首元素地址與整數相加,而首元素的存儲大小就是整數的單位】 對多維數組的解析: 我們可以用上面那種從前到后的解析方式來思考,a:就表示整個多維數組。a[m]:就表示第m+1大組(大組即數組最大的維),a[m][n]:就表示第m+1大組中的第n+1小組。(小組即次大的維),以此類推,即多維數組的解析是層層細化的。 **◎☆指針數組與數組指針:** 指針數組:首先它是一個數組。數組的元素都是指針。它是“存儲指針的數組”的簡稱。 數組指針:首先它是一個指針。它指向一個數組。它是“指向數組的指針”的簡稱。 例:int * p1[10]; //它是指針數組。(因為[]的優先級比*高,p1先與[]結合,構成一個數組的定義) int? (*p2)[10] ; //它是數組指針。(括號的優先級較高,*與p2構成一個指針的定義) 它指向一個包含10個int型數據的數組。 若有:int(*p)[10][5] ;? //則p指向一個int型的二維數組a[10][5]。 【**規律:數組指針,把定義中括號內的指針看成是一個普通的字母,則其表示的就是 數組指針所指的對象類型**】 ◎☆ ~~~ int a[5][5] ; int (*p)[4] ; p=a ; 問:&p[4][2]-&a[4][2]的值為多少? ~~~ 設二維數組的首地址為0,則a[4][2]為第5組的第3個位置(以后見到多維數組要這么想,不要總想著是幾排幾列的模式),因為int a[5][5];即有5組,每組有5個元素。故:&a[4][2]是(4*5+2)*sizeof(int). int (*p)[4] ; 指針指向一個含4個int型的元素的數組,故p[4]相對于p[0]向后移動了“4個int型數組”的長度,然后在此基礎上再向后移動2個int型的長度(即,其步長按維度逐步遞減,多維數組也可按此方式理解)。最后其值為(4*4+2)* sizeof(int) 最后**切記:地址值參與的加減運算(地址不能被乘),整數的單位是地址值代表的元素的存儲大小!** &p[4][2]-&a[4][2]結果為-4。若分開比較&p[4][2]和&a[4][2]則相差4* sizeof(int)個字節** 【**◎☆規律:數組指針的連續解引用** 數組指針的定義提供了其逐次解引用時的偏移單位,例int (*p)[m][n][k],則意為:數組指針的第一次解引用的偏移單位是m*n*k個int型長度,再次解引用的偏移單位是n*k個int型長度,又一次解引用的偏移單位是k個int型長度,最后一次解引用的偏移單位是1個int型長度。它只能連續解引用4次。 故:p[2][3][4][5]與四維數組首地址相距(2*m*n*k + 3*n*k + 4*k + 5 )個int型長度】 故:**數組指針指向的是哪個數組,就可以把它當做那個數組的數組名來用。** 例:inta[3][10][5] ; int (*p)[10][5] ; p = a ; 則:p[1][2][3] == a[1][2][3]?;? p[1][2] ==a[1][2] 即:用數組指針訪問數組和用數組名訪問,效果是相同的。 **WHY?**以int(*p)[10][5]為例,它指向一個[10][5]的二維數組,故第一次解引用時以二維數組[10][5]的長度作為偏移單位,一次解引用后p[1]就是一個[10][5]二維數組了。(解引用就是提取出指針偏移后 指向的對象) 即為:一維數組[5]的首地址。故再次解引用就以一維數組[5]的長度作為偏移單位,二次解引用后p[1][2]就是一個[5]一維數組了,即是一維數組首元素的地址。所以三次引用后,偏移單位為1個元素。 **數組參數與指針參數:** 1,二維數組名做實參 ~~~ int main(void) { int a[4][5] ; ………. ……… fun(a); ………. } 被調函數: ①fun( inta[4][5] ) ②fun( inta[ ][5] ) ③fun( int(*a)[5] ) { ………. a[i][j]=………. ……… } ~~~ 以上三種方式皆可。無論是那種方式,它們只是寫法不同,但編譯器的處理方式相同,都把它們看做是一維數組指針。 因為二維數組名退化為一個一維數組指針,故是以一維數組指針的形式來傳遞二維數組的。 2,指針數組做實參 ~~~ int main(void) { int a[4][5] , i, *p[4] ; for(i=0;i<4; i++) p[i]= a[i] ; ………. fun(p); ………. } 被調函數: ①fun(int*q[4]) ②fun(int *q[]) ③fun(int **q) { ………. q[i][j]=……….//取出指針數組中的第i個元素(為指針),再偏移j個單位 //也可從雙重指針的角度理解:[i]為第一次解引用,偏移量是i個指針的大小(因為雙重指針指向的是指針變量),[j]為第二次解引用,偏移量是j個int型變量大小(因為此時指針指向的是一個int型變量:某組的首元素) ……… } ~~~ 以上三種方式皆可。無論是那種方式,寫法不同,但編譯器的處理方式相同,都把它們看做是二級指針。 因為指針數組名退化為數組首元素的地址,即二級指針,故是以二級指針的形式來傳遞指針數組的。 而多維數組名退化為次維數組的指針,即數組指針,故是以數組指針的形式來傳遞多維數組的。 【數組指針的連續解引用,其指針的步長對應數組的維度值 是逐漸減小的 多級指針的連續解引用,其指針的步長 前幾次解引用的步長為1個指針的長度,最后一次解引用的步長為最終指向的對象長度。(操作系統常用多級指針在多張表中做查詢操作)】 【C中函數實參與形參之間是傳值引用的,所以你要改變這個值,就傳遞它的地址(無需多言)】 **函數指針**: 函數指針就是函數的指針。它是一個指針,指向一個函數。 (即函數在內存中的起始位置地址) 實際上,所有的函數名在表達式和初始化中,總是隱式地退化為指針。 例:int? r , (*fp)( ) , func( ) ; ????? fp= func ;????? //函數名退化為指針 ????? r= (*fp)( ) ;? //等價于r=fp( ) ; **無論fp是函數名還是函數指針,都能正確工作。因為函數總是通過指針進行調用的!** 例:int? f(int) ; //函數聲明 ????? int? (*fp)(int) = &f ?;//此取地址符是可選的。編譯器就把函數名當做函數的入口地址。 //在引用這個函數地址之前,f函數應先聲明。 ????? int? ans ; ????? //以下三種方式可調用函數 ????? ans= f(25) ; //函數名后的括號是“函數調用操作符”。 ????? ans= (*fp)(25) ; ????? ans= fp(25) ; **函數名就是一個函數指針常量,函數調用操作符(即一對括號)相當于解引用** 函數的執行過程: 函數名首先被轉換為一個函數指針常量,該指針指定函數在內存中的位置。然后函數調用操作符調用該函數,執行開始于這個地址的代碼。 **再說強制類型轉換:** ~~~ void fun() { printf("Call fun "); } int main(void) { void(*p)( ) ; *(int*)&p = (int)fun ; (*p)() ; return0 ; } ~~~ 參見前面文章的強制類型轉換。強制類型轉換只不過是改變了編譯器對位的解釋方法罷了。 *(int *)&p = (int)fun ;中的fun是一個函數地址,被強制轉換為int數字。左邊的(int*)&p是把函數指針p轉換為int型指針。*(int *)&p = (int)fun ;表示將函數的入口地址賦值給指針變量p。(*p)( ) ;表示對函數的調用。 **函數指針數組:** 即是存儲函數指針的數組。(有時非常有用) 例:char *(*pf[3])(char *) ; **函數指針的用途:** 1,**轉移表**(轉移表就是一個函數指針數組) 即可用來實現“菜單驅動系統”。系統提示用戶從菜單中選擇一個選項,每個選項由不同的函數提供服務。 【若每個選項包含許多操作,用switch操作,會使程序變得很長,可讀性差。這時可用轉移表的方式】 例:void(*f[3])(int) = {function1, function2, function3} ; //定義一個轉移表 ????? (*f[choice])( ) ; //根據用戶的選擇來調用相應的函數 2,**回調函數**(用函數指針做形參,用戶根據自己的環境寫個簡單的函數模塊,傳給回調函數,這樣回調函數就能在不同的環境下運行了,**提高了模塊的復用性**) 【回調函數實現與環境無關的核心操作,而把與環境有關的簡單操作留給用戶完成,在實際運行時回調函數通過函數指針調用用戶的函數,這樣其就能適應多種用戶需求】 例:C庫函數中的快速排序函數 ????? voidqsort(void *base, int nelem, size_t width, int ?(*fcmp)(void*, void*) ); //base為待排序的數組基址,nelem為數組中元素個數,width為元素的大小,fcmp為函數指針。 這樣,由用戶實現fcmp的比較功能(用戶可根據需要,寫整型值的比較、浮點值的比較,字符串的比較 等)這樣qsort函數就能適應各種不同的類型值的排序。 **使用函數指針的好處在于:** 可以將實現同一功能的多個模塊統一起來標識,這樣一來更容易后期維護,系統結構更加清晰。或者歸納為:便于分層設計、利于系統抽象、降低耦合度以及使接口與實現分開。 **函數指針數組的指針:**(基本用不到) 例:char *(*(*pf)[3])(char *) 這個指針指向一個數組,這個數組里存儲的都是指向函數的指針。它們指向的是一種返回值為字符指針,參數為字符指針的函數。 [對于這種復雜的聲明,《C和指針》《C專家編程》中有專門的論述。我的方法就是:從核心到外層,層層分析。先找到這個聲明的核心,看他的本質是什么。就像本例,最內層的括號里是一個指針,再看外層來確定它是個什么指針。外層是一個3個元素的數組,再看這個數組的元素類型是什么。是一個函數指針。 故總體來說此聲明是一個函數指針數組的指針。] **復雜指針的舉例:** int* (*a[5])(int, char*); void (*b[10]) (void (*)()); doube(*)() (*pa)[9]; 讓我們一層一層剝開它的心。 第1個、首先找到核心,即標識符a,[ ] 優先級大于“*”,a與“[5]”先結合。所以a是一個數組,這個數組有5個元素,每一個元素都是一個指針。再往外層看:指針指向“(int,char*)”,對,指向一個函數,函數參數是“int, char*”,返回值是“int*”。完畢! 第2個、首先找到核心:b是一個數組,這個數組有10個元素,每一個元素都是一個指針,指針指向一個函數,函數參數是“void(*)()”【 這個參數又是一個指針,指向一個函數,函數參數為空,返回值是“void”】 返回值是“void”。完畢! 第3個、核心pa是一個指針,指針指向一個數組,這個數組有9個元素。再往外層看:每一個元素都是“doube(*)()”【也即一個指針,指向一個函數,函數參數為空,返回值是“double”】 **使用typedef簡化聲明:** 某大牛對typedef用法做過一個總結:“建立一個類型別名的方法很簡單,在傳統的變量聲明表達式里用類型名替代變量名,然后把關鍵字typedef加在該語句的開頭”。 舉例: 例1,void (*b[10]) (void (*)()); typedef void (*pfv)();? ????????????????????? //先把上式的后半部分用typedef換掉 typedef void (*pf_taking_pfv)(pfv);???? //再把前半部分用typedef換掉 pf_taking_pfv b[10]; ?????????? ????????????? //整個用typedef換掉 跟void (*b[10]) (void (*)());的效果一樣! 例2,doube(*)() (*pa)[9];? typedef double(*PF)(); ??????? //先替換前半部分 typedef PF (*PA)[9];??????????? //再替換后半部分 PA ?pa; ??????? //跟doube(*)() (*pa)[9];的效果一樣! **反思:** 1,我們為什么需要指針? 因為我們要訪問一個對象,我們要改變一個對象。要訪問一個對象,必須先知道它在哪,也就是它在內存中的地址。地址就是指針值。 所以我們有 函數指針:某塊函數代碼的起始位置(地址) 指針的指針:因為我要訪問(或改變)某個變量,只是這個變量是指針罷了 2,為什么要有指針類型? 因為我們訪問的對象一般占據多個字節,而代表它們的地址值只是其中最低字節的地址,我們要完整的訪問對象,必須知道它們總共占據了多少字節。而指針類型即向我們提供這樣的信息。 注意:一個指針變量向我們提供了三種信息**:** ①一個首字節的地址值 ②這個指針的作用范圍(步長) ③對這個范圍中的數位的解釋規則(解碼規則) 【編譯器就像一個以步數測量距離的盲人。故你要告訴它從哪開始走,走多少步。】 3,強制類型轉換的真相? 學過匯編的人都知道,什么尼瑪指針,什么char,int,double,什么數組指針,函數指針,指針的指針,在內存中都尼瑪是一串二進制數罷了。**只是我們賦予了這些二進制數不同的含義,給它們設定一些不同的解釋規則,讓它們代表不同的事物。**(比如1000 0000 0000 0001 是內存中某4個字節中的內容,如果我們認為它是int型,則按int型的規則解釋它為-231+ 1;如果我們認為它是unsigned int ,則被解釋為231+ 1;當然我們也可把它解釋為一個地址值,數組的地址,函數的地址,指針的地址等) 如果我們使用匯編編程,我們必須根據上下文需要,用大腦記住這個值當前的代表含義,當程序中有很多這樣的值時,我們必須分別記清它們當前代表的含義。這樣極易導致誤用,所以編譯器出現了,讓它來幫我們記住這些值當前表示的含義。當我們想讓某個值換一種解釋的方案時,就用強制類型轉換的方式來告訴編譯器,編譯器則修改解釋它的規則,而內存中的二進制數位是不變的(涉及浮點型的強制轉換除外,它們是舍掉一些位,保留一些位) 4,涉及浮點型的強制轉 詳情參見《深入理解計算機系統》 5,難點 多維數組、數組指針、多級指針。 **抓住問題的核心:指針值是誰的地址,這個地址代表的是哪個對象。** 搞清楚這個問題,關于指針移動時偏移量(步長)的計算就不會出錯。 指針類型只是C語言提供的一種抽象,來幫助程序員避免尋址錯誤。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看