讓你不再害怕指針
1.細說指針
指針是一個特殊的變量, 它裏面存儲的數值被解釋成為內存裏的一個地址。要搞清一個指針需要搞清指針的四方面的內容: 指針的類型、指針所指向的類型、指針的值或者叫指針所指向的內存區、 指針本身所占據的內存區。
2.指針的類型
最簡便辦法就是把指針聲明語句裏的指針名字去掉,剩下的部分就是這個指針的類型。
int *ptr; //指針類型是int* char *ptr; //指針類型是char* int **ptr;//指針類型是int ** int (*ptr)[3];//指針類型是int(*)[3] int* (*ptr)[4];//指針類型是int* (*)[4] int(*func)(int,int); //指針類型 int(*)(int,int)
3.指針所指向類型
當你通過指針來訪問指針所指向的內存區時,指針所指向的類型決定了編譯器將把那片內存區裏的內容當作什麽來看看待。
從語法上看,你只須把指針聲明語句中的指針的名字和名字左邊的指針聲明符*去掉,剩下的就是指針所指向的類型。
int *ptr; //指針類型指向是int char *ptr; //指針類型是char int **ptr;//指針類型是int * int (*ptr)[3];//指針類型是int()[3] int* (*ptr)[4];//指針類型是int* ()[4] int (*func)(int,int); //指針類型 int()(int,int)
在指針的算術運算中, 指針所指向的類型有很大的作用 。
指針的類型(即指針本身的類型) 和指針所指向的類型是兩個概念。
4.指針的值----或者叫 指針所指向的內存區或地址
指針的值是指針本身存儲的數值,這個值將被編譯器當作一個地址,而不是一個一般的數值。在32位程序裏,所有類型的指針的值都是一個32位整數,因為32位程序裏內存地址全都是32位長。指針所指向的內存區就是從指針的值所代表的那個內存地址開始,長度為sizeof(指針所指向的類型) 的一片內存區。
以後,我們說一個指針的值是 XX,就相當於說該指針指向了以 XX 為首地址的一片內存區域
5.指針本身所占內存空間的大小
只需要用sizeof函數測一下就知道了。在32位平臺下,指針本身占據了4個字節的大小。
指針本身所占的內存空間的大小,在判斷指針表達式是否能夠作為左值時很有用。
6.指針運算
指針可以加上或減去一個整數。 指針的這種運算的意義和通常的數值的加減運算的意義是不一樣的, 以單元為單位。
如:
char a[20]; int *ptr = (int *)a; //強制轉換並不會改變a的類型 ptr++;
指針ptr的類型是int*,它指針想的內存空間的類型是Int,它被初始化為指向整型變量a。ptr++自加1,向前移動1一個int類型的長度,因此,ptr所指向的地址由原來的a[0],指向了a[4]。
在比如:
#include<iostream> using namespace std; int main() { int array[20] = { 0 }; int *ptr = array; for (int i = 0; i < 20; ++i) { /* code */ (*ptr)++; //每個單元加1 ptr++; //向後移動 } for (int i = 0; i < 20;i++) { cout << *(ptr + i) << endl; } system("pause"); return 0; }
在如:
char a[20] = "you are a girl"; int *ptr = (int*)a; ptr += 5;
誤區:
#include<iostream> using namespace std; int main() { char a[20] = "you are a girl"; char *p = a; char **ptr = &p; cout << p << endl; cout << ptr << endl; cout << *ptr << endl; cout << **ptr << endl; ptr++; //指向類型是char* 執行此段 即 &p + 4 cout << ptr << endl; cout << *ptr << endl; cout << **ptr << endl; system("pause"); return 0; }
總結,一個指針ptrold加(減)一個整數n後,結果是一個新的指針ptrnew,ptrnew 的類型和 ptrold 的類型相同,ptrnew 所指向的類型和 ptrold所指向的類型也相同 。ptrnew 的值將比 ptrold 的值增加(減少) 了 n 乘sizeof(ptrold 所指向的類型) 個字節。就是說,ptrnew 所指向的內存區將比ptrold所指向的內存區向高(低)地址方向移動了n個sizeof(ptrold 所指向的類型)個字節。
指針不能進行加法運算,因為兩個指針加減得到的結果,不知指向了哪個地方。但是兩個指針可以進行減法運算。
7.運算符&和*
&p的運算結果是一個指針,指針的類型是a的類型加個*,指針所指向的類型是a的類型,指針所指向的地址,就是a的地址。
8.指針表達式
int array[10]; int *pa; pa = &a; int **ptr = &pa; *ptr = &b; pa = array; pa++; char *arr[20]; char **parr = arr; char *str; str == *parr; str == *(parr+1);
9.數組和指針的關系
數組名是一個常量指針。
int arr[10] = {1,2,3,4,5,6,7,8,9,10}; int *ptr = arr; char *str[3]={"hello world","hi,james","hello,xiaohua"}; //指針數組,每一個指針指向一個字符串的首地址 char s[80]; strcmp(s,str[0]);//等價於strcmp(s,*str); strcmp(s,str[1]);//等價於strcmp(s,*(str+1))
10.指針與結構體的關系
11.指針與函數的關系
12.指針類型轉換
當我們初始化一個指針或給一個指針賦值時, 賦值號的左邊是一個指針, 賦值號的右邊是一個指針表達式。 在我們前面所舉的例子中, 絕大多 數情況下, 指針的類型和指針表達式的類型是一樣的, 指針所指向的類型和指針表達式所指向的類型是一樣的。
float f = 12.3; float *fptr = &f; int *p = NULL;
上述例子中,我們想讓指針p指向f,應該怎麽辦?
p = &f; //是這樣麽?
顯然不對,因為指針p的類型是int * ,它所指向的類型是Int。而&f的結果是一個指針,其類型是float *,它所指向的類型是float。兩者不一致。這樣是行不通的。但是,為了實現我們的目錄,我們可以嘗試強制轉換。
p = (int*)&f;
這樣,我們可以得出,如果有一個指針p,我們需要把它的類型和指向的類型改為TYPE *TYPE,語法格式為:(TYPE*)p;
這樣強制轉換的結果是一個指針,該新指針的類型是TYPE*,它指向的類型是TYPE,它指向的地址是原指針指向的地址,而原來的指針Pde的一切屬性都沒有被修改。
一個函數如果使用了指針作為參數,那麽在函數調用語句的實參和形參的結合過程總,必須保證類型一致,否則要進行強制轉換。
void func(char*); int a = 123,b; func((char*)&a); void func(char *s){ char c; c = *(s + 3); *(s + 3) = *(s +0); *(s + 0) = c; }
那麽,可不可以把一個整數賦值給指針呢?
unsigned int a; int *prt; a = 2034533223; ptr = 2034533223; //錯誤 ptr = a ; //錯誤
正確處理方式:
prt = (int*) a;
既然,能夠把一個整數賦值給指針,那麽能不能把指針裏面的值當作一個整數取出來呢?
int a = 1223,b; int *ptr = &a; char *str; b = (int)ptr; //把指針的值當作一個整數取出來 str = (char *)b; //把這個值賦值給str
13.指針的安全問題
char s = ‘a‘; int *ptr; ptr = (int*)&s; *ptr = 1298;
讓你不再害怕指針