小萌新打算写一篇关于C语言中指针方面的学习心得,因为自己是一位在校大学生,专业课也涉及到C语言,在学习C语言指针的时候,相信也和大家一样,在这块知识面感觉晦涩难懂,学不好。但是小萌新并没有放弃,指针是C语言的灵魂,热心的小萌新博主想帮助大家,前车之覆,后车之鉴,我们一起来学习指针。
有不足的地方,还请大家多多批评指正!若写的好了,大家多多支持一下。谢谢啦~么么哒!
废话不多说!C指针学习之路让我们开始吧!
1. 什么是变量
在介绍指针之前,我们先必须知道什么是变量,就这一点萌新认为在理解C语言指针非常重要!
变量,从字面上理解,就是计算机中存储的数据,并且这个数据是可以变化的,电脑使用内存来记忆计算时所使用的数据
1. 那么内存是如何存储数据的呢?
存储数据之前,首先我们必须申请内存空间,就好比我们去住酒店开房间一样,只有当我们开好房间后,我们才能入住,对不对!换句话说,内存也是一样的,内存就像旅馆,数据各式各样,要先根据数据的需求(即类型)为它申请一块合适的空间。
同样数据在内存中占用的字节大小也不一样 ,所以申请的空间也就不一样。
2. 那一次要申请多大的内存空间呢?
我们是根据数据类型来决定内存空间的大小的:
- short int(短整数类型)占用2个字节空间
- int(整数类型)占用2个字节空间
- long int(长整数类型)占用4个字节空间
- long long int(超长整数类型)占用4个字节空间
同时,我们可以使用sizeof()函数来输出不同数据类型的字节长度,在不同的环境下,相同数据占用的字节长度可能不同。
1 | int main(void) |
根据数据类型,决定空间大小
3. 那么由谁去申请内存空间呢?
实际上当我们在写声明变量的时候,就已经在申请内存空间了,声明变量后,程序执行时由系统去自动分配内存空间,根据数据类型的字节长度去分配合适大小的内存空间,这个过程是由编译器完成的。这就好比有些软件占用的内存空间小,而有些软件占用的内存的大,占用内存空间大的程序运行时调用的变量就多,而内存小的,调用的变量就少,一个道理。
4. 那么已经将数据存入内存,如何找到它呢?
我们先要知道数据存入内存中,不是为了玩的,而是要让CPU去调用,去运算的。
- 不同的数据存入内存中有不同的地址空间,一块地址空间只能存放一个数据,而这个地址空间就是变量
- 每申请出的一块空间,就是一个变量,变量就是一块地址空间。
还是和开房间一样,当我们退房的时候,下一次的顾客依然可以入住上一次已经退过房的房间,而我们常说的变量,就是一块可以存放数据的内存空间。
5. 那我们如何找到这个地址空间呢?
假如张三住401,李四住307,我们在找张三的时候,只需要知道401这个房间号就可以了。内存空间也是一样,也有它的房间号。内存空间由32位二进制数表示,常用标记方法是:0xFFFFFFFF,是由0x八段十六进制整数表示的。
- 0x0001ab2e————存储值4
- 0x0001ab2a————存储值5
这就是内存空间的房间号了,当我们在找存储在这个空间里面的数据(存储值)的时候,用这个地址就可以找到了。但是萌新相信没有哪个小伙伴会这么记忆内存地址的吧,这么一长串的数字,不过那不排除记忆力好的小伙伴。
6. 那么内存地址不好记,怎么办?
计算机可以通过内存地址找到它所存储的值,我们通过引入变量名来方便人的记忆,变量名就是给内存空间起的别名。
变量名:给内存地址起的名字
对于直接输出变量值,我们无需知道变量具体存储在哪块内存空间,只需要知道变量名即可
1 | int num1=1; |
内存地址 | 别名 | 存储的数据 |
---|---|---|
0x0001ab10 | num1 | 1 |
0x0001ab14 | num2 | 2 |
0x0000ab18 | num3 | 3 |
7. 那我们知道了内存地址,如何去操作它?
2. 什么是指针
我们使用指针去保存并操作变量的地址(注意是保存和操作内存空间的地址)
8. 那我们为什么要用指针?
- 原因如下:
- 提高程序执行效率
- 实现函数与指针的双向通信
- 利用指针实现动态内存分配
- 利用指针操作内存
我们通过指针去访问变量(内存空间)这个过程,叫做:间接访问
- 即通过访问变量a,b的地址,然后找到变量a,b的值的过程,就是间接访问
我们先声明两个指针变量
1 | int *p1=&a; 指针p1保存a的地址 |
1 | 用指针p1保存变量a的地址,然后就可以找到变量a |
9. 那么使用&地址符也能取出变量地址,干嘛还需要指针呢?
因为在程序运行中,变量的地址是变化的!不是静态的。
3. 指针的含义
指针:是一种数据类型,用于保存其他变量的地址
指针又分为:整数指针,浮点数指针,字符指针,函数指针,数组指针,指针的指针等。
- 指针变量保存其他变量的地址,通过对变量地址的操作来间接的操作变量,实现更多的功能。
- 声明的变量是指针类型,这个变量就是指针变量,指针变量保存其它变量的地址,来间接访问变量。(切记!)
10. 我们如何来定义一个指针变量?
1 | 数据类型名 *指针变量名; |
*必须存在,且最好靠近指针变量名
int * | 指向整数类型的指针 |
---|---|
float * | 指向浮点数类型的指针 |
来个例子吧:
- 先声明
int num1=6
我们有一个变量,叫num1,它是一个整数类型,在这个变量里存放了6这个整型数据; - 我们再声明一个指针p,去保存num1的地址(叫作指针p指向num1),所以我们声明的指针为
int *p
11. 与指针相关的操作符
指针声明好后,我们就要给它赋值了,但是我们不能随便赋值。注意:指针保存的是地址,这个地址是以32位二进制整数的形式表示的,当我们赋一个随便的整数,那么指针就指向了一块未知的地址。那么问题来了,在这个连你都不知道的地址里怎么能找到有你想要的数据呢?答案当然是不!
12. 那么我们如何给指针赋值呢?
- 先得到地址空间的地址(即取地址)
- 然后把这个地址交给指针来保存(指针就是一种用来保存地址的数据类型,上面已经讲过了)
我们这样给指针赋值:
1 | p=&a; |
记作:指针 p 指向变量 a,实际上就是指针 p 保存了变量 a 的地址
既然我们已经得到了地址,就可以取在地址里的数据了,但是问题又来了!
13. 那我们怎么取这个地址里的数据呢?
我们声明一个指针:int *p
,然后用指针p来取值
这时有小伙伴要用指针p去接收变量a的地址,于是写成*p=&a
这是错误的!
我们在操作指针p的时候是不能带星号的,应该写成p=&a
,这才叫用指针p去接收变量a的地址。
我们在声明完int *p
后,以后出现的星号就代表取地址里的值了,这一点大家要好好理解。
- p:表示保存变量的地址
- *p:表示取出已保存地址中的值
所以注意!
声明一定要带星号,赋值不能带星号
1 | int *p; 声明指针 |
我们再来个例子吧!
1 |
|
运行结果:
1 | var a add=0x7fff2c4dc274 |
14. 指针变量关联的四个属性
1 | int *p1=&a; |
- 指针变量的地址——指针自身的内存地址
- 指针变量的值——变量a的地址
- 指针指向的地址——指针p保存a的地址
- 指针指向的值——地址空间中a的值
1 | int i=1; //假设i的地址为100,实际地址不是这样的 |
指针变量的值也就是指针的值,也就是变量i的地址,也就是说p=100
而指针指向的值,指的是指针所指向的地址100这个内存位置,所对应的值,也就是变量i的值,为1
所以【指针变量的值】与【指针指向的地址】是两个相同的概念,小伙伴们切记!
15. 什么叫指向?
用指针保存某个变量的地址,就叫做指向
16.指针的值和变量的地址之间的联系
1 |
|
结果:
1 | p date=0x7fff7670e214 |
我们可以看出,我们可以通过指针去保存某个变量的值,但是我们不能去更改变量的地址。好比我们只能记录张三退房后又搬到哪里去了,但是我们不能去更改张三住过的房间,因为房间是固定的,同样地址空间也是固定的。
17. 指针变量的赋值
- 指针变量初始化必须赋值,而且只能赋值已经声明后的变量地址(先有变量,再有指针)
- 给指针变量直接就赋值一个整数,这是十分危险的
- 对指针变量赋值,实际就是让指针指向一个新的内存空间
再举个例子
1 | int a=0; |
如果我们直接用int *p=23
声明一个整数,这么做,指针直接指向23这个内存空间,你能保证这个地址里一定有你要的值吗?
18. 指针变量的字节长度与所指向的内容无关
指针既然是变量,也会有它的字节长度,但是指针变量的字节长度与所指向的内容无关
在32位的操作系统中,指针变量的字节长度是4。
在64位的操作系统中,指针变量的字节长度是8。
1 | int main() |
19. 比较讨厌的星号,因为它有双重意义
星号在声明的时候和在取值的时候,意义不同:
- 在声明指针的时候,加星号只是表示:为声明的是一个指针;
- 在取值的时候,加星号表示:我要从该地址取值出来;
最后注意
- 指针的类型和它所指向的变量类型要匹配
1
2
3char c='e';
int *p=&c;
//指针的类型为int型,但是指针保存了一个char型的内存空间 - 声明指针的时候,同时给指针赋值,容易受到惯性思维的影响,所以不要一开始就给指针一个整数常量
1 | int *p=0xfff20321; |
推荐
1 | int *p=NULL; //声明指针的时候先给指针p赋空值 |
- 不经过初始化的指针,是不安全的,一般可以给它赋上空值。
1 | int a=0; |
- 声明的时候,星号的作用只是为了区别普通变量与指针
- 操作的时侯,星p代表的是取变量(内存地址)的值,所以声明以后再用星p就只能表示:把&a(a的地址)中的值赋给指针p,即a地址中存储的值被指针p取出。
4. 指针的运算规则
两个指针永远不能做加法运算
张三住301房间,李四住407房间,我们把301和407加起来,是没有意义的。同类型的指针相减:两个指针所指的两个地址之间有多少个该类型的变量
通过407减301,我们就能得出在407到301之间有多少房间了,对不对!不同类型的指针不能相减
指针加n
表示指针在原来的值+n*sizeof(指针所指变量类型)指针减n
表示指针在原来的值-n*sizeof(指针所指变量类型)
1 | int a=0; |
- 只有相同类型的指针,才能相互赋值,否则必须强制类型转换一个变量
1 | int a=3; |
a=b或pa=pb是等效操作,但不是等价的,因为虽然a的值变化了,但是原来那个a和b的地址没有变化。
指针的自加运算
C语言中*p++
,(*p)++
,*++p
,++*p
的区别:
1 | 1.*p++ |
*
和++
两个处于同一优先级,结合方向是自右向左,但是前提是当++在变量前面的时候才处理同一优先级,当++在变量之后时,你可以将++的优先级看成最低级的。
因为程序自左向右运行,先取出*p
(保存的地址空间中的值),再使指针p保存的地址空间下移1个空间。
相当于取出存储的值之后,指针p移到下一个地址空间,存储的值并没有改变。可以这么理解:p是个指针,给p加1,相当于让p指向了x所在地址的下一个位置。
1 | 2.*++p |
*
和++
两个处于同一优先级,结合方向是自右向左。先进行++p
,即先将指针p自增1, 操作再取出该值*(++p)
,即取出下一地址空间的值。
1 | 3.++*p |
*
和++
两个处于同一优先级,结合方向是自右向左。先进行*p
,即先取指针p指向的值,再将该值自增1,即(*p)+1;
1 | 4.(*p)++ |
括号优先级最高,先取出p指向的变量(地址空间)中的值,再对存储的值进行自加,与++*p
相同。*p
是指针p所指向的地址中对应的值。若为3,当输出*p
之后,再给*p
自加1,所以空间x中存储的值由3就变为4,而此时地址空间x并没有移动。
最后,C指针还有许许多多的知识内容,萌新这里仅仅介绍了C指针的初识,尽力而为,最后谢谢大家了,真心希望可以通过自己的博客帮到大家。