【C】初阶指针

目录

 

一、指针是什么

二、指针和指针类型

三、野指针

四、指针运算

五、指针和数组

六、二级指针

七、指针数组


一、指针是什么

1、指针就是地址

2、我们平时说的指针时指针变量,用来存放地址

地址

内存中的每一块儿地方我们都会给上一个编号,为了方便区分和访问,一个字节就给一个编号,而内存又是连续的,所以编号也就有顺序。

指针变量

我们可以创建一个变量a,再用&取出变量的地址将它放入另一个变量b中,那么存放a的地址的变量b就称为指针变量

32位系统和64位系统

在32位系统上,有物理的32根地址线,它们可以产生2^32个地址,也就是:

00000000 00000000 00000000 00000000

00000000 00000000 00000000 00000001

00000000 00000000 00000000 00000010

......

11111111 11111111 11111111 11111111

我们知道一个地址是1字节,所以总大小就是2^32Byte/1024KB/1024MB/1024GB=4GB

一个字节是8个比特位,一个比特位等于一个二进制位,在32位系统上一个地址是32个二进制位,就等于4字节,所以当我们创建一个指针变量存放地址的时候,这个指针变量就是4字节。

在64位系统上,有64根地址线,可以产生2^64个地址,也就是:

00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

......

11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111

那么64个Bit就等于8字节,所以在64位系统上,指针变量是8字节。

其实我们可以测试一下,我们可以创建三个不同类型的变量,分别在32和64位系统下输出他们的大小。

32位: 

64位:

虽然不同类型所占字节不同,但所有类型的指针大小都是一样的。 

二、指针和指针类型

既然不同类型的指针大小都是一样的,那为什么还要分不同类型,直接用一个统一的名字来定义指针不是更方便吗?

其实,不同指针类型访问内存的数量是不一样的

我们创建一个int类型的a,再来一个int*类型的指针变量来存放a的地址,*p解引用找到a的值并将它赋值为500

 这个时候如果我们将int *改为char *的话,结果还会一样吗?

 可以看到结果输出了244,因为char*类型的指针解引用的时候,由于char是一个字节,所以只向后访问一个字节的内存,而500这个数字大于一个字节,所以取不到一整个500,那就只取了能取到的那部分。

同样的,比如double占8字节,当对double*类型的指针解引用的时候,就是访问8个字节。

三、野指针

当一个指针变量中存放的是一个随机值,我们就称它为野指针。

我们知道当我们创建一个变量但是不给它赋值的时候,它里面放的就是一个随机值,当我们创建一个指针变量但是不指定它指向的地址时,它就会随便指向一个地址,哪里都有可能,当它去访问一块儿不允许访问的内存时,就会导致程序崩溃。

当我们指向一块儿已经销毁的内存时,也会出现野指针,导致程序故障。比如我们创建一个函数,在函数中创建一个局部变量a,再返回a的地址,在main函数中用一个指针变量p去接收这个地址,这时候这个指针就是野指针,因为局部变量的生命周期只在函数内部,出了函数外就直接销毁了,而销毁之后,p指向的就是一块儿销毁的内存。

 当越界访问的时候,也会导致野指针的出现。

 我们创建的arr数组中有10个元素,而我们的for循环从0开始一直到10总共循环了11次,也就是说最后会对arr数组后面的那块儿内存进行赋值,这就造成了非法访问,导致程序崩溃。

如何避免野指针

当我们创建指针变量的时候一定要初始化,如果不知道初始化什么值,就可以将它指向NULL,比如 int * p = NULL,当我们要使用它的时候再进行赋值,NULL相当于内存中的0地址,是不能访问的,当使用它的时候就会报错,所以我们使用它的时候可以这样写:

 这个程序的意思就是当p不为NULL的时候就给它赋值,如果还是指向NULL就跳过,这样就不会造成野指针。

当然了,造成野指针的出现有很多种方式,所以在使用指针的时候一定要小心。

四、指针运算

指针±整数

当指针±一个整数的时候,假设这个整数是a,它就会跳过(a*指针类型大小)个字节。

比如我们创建一个int *类型的指针变量p,然后p+3,这个时候就会往后跳过12个字节(3*4)。

如何验证

我们创建一个int *类型的p指向空,让他加几次1,看看结果。 

当我们让它指向空的时候,里面放的是0,接下来我们让它+1

 

 可以看到,依次加一,地址就依次向后跳4个字节。

利用指针初始化数组

我们先让p指向最后一个元素后面的位置

再对p--之后解引用将它赋值为1

 以此类推,直到p不大于数组首元素地址。

虽然有这种写法,但是这种写法并不推荐,因为标准规定: 指向数组中元素的指针允许与数组外后面的地址比较,但不允许与数组外前面的地址做比较。

 指针减指针

在相同数组中,指针减去指针的绝对值等于两个指针之间的元素个数

通过指针减指针求字符串长度

把p传入函数中,当函数中p为‘\0’时则返回当前地址(在创建字符串的时候末尾会自动放一个‘\0’),也就是最后一个元素的地址了 ,‘\0’的地址减去第一个元素地址就是元素个数了。

 但是当两个指针相加的时候编译器就会报错,因为不存在指针相加,它没有任何的实际意义。

五、指针和数组

通过指针可以找到数组中的每一个元素。

我们让指针指向数组,当我们需要找到数组的第一个元素时,直接解引用就好了。 

当我们需要第二个元素时,让p+1再解引用就可以了。

利用这个原理,我们就可以遍历整个数组了。 

其实将这里函数中的参数从指针接收改为地址接收也是一样的:

 这里就很奇怪了,一个数组怎么可以接收一个地址呢?既然要传入一个地址,不就应该要用指针接收吗?其实,这里的int arr []就等价于int * arr,用鹏哥的话来讲就是:“不要看它人模狗样的是个数组,实际上它就是一个指针变量。”  如果它真的是一个指针变量,那当我们求它的大小时,输出的就应该是指针变量的大小,这里可以试验一下。

32位系统下:

64位系统下:

  

不仅如此,这里函数中的arr [0]也不是我们表面所看到的那样,按照前面所说的,arr是一个指针,那函数中写的 arr [0] 前面的arr就是一个指针,指针后面加个方括号,什么鬼,怎么想都不对吧?所以这里的arr [0] 实际上是 *arr+0,那么arr [1] 的话,就相当于是 *arr + 1。那么这边我们可以来验证一下。

通过上面这段代码就可以看出,两种方式打印的结果都是一样的。 

六、二级指针

指向一个指针变量的指针就叫二级指针。

像这样创建出来的指针就叫做二级指针。

我们创建a变量的时候,在内存中开辟了一块儿空间,每块儿空间都有自己的地址,这时创建一个指针变量pa指向a的地址,因为pa也是一个变量,所以它也会有一块儿属于自己的地址,当我们想要再创建一个变量指向pa的地址时,就可以使用二级指针。

七、指针数组

存放地址的数组就叫指针数组。

当我们将变量的地址放进一个数组中的时候,数组的类型就应该是一个指针类型来接收地址,这个数组就叫做指针数组。通过指针变量,我们也可以打印出数组里面的每一个地址对应的值。 

 同样的,我们也可以使用变量的地址创建一个二维数组,同时通过指针解引用对每个元素进行打印。

这边不需要解引用,因为arr[0]就等于*(arr+0),而arr[0][0]就等于*(arr[0]+0)。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值