翁财喜
一、指针描述
很多人都说指针是C语言的精髓,那什么是指针?其实本质上来讲,指针就是地址,指针变量的值也就是地址的值。要搞清一个指针需要搞清指针的四方面的内容:指针的类型、指针所指向的类型、指针的值、指针本身所占据的内存区。
例如:定义一个int *p,指针的类型是int*,指针所指向的类型是int,指针的值就是它所指向的内存区或者说地址。指针本身占据的内存区,在32位的平台是占据了4个字节,或者使用sizeof(指针的类型)就能知道大小。
二、指针的运算符
指针的运算需要知道两个运算符:&是取地址运算符,*是间接运算符。
例如:int a=1; int *p;//定义一个变量a值为1,定义一个指针变量p
p=&a; //使p指向了a所在的地址
*p=2; //操作指针变量p赋值为2,这边其实也是相当于把a的值重新赋值为2
三、指针和数组的关系
举例1:
int value;//定义一个变量
int a[20]={0};//定义一个大小为20的数组
int *p=a;//定义一个指针p指向数组a的首地址
value=*p;//这边其实就是等于value=a[0]
value=*(p+3);//这边其实就是等于value=a[3]
for(i=0;i<20;i++)
{
(*p)++;//将指针指向的数组地址的值加1
p++;//移动指针的地址,就是将指针指向下一个数组单元地址
}
以上代码实现将20个数组单元的值由0变成1,正常而言数组名a代表数组本身,类型是int[20]。但是如果将a看作为指针的话,他的类型是int*,是指向数组第0个单元的地址,也就是首地址。注意:该指针是不能进行修改的,也就是像a++这种操作是不行的。
举例2:
char *p[2]={
"Hello",
"BTZZ",
};//定义指针数组,里面存储2个字符串
int (*pt)[10];//指向数组的指针,也就是行指针
p是一个三单元的数组,该数组的每个单元都是一个指针,这些指针各指向一个字符串。把指针数组名p当作一个指针的话,它指向数组的第0号单元,它的类型是char **,它指向的类型是char *。
*p也是一个指针,它的类型是char *,它所指向的类型是char,它指向的地址是字符串"Hello!"的第一个字符的地址,即'H'的地址。注意:字符串相当于是一个数组,在内存中以数组的形式储存,只不过字符串是一个数组常量,内容不可改变,且只能是右值。如果看成指针的话,他即是常量指针,也是指针常量。
p+1也是一个指针,它指向数组的第1号单元,它的类型是char**,它指向的类型是char*。*(p+1)也是一个指针,它的类型是char*,它所指向的类型是char,它指向"BTZZ"的第一个字符’B’。
四、指针和结构类型的关系
举例:
struct MyStruct
{
int a;
int b;
int c;
};
struct MyStruct ss={20,30,40}; //声明了结构对象ss,并把ss 的成员初始化为20、30和40。
struct MyStruct *ptr=&ss; //声明了一个指向结构对象ss的指针,它的类型是 MyStruct *,它指向的类型是MyStruct。
这边会用到一个指向运算符->,指针ptr通过使用指向运算符来对结构体成员进行访问,例如:ptr->a;ptr->b;ptr->b。
五、指针和函数的关系
举例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的地址作为参数传过去
}
以上代码实现a和b值互换,函数fun将形参保存的地址内容进行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)//回调函数,使用函数指针p指向add函数
{
int c;
c= (*p)(a, b); //调用add函数
return c;
}
void main(void)
{
int num;
num = process(add, 2 ,3); //将add函数作为参数传递给另一个函数
}
以上代码实现num等于5,add函数是已经事先封装好的函数,但是函数的2个参数没有先知道。所以,这种时候可以采用回调函数。
六、其他
1、int *f(void)和int (*f)(void)的区别
*f没加括号本质是一个函数,返回值是一个int*的类型。加括号主要为了保存函数的首地址,通过函数指针变量来替换函数名的使用。
2、空类型指针(void *)
void * 通用指针,任何类型的指针都可以给void*类型的指针变量赋值,主要也是用在函数的参数和返回值的位置。
3、野指针
例如:int *p;*p=10; 这种情况就属于野指针,是有问题的。因为指针变量又初始化,所以指针所指向的地址是随机的值,解引用的时候也会造成访问了一个不确定的值。
4、越界访问
指针指向的空间不合理,比如:一个10个元素整型的数组,指针指向了第11个单元,那么第十一个元素的值就是随机值了。
5、动态内存分配
使用 malloc、calloc 和 realloc 等函数进行动态内存分配时,需要确保在不再需要内存时使用 free 函数释放它,否则会导致内存泄漏。