指针的疑难杂症,该治治了(建议收藏,一剂良方)

31 篇文章 2 订阅

目录

导语:

思维导图:

1、指针是什么

2、指针和指针类型

2.1、指针+-整数

2.2、指针的解引用

3、野指针

3.1、野指针成因

3.2、如何规避野指针

4、指针运算

4.1、指针+-整数

4.2、指针-指针

4.3、指针的关系运算

5、指针和数组

6、二级指针 

7、指针数组

结语:


导语:

  本篇文章将会为大家手撕C语言重要内容——指针,跟着我的步伐,对指针抽丝剥茧,相信看完后一定会对指针有更加全面的了解。

思维导图:

2981a0ba4430493eaf6990492c6441ff.png

1、指针是什么

1. 指针是内存中一个最小单元的编号,也就是地址

2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量

总结:指针就是地址,口语中说的指针通常指的是指针变量。

要了解指针,最好先是对内存有一个大概的了解,之前一篇文章讲过,链接放下面,有兴趣的可以了解一下。
指针简介(含内存讲解)

2、指针和指针类型

32位机器,地址是4个字节,指针变量大小是4个字节
64位机器,地址是8个字节,指针变量大小是8个字节

那我们假设,我们的机器都是32位机器(现在大多数都已经是64位了),那我们为什么还要区分指针的类型呢?反正都是4个字节,那怎么不用一个通用的类型来定义指针的呢?

例子:

char* p1;

int* p2;

float* p3;

但并没有出现这样的通用类型,因为每个类型的指针都有自己的特殊意义,那接下来为大家讲解指针类型的意义。

2.1、指针+-整数

案例1

int main()
{
	int a = 0x11223344;
	int* pa = &a;
	char* pc = &a;
	printf("%p\n", pa);
	printf("%p\n", pa+1);
	printf("%p\n", pc);
	printf("%p\n", pc+1);
	return 0;
}

运行对比:  

e5bfe7aa258043e091c7149c88552488.png

结论1:指针类型决定指针的步长(指针+1跳过几个字节)

整型指针+1,跳过4个字节

字符指针+1,跳过1个字节

int* p1;

char* p2

p1+4 -->4*sizeof(int)  

p2+4 -->4*sizeof(char)   

2.2、指针的解引用

案例2:

整型指针

int main()
{
	int a = 0x12345678;
	int* pa = &a;//整型指针
	*pa = 0;
	return 0;
}

字符类型指针

int main()
{
	int a = 0x12345678;
	//int* pa = &a;//整型指针
	char* pa = &a;//字符类型指针
	*pa = 0;
	return 0;
}

 区别对比:

99b7f8a75d2d4f2a8502007aa87eca0e.png

结论2:指针类型决定了,指针进行解引用的时候,一次性访问几个字节(访问权限的大小)

如果是char*的指针,解引用访问1个字节

如果是int*的指针,解引用访问4个字节

指针类型的意义:

指针不同的类型,其实提供了不同的视角去观察和访问内存(C语言真的很接近内存啊),类型决定了指针向前或者向后走一步有多大(距离)。

3、野指针

概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

3.1、野指针成因

1.指针为初始化

int main()
{
	int* p;//指针未初始化
	*p = 5;
	return 0;
}

p是一个局部变量,没有初始化,里边是随机值,那么在对p解引用赋值的时候,相当于这个值是随机放的,这个操作是十分危险的。

2.指针越界访问

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	int* p = arr;
	int i = 0;
	for (i = 0; i <= 5; i++)//越界访问
	{
		printf("%d ", *p);
		p++;
	}
	return 0;
}

p超出了范围之外,变成了野指针。

77a299c6e209451fa39e77f8f12391a7.png

 3.指向空间释放

int* test()
{
	int a = 10;
	return &a;
}
int main()
{
	int* p = test();
	printf("%d", *p);
	return 0;
}

cd526af51adf4211b2323fc4209f027b.png

打个比方:
我临时找了个旅馆住,我通知张三,说我在xx旅馆xx房间,可以来找我玩。我第二天早上就退房了,但是中午张三来xx旅馆xx房间找我,跟客服说我在这个房间里面,要进去。这是不是就不符合规矩了。

3.2、如何规避野指针

 1.指针初始化

int main()
{
	//指针初始化
	int a = 3;
	int* pa = &a;
	//如果是真不知道指针指向哪里,将指针设为空
	int* p = NULL;  //NULL -->空指针  专门原来用来初始化指针
	return 0;
}

当然了,将指针设为空指针就安全了吗?不是,这个空指针是不能直接用的

就比如农场门口栓了一条恶犬,你不去跟前惹它那当然没什么事,如果硬是要去比试比试,那大概率受伤。 

如图,程序直接挂掉了

6d1e4b52df8b40678ea4c56465815106.png

正确用法:

int main()
{
    int *p = NULL;
    //....
    int a = 10;
    p = &a;
    if(p != NULL)
   {
        *p = 20;
   }
    return 0;
}

2.小心指针越界

3.指针指向空间释放,及时置NULL

4. 避免返回局部变量的地址

5.指针使用前检查有效性

4、指针运算

4.1、指针+-整数

代码示例:

int main()
{
	int arr[5];
	int* a;
	for (a = &arr[0]; a < &arr[5];)
	{
		*a++ = 0;
	}
	return 0;
}

分析:

5b456a70cdac4a7a9cd6a15496cc03ca.png

这个我们前面提到过,这里就不再赘述了。当然能加整数就能减整数,我们应该在以后写代码的时候考虑到底加几、指针类型是什么。

4.2、指针-指针

前提:两个指针要指向同一块空间,两个指针类型相同

代码示例:

int main()
{
	int arr[10] = { 0 };
	printf("%d\n", &arr[9] - &arr[0]);
	return 0;
}
输出:
9

 指针-指针的绝对值,得到的是两个指针之间的元素个数

用法示例:

int my_strlen(char* str)
{
	char* start = str;
	while (*str != '\0')
	{
		str++;
	}
	//指针-指针得到中间的元素
	return str - start;
}
int main()
{
	char arr[] = "abcdef";
	int len = my_strlen(arr);//写一个函数求字符串长度
	printf("%d", len);
	return 0;
}

4.3、指针的关系运算

代码示例:

int main()
{
	int arr[5];
	int* a;
	for (a = &arr[5]; a > &arr[0];)
	{
		*--a = 0;
	}
	return 0;
}

012c1538023441a5855e26766efef1f3.png

思路和4.1的例子相同,但我们还能将其优化

代码如下:

int main()
{
	int arr[5];
	int* a;
	for (a = &arr[4]; a > &arr[0];a--)
	{
		*a = 0;
	}
	return 0;
}

1ca4fcb99ed7453e9353e81a1f2d3d81.png

实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。

标准规定:

允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。

5、指针和数组

1. 指针和数组是不同的对象

指针是一种变量,存放地址的,大小4/8 字节的

数组是一组相同类型元素的集合,是可以放多个元素的,大小是取决于元素个数和元素的类型的

2.数组的数组名是数组首元素的地址,地址是可以放在指针变量中

可以通过指针访问数组

通过指针访问数组示例:

int main()
{
	int arr[10] = { 0 };
	int* pa = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	//赋值
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		*(pa + i) = i + 1;
	}
	//输出
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(pa + i));
	}
	return 0;
}

输出:
1 2 3 4 5 6 7 8 9 10

未来,只要给我们一块空间,告诉我们从哪里开始访问,往后的空间,我们都能访问到,十分灵活。

拓展:
我们将数组的首元素地址给了pa,那么其实pa就等价于arr,之前文章也讲到过[]是一个操作符,arr[i]其中arr和i都是[]的操作数,那我们就能将arr[i]写为*(arr+i)。
我们来一波花式打印:

8d665d03e98c40958b9d352886997806.png

6、二级指针 

 什么是二级指针?可以简单的理解为套娃。指针是变量,是变量就有地址,那我们就能再用一个指针存放它的地址,那么这个指针就是二级指针。
打个比方(如图):

 

代码示例:

int main()
{
	int a = 10;
	int* pa = &a;
	int** ppa = &pa;//二级指针
	return 0;
}

81e85a4680364265ab1f3774a5bc3a93.png

 使用二级指针:

int main()
{
	int a = 10;
	int * pa = &a;
	int * * ppa = &pa;//二级指针
	**ppa = 20;
	printf("%d\n", **ppa);
	printf("%d\n", a);
	return 0;
}
输出:
20
20

b2ef6cf5b92d4e7dbd5005133c751512.png

7、指针数组

指针数组是指针还是数组?
答案:数组,存放指针的数组。

字符数组——存放字符的数组

整型数组——存放整型的数组

指针数组,那么当然就是存放指针的数组

直接展示:

32b146b48821403e8bb4aabc9646e789.png

 用一维数组模拟二维数组:

思路:

d1ccc9104e0f4ef2a59a38eda7dde9fb.png

 代码实现:

int main()
{
	int arr1[4] = { 1,2,3,4 };
	int arr2[4] = { 5,6,7,8 };
	int arr3[4] = { 9,10,11,12 };
	int* arr[3] = { arr1,arr2,arr3 };
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 4; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;

}
输出:
1 2 3 4
5 6 7 8
9 10 11 12

结语:

  如果大家认真看完本篇文章,在短期内,只要不玩太高级东西,基本上对于指针都能理解了,够玩了。对大家有帮助的话,不妨点个赞支持一下。
  本次分享就到这里,再见,下机!

  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

加法器+

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

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

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

打赏作者

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

抵扣说明:

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

余额充值