浅析一级指针和二级指针、一维数组和二维数组

指针与数组

  • 一级指针
  • 二级指针
  • 一维数组
  • 二维数组
  • 数组指针
  • 指针数组
  • 函数指针
  • 指针函数

一级指针

指针是C语言最难的部分,是C语言的灵魂所在,C语言中常见的有一级指针和二级指针,一维数组和二维数组,今天就来简单的总结一下区别和用法。

1、说指针之前,先来看看下面这个例子。

#include 
#include 

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、一级指针和二级指针的定义:

  • 一级指针指向的是一块内存地址,而一级指针变量的值就是这块内存地址,若对一级指针取“ * ”运算,那么得到的就是这块内存地址所存的值。
  • 二级指针是指向指针的指针,二级指针变量的值就是他所指向的一级指针的地址。若对二级指针取“ * ”运算,那么得到的就是一级指针变量的值,取“ ** ”运算,得到的就是一级指针的指向的值。

下面通过一幅图可以更清楚的理解他们之间的区别。
浅析一级指针和二级指针、一维数组和二维数组_第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 
#include 

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 
#include 

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.

你可能感兴趣的:(笔记,c语言,指针,数组)