1. 指針的基礎知識
指針的定義:指針的全稱是指針變量。
指針本質上是一種類型,和int型,float型,double型,char型一樣的,這種類型叫指針類型,定義的變量叫指針變量。
int *p; 這句表達式的意思是定義了一個變量p,*符號表示我定義的這個變量p是指針類型的,而前面的int表示我定義的這個指針變量p指向一個整型,指針變量p的值就是p所指向的這個整型變量的地址,所以給指針賦值時是一個地址值。
&: 取地址符號,在變量前面加上&,則這個整體表示這個變量的地址
*:在變量定義時使用*表示我定義的是一個指針類型變量 在其他地方使用時表示我對指針變量所指向的那個變量地址中存儲的數據進行操作。例如 int a = 5; int *p; *p =& a
int a = 5; 定義了一個整型變量a,a初始化賦值爲5
p = &a; a是一個整型變量,a前面加上&取地址符號,則&a這個整體表示這個變量a的地址,將這個地址賦值 給指針變量p,也就是說指針變量p的值就是指針變量p所指向的int型變量a
2.指針變量的運算
指針本質上是一個變量,只不過這個變量是指針類型的,和int型等變量類型並沒有什麼本質的區別。既然是變量就可以 + - * / % 這些算數運算。只不過指針類型變量的值是一個地址 * / % 等這些運算可以,但沒有實際意義的。最常用的使用方法是對指針的+ - 操作。結合數組值操作的方法看最明顯:
int a[4] = {100,200,300,400}; 定義一個整型數組,這個數組有五個整型類型數據
int *p; 定義一個指向整型變量的指針變量p,p的值是指向的那個整型變量的地址。
p = a; 數組名做右值時的含義是數組名就是數組首元素的地址,也就是a[0]的地址,a和&a[0]等價的都表示數組元素的首地址
p 指向 a[0]的首地址 *p的值就是a[0]
p+1指向a[1]的地址 *(p+1)的值就是a[1]
p+2指向a[2]的地址 *(p+2)的值就是a[2]
以*(p+1)來說,()優先級最高,所以先運算()的表達式,p+1表示對p所指向的那個變量的地址進行+1操作,這裏+1並不是對地址值進行+1,而是對地址進行增加了4,這個1的含義是增加了p指向的變量類型的大小,p所指向的變量是整型類型的,整型類型的大小是佔用4個字節數據大小,所以增加了4。以下表爲例:
*p=a[0] *(p+1)=a[1] *(p+2)=a[2] *(p+3)=a[3]
a[0] =100 | a[1]=200 | a[2]=300 | a[3]=400 |
p p+1 p+2 p+3
通過上表可以看出 p=&a[0]; &a[0]的地址值是0x1000,則p的值是0x1000。 p+1的值是0x1004,不是0x1001,所以這個+1的值其實是int在內存中所佔用的長度,也就是4字節。
p = &a[0];
printf("*p=%d\n",*p); *p的值是p所指向的變量的地址存放的值。p所指向的變量是a[0],p所指向的變量的地址是0x1000,0x1000地址存放的值是100,所以*p = 100;
*p = 200; 相當於修改了p所指向的變量的地址存放的值,也就是0x1000存放的100的值改爲200,也就是a[0]的值是200.
*(p+1)和*p的含義是一樣的。首先判斷優先級最高的也就是()裏的,p是一個指針變量,p的值是p所指向的變量的地址,p所指向的變量是a[0],a[0]的地址是0x1000,所以p的值就是0x1000, p+1就是p的值在原來的基礎上增加了sizeof(int)個字節也就是4字節,所以p+1=0x1004,也就是說p+1的值就是&a[1],所以p+1指向整型變量a[1],p+1的值就是a[1]的地址。*在運算表達式中的含義是對指針所指向的變量的地址的值進行操作,而現在p+1指向整型變量a[1],所以
*(p+1) = a[1],也就是說*(p+1) = 200;後面的運算以此類推。
3.指針類型的強制轉換
int a = 0x11223344;
char *p;
p = &a;
分析上面這段代碼。指針變量p是指向一個char型的變量,而在給指針變量賦值時p所指向的變量a的類型是int型的,這種賦值方法是不合理的,編譯器會報警告或者錯誤,在實際項目中如果使用這種方法賦值,則會出現不可預估的風險。只有重新定義指針變量p,使指針指向一個整型變量。如下
int *p;
p = &a;
則*p的值就是0x11223344;
加入我們按一開始的方法定義,只不過我們需要單獨取出0x11,0x22,0x33,0x44這四個值該咋麼操作吶?這就涉及到指針類型的強制轉換了。如下操作:
int a = 0x11223344;
char *p;
p=(char *)&a;
分析:()的優先級是最高的,所以最先運算是從()內部開始的 ,char *的意思是將指針所指向的變量類型強制轉換爲char型,指針p所指向的變量是a,a的類型是int型,將int型強制轉換爲char型。指針變量p的值本身是沒有變化的,還是a的地址值&a;唯一發生變化的是指針變量p所指向的變量a的類型給強制轉換了。那*p的值是多少?
強制轉換後p所指向的是一個char類型的變量,所以*p的值是一個char型的值。通過下表分析:
0x11 | 0x22 | 0x33 | 0x44 |
0x1000 0x1001 0x1002 0x1003
p p+1 p+2 p+3
p = (char*)&a; p的值還是a的地址,就是p的值是0x1000;但是p所指向的變量也就是a的類型強制轉換爲char型,a在內存中還是按照整型數據存儲的,但我們取值的時候是按照char型取出的。
說以*p取值時取的是一個char型變量,也就是0x11,
*(p+1) 這裏+1的意思是地址+sizeof(char)個字節,不是+sizeof(int)個字節,因爲我們在指針賦值時強制將指針所指向的變量類型轉換爲char型,所以p+1的值是0x1000+1就是0x1001,而0x1001的地址存放的值是0x22,所以
*(p+1) = 0x22;
*(p+2) = 0x33;
*(p+3) = 0x44;
這種操作一般用在函數傳參時的類型強制轉換,一般用在內存讀寫上。
注:一定要明白指針類型的強制轉換的含義,強制轉換的是指針所指向的變量類型,而不是指針類型,因爲指針本來就是一個類型了。對指針進行+ -操作時,每次+1的值含義是地址偏移sizeof(char)字節,而不是sizeof(int)字節。
如何將一個有意義的整型變量轉換爲一個地址?
char *p;
int a = 0x1200;
p = (char* )a;
分析:將a強制轉換爲指針類型,則a的值由一個int型的值轉換爲一個地址值了,這個p指針變量指向char型變量。
舉例:
void ReadFlash(uint32_t addr, uint8_t *ptr, int len)
{
int i;
for(i = 0; i < len; i++)
{
*ptr++ = (char *)addr++; //將整型addr的值賦值給ptr指針,則ptr的地址值就是addr的值,ptr所指向的變量
//char型的,也就是ptr取值時是按照char數據類型大小來取值的。
}
}
#define BUFF_LEN (100)
int main(void)
{
uint8_t buff[BUFF_LEN] = {0};
ReadFlash(0x10000,buff, BUFF_LEN);
}
整型變量強轉爲地址值時用的很少,但在內存中取值時這種用法有時候是必須的。
4.函數傳參的倆種方式,傳值和傳地址
調用函數時經常要給函數的形參列表來傳遞參數,將實參傳遞給形參有倆種方法
第一種:傳值
調用函數時將實參的值傳遞給形參,實際上是將實參的值拷貝一份傳遞給形參,而實參並沒有參與調用函數的運算,所以傳值傳參是不會改變實參的值的,因爲是將實參值拷貝了一份傳給形參。
第二種:傳地址
實參向形參傳地址,則形參是指針類型的,將地址賦值給形參,則形參和實參共用同一片內存地址,也就是說通過傳進來的地址對形參進行操作,會改變實參的值,因爲形參和實參的地址是同一個,通過指針操作修改形參值,也就修改了實參的值。