翁財喜
一、指針描述
很多人都說指針是C語言的精髓,那什么是指針?其實本質(zhì)上來講,指針就是地址,指針變量的值也就是地址的值。要搞清一個指針需要搞清指針的四方面的內(nèi)容:指針的類型、指針所指向的類型、指針的值、指針本身所占據(jù)的內(nèi)存區(qū)。
例如:定義一個int *p,指針的類型是int*,指針所指向的類型是int,指針的值就是它所指向的內(nèi)存區(qū)或者說地址。指針本身占據(jù)的內(nèi)存區(qū),在32位的平臺是占據(jù)了4個字節(jié),或者使用sizeof(指針的類型)就能知道大小。
二、指針的運算符
指針的運算需要知道兩個運算符:&是取地址運算符,*是間接運算符。
例如:int a=1; int *p;//定義一個變量a值為1,定義一個指針變量p
p=&a; //使p指向了a所在的地址
*p=2; //操作指針變量p賦值為2,這邊其實也是相當于把a的值重新賦值為2
三、指針和數(shù)組的關(guān)系
舉例1:
int value;//定義一個變量
int a[20]={0};//定義一個大小為20的數(shù)組
int *p=a;//定義一個指針p指向數(shù)組a的首地址
value=*p;//這邊其實就是等于value=a[0]
value=*(p+3);//這邊其實就是等于value=a[3]
for(i=0;i<20;i++)
{
(*p)++;//將指針指向的數(shù)組地址的值加1
p++;//移動指針的地址,就是將指針指向下一個數(shù)組單元地址
}
以上代碼實現(xiàn)將20個數(shù)組單元的值由0變成1,正常而言數(shù)組名a代表數(shù)組本身,類型是int[20]。但是如果將a看作為指針的話,他的類型是int*,是指向數(shù)組第0個單元的地址,也就是首地址。注意:該指針是不能進行修改的,也就是像a++這種操作是不行的。
舉例2:
char *p[2]={
"Hello",
"BTZZ",
};//定義指針數(shù)組,里面存儲2個字符串
int (*pt)[10];//指向數(shù)組的指針,也就是行指針
p是一個三單元的數(shù)組,該數(shù)組的每個單元都是一個指針,這些指針各指向一個字符串。把指針數(shù)組名p當作一個指針的話,它指向數(shù)組的第0號單元,它的類型是char **,它指向的類型是char *。
*p也是一個指針,它的類型是char *,它所指向的類型是char,它指向的地址是字符串"Hello!"的第一個字符的地址,即'H'的地址。注意:字符串相當于是一個數(shù)組,在內(nèi)存中以數(shù)組的形式儲存,只不過字符串是一個數(shù)組常量,內(nèi)容不可改變,且只能是右值。如果看成指針的話,他即是常量指針,也是指針常量。
p+1也是一個指針,它指向數(shù)組的第1號單元,它的類型是char**,它指向的類型是char*。*(p+1)也是一個指針,它的類型是char*,它所指向的類型是char,它指向"BTZZ"的第一個字符’B’。
四、指針和結(jié)構(gòu)類型的關(guān)系
舉例:
struct MyStruct
{
int a;
int b;
int c;
};
struct MyStruct ss={20,30,40}; //聲明了結(jié)構(gòu)對象ss,并把ss 的成員初始化為20、30和40。
struct MyStruct *ptr=&ss; //聲明了一個指向結(jié)構(gòu)對象ss的指針,它的類型是 MyStruct *,它指向的類型是MyStruct。
這邊會用到一個指向運算符->,指針ptr通過使用指向運算符來對結(jié)構(gòu)體成員進行訪問,例如:ptr->a;ptr->b;ptr->b。
五、指針和函數(shù)的關(guān)系
舉例1:
void fun(int *a, int *b)//使用*接收地址
{
int temp;
temp = *a;
*a = *b;
*b =temp;
}
void main(void)
{
int a =10, b = 20;
fun2(&a, &b); //將ab的地址作為參數(shù)傳過去
}
以上代碼實現(xiàn)a和b值互換,函數(shù)fun將形參保存的地址內(nèi)容進行ab值互換操作,那么實參的值也會進行改變。
舉例2:
void fun(char **p) //要使用**進行接收
{
*p = "BTZZ";
}
void main(void )
{
char *p = "hello";
fun(&p); //傳實參地址
}
以上代碼將指針p指向了字符串BTZZ,因為傳遞的實參是一個一級指針,所以地址傳參的時候需要形參是一個二級指針,也就是使用**來進行接收。
舉例3:
int add(int x, int y)
{
return x + y;
}
int process(int (*p)(int, int), int a, int b)//回調(diào)函數(shù),使用函數(shù)指針p指向add函數(shù)
{
int c;
c= (*p)(a, b); //調(diào)用add函數(shù)
return c;
}
void main(void)
{
int num;
num = process(add, 2 ,3); //將add函數(shù)作為參數(shù)傳遞給另一個函數(shù)
}
以上代碼實現(xiàn)num等于5,add函數(shù)是已經(jīng)事先封裝好的函數(shù),但是函數(shù)的2個參數(shù)沒有先知道。所以,這種時候可以采用回調(diào)函數(shù)。
六、其他
1、int *f(void)和int (*f)(void)的區(qū)別
*f沒加括號本質(zhì)是一個函數(shù),返回值是一個int*的類型。加括號主要為了保存函數(shù)的首地址,通過函數(shù)指針變量來替換函數(shù)名的使用。
2、空類型指針(void *)
void * 通用指針,任何類型的指針都可以給void*類型的指針變量賦值,主要也是用在函數(shù)的參數(shù)和返回值的位置。
3、野指針
例如:int *p;*p=10; 這種情況就屬于野指針,是有問題的。因為指針變量又初始化,所以指針所指向的地址是隨機的值,解引用的時候也會造成訪問了一個不確定的值。
4、越界訪問
指針指向的空間不合理,比如:一個10個元素整型的數(shù)組,指針指向了第11個單元,那么第十一個元素的值就是隨機值了。
5、動態(tài)內(nèi)存分配
使用 malloc、calloc 和 realloc 等函數(shù)進行動態(tài)內(nèi)存分配時,需要確保在不再需要內(nèi)存時使用 free 函數(shù)釋放它,否則會導致內(nèi)存泄漏。