橘生淮南

记录学习中的点点滴滴

0%

仅这一篇,带你敲开C语言指针的大门

小萌新打算写一篇关于C语言中指针方面的学习心得,因为自己是一位在校大学生,专业课也涉及到C语言,在学习C语言指针的时候,相信也和大家一样,在这块知识面感觉晦涩难懂,学不好。但是小萌新并没有放弃,指针是C语言的灵魂,热心的小萌新博主想帮助大家,前车之覆,后车之鉴,我们一起来学习指针。

有不足的地方,还请大家多多批评指正!若写的好了,大家多多支持一下。谢谢啦~么么哒!

废话不多说!C指针学习之路让我们开始吧!


1. 什么是变量

在介绍指针之前,我们先必须知道什么是变量,就这一点萌新认为在理解C语言指针非常重要!

变量,从字面上理解,就是计算机中存储的数据,并且这个数据是可以变化的,电脑使用内存来记忆计算时所使用的数据

1. 那么内存是如何存储数据的呢?

存储数据之前,首先我们必须申请内存空间,就好比我们去住酒店开房间一样,只有当我们开好房间后,我们才能入住,对不对!换句话说,内存也是一样的,内存就像旅馆,数据各式各样,要先根据数据的需求(即类型)为它申请一块合适的空间。

同样数据在内存中占用的字节大小也不一样 ,所以申请的空间也就不一样。

2. 那一次要申请多大的内存空间呢?
我们是根据数据类型来决定内存空间的大小的:

  1. short int(短整数类型)占用2个字节空间
  2. int(整数类型)占用2个字节空间
  3. long int(长整数类型)占用4个字节空间
  4. long long int(超长整数类型)占用4个字节空间

同时,我们可以使用sizeof()函数来输出不同数据类型的字节长度,在不同的环境下,相同数据占用的字节长度可能不同。

1
2
3
4
5
6
7
8
int main(void)
{
printf("sizeof(int)=%d\n",sizeof(int)); //占4个字节空间
printf("sizeof(float)=%d\n",sizeof(float)); //占4字节空间
printf("sizeof(double)=%d\n",sizeof(double));//占8字节空间
printf("sizeof(char)=%d\n",sizeof(char)); //占1字节空间
printf("sizeof(long int)=%d\n",sizeof(long int)); //占8字节空间
}

根据数据类型,决定空间大小

3. 那么由谁去申请内存空间呢?
实际上当我们在写声明变量的时候,就已经在申请内存空间了,声明变量后,程序执行时由系统去自动分配内存空间,根据数据类型的字节长度去分配合适大小的内存空间,这个过程是由编译器完成的。这就好比有些软件占用的内存空间小,而有些软件占用的内存的大,占用内存空间大的程序运行时调用的变量就多,而内存小的,调用的变量就少,一个道理。

4. 那么已经将数据存入内存,如何找到它呢?
我们先要知道数据存入内存中,不是为了玩的,而是要让CPU去调用,去运算的。

  1. 不同的数据存入内存中有不同的地址空间,一块地址空间只能存放一个数据,而这个地址空间就是变量
  2. 每申请出的一块空间,就是一个变量,变量就是一块地址空间。

还是和开房间一样,当我们退房的时候,下一次的顾客依然可以入住上一次已经退过房的房间,而我们常说的变量,就是一块可以存放数据的内存空间。

5. 那我们如何找到这个地址空间呢?
假如张三住401,李四住307,我们在找张三的时候,只需要知道401这个房间号就可以了。内存空间也是一样,也有它的房间号。内存空间由32位二进制数表示,常用标记方法是:0xFFFFFFFF,是由0x八段十六进制整数表示的。

  1. 0x0001ab2e————存储值4
  2. 0x0001ab2a————存储值5

这就是内存空间的房间号了,当我们在找存储在这个空间里面的数据(存储值)的时候,用这个地址就可以找到了。但是萌新相信没有哪个小伙伴会这么记忆内存地址的吧,这么一长串的数字,不过那不排除记忆力好的小伙伴。

6. 那么内存地址不好记,怎么办?
计算机可以通过内存地址找到它所存储的值,我们通过引入变量名来方便人的记忆,变量名就是给内存空间起的别名。

变量名:给内存地址起的名字

对于直接输出变量值,我们无需知道变量具体存储在哪块内存空间,只需要知道变量名即可

1
2
3
int num1=1;
int num2=2;
int num3=3;
内存地址 别名 存储的数据
0x0001ab10 num1 1
0x0001ab14 num2 2
0x0000ab18 num3 3

7. 那我们知道了内存地址,如何去操作它?

2. 什么是指针

在这里插入图片描述

我们使用指针去保存并操作变量的地址(注意是保存和操作内存空间的地址)

8. 那我们为什么要用指针?

  • 原因如下:
  • 提高程序执行效率
  • 实现函数与指针的双向通信
  • 利用指针实现动态内存分配
  • 利用指针操作内存

我们通过指针去访问变量(内存空间)这个过程,叫做:间接访问

  • 即通过访问变量a,b的地址,然后找到变量a,b的值的过程,就是间接访问

我们先声明两个指针变量

1
2
int *p1=&a;  指针p1保存a的地址
int *p2=&b; 指针p2保存b的地址
1
2
用指针p1保存变量a的地址,然后就可以找到变量a
用指针p2保存变量b的地址,然后就可以找到变量b

9. 那么使用&地址符也能取出变量地址,干嘛还需要指针呢?
因为在程序运行中,变量的地址是变化的!不是静态的。

3. 指针的含义

指针:是一种数据类型,用于保存其他变量的地址
指针又分为:整数指针,浮点数指针,字符指针,函数指针,数组指针,指针的指针等。

  • 指针变量保存其他变量的地址,通过对变量地址的操作来间接的操作变量,实现更多的功能。
  • 声明的变量是指针类型,这个变量就是指针变量,指针变量保存其它变量的地址,来间接访问变量。(切记!)

10. 我们如何来定义一个指针变量?

1
数据类型名  *指针变量名;

*必须存在,且最好靠近指针变量名

int * 指向整数类型的指针
float * 指向浮点数类型的指针

来个例子吧:

  1. 先声明int num1=6
    我们有一个变量,叫num1,它是一个整数类型,在这个变量里存放了6这个整型数据;
  2. 我们再声明一个指针p,去保存num1的地址(叫作指针p指向num1),所以我们声明的指针为int *p

11. 与指针相关的操作符
指针声明好后,我们就要给它赋值了,但是我们不能随便赋值。注意:指针保存的是地址,这个地址是以32位二进制整数的形式表示的,当我们赋一个随便的整数,那么指针就指向了一块未知的地址。那么问题来了,在这个连你都不知道的地址里怎么能找到有你想要的数据呢?答案当然是不!

12. 那么我们如何给指针赋值呢?

  1. 先得到地址空间的地址(即取地址)
  2. 然后把这个地址交给指针来保存(指针就是一种用来保存地址的数据类型,上面已经讲过了)

我们这样给指针赋值:

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
2
int *p;  声明指针
p=&a; 指针p保存了变量a的地址

我们再来个例子吧!

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
int main(void)
{
int a=3; //声明变量a,把数值3赋给a
int *p=NULL;//声明指针p,指针初始化赋空值,否则成为野指针
p=&a; //让指针p保存a的地址
printf{"var a add=%p\n",&a};//输出变量a的地址
printf{"p=%p\n",p}; //输出指针p的值,也就是保存了a的地址空间
printf{"date a=%d\n",a}; //输出a的值
printf{"*p=%d\n",*p}; //对指针p取值,也就是在a的地址空间里取值
return 0;
}

运行结果:

1
2
3
4
var a add=0x7fff2c4dc274 
p=0x7fff2c4dc274
date a=3
*p=3

在这里插入图片描述

14. 指针变量关联的四个属性

1
int *p1=&a;
  • 指针变量的地址——指针自身的内存地址
  • 指针变量的值——变量a的地址
  • 指针指向的地址——指针p保存a的地址
  • 指针指向的——地址空间中a的值
1
2
int i=1; //假设i的地址为100,实际地址不是这样的
int *p=&i; //假设指针p的地址为200

指针变量的值也就是指针的值,也就是变量i的地址,也就是说p=100
而指针指向的值,指的是指针所指向的地址100这个内存位置,所对应的值,也就是变量i的值,为1

所以【指针变量的值】与【指针指向的地址】是两个相同的概念,小伙伴们切记!

15. 什么叫指向?
用指针保存某个变量的地址,就叫做指向

16.指针的值和变量的地址之间的联系
在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
int main(void)
{
int num1=3;
int num2=2;
int *p=NULL;
p=&num1;
printf("p date=%p\n",p);
p=&num2;
printf("p date2=%p\n",p);
printf("address num1=%p\n",&num1);
printf("address num2=%p\n",&num2);
return 0;
}

结果:

1
2
3
4
p date=0x7fff7670e214 
p date2=0x7fff7670e210
address num1=0x7fff7670e214
address num2=0x7fff7670e210

我们可以看出,我们可以通过指针去保存某个变量的值,但是我们不能去更改变量的地址。好比我们只能记录张三退房后又搬到哪里去了,但是我们不能去更改张三住过的房间,因为房间是固定的,同样地址空间也是固定的。

17. 指针变量的赋值

  • 指针变量初始化必须赋值,而且只能赋值已经声明后的变量地址(先有变量,再有指针)
  • 给指针变量直接就赋值一个整数,这是十分危险的
  • 对指针变量赋值,实际就是让指针指向一个新的内存空间

再举个例子

1
2
3
int a=0;
int *p1=&a;
int *p2=23;

如果我们直接用int *p=23声明一个整数,这么做,指针直接指向23这个内存空间,你能保证这个地址里一定有你要的值吗?

18. 指针变量的字节长度与所指向的内容无关

指针既然是变量,也会有它的字节长度,但是指针变量的字节长度与所指向的内容无关

在32位的操作系统中,指针变量的字节长度是4。
在64位的操作系统中,指针变量的字节长度是8。

1
2
3
4
5
6
7
8
9
int main()
{
printf("sizeof(int *)=%d\n",sizeof(int *)); //8个字节
printf("sizeof(float *)=%d\n",sizeof(float *)); //8个字节
printf("sizeof(double *)=%d\n",sizeof(double *)); //8个字节
printf("sizeof(char *)=%d\n",sizeof(char *)); //8个字节
printf("sizeof(long int *)=%d\n",sizeof(long int *));//8个字节
return 0;
}

19. 比较讨厌的星号,因为它有双重意义
星号在声明的时候和在取值的时候,意义不同:

  • 在声明指针的时候,加星号只是表示:为声明的是一个指针;
  • 在取值的时候,加星号表示:我要从该地址取值出来;

最后注意

  1. 指针的类型和它所指向的变量类型要匹配
    1
    2
    3
    char c='e';
    int *p=&c;
    //指针的类型为int型,但是指针保存了一个char型的内存空间
  2. 声明指针的时候,同时给指针赋值,容易受到惯性思维的影响,所以不要一开始就给指针一个整数常量
1
int *p=0xfff20321;

推荐

1
2
int *p=NULL; //声明指针的时候先给指针p赋空值
p=&a; //再让指针保存其它变量的地址
  1. 不经过初始化的指针,是不安全的,一般可以给它赋上空值。
1
2
3
int a=0;
int *p;
*p=&a;
  • 声明的时候,星号的作用只是为了区别普通变量与指针
  • 操作的时侯,星p代表的是取变量(内存地址)的值,所以声明以后再用星p就只能表示:把&a(a的地址)中的值赋给指针p,即a地址中存储的值被指针p取出

4. 指针的运算规则

  1. 两个指针永远不能做加法运算
    张三住301房间,李四住407房间,我们把301和407加起来,是没有意义的。

  2. 同类型的指针相减:两个指针所指的两个地址之间有多少个该类型的变量
    通过407减301,我们就能得出在407到301之间有多少房间了,对不对!

  3. 不同类型的指针不能相减

  4. 指针加n
    表示指针在原来的值+n*sizeof(指针所指变量类型)

  5. 指针减n
    表示指针在原来的值-n*sizeof(指针所指变量类型)

1
2
3
4
5
6
7
8
9
int a=0;
int *p=NULL;
p=&a;
&a=0x00001000;
p=0x00001000;
*p=0;
p+1=0x00001000 + sizeof(int);
p+2=0x00001000 + 2×sizeof(int);
p+3=0x00001000 + 3×sizeof(int);
  1. 只有相同类型的指针,才能相互赋值,否则必须强制类型转换一个变量
1
2
3
4
5
int a=3;
int b=4;
int *pa=&a;
int *pa=&b;
pa=pb;

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指针的初识,尽力而为,最后谢谢大家了,真心希望可以通过自己的博客帮到大家。

欢迎关注我的其它发布渠道

-------------本文结束感谢您的阅读-------------