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

“指針的内容”,“指針所指向的内容”,“指針變量的位址”:
指針的内容:指針變量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