浅谈C语言中的指针

栏目:技术专题 发布时间:2025-01-02
作者:翁财喜

                                             翁财喜

 

一、指针描述

很多人都说指针是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 的成员初始化为2030和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 函数释放它,否则会导致内存泄漏。