一、指標
1、指標的概念:用來儲存地址的“變數”叫做指標,可以理解成指標是地址的一個別名。
例:定義一個整形指標
2、“指標的內容”,“指標所指向的內容”,“指標變數的地址”
(1)、指標的內容:
指標變數p裡面存放的是a的地址,也就是0x0018ff44.
(2)、指標所指向的內容:
指標變數p裡面存放的地址(0x18ff44)這塊空間所對應的值,也就是10,我們通過*p(解引用)可以訪問到這個值。即:*p作為右值時,*p==10,當*p作為左值時代表的就是a這塊空間。
(3)、指標的地址:
指標p本身有一個地址,由編譯器分配的我們不知道,注意:不要講指標變數p本身的地址和p所儲存的地址(0x0018ff44)相混淆。
3、"未初始化的指標"和"NULL指標"
例:定義兩個指標變數p1和p2:
int *p1; //未初始化的指標
int *p2=NULL; //null 指標
*p1=10;
*p2=10;
這樣賦值顯然是錯誤的,為什麼呢。。。
試想一下,p1,p2是一個指標變數,所以p1,p2裡面應該存放的應該是一個地址。而對於p1我們沒有往裡面放地址,這時p1是隨機指向的。如果此時解引用*p1,訪問到的是塊隨機的地址,再修改這個隨機地址裡面的值,假設這個隨機空間裡面的值非常重要,那你就再也找不回來了,所以通常定義一個指標就將他初始化為NULL。
對於p2:雖然我們將它初始化為NULL,但它指向的是一塊空地址啊。。。向一塊空地址裡面放東西,根本是不可能的。
4、指標常量:
例:*(int *)200=10;
相信很多人對這個表達是都會產生疑惑,其實很好理解的,200是一個常數,我們將它強制轉化為 int *(也就是將常數200轉化成一個整形地址),再對這塊地址進行*引用,就訪問到一塊空間,可以對這塊空間進行賦值。
5、常量指標
例:int *p=&100;
讓指標指向一個常量,一般用不到。
二、進階指標
1、二級指標概念:
什麼是指標。。。存放地址的變數就叫做指標,所以二級指標就是存放一級指標地址的指標變數。
注意:跟一級指標一樣,二級指標變數p2裡面存放的是一級指標變數p1的地址,一級指標變數p1裡面存放的是a的地址。要想訪問一塊地址裡面的內容可以使用間接訪問符“*”,所以:
*p2==&p1, *p2就是訪問p2這塊空間裡面存放的地址所對應的內容。
**p2==10,因為*p2得到的結果是p1的地址,在對p1的地址進行解引用*p1就訪問到了10.
例1:分析下面幾種情況下能不能作為可修改的左值(可修改的左值必須代表一塊空間)
int a=10;
int *cp=&a;
(1)*cp=20 //可以作為左值,當cp指向a時,*cp放到等號左邊代表a這塊空間,當*cp放到等號右邊代表a這塊空間的值。
(2)&a=20 //錯誤,&a不可以作為左值,因為他不能表示一塊特定的空間,&a得到的結果是a的地址,但並不代表a這塊空間,要想使用這塊空間,必須進行*引用,*&a=20正確。&a可以作為右值,代表是a的地址這個數值。
(3)*cp+1 //不可以作為左值,因為*優先順序高於+,所以*cp先結合,再加1相當於10+1,不代表一塊空間。
(4)*(cp+1) //可以作為左值,cp+1先結合,指向a的下一塊空間,再對這塊空間進行*引用,放在等號左邊就代表這塊空間。
(5)++cp //不可以作為左值,因為++cp只是將cp裡面的內容加一,並沒有進行*引用
(6)cp++ //不可以作為左值
(7)*cp++ //可以作為左值,先對cp裡面的地址進行*引用。再讓cp=cp+1(也就是讓cp指向a的下一塊空間,因為++優先順序高於*)
(8)++*cp //不可以作為左值,*cp代表cp所指向的內容,再讓其加一,相當於10+1
注意:C中++cp和--cp都不能做左值,C++中++cp可以作為左值。
例2:const 修飾一級指標,修飾二級指標(const修飾的變數,還是一個變數,只不過只是可讀的 )
int const a=10;
int b=30;
1、a=20; //錯誤,const修飾a,所以不能改變a的值
2、int const *p; //const修飾的是*p,所以不能改變*p
p=&a; //正確
*p=20; //錯誤 不能通過*p改變a的值
3、const int *p; //const修飾的是*p
p=&a; //正確
*p=20; //錯誤
4、int *const p=&a; //const修飾的是p
p=&b; //錯誤 不能改變p
*p=20; //正確
5、int const * const p; //const修飾*p,也修飾p,所以*p,p都不能改變
p=&b; //錯誤
*p=20; //錯誤
注意:const修飾變數的原則是離誰近修飾誰。const int *p與int const *p完全一樣。
2、指標和數組的關係 ,指標數組,數組指標,指標的運算
2.1、指標和數組的關係:
很多人都分不清指標和數組之間的關係,嚴格的來講指標和數組之間沒關係,指標是指標,數組是數組。只不過他們兩個都可以通過“*”引用的方式和下標的方式來訪問元素而已。
例1:
int a[5]={1,2,3,4,5};
int *p=a;
a[5]佔20個位元組的大小,而p只佔4個位元組的大小,其次p本身有自己的地址,只不過他裡面存放的是數組首元素的地址。
要訪問3則有兩種方式:a[2]或者*(a+2).
其中*(a+2)就是*的形式訪問的,因為a表示首元素的地址,加2表示向後位移2個整形大小,找到3的地址,在通過*得到3.
在編譯器中a[2]會被先解析成*(a+2)訪問的。
例2:
所以必須保持定義和聲明的一致性,指標就是指標,數組就是數組。
3、指標數組,數組指標
注意:[]的優先順序高於*,指標數組是一個數組,只不過裡面的元素全部都是指標。數組指標是一個指標,指向數組的指標,位移的單位是整個數組。
例1:
int a[6]={1,2,3,4,5,6};
int (*p2)[6];
p2=a;
這是錯誤的,因為指標p2的類型是int [6],所以應該是p2=&a;
int (*p2)[3];
這樣的話p2的類型是int [3],所以p2=(int(*) [3])&a; 要強制轉換成數組指標的類型。
注意:數組指標“所指向”的類型就是去掉指標變數之後所剩下的內容。
數組指標的類型就是去掉指標後剩下的內容。
例2:int (*p3)[5];
p3的類型是 int(*)[5];
p3所指向的類型是 int [5];
4、指標的運算:
1、指標相減得到的結果是兩指標之間元素的個數,而不是位元組數。
2、指標的運算
例1:
struct test
{
int name;
char *pcname;
short data;
char c[2];
}*p;
假設p的值是0x100000,結構體的大小是12;
則:
1、 p+0x1=0x10000c //p指向結構體 則p+1表示的是p+sizeof(test)*1
2、(unsigned int)p+0x1=0x100001 //p已經被轉化為一個無符號的數,加一就相當於兩個數相加
3、(int *)p+0x1=0x100004 //p被轉化為int *,所以p+1就表示p+sizeof(int *)*1
例2:假設當前機器小端儲存
int a[4] = { 1, 2, 3, 4 };
int *p = (int *)(a + 1);
int *p1 = (int *)(&a + 1);
int *p2 = (int *)(( int)&a + 1);
printf( "*p=%d,*p1=%d,*p2=%d\n" , *p, p1[-1],*p2);
輸出結果:*p=2,*p1=4,*p2=2000000
解析:p = (int *)(a + 1); a代表首元素地址,加1指向第二個元素,所以是2.
p1 = (int *)(&a + 1); &a表示數組的地址,&a+1就相當於&a+sizeof(a),p1指向的就是a[3]後面的地址,p1[-1]被解析成*(p1-1),所以是4.
p2 = (int *)(( int)&a + 1); (int)&a是把數組的地址取出來再轉化為一個int類型的數再加1,這時的加1就相當於兩個數相加(效果相當於讓&a向後位移一個位元組),相加的結果再轉化成(int *)地址,使p2指向這個地址。
當前數組的儲存:01 00 00 00 02 00 00 00 ........... 因為&a指向的是01這個位元組的位置,(int)&a+1的結果是指向了01的下一個位元組,這時在強制類型轉換成int *,則p2這時指向的空間的內容就是 00 00 00 02,因為是小端儲存,所以大端就是 02 00 00 00,輸出後就是2000000.
<