C语言:指针易错点、二级指针简单理解、二级指针做函数参数的输入输出特性、二级指针进行文件读写

一、指针易错点

1.越界问题
当我们写出下列代码时,编译器便自动给我们报错。原因是buf只有3个字符,但"abc"其中包含’\0’为4个字符,赋值时出现越界问题编译器自动报错。

void test()
{
	char buf[3] = "abc";
	printf("%s\n",buf);
}

2.指针叠加会不断改变指针指向
当我们执行下面代码时,看似没有问题,结果也正常输出。

void test()
{
	char* p = (char*)malloc(sizeof(char)*64);

	for (int i=0; i<10; i++)
	{
		*p = i+97;
		printf("%c",*p);
		p++;
	}
}

输出为:
在这里插入图片描述
但是我们好像忘了一件事情:那就是我们手动开辟的内存,要手动释放!
于是我们将开辟的内存释放。

if (p != NULL)
	{
		free(p);
		p = NULL;
	}

在这里插入图片描述
程序出现错误,费了老大力气才关掉它!为什么会出现这种情况?主要原因在这一行:当我们申请的首地址指针偏移后,就不可以利用这个指针释放堆内存了。

p++;//申请的首地址指针偏移后,就不可以利用这个指针释放堆内存了。

在这里插入图片描述
那么针对这种问题如何改进呢?我们利用临时指针来对其进行操作,释放的时候还是释放原来的地址,程序便不会出现问题了。

void test()
{
	char* p = (char*)malloc(sizeof(char)*64);

	char* pp = p;//利用临时指针进行操作
	for (int i=0; i<10; i++)
	{
		*pp = i+97;
		printf("%c",*pp);
		pp++;
	}

	if (p != NULL)
	{
		free(p);
		p = NULL;
	}
}

输出正确:
在这里插入图片描述
3.返回局部变量地址
编译器会报错,因为str是在栈上开辟的,函数的生命周期为该函数结束时候,后面别和函数接收后,它已经消亡,再返回即为随机值。

void test()
{
	char str[] = "abcdefg";
	printf("str = %s\n",str);

	return str;
}

4.同一块内存释放多次(不可以释放野指针) :内存被释放,不能够再释放这块内存,此时已无权限对其操作了;

void test()
{
	int* p2 = (int*)malloc(100);
	free(p2);//释放p2,不能再使用了
	free(p2);//再次操作,程序崩溃
}

二、二级指针简单理解

二级指针:二级指针也是一个变量,它指向的一定是“指针的地址”。
我们简单理解一下:一级指针指向的是普通变量的地址,那么二级指针指向的是“一级指针的地址”,以此类推,三级指针对应的是“二级指针的地址”。
如下代码:

#include <stdio.h>
#include <stdlib.h>
 
int main(int argc, char *argv[]) {
	//定义普通变量、一级指针、二级指针,不初始化 
	int a;
	int *p1;
	int **p2;
 
	//对前面的普通指针、一级指针、二级指针进行初始化赋值 
	a = 10;
	p1 = &a;
	p2 = &p1;
	
	printf("a = %d\n", a); 
	printf("&a = %d\n", &a); 
	printf("p1 = %d\n", p1);
	printf("*p1 = %d\n", *p1);
	printf("p2 = %d\n", p2);
	printf("*p2 = %d\n", *p2);
	printf("**p2 = %d\n", **p2);
	printf("&p2 = %d\n", &p2);
 
	return 0;
}

输出结果为:p1为a的地址,*p2为p1的地址,即a的地址。验证了一级指针指向的是普通变量的地址,那么二级指针指向的是一级指针的地址。
在这里插入图片描述
 

三、二级指针做函数参数的输入特性

输入特性:主调函数分配内存,被调函数使用内存。
实例1:主调函数在堆区开辟内存,被调函数在栈区使用。
在这里插入图片描述
代码实现:

void printArray(int** pArray,int len)//被调函数
{
	for (int i=0; i<len; i++)
	{
		printf("%d\n",*pArray[i]);
	}
}

void test()//主调函数
{
	int** p = (int**)malloc(sizeof(int*)*5);//在堆上分配内存

	//在栈上创建数据
	int a1 = 10;
	int a2 = 20;
	int a3 = 30;
	int a4 = 40;
	int a5 = 50;

	p[0] = &a1;
	p[1] = &a2;
	p[2] = &a3;
	p[3] = &a4;
	p[4] = &a5;

	printArray(p,5);

	if (p != NULL)
	{
		free(p);
		p = NULL;
	}
}

输出结果:主调函数分配内存,被调函数使用内存成功。
在这里插入图片描述
实例2:主调函数在栈上开辟内存,被调函数堆区使用。
在这里插入图片描述
代码实现:

void printArray(int** pArray,int len)
{
	for (int i=0; i<len; i++)
	{
		printf("%d\n",*pArray[i]);
	}
}

void test2()
{
	//在栈上创建
	int* pArray[5];

	for (int i=0; i<5; i++)
	{
		pArray[i] = (int*)malloc(4);//堆上分配
		*(pArray[i]) = 100+i;
	}

	int len = sizeof(pArray)/sizeof(int*);
	printArray(pArray,len);

	for (int i=0; i<5; i++)
	{
		if (pArray[i] != NULL)//要注意释放
		{
			free(pArray[i]);
			pArray[i] = NULL;
		}
	}
}

输出结果:主调函数分配内存,被调函数使用内存成功。
在这里插入图片描述
 

四、二级指针做函数参数的输出特性

输入特性:被调函数分配内存,主调函数使用内存。
实例1:被调函数在堆区开辟内存,主调函数栈区使用。

void allocateSpace(int** p)//被调函数分配内存
{
	int* arr = (int*)malloc(sizeof(int)*10);

	for (int i=0; i<10; i++)
	{
		arr[i] = i+10;
	}
	*p = arr;
}
void printArray(int** pArray,int len)
{
	for (int i=0; i<10; i++)
	{
		printf("%d\n",(*pArray)[i]);
	}
}

void test()//主调函数使用
{
	int* p = NULL;
	allocateSpace(&p);
	printArray(&p,10);

	if (p != NULL)
	{
		free(p);
		p = NULL;
	}
}

输出结果:被调函数分配内存,主调函数使用内存成功。
在这里插入图片描述
 

五、二级指针进行文件读写

我们利用二级指针进行文件的读写操作:从文件中读取数据,并且将数据存放在堆区数组中。
1.首先创建一个源文件,打开其所在目录,添加一个test.txt文件并写入一些数据。
在这里插入图片描述
2.从文件中读取数据,并且将数据存放在堆区数组中。
在这里插入图片描述

void ShowFileData(char** pArray,int len)//显示数组
{
	for (int i=0; i<len; i++)
	{
		printf("第%d行的数据为:%s\n",i+1,pArray[i]);
	}
}

int GetFileLines(FILE* file)//获取文件有效行数
{
	if (file == NULL)
	{
		return -1;
	}
	char buf[1024];//读取到的数据存储到缓冲区buf中

	int num = 0;
	while (fgets(buf,1024,file) != NULL)
	{
		num++;
	}
	//将文件光标置为文件首,若不置到文件首,文件光标获取后将在最后
	fseek(file,0,SEEK_SET);

	return num;
}

//参数1:文件指针 参数2:有效长度 参数3:堆区数组
void ReadFileData(FILE* file,int len,char** pArray)
{
	if ((file == NULL) || (len <= 0) || (pArray == NULL))
	{
		return;
	}

	char buf[1024];
	int index = 0;
	while (fgets(buf,1024,file) != NULL)//buf中就是存放的每行数据
	{   //buf就是存放的每行数据
		int currentLen = strlen(buf)+1;
		char* currentP = (char*)malloc(sizeof(char)*currentLen);
		//将数据拷贝到堆区内存中
		strcpy(currentP,buf);
		pArray[index++] = currentP;

		//清空缓冲区
		memset(buf,0,1024);
	}
}

void FreeSpace(char** pArray,int len)//释放
{
	for (int i=0; i<len; i++)
	{
		if (pArray[i] != NULL)
		{
			free(pArray[i]);
			pArray[i] = NULL;
		}
	}

	free(pArray);
	pArray = NULL;
}

void test()
{
	FILE* file = fopen("./test.txt","r");//以只读方式打开文件
	if (file == NULL)
	{
		printf("文件打开失败\n");
		return;
	}

	int len = GetFileLines(file);
	printf("文件的有效行数为:%d\n",len);

	char** pArray = (char**)malloc(sizeof(char*)*len);
	//将文件中的有效数据读取后放入到pArray中
	ReadFileData(file,len,pArray);

	//打印数据
	ShowFileData(pArray,len);

	//释放数据
	FreeSpace(pArray,len);
	pArray = NULL;
}

输出结果:成功获取。
在这里插入图片描述
参考博客:
指针(一级指针,二级指针)
如何理解C指针及二级指针(1)

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
回答: 当函数参数传入二级指针时,可以实现返回一个地址的目的。二级指针作为入参,可以在函数内部修改一级指针指向的地址,从而改变实参的值。通过传入一级指针的地址,可以在函数内部将二级指针指向一个新的地址,并返回该地址。这样既能返回一个地址,又能返回一个结果。\[2\] 例如,可以使用二级指针作为入参来实现动态分配内存,并将分配的内存地址返回给调用者。在函数内部,可以通过修改二级指针指向的地址来分配内存,并将分配的地址赋值给一级指针。\[3\] 这样,调用者就可以通过传入一级指针的地址来获取分配的内存地址。 #### 引用[.reference_title] - *1* *3* [C语言函数参数:一级指针二级指针的应用小结](https://blog.csdn.net/u012351051/article/details/120937818)[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^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [函数传参过程中的二级指针问题](https://blog.csdn.net/m0_60413945/article/details/128102042)[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^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值