C语言指针·高级用法超详解(指针运算、野指针、悬空指针、void类型指针、二级以及多级指针)

目录

1.  指针的运算

2.  野指针和悬空指针

2.1  野指针

2.2  悬空指针

3.  void类型指针

4.  二级指针和多级指针

4.1  命名规则

4.2  作用

4.2.1  二级指针可以操作一级指针记录的地址

4.2.2  利用二级指针获取变量中记录的数据


1.  指针的运算

文章开始前可以先了解:C语言指针·入门用法超详解-CSDN博客

        通过上一章我们初步了解了指针的用法,知道指针的类型是:

        其中指针中数据类型的作用是获取字节数据的个数,正常情况下变量p只能获取到0x001的内存地址,而int型数据占4字节,因此:

        那么我们可以思考一下,若是:

int a=10;
int* p=&a;

p+1

        假设a的地址为:0x01,那么p+1指向的地址会是什么呢?

        会是0x02还是0x05呢?

        正确答案应该是:0x05,这是为什么呢?

步长:指针移动一次的字节个数

        因为,指针的加法操作实际上是将指针向后移动若干个存储单元,而不是简单的数学加法,上面我们也提到了正常情况下变量p只能获取到0x01的内存地址,而int型数据占4字节,而p+1就是指针移动一步,一步在这里是四个字节,因此p+1,最终会移动到0x05的位置。我们拿代码验证一下:

#include <stdio.h>

int main()
{
	/*
	* 指针的运算
	* 步长:指针移动一次,走了多少字节
	* char:1
	* short:2
	* int:4
	* long:4
	* long long:8
	*/

	//加法:指针往后移动了N步P+N
	//减法:指针往前移动了N步P-N

	int a = 10;
	int* p = &a;

	printf("%p\n", p);
	printf("%p\n", p + 1);
	printf("%p\n", p + 2);
	printf("%p\n", p - 1);
	printf("%p\n", p - 2);
	return 0;
}


注意:

指针有意义的操作:

        指针跟整数进行加、减操作(每次移动N个步长)

        指针跟指针进行减操作(间隔步长)

#include <stdio.h>

int main()
{
	/*
	* 有意义的操作:
		指针跟整数进行加、减操作(每次移动N个步长)
		指针跟指针进行减操作(间隔步长)
	  无意义的操作:
		指针跟整数进行乘除操作
		原因:此时指针指向不明
		指针跟指针进行加、乘、除操作

	*/

	//前提条件:保证内存空间是连续的
	//数组
	int arr[] = { 1,2,3,4,5,6,7,8,9 };

	//获取0索引的内存地址
	int* p1 = &arr[0];

	//指针跟整数进行加、减操作(每次移动N个步长)
	//通过内存地址获取数据
	printf("%d\n", *p1);
	printf("%d\n", *(p1 + 1));

	//获取5索引的内存地址
	int* p2 = &arr[5];

	//p2 - p1间隔了多少步长
	printf("%d\n", p2 - p1);
	printf("%p\n", p1);
	printf("%p\n", p2);


	return 0;
}

指针无意义的操作:

        指针跟整数进行乘除操作

        原因:此时指针指向不明

        指针跟指针进行加、乘、除操作


2.  野指针和悬空指针

2.1  野指针

        野指针:指针指向的空间未分配。

#include <stdio.h>

int main()
{
	/*
	野指针:指针指向的空间未分配
	悬空指针:指针指向的空间已分配,但是被释放了
	*/

	//野指针:指针指向的空间未分配
	int a = 10;
	int* p1 = &a;

	printf("%p\n", p1);
	printf("%d\n", *p1);

	//p2为野指针
	int* p2 = p1 + 10;
	printf("%p\n", p2);
	printf("%d\n", *p2);


	return 0;
}

        此时运行程序,虽然也能得到p2的地址,但是要知道,这块地址我们并没有给他分配,他虽然能找到地址,但是若是随意修改数据,在正常使用中,可能会修改别的函数的数据,导致最终运行错误: 

2.2  悬空指针

        悬空指针:指针指向的空间已分配,但是被释放了。

#include <stdio.h>

int* method();

int main()
{
	/*
	野指针:指针指向的空间未分配
	悬空指针:指针指向的空间已分配,但是被释放了
	*/

	//悬空指针:指针指向的空间已分配,但是被释放了
	int* p3 = method();

	printf("拖点时间\n");
	printf("拖点时间\n");
	printf("拖点时间\n");
	printf("拖点时间\n");
	printf("拖点时间\n");
	printf("拖点时间\n");

	printf("%p\n", p3);
	printf("%d\n", *p3);

	return 0;
}

int* method()
{
	int num = 10;
	int* p = &num;
	return p;
}

        会发现此时代码数据错误,正常p3应当显示10: 

        这里可以调用static来使用:

#include <stdio.h>

int* method();

int main()
{
	/*
	野指针:指针指向的空间未分配
	悬空指针:指针指向的空间已分配,但是被释放了
	*/

	//悬空指针:指针指向的空间已分配,但是被释放了
	int* p3 = method();

	printf("拖点时间\n");
	printf("拖点时间\n");
	printf("拖点时间\n");
	printf("拖点时间\n");
	printf("拖点时间\n");
	printf("拖点时间\n");

	printf("%p\n", p3);
	printf("%d\n", *p3);

	return 0;
}

int* method()
{
	static int num = 10;
	int* p = &num;
	return p;
}

3.  void类型指针

特殊类型:void* p

不表示任何类型,只能记录地址值,不能获取到变量里面的数据,也不能进行加减的计算。

特点:无法获取数据,无法计算,但是可以接收任意地址数据。

        在我们使用指针的过程中会发现,不同类型的指针之间是不能相互赋值的,否则会发生编辑错误:

#include <stdio.h>

int main()
{
	//void类型指针

	//定义两个变量
	int a = 10;
	short b = 20;

	//定义两个指针
	int* p1 = &a;
	short* p2 = &b;

	//输出打印
	printf("%d\n", *p1);
	printf("%d\n", *p2);

	//不同类型的指针之间是不能相互赋值的
	char* p3 = p1;
	printf("%d\n", *p3);

	//这里编译器为我们进行了强制类型转换,上面的过程相当于如下
	char* p4 = (char*)p1;
	printf("%d\n", *p4);

	return 0;
}

         这里的指针间进行复制那是因为编译器在编译过程中,对p1进行了强制类型转换,p3的实际流程,如p4所示:

        为了解决这一问题我们可以将数据类型定义为void,不过这也会导致,不能printf输出数据,因为void不能获取到变量里面的数据,也不能进行加减的计算,否则编译器会报错:

#include <stdio.h>

int main()
{
	//void类型指针

	//定义两个变量
	int a = 10;
	short b = 20;

	//定义两个指针
	int* p1 = &a;
	short* p2 = &b;

	//输出打印
	printf("%d\n", *p1);
	printf("%d\n", *p2);

	//不同类型的指针之间是不能相互赋值的
	char* p3 = p1;
	printf("%d\n", *p3);

	//这里编译器为我们进行了强制类型转换,上面的过程相当于如下
	char* p4 = (char*)p1;
	printf("%d\n", *p4);

	//void类型指针打破上面的观念
	//void没有任何类型,好处可以接收任意类型指针记录内存地址
	void* p5 = p1;
	void* p6 = p2;

	//缺点:不能获取到变量里面的数据,也不能进行加减的计算
	//printf("%d\n", *p5);
	//printf("%d\n", *p6);

	//printf("%d\n", *p5 - 1);
	//printf("%d\n", *p6 + 1);

	return 0;
}

        那么我们引用void有什么用呢?

        例如,我们用于调用函数进行交换数据,我们可以编写如下代码:

#include <stdio.h>

void swap(void* p1, void* p2);

int main()
{
	//void类型指针

	//定义两个变量
	int a = 10;
	short b = 20;

	//定义两个指针
	int* p1 = &a;
	short* p2 = &b;

	//输出打印
	printf("%d\n", *p1);
	printf("%d\n", *p2);

	//不同类型的指针之间是不能相互赋值的
	char* p3 = p1;
	printf("%d\n", *p3);

	//这里编译器为我们进行了强制类型转换,上面的过程相当于如下
	char* p4 = (char*)p1;
	printf("%d\n", *p4);

	//void类型指针打破上面的观念
	//void没有任何类型,好处可以接收任意类型指针记录内存地址
	void* p5 = p1;
	void* p6 = p2;

	//缺点:不能获取到变量里面的数据,也不能进行加减的计算
	//printf("%d\n", *p5);
	//printf("%d\n", *p6);

	//printf("%d\n", *p5 - 1);
	//printf("%d\n", *p6 + 1);

	//调用函数交换数据
	int c = 100;
	int d = 200;

	swap(&c, &d);

	printf("c=%d,d=%d\n", c, d);

	return 0;
}

//函数:用来交换两个变量记录数据

void swap(int* p1, int* p2)
{
	int temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}

         此时可以实现数据的交换:

        但是若是此时我想使用long long型数据呢?数据会发生报错(我在DevC++上运行报错,在VS上运行正确,因为VS在这里编辑器给他自动强制类型转换了):

#include <stdio.h>

void swap(void* p1, void* p2);

int main()
{
	//void类型指针

	//定义两个变量
	int a = 10;
	short b = 20;

	//定义两个指针
	int* p1 = &a;
	short* p2 = &b;

	//输出打印
	printf("%d\n", *p1);
	printf("%d\n", *p2);

	//不同类型的指针之间是不能相互赋值的
	char* p3 = p1;
	printf("%d\n", *p3);

	//这里编译器为我们进行了强制类型转换,上面的过程相当于如下
	char* p4 = (char*)p1;
	printf("%d\n", *p4);

	//void类型指针打破上面的观念
	//void没有任何类型,好处可以接收任意类型指针记录内存地址
	void* p5 = p1;
	void* p6 = p2;

	//缺点:不能获取到变量里面的数据,也不能进行加减的计算
	//printf("%d\n", *p5);
	//printf("%d\n", *p6);

	//printf("%d\n", *p5 - 1);
	//printf("%d\n", *p6 + 1);

	//调用函数交换数据
	long long c = 100L;
	long long d = 200L;

	swap(&c, &d);

	printf("c=%lld,d=%lld\n", c, d);

	return 0;
}

//函数:用来交换两个变量记录数据

void swap(int* p1, int* p2)
{
	int temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}

        那么我们如何才能进行不同类型都能调用该函数呢?可以使用void类型,

void swap(void* p1, void* p2)
{
	int temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}

        但是如果使用void又会出现新的问题就是:void不能获取到变量里面的数据,也不能进行加减的计算,那么要如何解决呢?

           这里将void*类型的指针p1和p2分别转换为char*类型的指针pc1和pc2。这是因为char类型是 C 语言中的基本数据类型,且大小为1字节,所以可以通过char*指针逐字节访问内存。     

        同时函数void swap(void* p1, void* p2, int len)加上一个len用于表示字节数,要是int型len就等于4,long long就是等于8(在x64环境下):

void swap(void* p1, void* p2, int len)
{
	//把void类型的指针,转换char类型的指针
	char* pc1 = p1;
	char* pc2 = p2;

	char temp = 0;

	//以字节为单位,一个字节一个字节的进行转换
	for (int i = 0; i < len; i++)
	{
		temp = *pc1;
		*pc1 = *pc2;
		*pc2 = temp;

		pc1++;
		pc2++;
	}
}

        完整程序代码: 

#include <stdio.h>

void swap(void* p1, void* p2, int len);

int main()
{
	//void类型指针

	//定义两个变量
	int a = 10;
	short b = 20;

	//定义两个指针
	int* p1 = &a;
	short* p2 = &b;

	//输出打印
	printf("%d\n", *p1);
	printf("%d\n", *p2);

	//不同类型的指针之间是不能相互赋值的
	char* p3 = p1;
	printf("%d\n", *p3);

	//这里编译器为我们进行了强制类型转换,上面的过程相当于如下
	char* p4 = (char*)p1;
	printf("%d\n", *p4);

	//void类型指针打破上面的观念
	//void没有任何类型,好处可以接收任意类型指针记录内存地址
	void* p5 = p1;
	void* p6 = p2;

	//缺点:不能获取到变量里面的数据,也不能进行加减的计算
	//printf("%d\n", *p5);
	//printf("%d\n", *p6);

	//printf("%d\n", *p5 - 1);
	//printf("%d\n", *p6 + 1);

	//调用函数交换数据
	int c = 100;
	int d = 200;

	swap(&c, &d, 4);

	printf("c=%d,d=%d\n", c, d);

	return 0;
}

//函数:用来交换两个变量记录数据
/*
void swap(int* p1, int* p2)
{
	int temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}
*/

//修改一下函数,更具有通用性
//因为以上函数,若是主函数调用的话,只能int类型的数据
void swap(void* p1, void* p2, int len)
{
	//把void类型的指针,转换char类型的指针
	char* pc1 = p1;
	char* pc2 = p2;

	char temp = 0;

	//以字节为单位,一个字节一个字节的进行转换
	for (int i = 0; i < len; i++)
	{
		temp = *pc1;
		*pc1 = *pc2;
		*pc2 = temp;

		pc1++;
		pc2++;
	}
}

4.  二级指针和多级指针

4.1  命名规则

        以二级指针为例,上一章最初我们了解了什么是指针?

C语言指针·入门用法超详解-CSDN博客

         那么既然普通变量就有指针,指针也是一个变量为什么不能有一个指针继续指向他呢?那么我们可以了解到:

        既然指针也有他的指针,那么指针的指针要怎么命名呢?

        其和指针的命名规则是一样的也是:

数据类型  *  变量名;

        但是需要注意的是:指针的数据类型要跟指向空间中的数据的类型保持一致。

        首先对于指针p,其指向数据a的地址,需要和a数据类型保持一致,那么指针的命名就是:

int                       *                    p;

数据类型           标记            变量名

        然后,二级指针指向的是指针的数据类型,此时指针空间内存储的不是数据,而是a的地址,他的数据类型是int*,也就是说二级指针的命名:

int*                       *                    p;

数据类型           标记            变量名

4.2  作用

4.2.1  二级指针可以操作一级指针记录的地址

        上代码:

#include <stdio.h>

int main()
{
	//定义变量
	int a = 10;
	int b = 20;

	//定义一级指针
	int* p = &a;

	printf("a的地址:%p\n", &a);
	printf("b的地址:%p\n", &b);
	printf("修改前p的地址:%p\n", p);

	//定义二级指针
	int** pp = &p;

	*pp = &b;

	printf("修改后p的地址:%p\n", p);

	return 0;
}

        可以发现p的地址被修改了,简单点来说:初始时,p指向变量a的地址。然后,通过pp这个二级指针,修改了p的指向,使其指向了变量b的地址。因此,最后输出显示p的地址发生了变化,从指向a的地址变为指向b的地址。

4.2.2  利用二级指针获取变量中记录的数据

#include <stdio.h>

int main()
{
	//定义变量
	int a = 10;
	int b = 20;

	//定义一级指针
	int* p = &a;

	//定义二级指针
	int** pp = &p;

	printf("修改前获取变量里面的数据:%d\n", **pp);

	*pp = &b;

	printf("修改后获取变量里面的数据:%d\n", **pp);

	return 0;
}

C语言指针·入门用法超详解-CSDN博客

指针_时光の尘的博客-CSDN博客

  • 12
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

时光の尘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值