1、指針:用來保存地址的「變量」叫作指針,能夠理解成指針是地址的一個別名。php
例:定義一個×××指針數組
「指針的內容」,「指針所指向的內容」,「指針變量的地址」:ide
指針的內容:指針變量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;
讓指針指向一個常量,通常用不到。
二級指針:什麼是指針???存放地址的變量就叫作指針,因此二級指針就是存放一級指針地址的指針變量。
注意:跟一級指針同樣,二級指針變量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:
因此必須保持定義和聲明的一致性,指針就是指針,數組就是數組。
指針數組,數組指針:
注意:[]的優先級高於*,指針數組是一個數組,只不過裏面的元素所有都是指針。數組指針是一個指針,指向數組的指針,偏移的單位是整個數組。
例:
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