第三部分:嵌入式linux高級c--指針3
2.1指針數組與數組指針
2.1.1字面意思來理解指針數組與數組指針
(1)指針數組的實質是一個數組,這個數組中存儲的內容全部是指針變量。
(2)數組指針的實質是一個指針,這個指針指向的是一個數組。
2.1.2、分析指針數組與數組指針的表達式
看變量定義的一般規律:
第一:找核心(如果核心和*結合,表示核心是指針;如果核心和[]結合,表示核心是數組;如果核心和()結合,表示核心是函數)。
第二:向外擴展(找完核心,繼續向外找其他符號)
題目1:int *p[5];int (*p)[5];int *(p[5]);
第一題分析:核心是p,因爲【】的優先級比*高,故p和【】結合後是一個數組,數組大小爲5個。再和*號結合,則數組中的元素都是指針,指針指向的元素類型是int類型的;整個符號是一個指針數組。
第二題分析:核心是p,p先和*結合,故p是一個指針;p再和【】結合,則p是指向數組(大小爲5)的指針;數組中存的元素是int類型。整個符號是一個數組指針。
第三題分析:第三題和第一題的解析一樣。即int *p[5]; 和int *(p[5]);
是等價的。
2.2.函數指針與typedef
2.2.1函數指針的實質
(1)函數指針的實質還是指針,還是指針變量。本身佔4字節(在32位系統中,所有的指針都是4字節)
(2)函數指針、數組指針、普通指針之間並沒有本質區別,區別在於指針指向的東西是個什麼玩意。
(3)函數的實質是一段代碼,這一段代碼在內存中是連續分佈的(一個函數的大括號括起來的所有語句將來編譯出來生成的可執行程序是連續的),所以對於函數來說很關鍵的就是函數中的第一句代碼的地址,這個地址就是所謂的函數地址,在C語言中用函數名這個符號來表示。
綜上,結合函數的實質,函數指針其實就是一個普通變量,這個普通變量的類型是函數指針變量類型,它的值就是某個函數的地址(也就是它的函數名這個符號在編譯器中對應的值)
2.2.2函數指針的書寫和分析方法
C語言本身是強類型語言(每一個變量都有自己的變量類型),編譯器可以幫我們做嚴格的類型檢查。
題目1:數組指針的定義和使
數組是int a[10];對應的數組指針:int (*變量名)[10];類型是:int (*)[10];
- #include <stdio.h>
- int main(void)
- {
- int a[10];
- int (*p1)[10];
- int (*p2)[5];
- int *p3;
- p1 = &a;
- //注意以下的三種情況都出現以下警告(指針類型不匹配)
- //warning: assignment from incompatible pointer type [enabled by default]
- p1 = a;
- p2 = &a;
- p3 = &a;
- return 0;
- }
題目2:函數指針的定義與使用
函數是:void func(void);對應的函數指針:void (*p)(void);類型是:void (*)(void);
- #include <stdio.h>
- void func(void)
- {
- printf("test for function pointer\n");
- }
- int main(void)
- {
- void (*pFunc)(void); //函數指針定義
- pFunc = func; //第一種方法初始化指針
- pFunc = &func; //第二種方法初始化指針
- pFunc(); //用指針調用函數func();
- return 0;
- }
對比題目1和2我們發現函數名和數組名最大的區別就是:函數名做右值時加不加&效果和意義都是一樣的;但是數組名做右值時加不加&意義就不一樣,如果直接用數組名給數組指針賦值,則會出現類型不匹配的警告。
原因就是編譯器規定的!
題目3:寫一個複雜的函數指針的實例:譬如函數是strcpy函數(char * strcpy(char *dest, const char *src);)
對應的函數指針是:char *(*pFunc)(char *dest, const char *src);
總結:定義指針變量的方法(普通變量指針,數組指針,函數指針)就是講其核心名字替換成(*+名字)就是其對於的指針定義.
變量/函數 | 指針 |
Int a; | Int (*p);/Int *p; |
Int a[10]; | Int (*p)[10] |
Void a(void); | Int (*p)(void) |
2.2.3 typedef關鍵字的用法
(1)typedef是C語言中一個關鍵字,作用是用來定義(或者叫重命名類型)
(2)C語言中的類型一共有2種:一種是編譯器定義的原生類型(基礎數據類型,如int、double之類的);第二種是用戶自定義類型,不是語言自帶的是程序員自己定義的(譬如數組類型、結構體類型、函數類型·····)。
(3)我們今天講的數組指針、指針數組、函數指針等都屬於用戶自定義類型。
(4)有時候自定義類型太長了,用起來不方便,所以用typedef給它重命名一個短點的名字。
(5)注意:typedef是給類型重命名,也就是說typedef加工出來的都是類型,而不是變量。
定義函數char * strcpy(char *dest, const char *src);的指針
- 第一種方法: char *(*p)(char *dest, const char *src);
- 第二種方法:typedef char *(*pType)(char *dest, const char *src);
- pType p<span style="font-family:宋體;">;</span>
2.3.函數指針實戰1
2.3.1用函數指針調用執行函數
題目1:用函數指針來製作一個簡單計算器
- #include <stdio.h>
- //定義了一個類型pFunc
- typedef int (*pFunc)(int, int);
- int add(int a, int b);
- int sub(int a, int b);
- int multiply(int a, int b);
- int divide(int a, int b);
- int main(void)
- {
- pFunc p1 = NULL; //用pFunc 定義一個函數指針變量
- char c=0;
- int a=0,b=0;
- printf("please import + , - , * , / \n");
- scanf("%c",&c);
- switch (c)
- {
- case '+': p1 = add;break;
- case '-': p1 = sub;break;
- case '*': p1 = multiply;break;
- case '/': p1 = divide;break;
- default : p1 = NULL ; break;
- }
- printf("please import two int number\n");
- printf("a = ");
- scanf("%d",&a);
- printf("b = ");
- scanf("%d",&b);
- printf("a %c b = %d\n",c,p1(a,b));
- return 0;
- }
- int add(int a, int b) //加法
- {
- return a + b;
- }
- int sub(int a, int b) //減法
- {
- return a - b;
- }
- int multiply(int a, int b)// 乘法
- {
- return a * b;
- }
- int divide(int a, int b) //除法
- {
- return a / b;
- }
2.4.結構體內嵌函數指針實現分層
第一:分層寫代碼的思路是:有多個層次結合來完成任務,每個層次專注各自不同的領域和任務;不同層次之間用頭文件來交互。
第二:分層之後上層爲下層提供服務,上層寫的代碼是爲了在下層中被調用。
第三:上層注重業務邏輯,與我們最終的目標相直接關聯,而沒有具體幹活的函數。
第四:下層注重實際幹活的函數,注重爲上層填充變量,並且將變量傳遞給上層中的函數(其實就是調用上層提供的接口函數)來完成任務。
第五:下層代碼中其實核心是一個結構體變量(譬如本例中的struct cal_t),寫下層代碼的邏輯其實很簡單:第一步先定義結構體變量;第二步填充結構體變量;第三步調用上層寫好的接口函數,把結構體變量傳給它既可。
2.5.再論typedef
2.5.1、C語言的2種類型:內建類型與用戶自定義類型
(1)內建類型ADT、自定義類型UDT
2.5.2、typedef定義(或者叫重命名)類型而不是變量
(1)類型是一個數據模板,變量是一個實在的數據。類型是不佔內存的,而變量是佔內存的。
(2)面向對象的語言中:類型就是類class,變量就是對象。
2.5.3、typedef與#define宏的區別
- typedef char *pChar; //新的類型
- #define pChar char * //只是替換代碼中的pchar的內容爲char*
2.5.4、typedef與結構體
第一種:結構體的定義與使用
- //定義結構體類型:struct student
- typedef struct student
- {
- char name[20];
- int age;
- };
- struct student s1; //定義struct student類型的變量
第二種:結構體定義中添加typedef
//定義結構體類型有兩個名字(1)struct student (2)student_t
//定義結構體類型有兩個名字(1)struct student* (2)pStudent*
- typedef struct student
- {
- char name[20];
- int age;
- }student_t,*pStudent;
- student_t s1; //定義struct student類型的變量
(1)結構體在使用時都是先定義結構體類型,再用結構體類型去定義變量。
(2)C語言語法規定,結構體類型使用時必須是struct結構體類型名 結構體變量名;這樣的方式來定義變量。
(3)使用typedef一次定義2個類型,分別是結構體變量類型,和結構體變量指針類型。
2.5.5、typedef與const
- (1)typedef int *PINT; const PINT p2; 相當於是int *const p2;
- (2)typedef int *PINT; PINT const p2; 相當於是int *const p2;
- (3)如果確實想得到const int *p;這種效果,只能typedef const int *CPINT; CPINT p1;
2.5.6、使用typedef的重要意義(2個:簡化類型、創造平臺無關類型)
(1)簡化類型的描述。
- char *(*)(char *, char *);
- typedef char *(*pFunc)(char *, char *);
(2)很多編程體系下,人們傾向於不使用int、double等C語言內建類型,因爲這些類型本身和平臺是相關的(譬如int在16位機器上是16位的,在32位機器上就是32位的)。爲了解決這個問題,很多程序使用自定義的中間類型來做緩衝。譬如linux內核中大量使用了這種技術.
內核中先定義:typedef int size_t;然後在特定的編碼需要下用size_t來替代int(譬如可能還有typedef int len_t)
(3)STM32的庫中全部使用了自定義類型,譬如typedef volatile unsigned int vu32
2.6.二重指針
2.6.1、二重指針與普通一重指針的區別
(1)本質上來說,二重指針和一重指針的本質都是指針變量,指針變量的本質就是變量。
(2)一重指針變量和二重指針變量本身都佔4字節內存空間,
2.6.2、二重指針的本質
(1)二重指針本質上也是指針變量,和普通指針的差別就是它指向的變量類型必須是個一重指針。二重指針其實也是一種數據類型,編譯器在編譯時會根據二重指針的數據類型來做靜態類型檢查,一旦發現運算時數據類型不匹配編譯器就會報錯。
(2)C語言中如果沒有二重指針行不行?其實是可以的。一重指針完全可以做二重指針做的事情,之所以要發明二重指針(函數指針、數組指針),就是爲了讓編譯器瞭解這個指針被定義時定義它的程序員希望這個指針被用來指向什麼東西(定義指針時用數據類型來標記,譬如int *p,就表示p要指向int型數據),編譯器知道指針類型之後可以幫我們做靜態類型檢查。編譯器的這種靜態類型檢查可以輔助程序員發現一些隱含性的編程錯誤,這是C語言給程序員提供的一種編譯時的查錯機制。
(3)爲什麼C語言需要發明二重指針?原因和發明函數指針、數組指針、結構體指針等一樣的。
2.6.3、二重指針的用法
(1)二重指針指向一重指針的地址(int *p1; int **p2; p2 = &p1)
(2)二重指針指向指針數組的(int *a[10]; int **p; p = a)
(3)實踐編程中二重指針用的比較少,大部分時候就是和指針數組糾結起來用的。
(4)實踐編程中有時在函數傳參時爲了通過函數內部改變外部的一個指針變量,會傳這個指針變量的地址(也就是二重指針)進去
2.6.4、二重指針與數組指針
(1)二重指針、數組指針、結構體指針、一重指針、普通變量的本質都是相同的,都是變量。
(2)所有的指針變量本質都是相同的,都是4個字節,都是用來指向別的東西的,不同類型的指針變量只是可以指向的(編譯器允許你指向的)變量類型不同。
(3)二重指針就是:指針數組指針
2.7.二維數組
2.7.1、二維數組的內存映像
一維數組在內存中是連續分佈的多個內存單元組成的,而二維數組在內存中也是連續分佈的多個內存單元組成的。
(1)從內存角度來看,一維數組和二維數組沒有本質差別。
(2)二維數組int a[2][5]和一維數組int b[10]其實沒有任何本質差別。我們可以把兩者的同一單元的對應關係寫下來。
a[0][0] a[0][1] a[0][4] a[1][0]a[1][1] a[1][4]
b[0] b[1] b[4] b[5] b[6] b[9]
(3)既然二維數組都可以用一維數組來表示,那二維數組存在的意義和價值在哪裏?明確告訴大家:二維數組a和一維數組b在內存使用效率、訪問效率上是完全一樣的(或者說差異是忽略不計的)。在某種情況下用二維數組而不用一維數組,原因在於二維數組好理解、代碼好寫、利於組織。
(4)總結:我們使用二維數組(C語言提供二維數組),並不是必須,而是一種簡化編程的方式。想一下,一維數組的出現其實也不是必然的,也是爲了簡化編程。
2.7.2、哪個是第一維哪個是第二維?
(1)二維數組int a[2][5]中,2是第一維,5是第二維。
(2)結合內存映像來理解二維數組的第一維和第二維的意義。首先第一維是最外面一層的數組,所以int a[2][5]這個數組有2個元素;其中每一個元素又是一個含有5個元素的一維數組(這個數組就是第二維)。
(3)總結:二維數組的第一維是最外部的那一層,第一維本身是個數組,這個數組中存儲的元素也是個一維數組;二維數組的第二維是裏面的那一層,第二維本身是個一維數組,數組中存的元素是普通元素,第二維這個一維數組本身作爲元素存儲在第一維的二維數組中。
2.7.3、二維數組的下標式訪問和指針式訪問
(1)回顧:一維數組的兩種訪問方式。以int b[10]爲例, int *p = b;。
b[0] 等同於 *(p+0); b[9] 等同於*(p+9); b[i]等同於*(p+i)
(2)二維數組的兩種訪問方式:以int a[2][5]爲例,(合適類型的)p = a;
a[0][0]等同於*(*(p+0)+0);a[i][j]等同於*(*(p+i)+j)
2.7.4、二維數組的應用和更多維數組
(1)最簡單情況,有10個學生成績要統計;如果這10個學生沒有差別的一組,就用b[10];如果這10個學生天然就分爲2組,每組5個,就適合用int a[2][5]來管理。
(2)最常用情況:一維數組用來表示直線,二維數組用來描述平面。數學上,用平面直角座標系來比擬二維數組就很好理解了。
(3)三維數組和三維座標系來比擬理解。三維數組其實就是立體空間。
(4)四維數組也是可以存在的,但是數學上有意義,現在空間中沒有對應(因爲人類生存的宇宙是三維的)。
總結:一般常用最多就到二維數組,三維數組除了做一些特殊與數學運算有關的之外基本用不到。(四軸飛行器中運算飛行器角度、姿態時就要用到三維數組)
2.8.二維數組的運算和指針
2.8.1、指針指向二維數組的數組名
(1)二維數組的數組名錶示二維數組的第一維數組中首元素(也就是第二維的數組)的首地址
(2)二維數組的數組名a等同於&a[0],這個和一維數組的符號含義是相符的。
(3)用數組指針來指向二維數組的數組名是類型匹配的。
Int a[2][5]; //二維數組
int (*p)[5] = a(&a[0]); //數組指針
訪問a[1][3]:*(*(p+1)+3)
2.8.2、指針指向二維數組的第一維
(1)用int *p來指向二維數組的第一維a[i]
Int *p = a[0](&a[0][0])
2.8.3、指針指向二維數組的第二維
(1)二維數組的第二維元素其實就是普通變量了(a[1][1]其實就是int類型的7),已經不能用指針類型和它相互賦值了。
(2)除非int *p = &a[i][j];,類似於指針指向二維數組的第一維。
總結:二維數組和指針的糾葛,關鍵就是2點:
1、數組中各個符號的含義。
2、數組的指針式訪問,尤其是二維數組的指針式訪問。