1、指針php
一、指針的概念:用來保存地址的「變量」叫作指針,能夠理解成指針是地址的一個別名。數組
例:定義一個×××指針ide
二、「指針的內容」,「指針所指向的內容」,「指針變量的地址」函數
(1)、指針的內容:url
指針變量p裏面存放的是a的地址,也就是0x0018ff44.spa
(2)、指針所指向的內容:3d
指針變量p裏面存放的地址(0x18ff44)這塊空間所對應的值,也就是10,咱們經過*p(解引用)能夠訪問到這個值。即:*p做爲右值時,*p==10,當*p做爲左值時表明的就是a這塊空間。指針
(3)、指針的地址:orm
指針p自己有一個地址,由編譯器分配的咱們不知道,注意:不要講指針變量p自己的地址和p所保存的地址(0x0018ff44)相混淆。blog
三、"未初始化的指針"和"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;
讓指針指向一個常量,通常用不到。
2、高級指針
一、二級指針概念:
什麼是指針???存放地址的變量就叫作指針,因此二級指針就是存放一級指針地址的指針變量。
注意:跟一級指針同樣,二級指針變量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;
一、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.一、指針和數組的關係:
不少人都分不清指針和數組之間的關係,嚴格的來說指針和數組之間不要緊,指針是指針,數組是數組。只不過他們兩個均可以經過「*」引用的方式和下標的方式來訪問元素而已。
例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:
因此必須保持定義和聲明的一致性,指針就是指針,數組就是數組。
三、指針數組,數組指針
注意:[]的優先級高於*,指針數組是一個數組,只不過裏面的元素所有都是指針。數組指針是一個指針,指向數組的指針,偏移的單位是整個數組。
例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];
四、指針的運算:
一、指針相減獲得的結果是兩指針之間元素的個數,而不是字節數。
二、指針的運算
例1:
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.
五、二維數組與二級指針參數
(1)、二維數組作參數:
二維數組作參數與一維數組作參數同樣,傳遞的都是首元素的地址,只不過二維數組的每一個元素又是一個一維數組 。
例:int arr[5][10];
這是一個5行10列的×××數組,能夠將它當作一個只有5個元素的一維數組,只不過每一個元素又是一個大小爲10的一維數組。
咱們來分析一下當arr做爲實參是,須要什麼類型的形參來接受它???
arr做爲參數時,表明首元素地址,要接受這個地址必須是一個指針或者是一個數組,可是他的每一個元素的類型是int[10]。
因此咱們能夠用一個指向類型爲int [10]的數組指針來接受arr[5][10],即:int (*p)[10] 。
一樣也能夠用一個數組來接受arr,即:int arr1[][10],這裏第二維的大小不能省略,在這裏int arr1[][10] 也能夠被解析成int (*arr1)[10];
(2)、二級指針作參數:
例:char *p[5];
這是一個大小爲5,每一個元素都是char *類型的數組,當他做爲實參時,須要什麼樣類型的形參來接受他呢???
分析:既然p是一個數組,那麼數組在做爲實參時,實際上傳遞過去的是首元素的地址,可是p的每個元素都是一個char *類型,也是一個地址,因此形參的類型應該是char **。
總結:
數組名只有在sizeof()和取&時纔不發生降級,其他地方都表明首元素地址。
六、函數指針
(1)函數指針: 是一個指向函數的指針
聲明:
例:void (*p)(); 首先p是一個指針,它指向一個返回值爲空,參數爲空的函數。
初始化:
要明確,函數指針本質仍是一個指針,既然是指針,那麼就必須給他進行初始化才能使用它,下面來看一看函數指針的初始化,定義一個返回值爲空參數爲空的函數:void fun()。
p=fun;
p=&fun;
這兩種初始化的方式都是正確的。由於函數名在被編譯後其實就是一個地址,因此這兩種方式本質上沒有什麼區別。
調用:
p();
(*p)();
這兩種方式都是正確的。p裏面存的fun的地址,而fun與&fun又是同樣的。
例:分析一下(*(void (*) () ) 0 ) ()是個什麼東東!!!
首先,void (*) ();是一個返回值爲空,參數爲空的函數指針類型。
void (*) () 0 ; 它的做用是把0強制類型轉換成一個返回值爲空,參數爲空的函數指針。
(*(void (*) () )0) ; 找到保存在0地址處的函數。
(*(void (*) () ) 0 ) (); 對這個函數進行調用。
用途:
一、回調函數:用戶將一個函數指針做爲參數傳遞給其餘函數,後者再「回調」用戶的函數,這種方式稱爲「回調函數」,若是想要你編寫的函數在不一樣的時候執行不一樣的工做,這時就可使用回調函數。回調函數也算是c語言裏面爲數很少的一個高大上的東西。
二、轉換表:轉換表本質上是一個函數指針數組,說白了就是一個數組,只不過數組裏面存放的所有都是函數指針。
例:實現一個計算器
要求:有「+、-、*、/」四個功能。用戶輸入操做數,得出結果。
#include<stdio.h> #include<stdlib.h> #include<assert.h> int Add(int a, int b) { return a + b; } int Sub(int a, int b) { return a - b; } int Mul(int a, int b) { return a *b; } int Div(int a, int b) { assert(b != 0); return a / b; } int operator(int (*fun)(int,int)) //回調函數 { int a = 0; int b = 0; printf( "請輸入操做數:" ); scanf( "%d%d", &a, &b); return (*fun )(a,b); } int main() { printf( "*************************************\n" ); printf( "*0.exit 1.Add****\n" ); printf( "*2.Sub 3.Mul****\n" ); printf( "*4.Div *********\n" ); int(*fun[5])(int ,int); //轉換表 fun[1] = Add; fun[2] = Sub; fun[3] = Mul; fun[4] = Div; int input = 1; while (input) { printf( "請選擇> " ); scanf( "%d", &input); if (input<0 || input>4) { printf( "選擇無效\n" ); } else if (input == 0) { break; } else { int ret = operator(fun[input]); printf( "%d\n", ret); } } system( "pause"); return 0; }
在這個計算器程序中,就使用到了轉換表fun[]。fun[]裏面存放了加減乘除四個函數,根據input的不一樣,fun[input]調用不一樣的函數。這種方式與switch() case的功能比較類似,不過轉換表比switch()case更簡單。
(2)、函數指針數組:
函數指針數組是一個指針數組,也就是一個數組,只不過裏面存放的全都是函數指針。
聲明:例: char* (*p[5])(int,int);
p與[5]先結合成一個數組,因此這是一個大小爲5的數組,數組元素的類型是一個函數指針,這個函數指針指向一個返回值爲char *,有兩個參數,且參數類型爲int,int的數組。
能夠這樣理解:char * (*)(int,int) p[5]; 其中char*(*)(int int)是p[5]的類型。
(3)、函數指針數組的指針:
函數指針數組的指針是一個數組指針,本質上是一個指針,只不過指向的是一個存放函數指針的數組。
聲明:例:char *(*(*p)[5])(int,int);
*與p先結合成一個指針,因此p是一個指針,指針所指向的是一個大小爲5的數組,這個數組存放的是函數指針,這些函數指針所指向的類型是返回值爲char *,有兩個int型參數的函數。
能夠這樣理解: char *(*[5])(int,int) *p; p是一個指針,類型是char*(*[5])(int,int).
總結:指針數組,是一個數組,裏面存放的是指針。
數組指針,是一個指針,指向一個數組的指針。
函數指針數組,是一個數組,裏面存放的是函數指針。
函數指針數組指針,是一個指針,指向一個存放函數指針的數組。
其實規律很簡單,強調的都是最後兩個字,最後兩個字是什麼他就是什麼,而前面的字就是他的類型。
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