C语言的简单使用(十一、二级指针和它的常见用法)

二级指针是一个指向指针的指针。
用形象的话说就是:
一个变量的内存就是一个饭馆指针就是一张纸,上面写了这个饭馆的地址(红魔馆以南200米路西),你照着地址走就能找到。而二级指针也是一张纸,你翻开一看写了一个纸条的位置(门卫的桌子上),你要先在门卫的桌子上找到纸条,然后在根据纸条上的信息找到饭馆。纸条是一级指针,叙述纸条位置的纸是二级指针,三级四级以此类推。

那为啥要用二级指针呢?

二级指针可以做参数输出

这是很容易理解的,给一个函数传入一个参数,希望函数把这个函数处理一下。
说的更具体一点就是,我要经常使用一类字符串,但是它们有细微的差别,每次都自己定义又太麻烦,于是想把这个任务交给一个函数。

下面的程序中,通过输入函数的指针 p 来获取字符串"hello world",但是第一各函数失败了,第二个函数成功了,就是因为第二个函数用到了二级指针。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int getMem1(char *p)
{
	//从堆中申请空间交给p
	p = (char *)malloc(sizeof(char) * 100);
	//存入字符串
	strcpy(p, "hello world");
	printf("getMem1 p = %s\n",p);
	return 0;
}
int getMem2(char **p)
{
	if (p == NULL)
	{
		return -1;
	}
	//从堆中申请空间
	char *temp = (char *)malloc(sizeof(char) * 100);
	if (temp == NULL)//判断空间是否被申请,可以忽略
	{
		return -2;
	}
	//存入字符串
	strcpy(temp, "hello world");
	//把字符串交给*p
	*p = temp;
	printf("getMem2 p = %s\n", temp);
	return 0;
}
int main(void)
{
	char *p = NULL;
	//一级指针做函数的参数输出
	int ret = getMem1(p);
	//判断函数是否正常结束,可忽略
	if (ret != 0)
	{
		printf("err :%d\n",ret);
		return ret;
	}
	printf("p = %s\n",p);

	if (p != NULL)
	{
		free(p);
		p = NULL;
	}
	//二级指针做函数的参数输出
	ret = getMem2(&p);
	//判断函数是否正常结束,可忽略
	if (ret != 0)
	{
		printf("err :%d\n", ret);
		return ret;
	}
	printf("p = %s\n", p);

	if (p != NULL)
	{
		free(p);
		p = NULL;
	}
	system("pause");
	return 0;
}

二级指针做参数输出
可以看出 getMem1() 在函数体内确实给了 p 一个 “hello world” ,不过出了函数就没了,因为它把形参指向了字符串,而函数一结束形参就没了。
getMem2() 在函数体内部和外部都能保证 p 拥有 “hello world” ,因为它是把 p 指向了字符串。

读懂了上面的例子就能介绍二级指针更花样的玩法了

二级指针做输入的三种模型

指针数组

第一个模型就是一个指针数组,很好理解的一种模型:
数组本身就是一个指针,而如果数组中的每个元素都是指针的话,数组名自然就是一个二级指针了(指向指针的指针)。

下面的一个程序就是对指针数组的排序操作

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//打印数组中的元素
void print_array(char **p, int n)
{
	int i = 0;
	for (i = 0; i < n; i++)
	{
		printf("p[%d]:%s\n", i, p[i]);
	}
}

void sort_array(char **p, int n)
{
	int i, j;
	char *temp;
	//选择法排序
	for (i = 0; i < n; i++)
	{
		for (j = i + 1; j < n; j++)
		{
			if (strcmp(p[i], p[j]) > 0)
			{
				temp = p[i];
				p[i] = p[j];
				p[j] = temp;
			}
		}
	}
}
int main(void)
{
	int i = 0, j = 0;
	char *temp = NULL;
	//指针数组,指针的数组,是一个数组,每个元素都是指针
	char *p[] = { "11111","00000","33333","22222" };
	//获取数组中元素的个数
	int n = sizeof(p) / sizeof(p[0]);
	printf("排序前:\n");
	print_array(p,n);
	//选择法排序
	sort_array(p,n);
	printf("排序后:\n");
	print_array(p,n);

	system("pause");
	return 0;
}

指针数组模型

二维数组

其实二维数组也是个二级指针,它的首先指向一堆一维数组(行),然后在根据一维数组指向各个元素的内存。
如 a[2] [3]
a是一个二级指针,a[0],a[1],a[2]都是些一级指针,a[0][1]之类的才是真正的内存
下面的程序就是展示a[0]到底有多长的

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//a[][30]是为了告诉函数 a + 1要移动的步长
void print_array(char a[][30], int n)
{
	int i = 0;
	printf("a:%d,a+1:%d\n", a, a + 1);//a+1成为了&a+4
	for (i = 0; i < n; i++)
	{
			printf("%s ",a[i]);
			//printf("%s ", *(a + i));//首行地址和首元素的地址相同
	}
	printf("\n");
}
void sort_array(char a[][30],int n)
{
	int i = 0, j = 0;
	char tmp[30];
	for (i = 0; i < n; i++)
	{
		for (j = i + 1; j < n; j++)
		{
			if (strcmp(a[i], a[j]) > 0)
			{
				strcpy(tmp,a[i]);
				strcpy(a[i],a[j]);
				strcpy(a[j],tmp);
			}
		}
	}
}
int main(void)
{
	//a[]等价于*a
	int i = 0;
	//4个a[30]的一维数组,二维数组
	//a代表首行地址,首行地址和首行首元素的地址有区别,但值是一样的,区别:步长不一样
	char a[4][30] = { "aaaaaaa" ,"bbbbbbb" ,"vvvvvvv" ,"1111111" };
	//获取行数
	int n = sizeof(a) / (sizeof(a[1]));
	printf("排序前\n");
	print_array(a,n);
	sort_array(a, n);
	printf("排序后\n");
	print_array(a, n);

	system("pause");
	return 0;
}

二维数组

手动创建的二维数组

这种结构画出来是这样的,跟二维数组有极大的相似性,都是一个二级指针指向了一堆一级指针,然后这些一级指针再指向一些变量,不过这并不算二维数组,因为每一行(为了方便叙述)没有必要长度一样,比如p[0]长度8,p[1]完全可以长度是9,p[2]长256,没一点问题

类树形结构
下面的程序是对上面的结构的一个创建方式

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//分配空间
char ** getMem(int n)
{
	int i = 0;
	//声明一个二级指针,给它分配n个一级指针的空间
	char **buf = (char **)malloc(n * sizeof(char *));
	if (buf == NULL)
	{
		printf("buf err\n");
		return NULL;
	}
	for (i = 0; i < n; i++)
	{
		char str[30];
		//给buf的每个元素再分配30个char的空间
		buf[i] = (char *)malloc(30 * sizeof(char));
		//产生数据
		sprintf(str, "test%d%d", i, i);
		//装填数据
		strcpy(buf[i], str);

	}
	return buf;
}
//只是为了展示buf中的内容而写的函数
void print_buf(char **buf,int n)
{
	int i = 0;
	for (i = 0; i < n; i++)
	{
		printf("%s ", buf[i]);
	}
	printf("\n");
}
//为了完整释放掉buf的空间而写的函数
void free_buf(char **buf,int n)
{
	int i;
	//先释放buf中的元素
	for (i = 0; i < n; i++)
	{
		free(buf[i]);
		buf[i] = NULL;
	}
	//再释放buf
	if (buf != NULL)
	{
		free(buf);
		buf = NULL;
	}
}
int main(void)
{
	//10个char *,每一个的值都是空
	int n = 3;
	char **buf = NULL;
	buf = getMem(n);
	if (buf == NULL)
	{
		printf("getMem err\n");
		return -1;
	}
	print_buf(buf,n);
	free_buf(buf,n);
	buf = NULL;
	system("pause");
	return 0;
}

结果

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值