指針二三事

1、指針:用來保存地址的「變量」叫作指針,能夠理解成指針是地址的一個別名。php

例:定義一個×××指針數組

spacer.gif

wKiom1cfdxHw4FQsAAA37X6Ri0A666.png

「指針的內容」,「指針所指向的內容」,「指針變量的地址」:ide

wKiom1cfdybjUbapAACjM92HHMQ499.png

spacer.gif

指針的內容:指針變量p裏面存放的是a的地址,也就是0x0018ff44.url

指針所指向的內容:指針變量p裏面存放的地址(0x18ff44)這塊空間所對應的值,也就是10,咱們經過*p(解引用)能夠訪問到這個值。即:*p做爲右值時,*p==10,當*p做爲左值時表明的就是a這塊空間。spa

指針的地址:指針p自己有一個地址,由編譯器分配的咱們不知道,注意:不要講指針變量p自己的地址和p所保存的地址(0x0018ff44)相混淆。3d


"未初始化的指針"和"NULL指針":指針

例:定義兩個指針變量p1和p2:orm

int *p1;              //未初始化的指針blog

int *p2=NULL;         //空指針內存

*p1=10;

*p2=10;

這樣賦值顯然是錯誤的,爲何呢???

試想一下,p1,p2是一個指針變量,因此p1,p2裏面應該存放的應該是一個地址。而對於p1咱們沒有往裏面放地址,這時p1是隨機指向的。若是此時解引用*p1,訪問到的是塊隨機的地址,再修改這個隨機地址裏面的值,假設這個隨機空間裏面的值很是重要,那你就再也找不回來了,因此一般定義一個指針就將他初始化爲NULL。

對於p2:雖然咱們將它初始化爲NULL,但它指向的是一塊空地址啊!!!向一塊空地址裏面放東西,根本是不可能的。


指針常量:

例:*(int *)200=10;

相信不少人對這個表達是都會產生疑惑,其實很好理解的,200是一個常數,咱們將它強制轉化爲 int *(也就是將常數200轉化成一個×××地址),再對這塊地址進行*引用,就訪問到一塊空間,能夠對這塊空間進行賦值。

常量指針:

例:int *p=&100;

讓指針指向一個常量,通常用不到。


二級指針:什麼是指針???存放地址的變量就叫作指針,因此二級指針就是存放一級指針地址的指針變量。

wKiom1cfd2rCXgJrAAA5oj3837c189.png

spacer.gif

注意:跟一級指針同樣,二級指針變量p2裏面存放的是一級指針變量p1的地址,一級指針變量p1裏面存放的是a的地址。要想訪問一塊地址裏面的內容可使用間接訪問符「*」,因此:

*p2==&p1, *p2就是訪問p2這塊空間裏面存放的地址所對應的內容。

**p2==10,由於*p2獲得的結果是p1的地址,在對p1的地址進行解引用*p1就訪問到了10.



例:分析下面幾種狀況下能不能做爲可修改的左值(可修改的左值必須表明一塊空間)

int a=10;

int *cp=&a;


*cp=20 //能夠做爲左值,當cp指向a時,*cp放到等號左邊表明a這塊空間,當*cp放到等號右邊表明a這塊空間的值。


&a=20  //錯誤,&a不能夠做爲左值,由於他不能表示一塊特定的空間,&a獲得的結果是a的地址,但並不表明a這塊空間,要想使用這塊空間,必須進行*引用,*&a=20正確。&a能夠做爲右值,表明是a的地址這個數值。


*cp+1     //不能夠做爲左值,由於*優先級高於+,因此*cp先結合,再加1至關於10+1,不表明一塊空間。


*(cp+1)   //能夠做爲左值,cp+1先結合,指向a的下一塊空間,再對這塊空間進行*引用,放在等號左邊就表明這塊空間。


++cp  //不能夠做爲左值,由於++cp只是將cp裏面的內容加一,並無進行*引用

cp++  //不能夠做爲左值


*cp++   //能夠做爲左值,先對cp裏面的地址進行*引用。再讓cp=cp+1(也就是讓cp指向a的下一塊空間,由於++優先級高於*)


++*cp   //不能夠做爲左值,*cp表明cp所指向的內容,再讓其加一,至關於10+1

注意:C中++cp和--cp都不能作左值,C++中++cp能夠做爲左值。


const 修飾一級指針,修飾二級指針:(const修飾的變量,仍是一個變量,只不過只是可讀的

int const a=10;

int b=30;

一、a=20;   //錯誤,const修飾a,因此不能改變a的值


二、int const *p;    //const修飾的是*p,因此不能改變*p

p=&a;       //正確  

*p=20;      //錯誤 不能經過*p改變a的值


三、const int *p;    //const修飾的是*p

p=&a;       //正確      

*p=20;      //錯誤


四、int *const p=&a;          //const修飾的是p

p=&b;          //錯誤     不能改變p

*p=20;         //正確     


五、int const * const p;     //const修飾*p,也修飾p,因此*p,p都不能改變

p=&b;        //錯誤     

*p=20;       //錯誤

注意const修飾變量的原則是離誰近修飾誰。const int *p與int const *p徹底同樣。


2、指針和數組的關係 ,指針數組,數組指針,指針的運算

指針和數組的關係:

不少人都分不清指針和數組之間的關係,嚴格的來說指針和數組之間不要緊,指針是指針,數組是數組。只不過他們兩個均可以經過「*」引用的方式和下標的方式來訪問元素而已。

例:

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:

wKioL1cfeOvAekVuAAC6r3lQoAM257.png因此必須保持定義和聲明的一致性,指針就是指針,數組就是數組。


指針數組,數組指針:

spacer.gifwKiom1cfeFXidtNwAABQmND7fSo334.png

注意:[]的優先級高於*,指針數組是一個數組,只不過裏面的元素所有都是指針。數組指針是一個指針,指向數組的指針,偏移的單位是整個數組。

例:

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;  要強制轉換成數組指針的類型

注意:數組指針「所指向」的類型就是去掉指針變量的名字以後所剩下的內容。

數組指針的類型就是去掉指針後剩下的內容。

例:int (*p3)[5];

p3的類型是 int [5];

p3所指向的類型是 int (*)[5];


指針的運算:

一、指針相減獲得的結果是兩指針之間元素的個數,而不是字節數。

二、指針的運算

例:

struct test

{

int name;

char *pcname;

short data;

                 char c[2];

}*p;

假設p的值是0x100000,結構體的大小是12;

則:

p+0x1=0x10000c                            //p指向結構體 則p+1表示的是p+sizeof(test)*1

(unsigned int)p+0x1=0x100001     //p已經被轉化爲一個無符號的數,加一就至關於兩個數相加

(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.



3、求內存和長度的差別:

×××數組:

int a[] = { 1, 2, 3, 4 };

printf( "%p\n",a);          //a表明首元素地址

printf( "%p\n",a+1);  //a+1表示第二個元素的地址

printf( "%p\n",&a);      //&a表明整個數組的首地址

printf( "%p\n",&a+1);  //&a表明數組的地址,&a+1則跳過整個數組

printf( "%d\n", sizeof (a));         //a在sizeof()中表明整個數組,    16  

printf( "%d\n", sizeof (a + 0));   //表明&a[0],32位下全部的地址大小都爲4                4  

printf( "%d\n", sizeof (*a));     //表示a[0],int佔4個字節                   4

printf( "%d\n", sizeof (a + 1));    //表示&a[1],32位下全部的地址大小都爲4             4  

printf( "%d\n", sizeof (a[1]));      //表示a[1],int佔4個字節                 4

printf( "%d\n", sizeof (&a));        //表明整個數組的地址,32位下全部的地址大小都爲4    4

printf( "%d\n", sizeof (&a + 1));     //由於&a表明整個數組地址,&a+1跳過整個數組,但仍是一個地址       

printf( "%d\n", sizeof (&a[0]));       //表示a[0]的地址        4

printf( "%d\n", sizeof (&a[0] + 1)); //表示a[1]的地址        4

printf( "%d\n", sizeof (*&a));         //&a表明整個數組,再*引用訪問這塊地址,就至關於求取真個數組的大小                   16 


字符數組:

char name[] = "abcdef" ;                          //這時字符串不表明首元素地址,而是字符數組的一種初始化方式,而且字符串老是默認以’\0‘結尾

printf( "%d\n", sizeof (name[0]));        //表示a,32位下char大小爲1         1

printf( "%d\n", sizeof (&name));          //表示整個數組的地址,32位下地址就是4          4

printf( "%d\n", sizeof (*name));           //表示 a                                  1

printf( "%d\n", sizeof (&name+1));      //表示指向整個數組以後的一塊空間,但仍是一個地址  4

printf( "%d\n", sizeof (name+1));         //表示b的地址            4

printf( "%d\n", sizeof (name));              //表明整個數組,還要加上‘\0'      7      

printf( "%d\n", strlen(name));                //求取字符串長度,不包括’\0‘      6

printf( "%d\n", strlen(&name));             //表明整個數組的地址,&name==name,strlen遇到’\0‘停下                          6

printf( "%d\n", strlen(&name + 1));       //是一個隨機值,表示指向整個數組以後的一個地址,從這個地址開始向後尋找'\0',由於’\0‘位置不肯定因此是隨機值

printf( "%d\n", strlen(name + 1));         //表示b的地址


字符指針:

char *name = "abcdef" ;                      //字符串放在等號右邊表明首元素地址

printf( "%d\n", sizeof (name[0]));        //如下標形式訪問,表明 a         1

printf( "%d\n", sizeof (&name));         //表示字符指針name的地址        4

printf( "%d\n", sizeof (*name));          //經過*訪問,表示a                    1

printf( "%d\n", sizeof (&name+1));      //表示指針name以後的一塊地址      4

printf( "%d\n", sizeof (name+1));          //表示b的地址                                 4

printf( "%d\n", sizeof (name));              //表明a的地址,                          4

printf( "%d\n", strlen(name));                //表明字符串首元素地址            6

printf( "%d\n", strlen(&name));             //表示指針name自己地址,由於從name開始向後’\0‘的位置不肯定,因此是個隨機值

printf( "%d\n", strlen(&name + 1));         //表示指針name自己地址以後的地址,由於’\0‘的位置不肯定,因此是個隨機值

printf( "%d\n", strlen(name + 1));             //表明b的地址                   5

相關文章
相關標籤/搜索