深入理解指针————Part1

前言

本篇博客将为大家介绍C语言中最关键也是最复杂的的内容,它就是指针,指针的内容是大家普遍存在问题的模块儿,所以希望本篇博客可以位大家解除疑惑,希望大家动动发财的手,一键三连,多多支持。下面进入正文内容。

1. 内存和地址

1.1 内存

在讲内存和地址之前,我们想有个⽣活中的案例: 假设有⼀栋宿舍楼,把你放在楼里,楼上有100个房间,但是房间没有编号,你的⼀个朋友来找你玩,如果想找到你,就得挨个房子去找,这样效率很低,但是我们如果根据楼层和楼层的房间的情况,给每个房间编上号,如:

当有了房间号后,就可以快速地锁定房间。

把上面的例子放到内存中, 大家可以理解为,把内存划分为⼀个个的内存单元,每个内存单元的大小取1个字节。其中,每个内存单元,相当于⼀个学生宿舍,⼀ 个⼈字节空间里面能放8个比特位,就好比同学们住的八人间,每个人是⼀个比特位。每个内存单元也都有⼀个编号(这个编号就相当 于宿舍房间的门牌号),有了这个内存单元的编号,CPU就可以快速找到⼀个内存空间。

生活中我们把门牌号也叫地址,在计算机中我们 把内存单元的编号也称为地址。C语言中给地址起 了新的名字叫:指针

大家可以理解为:内存单元的编号==地址==指针

2. 指针变量和地址

2.1 取地址操作符

顾名思义,这个操作符的作用就是取出数据在内存中存放的位置,这个位置就是地址,下面通过代码为大家举例

上面为大家展示了取地址的操作,并且我们将取出来的地址打印了出来,大家注意,在打印地址的时候,需要用%p来打印。然后,还有一个小问题,就是&a取出的是a所占4个字节中地址较小的地址,这个点大家了解一下。

 说完取地址操作符,下面问题又来了,这个地址我们应该用什么去存放呢?

这里就要提到指针变量了,它就是用来存放地址的,大家请看下面的代码

大家可以看到,我们创建了一个指针,将a的地址存了进去。指针变量也是一种变量,存在指针变量中的值都会理解为指针。

2.2 指针变量和解引用操作符

我们将地址保存起来,未来是要使用的,那怎么使用呢?

在现实生活中,我们使用地址要找到⼀个房间,在房间里可以拿去或者存放物品。

C语言中其实也是⼀样的,我们只要拿到了地址(指针),就可以通过地址(指针)找到地址(指针) 指向的对象,这里必须学习⼀个操作符叫解引用操作符(*)。

大家可以看到,上面的代码中就使用了解引用操作符,*pa就表示通过pa中存放的地址,找到其指向的空间,其实*pa就是变量a,我们改变*pa的值就相当于改变a的值。

说到这儿,可能有的同学还不是特别理解,下面为大家拆解一下指针类型

大家注意,pa左边写的是int *,*在说明pa是指针变量,而前面的int说明pa指向的是整型类型的变量。

2.3 指针变量的大小

我们知道,要求数据的大小,我们要用到sizeof操作符,那么请大家看下面的代码

大家可以看到,这四个打印的结果是一样的,而且都等于4,这里我使用的是X86的环境,如果换成X64的平台,打印结果就会是8;打印结果相同说明了一个问题:指针变量的大小和类型无关,只要在相同的平台下,其大小是一样的。

3. 指针变量类型的意义

 通过上面指针类型大小的论述,可能会有人提出疑问,那既然指针变量的大小和类型无关,那为什么还要有不同类型的指针变量呢?

其实指针变量是有特殊意义的,下面为大家介绍

3.1 指针的解引用

大家可以看下面的代码

大家看完上面的代码,找到其中的不同之处了吗?我们可以看到,代码1会将n的4个字节全部改为0,但是代码2只是将n的第⼀个字节改为0。

所以,我们可以得出结论:指针的类型决定了,对指针解引用的时候有多大的权限(⼀次能操作几个字节)。

比如: char* 的指针解引用就只能访问⼀个字节,而int* 的指针的解引用就能访问四个字节

3.2 指针+-整数

这里为大家准备了一段代码

我们可以看出, char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。 这就是指针变量的类型差异带来的变化,所以我们可以得出结论:指针的类型决定了指针向前或者向后走⼀步有多大(距离)。

3.3 void* 指针

void*指针是一种非常特殊的指针,它可以接受任意类型的地址;与此同时,它也存在局限性,void*类型的指针不能进行解引用操作以及+-整数的操作。

大家可以看到,在上面的代码中,将⼀个int类型的变量的地址赋值给⼀个char*类型的指针变量。编译器给出了⼀个警告,是因为类型不兼容。而使用void*类型就不会有这样的问题,大家可以看一下面的代码

大家可以看到,void*类型的指针可以接受不同类型的地址,但是无法进行指针运算。

所以void*指针到底有什么用呢?

一般void*指针使用在函数参数部分。用来接受不同类型的地址,这样的设计可以实现泛型编程的效果,这个问题后面还会再深入讨论。

4. const修饰指针

4.1 const修饰变量

我们知道,变量是可以修改的,如果把一个变量的地址交给一个指针变量。那么通过指针变量也可以修改这个变量。但是当我们不希望某个变量被修改的时候,该怎么办呢?那么这个时候就需要引进const来对变量进行修饰了。大家看一下下面一段代码

大家可以看到,上面的代码中,变量n就是被const修饰的变量,它不可以被修改;其实话说回来,n本质上是一个变量,只不过被const修饰后,在语法上加了限制。只要我们去修改n,那么就不符合语法规则,这个时候编译器就会报错,所以导致n不能被修改。

但是可能有人会想,如果我非要去修改呢?那这也不是没有办法,我们只需要取出n的地址,将其放到一个指针变量中,通过修改指针变量,就可以间接修改n;大家来看下面的代码

通过上面的代码,大家会发现,当我们取出变量的地址,通过指针去修改变量时,并不会受到const的影响,那么这与我们前面说到的const的作用就产生了冲突,const就是为了不让变量被修改,但是现在经过const的修饰,变量依然可以被修改;所以我们如何让const真正发挥它的作用呢?下面就要为大家介绍const修饰指针了。

4.2 const修饰指针

关于const修饰指针,有两种情况

4.2.1 const在*的左边

对于这种情况,我们通过代码来看

通过上面的代码,大家可以发现,当const放在*左边时,相当于*pa被const修饰,这就意味着我们不能改变其指向的内容;但是指针变量本身的内容可变,

什么叫指针变量本身的内容可变呢?就是我们将n的地址存到pa中,也就是说pa既可以存放m的地址也可以存放n的地址,大家通过上面的代码会发现,我们修改了pa本身的内容,但是编译器并没有报错。

4.2.2 const放在*右边

当const放到*右边时,又会发生什么呢?大家可以根据上面的内容去猜测一下;

通过上面两张图,大家可以发现,当const放在*的右边时,相当于指针本身被const修饰,也就意味着指针本身的内容是不能被修改的;但是,其指向的内容可以被修改。

总结一下:

• const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。 但是指针变量本身的内容可变。

• const如果放在*的右边,修饰的是指针变量本身,保证了指针变量的内容不能修改,但是指针指 向的内容,可以通过指针改变。

5. 指针运算

5.1 指针+-整数

我们前面学过,数组中的元素是连续存放的,我们在数组章节也学过怎样访问数组的每一个元素;那么咱们现在用指针的方式来访问数组的元素,这里就需要用到指针+-整数,大家看下面的代码

大家观察上面的代码,第一张图展示的是用指针的方法进行访问;第二张图是用前面在数组章节学到的访问方法。大家会发现,两种方法其实很类似,它们其中一定有关联,这个后面还要为大家详细介绍,这里大家先学会如何使用指针去访问数组,理解指针+-整数的操作。

5.2 指针-指针

这里需要说明一下,指针-指针的两个指针必须指向同一个间;并且大家需要知道,指针-指针得到的是整数,下面大家来看一段代码,我们来运用一下指针-指针

首先,大家应该知道strlen函数是用来求字符串长度的,那我们可不可以自己创造一个函数,让其具有和strlen一样的功能呢?

 大家可以看到,上面的代码中,我使用了指针-指针的操作,这样也同样可以求出字符串的长度,  在my_strlen函数中,我创建了一个新指针start来存放str的起始地址,然后通过循环让str向后移动直到‘\0’,最后通过两个指针相减来得到两个指针中间的字符数。所以通过这个例子,希望大家可以理解指针-指针的用途。

5.3 指针的关系运算

 关于指针的关系运算,这里通过一段代码来说明,大家请看

大家可以看到,上面的代码中就展示了指针的关系运算,在while循环的判断条件里用到了指针大小的比较, 相信大家能够理解其中的含义。

6. 野指针 

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

6.1 野指针的成因

6.1.1 指针未初始化 

大家可以看到,上面的代码中, 指针p并为进行初始化,这个时候我们进行调试,会发现编译器报错,提示我们需要对指针进行初始化,所以大家在创建指针的时候,一定要进行初始化操作。

6.1.2 指针越界访问

这种情况就属于指针越界访问,这种情况我们在写代码的过程中也需要避免。

6.1.3 指针指向的空间被释放

大家注意,上面的代码中,当n出了test函数后,它申请的空间将归还给操作系统,这个时候p就成为了野指针,我们就不能去访问p了。这里大家其实可以想成下面的例子,比如我在一家环境很不错的酒店订了一间房,我计划入住一夜并且第二天早上退房;这个时候我将这个消息告诉了我的朋友,想给他推荐这个房间,于是我将房间号告诉了他,第二天早上我退房了,随后我的朋友就来到了这个房间,想要体验一下,但是这个时候,大家想想他能进入房间吗?结果是保安叔叔将他赶出了酒店,他的访问就属于“非法”访问。所以,讲完这个小故事,想必大家应该理解了。

6.2 如何规避野指针

6.2.1 指针的初始化

指针初始化的方法大家应该了解,这里说明一下,如果明确知道指针指向哪⾥就直接赋值地址,如果不知道指针应该指向哪里,可以给指针赋值NULL。NULL 是C语言中定义的⼀个标识符常量,值是0,0也是地址,这个地址是无法使用的,读写该地址会报错。

6.2.2 小心指针越界

⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问。

6.2.3 指针变量不再使用时,及时置NULL,指针使用之前检查有效性

当指针变量指向⼀块区域的时候,我们可以通过指针访问该区域,后期不再使⽤这个指针访问空间的 时候,我们可以把该指针置为NULL。因为约定俗成的⼀个规则就是:只要是NULL指针就不去访问, 同时使用指针之前可以判断指针是否为NULL。

我们可以把野指针想象成野狗,野狗放任不管是非常危险的,所以我们可以找⼀棵树把野狗拴起来, 就相对安全了,给指针变量及时赋值为NULL,其实就类似把野狗栓前来,就是把野指针暂时管理起来。

不过野狗即使拴起来我们也要绕着⾛,不能去挑逗野狗,有点危险;对于指针也是,在使用之前,我们也要判断是否为NULL,看看是不是被拴起来起来的野狗,如果是不能直接使用,如果不是我们再去使用。

7. assert 断言

assert.h 头⽂件定义了宏 assert() ,⽤于在运行时确保程序符合指定条件,如果不符合,就报
错终⽌运行。这个宏常常被称为“断言”。

assert() 宏接受⼀个表达式作为参数。如果该表达式为真(返回值非零), assert() 不会产⽣ 任何作用,程序继续运行。如果该表达式为假(返回值为零), assert() 就会报错,在标准错误流 stderr 中写⼊⼀条错误信息,显示没有通过的表达式,以及包含这个表达式的文件名和行号。

大家在使用assert断言的时候,需要加上#include<assert.h>头文件。

 8. 指针的使用和传址调用

 8.1 strlen的模拟使用

前面我们其实已经实现了strlen函数的模拟实现,当时我们使用了指针-指针的方式去完成的;在这里,再为大家介绍一种方法,同样可以模拟实现strlen函数,大家请看下面的代码。

 大家可以通过类比的方法去理解上面的代码,前面说过指针-指针的写法,这里也是使用指针的知识,相信大家可以理解。

8.2 传值调用和传址调用

我们学习指针是为了解决更多不同的问题,那么有没有什么问题必须用指针才可以解决呢?

这里有这么一道题:写一个函数,交换两个整型变量的值

这个问题大家应该也接触过,可能很多人的思路如下

这个代码乍一看好像没有什么问题。但是大家可以自己去运行一下,这里我将运行结果展示如下

大家发现,结果并没有发生交换,这里很多同学就会产生疑惑,明明代码是让它们交换,为什么没能实现效果?这里就要为大家介绍传值调用和传址调用了

我们发现在main函数内部,创建了a和b,a的地址是0x00cffdd0,b的地址是0x00cffdc4,在调⽤
Swap1函数时,将a和b传递给了Swap1函数,在Swap1函数内部创建了形参x和y接收a和b的值,但是x的地址是0x00cffcec,y的地址是0x00cffcf0,x和y确实接收到了a和b的值,不过x的地址和a的地址不一样,y的地址和b的地址不⼀样,相当于x和y是独立的空间,那么在Swap1函数内部交换x和y的值, 自然不会影响a和b,当Swap1函数调用结束后回到main函数,a和b的没法交换。Swap1函数在使用的时候,是把变量本身直接传递给了函数,这种调用函数的方式我们之前在函数的时候就知道了,这种叫 传值调用

结论:实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实
参。
所以大家应该理解了为什么Swap1是失败的了;
那么我们该如何去改代码呢?
我们现在要解决的就是当调用Swap函数的时候,Swap函数内部操作的就是main函数中的a和b,直接将a和b的值交换了。那么就可以使用指针了,在main函数中将a和b的地址传递给Swap函数,Swap函数里边通过地址间接的操作main函数中的a和b,并达到交换的效果就好了。

大家看到,我们将a,b的地址传过去后,用指针变量接受,再通过对指针的操作来间接地改变a,b的值,这种函数调用方式叫:传址调用。

传址调用,可以让函数和主调函数之间建立真正的联系,在函数内部可以修改主调函数中的变量;所以未来函数中只是需要主调函数中的变量值来实现计算,就可以采用传值调用。如果函数内部要修改 主调函数中的变量的值,就需要传址调用。

9. 总结

本篇博客为大家介绍了指针的初阶内容,属于指针的基础内容,大家要注意对其的理解,理解指针的用途与用法,后面还会为大家更新更多的关于指针的内容,同时也希望本篇博客能为大家带来帮助,如有错误,欢迎评论交流,谢谢!

  • 34
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值