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

一级指针

指针是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.

  • 3
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在使用getchar()输入二维字符数组时,可以采用以下方法: ```c int i = 0, j = 0, n; char a = ch; j++; } a[i][j = '\0'; //数组末尾补0 j = 0; } for (i = 0; i < n; i++) { printf("%s\n", a[i]); } ``` 上述代码示例使用了getchar()函数来逐个读取字符,并将字符存入二维字符数组a中。首先,使用scanf函数读取输入的行数n,并使用getchar()函数吃掉回车符。然后,使用两层循环,第一层循环遍历行数,第二层循环通过getchar()函数逐个读取字符并存入数组a中。当读取到换行符时,将该行字符串的末尾添加'\0'作为字符串的结束符,并重置列数j为0。最后,使用printf函数逐行打印二维字符数组a的内容。 请注意,使用getchar()函数输入二维字符数组需要注意换行符的处理,并且在使用二维字符数组之前要先声明好行数和列数。另外,使用getchar()函数输入字符数组存在一定的风险,推荐使用更安全的函数,如fgets或者scanf("%s")函数来输入字符串。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [二维字符数组的三种输入方式浅析(scanf()、gets()和fgets())](https://blog.csdn.net/aaaaeeen/article/details/128485626)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [用getchar输入二维数组](https://blog.csdn.net/google20/article/details/127909493)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值