安得指针千万间,大庇天下地址具欢颜(上)

hello,今天我们来分享一些关于指针的内容。我们都知道,指针可谓是博大精深,让一众小白望尘莫及。那么指针真的那么可怕吗?莫慌,让我们一起来征服它。

首先,我们来看看指针的定义。


那么什么是内存呢?
我们知道,计算机是有32位和64位之分的。所谓32位就是有32位地址线,64位就是有64位地址线。每根地址线有正负电之分,我们可以对应为0和1来表示,也就是二进制来表示。
我们以32位的计算机为例,它的表示范围为00000000000000000000000000000000到11111111111111111111111111111111
这之中的每一个编号都可以作为一个地址,也是一个字节。

明确了这个问题之后,我们再来往下看一段代码:

这就是指针变量储存地址的最直观案例了。那么也许你会有一个疑问,a是int型的,我们知道它占四个字节,那么指针变量指向的是其中的一个字节还是全部字节呢?答案在下图揭晓:

没错**,指针指向的就是首地址。**那么既然p指向的是首地址,我们又怎么能一次性地取到a的全部地址呢?我们要借用一个符号——*。对,就是星号。星号是解引用操作符。我们通过一个例子来看一下:

那么讲到这里你是否有一个疑问,我们上面有说道,**32位计算机指针变量大小为四个字节,64位计算机指针变量大小为八个字节。**那为什么还要给指针变量定义类型呢?这个问题我们继续往下看:

下面,我们就来看看指针和指针类型


我们先来看一个例子:



看完以上三张图,注意找不同哦!有没有什么发现?

谜底揭晓:

好的,通过以上的演示我们就可以明白,定义指针的类型,目的是决定指针访问几个字节。我们再来看一个例子;

通过这个例子我们还可以看出,指针类型不仅可以决定指针访问几个字节,也可以决定指针加一后,跳过几个字节。
根据这个规律,我们就可以这样写程序了:

好的,我们继续看下一个程序:

#include<stdio.h>
int *test()
{
   
	int a =10;
	return &a;
}
int main()
{
   
	int *p = test();
	printf("hh\n");
	printf("%d ", *p);
	return 0;
}

猜猜屏幕上会输出什么?
答案揭晓:

我们发现输出的竟然是一个随机值,那么这是为什么呢?聪明的小伙伴们想必已经猜到了,当test函数执行完成,a的空间就已经被释放了,所以后出现随机值。(有的同学可能会发现,如果我们不打印hh,输出的值就是10,这个问题比较复杂,我们在之后的文章中再讲。)打印出随机值,这里的p就成为了传说中的——野指针。

我们先来看看野指针的概念

野指针是指指针指向的位置是不可知的,包括随机的,不正确的或者没有明确限制的。
也许你曾经在网上刷到过野生大爷的段子,但野指针从破坏力上来说完胜野生大爷。

那么都有什么原因会造成野指针呢?
1.指针未初始化

2.指针越界访问


通过以上两张图对比,我们可以发现,第一张图报错的原因是数组大小仅为10,而我们放了11个元素。这就是指针越界。但有人可能会发现,为什么自己的程序明明越界了,但编译器为什么没有报错呢?难道厉害的你成功骗过了编译器?

这个问题呢,显然,虽然编译器没有报错,但你毕竟是错了,就好比小偷偷了东西,虽然没有被警察发现,但毕竟也是偷了,虽然读书人的事怎么能叫偷呢?所以,小伙伴们一定要注意,还是不要越界的好!!!
3.指针指向的空间被释放
比如这个例子:

野指针破坏力如此之强,那么我们又该如何避免野指针呢?
在下给出如下建议:
1、指针初始化
2、小心指针越界
3、指针指向空间释放即时置NULL(空指针)
4、指针使用之前检查有效性
好的,接下来,简单说完野指针,我们继续往下来:

我们讲一下指针运算

那么指针都有哪些运算呢?我们来看一下:

  1. 指针+/-整数
  2. 指针-指针
  3. 指针的关系运算
    好的,下面我们分别来看看这三种指针运算:
    1.指针+/-整数
    先来看这个例子:

通过这个例子我们可以发现,指针加减整数运算的一些规律。我们发现上面的程序两种写法输出的结果是一样的,那么这两种写法有什么不同呢?我们来逐一分析一下:

我们发现第一种写法,指针p其实是没有变的,一直指向起始地址,只是p加了整数之后,产生的新的p+i,指向新的数字。

我们发现这种写法和第一种的输出是一样的,我们来分析一下这个程序。在这个程序里,p的指向发生了变化,p指向的地址被赋予了i的值,当我们需要输出时,我们需要重新把p赋成arr,这样再利用for循环,就可以打印出0~9。
通过以上两个例子,我们可以发现,指针与整数的加减法,有时候可以产生新的地址,有时候可以改变指针指向的地址。
2.指针-指针
我们先来看一个例子:

#include<stdio.h>
int main()
{
   
	int arr[10] = {
    0 };
	printf("%d\n", &arr[9] - &arr[0]);
	return 0;
}

猜一猜这个小巧的程序输出的结果是什么?
10?40?随机值?————答案揭晓:

想不到吧,答案竟然是9!!!这就告诉我们——指针-指针得到的值的绝对值是指针之间的元素个数。有小伙伴可能会问了,为什么要加一个绝对值的限定呢?我们将上述程序做一点小改动,再来看看结果:

这下明白为什么要限定绝对值了吧。

那么有的小伙伴可能会突发奇想,写出下面这样的程序:

#include<stdio.h>
int main()
{
   
	int a[5] = {
    0 };
	char b[5] = {
    0 };
	printf("%d \n", &a[5] - &b[0]);
	return 0;
}

这个结果又会输出什么呢?让我们来看一下:

结果是9,有什么规律吗?现在我们还无法确定,我们更改一下这个程序,我们再创建一个char型的c数组,再来看一看,如果有什么规律,两次的结果应该是相同的:

输出的结果是13,与之前的9不同,我们从中也可以看出,不同类型的指针的差值是没有规律的,因为,我们不能确定他们开辟的空间相距多远。杠精会问,相同类型不是一个数组会有规律吗?我们用一个程序验证一下:

通过上面这个程序我们发现,即使是同种类型的不同数组,也没有什么规律。由此,我们必须清醒地认识到,指针相减是有条件滴,条件就是:两个指针指的是同一块连续空间。you know?
3.指针的关系运算:
先来看一个例子:

我们将这个程序稍加改动,再来看一下结果:


我们发现编译器报错了,这又是为什么呢?
原来C语言有一个标准标准规定:


好家伙,服气不?
我们来试着理解一下这个神奇地标准:

也就是说p是可以和R进行比较滴,但不可以和L进行比较。这就是规定。人再江湖,身不由己,该屈服就屈服,即使再抗拒,也就认了这规定吧!
好的,了解了指针运算之后,

我们再来看看指针和数组

一个问题,数组名是什么?想知道这个问题的答案快来看一看这迷人的小截图:

通过这个小程序我们可以发现:数组名表示的是数组首元素的地址
于是,我们就可以利用指针,写出这样的程序:

#include <stdio.h>
int main()
{
   
	int arr[] = {
    1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	int *p = arr; //指针存放数组首元素的地址
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	for (i = 0; i<sz; i++)
	{
   
		printf("&arr[%d] = %p <====> p+%d = %p\n", i, &arr[i], i, p + i);
	}
	return 0;
}


并且,二维数组的数组名也是数组首元素地址,但是二维数组的首元素是它的第一行,类似于一个一维数组。
对于一个二维数组:a[3][5]我们看下图:

好的,我们再来看下一个问题:

二级指针

指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里? 这就是 二级指针 。

接下来我们了解一下指针数组

什么是指针数组呢?指针数组本质上是一种数组,不过指针数组用来存放指针。
举一个例子:

#include<stdio.h>
int main()
{
   
	int a=0;
	int b=5;
	int c=10;
	int *arr[3] = {
    &a, &b, &c };//指针数组
	return 0;
}

为了更好理解,我们来对比一下普通数组和指针数组:

好的,今天我们的分享就到这里,这只是关于指针的一些基本的知识,博主会在下一次的博文中与大家分享关于指针的进阶知识,也就是我们的《安得指针千万间,大庇天下地址具欢颜(下)》,还请大家继续支持。
本篇博文历时两天完成,创作不易,如果各位觉得这篇文章还可以,欢迎大家点赞、收藏、转发、评论,由于本人水平有限,如有错漏,也欢迎各位批评指正。欢迎大家和我一起交流学习心得,一起加油!!!

全部评论

相关推荐

Noob1024:一笔传三代,人走笔还在
点赞 评论 收藏
分享
评论
点赞
收藏
分享
牛客网
牛客企业服务