【五一创作】速通C语言第六站 一篇博客带你掌握指针初阶

系列文章目录

 速通C语言系列

 速通C语言第一站 一篇博客带你初识C语言        http://t.csdn.cn/N57xl

 速通C语言第二站 一篇博客带你搞定分支循环   http://t.csdn.cn/Uwn7W

 速通C语言第三站  一篇博客带你搞定函数        http://t.csdn.cn/bfrUM

速通C语言第四站  一篇博客带你学会数组          http://t.csdn.cn/Ol3lz

 速通C语言第五站 一篇博客带你详解操作符      http://t.csdn.cn/OOUBr


感谢佬们支持!

文章目录

  • 系列文章目录
  • 前言
  • 一、指针类型
  •            指针类型的意义
  • 二、野指针
  •          1  定义
  •          2 成因
  •          3 如何避免野指针?
  • 三、指针运算
  •          1 指针减指针
  •          2 指针的关系运算
  •          3 指针加/减整数
  • 四、指针和数组
  •       拓展
  • 五、二级指针
  • 六、指针数组
  •   
  • 总结


前言


一、指针类型

int*pa;
char*pc;
struct stu*;
……

以上均为不同类型指针

虽然类型不同,但是指针的本质是存储地址,所以我们认为任何类型的指针均为内置类型

但是指针的不同类型还是有用的


指针类型的意义

1 指针解引用的权限有多大

例;

我们给到一个16进制的数

	int a = 0x11223344;

先用int*类型的指针对其解引用

通过调试之后

这个二进制位的每个字节都被改成了0.


再用char*的指针解引用下

char* pa = &a;
	*pa = 0;

进过调试之后

 只改了第一个字节。

在后期我们模拟实现string类函数时,为了能对数据逐字节操作,就会将指针强转为char*类型,就是利用了不同指针类型解引用权限的不同。


2 指针的类型决定了指针一步走多远

例:

int arr[10] = { 0 };
	//p和pc均放的是一个地址
		int* p = arr;
	char* pc=arr;
	printf("%p\n", p);
	printf("%p\n", p+1);
	printf("\n");
	printf("%p\n", pc);
	printf("%p\n", pc+1);

打印后发现,p+1跳过了一个整型(4字节),pc+1跳过了一个字符(1字节)


二、野指针

  1  定义

如果一个指针指向的位置是不可知的,这个指针就是个野指针。

  2 成因

1 指针未初始化

例:

int* p;
	//p是一个局部变量,没初始化默认是随机值
	*p = 20;

2 指针越界访问

int arr[10] = { 0 };
	int* p = arr;
	int i = 0;
	for (i = 0; i <= 10; i++)
		//从i=0到i<=10,循环了11次
	{
		*p = i;
		p++;
	}

由于循环了11次,所以这波会越界访问


3 指针指向的空间释放

指针指向的空间被释放主要有两种:动态内存开辟(这个放在之后再讲),我们先来介绍下面这种情况:函数

int* test()
{
	int a = 10;
	return &a;
}
int main()
{
	int* p = test();
	*p = 20; 
	return 0;
}

在这段代码中,test函数返回了a的地址,但是我们知道出了test函数的作用域,a就会销毁,也就是a所在空间被释放了,这个时候p就成了野指针。

为了让大家好理解,我们举一个生活中的例子。

一钢在酒店开了一个房间302,给松小打电话让他明天来住,结果第一天晚上一钢退了房了,所以第二天松小来酒店想住这个房间就住不了了。

野指针是个很麻烦的东西,我们应该避免它


 3 如何避免野指针?

1 指针初始化

//当前不知道指针应该初始化为什么地址时,直接给他初始化为NULL
	int* p = NULL;

2 小心越界

由于编译器并不会检查越界,所以我们需要自己注意,或加上assert断言

注:

理论上来说,越界是可以的,但是越界后不能对其访问、修改。在某些情况下,我们需要越界一下(当然,我们不会对其访问、修改)。


3 指针指向的空间释放时及时将其置成NULL

这个等我们讲到动态内存后再向大家介绍。


4 指针使用前进行有效性检查

由于我们不能对空指针解引用,所以在使用指针前,一定要进行有效性检查

例:

if (p != NULL)
    {
        *p = 10;
    }

三、指针运算

    1 指针减指针

例:

int arr[10] = { 0,1,2,3,4,5,6,7,8,9,10 };
	printf("%d\n", &arr[9] - &arr[0]);

  我们画波图给大家演示一下

得到了中间元素的个数

  注:指针-指针的前提是两个指针需指向同一块空间。

 例:

int arr[10] = { 1,2,3,4,5,6,7,8,9 ,10};
    char c[5];
    printf("%d\n", &arr[9] - &c[0]);

由于起始地址不同,所以这波得到的是随机值。


2 指针的关系运算 、 3 指针加/减整数

我们先上例子

例:

#define N 5
int main()
{
	float value[N];
	float* vp;
	for (vp = &value[N]; vp > &value[0];)
	{
//先--,再赋0
		*--vp=0;
	}
}

这段代码中,刚开始vp=5,5>0,vp会将value[4] 赋成0,之后vp--变成4

                                     然后4>0,vp会将value[3] 赋成0,之后vp--变成2

……

                                            0=0时,vp停下

在上述过程中,vp--为指针+/-整数的运算,

vp>value[0]为指针的关系运算。


但是我们觉得上面这个式子似乎不太符合平时写的风格,所以对其改良一下

float value[N];
	float* vp;
	for (vp = &value[N]; vp >= &value[0]; vp--)
	{
		*vp = 0;
	}

对应于画图上:

这样写确实更好理解,也在大多数编译器上可行。 但是并不符合标准。

标准规定,允许指向数组元素的指针和指向数组最后面的·那个内存位置的指针比较,

但不允许与指向第一个元素之前的那个内存位置的指针进行比较

总结就是,两种方法都是越界而不访问,但是


四、指针和数组

众所周知,数组名是数组首元素地址,那每个元素的地址呢?

#include<stdio.h>
int main()
{
	int arr[10] = { 0 };
	int* p = arr;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%p-%p\n", &arr[i], p + i);
	}
}

 打印的地址相同

所以我们可知 p+i 产生的地址等价于 arr[i] 产生的地址(即下标为i的地址)


我们可以通过p+i对其进行赋值

for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d\n", *(p + i));
		}

 (成功赋值)


拓展

拓展一点东西

刚才我们知道了*(p+i)等价于arr[i]

所以可推得p和arr是一个东西

所以 arr[2] 等价于 (arr+2)等价于 *(p+2)

由于加法有交换律

我们又可推得 *(p+2)等价于*(2+p)等价于*(2+arr) 等价于 2[arr]

那么这个看起来三观炸裂的东西到底对不对呢?

我们浅测一下

printf("%d\n", 2[arr]);
printf("%d\n", arr[2]);

结果为……

(真的一样)

其实,从操作符的角度理解,[ ] 是下标引用操作符,所以2和arr均为它的两个操作数,就像a+b=b+a, 2和arr也可交换一下位置。

注:在底层上arr【2】会转化为*(arr+2)来处理哦


五、二级指针

前面我们知道,我们可以用指针存一个变量的地址

int a = 10;
	int* pa = &a;

pa是个指针,也就是个指针变量,既然是变量,就有存放的地址,我们可以类似地“套娃”出

二级指针的概念

	int** ppa = &pa;

为什么pa的类型为int? 因为他存的是int类型的地址

所以我们可推得ppa的类型为int*,因为他存的是int*类型的地址

此时,ppa就是一个二级指针变量。

我们用打印来验证一波

 printf("%p %p %p %p",&a,pa,&pa,ppa);

即证pa存的是a的地址,ppa存的是pa的地址

二级指针将在我们学习链表时用到


在其基础上,我们可以再次进行套娃

int***pppa=&ppa;

……

但是二级指针之后就很少用到了


六、指针数组

指针数组,即为存放指针的数组,其每个元素为一个指针

例:

//整形数组-》存放整形的数组
	int arr[10];
	//字符数组-》存放字符的数组
	char ch[5];
	//整形指针数组->每个元素是int*
	int* parr[5];
	//字符指针数组->每个元素是char*
	char* pch[5];

我们简单的应用一下

先给5个值,再用一个数组分别存储这五个值的指针,用for循环将其打印出来

//给5个值
	int num1 = 10;
	int num2 = 20;
	int num3 = 30;
	int num4 = 40;
	int num5 = 50;
	//用个指针数组给它存起来
	int* arr[5] = { &num1,&num2,&num3,&num4,&num5 };
	//for循环遍历打印
	for (int i = 0; i < 5; i++)
	{
		printf("%d\n", *arr[i]);
	}

   (成功打印)

 指针和数组还有很多指针数组,数组指针,回调函数……之类的套娃操作,我们放到指针进阶在做讲解。


总结

  做总结,    这篇博客带大家学习了指针初阶,相信大家对指针又有了新的理解,其实指针远不止这么点内容,更多深的、难的内容将放至指针进阶再为大家讲解。

水平有限,还请各位大佬指正。如果觉得对你有帮助的话,还请三连关注一波。希望大家都能拿到心仪的offer哦。

每日gitee侠:今天你交gitee了嘛

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值