天天看點

指針二三事

一、指針:用來儲存位址的“變量”叫做指針,可以了解成指針是位址的一個别名。

例:定義一個×××指針

指針二三事

“指針的内容”,“指針所指向的内容”,“指針變量的位址”:

指針二三事

指針的内容:指針變量p裡面存放的是a的位址,也就是0x0018ff44.

指針所指向的内容:指針變量p裡面存放的位址(0x18ff44)這塊空間所對應的值,也就是10,我們通過*p(解引用)可以通路到這個值。即:*p作為右值時,*p==10,當*p作為左值時代表的就是a這塊空間。

指針的位址:指針p本身有一個位址,由編譯器配置設定的我們不知道,注意:不要講指針變量p本身的位址和p所儲存的位址(0x0018ff44)相混淆。

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

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

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

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;

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

二級指針:什麼是指針???存放位址的變量就叫做指針,是以二級指針就是存放一級指針位址的指針變量。

指針二三事

注意:跟一級指針一樣,二級指針變量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;

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完全一樣。

二、指針和數組的關系 ,指針數組,數組指針,指針的運算

指針和數組的關系:

很多人都分不清指針和數組之間的關系,嚴格的來講指針和數組之間沒關系,指針是指針,數組是數組。隻不過他們兩個都可以通過“*”引用的方式和下标的方式來通路元素而已。

例:

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:

指針二三事

是以必須保持定義和聲明的一緻性,指針就是指針,數組就是數組。

指針數組,數組指針:

指針二三事

注意:[]的優先級高于*,指針數組是一個數組,隻不過裡面的元素全部都是指針。數組指針是一個指針,指向數組的指針,偏移的機關是整個數組。

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];

指針的運算:

1、指針相減得到的結果是兩指針之間元素的個數,而不是位元組數。

2、指針的運算

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.

三、求記憶體和長度的差異:

×××數組:

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

繼續閱讀