一级指针
指针是C语言最难的部分,是C语言的灵魂所在,C语言中常见的有一级指针和二级指针,一维数组和二维数组,今天就来简单的总结一下区别和用法。
1、说指针之前,先来看看下面这个例子。
#include <stdio.h>
#include <stdlib.h>
int malloc_pointer(char *p)
{
if(p == NULL)
{
p = (char *)malloc(10);
}
return 0;
}
int free_pointer(char *p)
{
if(p != NULL)
{
free(p);
p = NULL;
}
return 0;
}
int main()
{
char* pointer1 = NULL;
malloc_pointer(pointer1);
printf("pointer1 is %s\n", pointer1 == NULL? "NULL":"NOT NULL");
char *pointer2 = malloc(10);
free_pointer(pointer2);
printf("pointer2 is %s\n", pointer2 == NULL? "NULL":"NOT NULL");
return 0;
}
最后打印的结果呢?当然是:
pointer1 is NULL
pointer2 is NOT NULL
如果对这个结果你感到奇怪的话,那么你很有必要接着看下去。
- 不是已经用malloc_pointer(pointer1)函数给pointer1指针分配内存了吗?为什么打印出来pointer1 is NULL呢?
- 同样free_pointer(pointer2)也已经释放了pointer2指向的内存,并且将指针指向了NULL,为什么打印出来pointer2 is NOT NULL呢?
这里有必要对以下两点作一个简单的说明:
1、一级指针和二级指针的定义:
- 一级指针指向的是一块内存地址,而一级指针变量的值就是这块内存地址,若对一级指针取“ * ”运算,那么得到的就是这块内存地址所存的值。
- 二级指针是指向指针的指针,二级指针变量的值就是他所指向的一级指针的地址。若对二级指针取“ * ”运算,那么得到的就是一级指针变量的值,取“ ** ”运算,得到的就是一级指针的指向的值。
下面通过一幅图可以更清楚的理解他们之间的区别。
2、函数传递的参数会存在一个副本:
- 拿上面的malloc_pointer(char *p)函数举例,当你传入pointer1时,函数中实际会给其分配一个副本,我们姑且叫做pointer1_copy变量,并给他分配10个字节的内存空间,但是我们想要的pointer1变量仍然是NULL,因为程序根本没操作到这个变量。
- 同样的free_pointer(char *p)函数,当传入一个已经分配过内存的的指针pointer2,那么函数中给其分配一个副本pointer2_copy,值为pointer2指向的内存地址,虽然可以达到释放内存的操作,但是由于操作不到pointer2变量,所以你无法在这个函数中试图用p = NULL来操作pointer2 = NULL。所以如果在函数体外不执行pointer2 = NULL的话,那么pointer2将成为一个野指针。
那么要是想在函数里去给指针分配内存或者释放内存,需要怎么做呢?当然就需要用二级指针。
二级指针
将上面的函数变成如下函数:
#include <stdio.h>
#include <stdlib.h>
int malloc_pointer(char **p)
{
if(*p == NULL)
{
*p = (char *)malloc(10);
}
return 0;
}
int free_pointer(char **p)
{
if(*p != NULL)
{
free(*p);
*p = NULL;
}
return 0;
}
int main()
{
char* pointer1 = NULL;
malloc_pointer(&pointer1);
printf("pointer1 is %s \n", pointer1 == NULL? "NULL":"NOT NULL");
char *pointer2 = malloc(10);
free_pointer(&pointer2);
printf("pointer2 is %s\n", pointer2 == NULL? "NULL":"NOT NULL");
return 0;
}
打印结果为:
pointer1 is NOT NULL
pointer2 is NULL
由此可见,传入二级指针来分配和释放内存是可行的。
因为虽然函数会给二级指针分配一个副本,但是*p不是副本,
*p跟传入的pointer1和pointer2就是同一个变量,操作*p即可达到目的。
一维数组
一维数组在C语言里也是非常常见的,一维数组占用的是内存中一串连续的地址空间,可以通过" 变量名[下标] "来访问相应的值,也可以通过指针来访问。一维数组名代表数组首地址,也就是说一维数组名也就是一个一级指针。假设有数组char array[10] = {0},那么:
- &array[0]、array的值是一样,都代表第一个元素地址
- array[i]、*(array+i)的值是一样的,代表第 i 个元素的值
- 若函数接收char *p或者char p[ ]类型的参数,那么可将array作为参数传递
二维数组
二维数组占用的也是内存中一串连续的地址空间,只不过维度上比一维数组多了一个,可以通过" 变量名[下标][下标] "来访问相应的值,也可以通过指针来访问,不过二维数组名和二级指针有所不同。假设有数组char array[10][10] = {0};
- 二维数组不等于二级指针,不能混用,假设有函数接收char **p类型的参数,若传入array,会报错。若函数参数为char (*p)[ ],不可写成char *p[ ],那么可以传入array作参数。这里涉及到数组指针和指针数组的概念。
- array、array[0]、&array[0]的值都是一样的,代表二维数组的起始地址。
- 二维数组名[下标]代表一个第 i 行的一级指针,例如,array[2]代表指向数组第2行的一级指针。
- *(array[2] + 5)、array[2][5]、 *(*(array+2)+5)都代表第2行第5个元素的值。由此可见*(array + i)也可以代表第 i 行的行指针,等价于array[i]
#include <stdio.h>
#include <stdlib.h>
int array2(char (*p)[])
{
return 0;
}
int main()
{
char array[10][10] = {0};
int i = 0, j = 0;
for(i = 0; i < 10; i++)
{
for(j = 0; j < 10; j++)
array[i][j] = j;
}
printf("array = %X, array[0] = %X, &array[0] = %X\n", array, array[0], &array[0]);
printf("array[2]+5 = %d, *(*(array+2)+5) = %d\n", *(array[2] + 5),*(*(array+2)+5) );
array2(array);
return 0;
}
打印情况为:
array = 62FDA0, array[0] = 62FDA0, &array[0] = 62FDA0
array[2]+5 = 5, *(*(array+2)+5) = 5
二维数组寻址方式比较多,也比较容易搞混淆,还需多加理解和记忆。
数组指针
数组指针,顾名思义就是指向数组的指针,定义方式:int (*p)[10] ;
这个括号不能省去,若写成int *p[10]就变成了接下来要说的指针数组。
int (*p)[10] 代表p是指向一个含有10个元素的一维数组,若给p分配内存,p = (int (*)[10])malloc(10); 那么p也就变成了一个含有十个元素的一位数组,此时等价于p[10][10]。这也就是上面二维数组讲到的,可按这种参数形式接收二维数组指针。
指针数组
顾名思义,就是包含元素为指针的数组,定义方式:int *p[10]; 注意这里没有括号
int *p[10] 代表有十个元素的一维数组p , 每个元素里都是一个一级指针,例如p[0]、p[1]、… 、p[9]都是一个指向int型数据的一级指针。既然是指针,当然也就可以分配内存,分配之后就又可以当二维数组用了。
不过使用指针的写法会更好一点,有助于自己理解指针的用法,用数组的写法虽然比较方便,但是不利于消化指针的用法。
通过以上数组指针和指针数组的概念,引出函数指针和指针函数的概念
函数指针
函数指针:指向函数的指针
正常函数指针的写法:
int (*pfun)(int a,int b);
定义一个函数指针pfun,可以指向一个int型的函数,参数也是两个int类型。比如:
int fun1(int a,int b)
{
printf("a=%d,b=%d\n");
return 0;
}
int main()
{
int (*pfun)(int a,int b);
pfun = fun1;
pfun(2,3); //会打印a=2,b=3
return 0;
}
使用typedef定义函数指针,是给函数指针取了一个别名。例如
typedef int (*pfun)(int a,int b);
这时候pfun相当于一个类型,而不是一个指针了。比如:
typedef int (*pfun)(int a,int b);
int fun1(int a,int b)
{
printf("a=%d,b=%d\n");
return 0;
}
int main()
{
pfun p1,p2;
p1 = fun1;
p2 = fun1;
p1(2,3);
p2(3,4);
return 0;
}
typedef给函数指针起别名,多用于回调函数,比如:
typedef int (*pfun)(int a,int b);
int fun1(int a,int b)
{
printf("a=%d,b=%d\n");
return 0;
}
int fun2(pfun cb)
{
cb(5,6); //会打印a=5,b=6.
return 0;
}
int main()
{
fun2(fun1);
return 0;
}
指针函数
指针函数没有什么特别的,就比如定义一个函数:int *pfun(int a ,int b); 就代表一个指针函数,表明这个函数返回值是一个int 型指针,只是需要注意这个是不带括号的,记得和函数指针带括号区分开。
No pains, no gains.