一、C语言基础变量的定义
首先,C语言变量定义的基本形式: <变量类型> <变量名称>
变量类型,就是确定了变量名称对应的实体是什么?比如
int a; //变量a是一个整型数
char b; //变量b是一个字符
现实生活也有<变量类型> <变量名称>这样的形式,比如,有一个名称angle,那angle是什么?一个人?一个物品?加上对angle的类型描述后,那就确定了angle的对象属性。
女人 angle; //angle是一个女人
物品 angle; // angle是一个物品
二、C语言指针的定义
理解了C语言基础变量定义的概念后,我们再来对应理解C语言指针的定义,指针也是一种变量,也符合变量定义的基本形式:
<变量类型><变量名称>
int *p;
很多人对指针不理解或者理解不透彻,有两个方面的原因:
1.被指针这个翻译/叫法带偏了。
2.定义指针变量时,被这个*习惯性地用法/写法带偏了
1.指针是一种变量,它的变量类型(变量实体)是一个地址,咱们把“指针”改成“地址变量”,以后碰到指针,就叫做“地址变量”,也就是说这个变量的实体内容是一个地址。
2.定义一个指针变量,习惯性写法是把*靠近变量名,按照C语言变量定义的基本形式<变量类型> <变量名称>,很容易产生错误的理解。那么咱们换一个写法,把*往前写,远离变量名,来看看效果。
以一个整型指针变量的定义为例,画个表格对比两种写法,帮助理解。
以上表格可以清晰的看出,在变量定义时,是一种变量类型,表明变量是一个指针类型的变量,也就前面说的地址变量。但是,在变量定义时,需要和其他基础变量类型一起使用,组成意义更具体的变量类型,比如,int 和一起使用,
int* p;
同样,以表格的形式来帮助理解
三、C语言指针的用途
现在理解了指针的定义以及指针就是一个地址变量,那么指针或者叫做地址变量在C语言里面具体代表什么?有什么用途呢?
在计算机的世界里,万事万物皆信息,信息都需要存储空间,存储空间都有地址。
类似现实生活中每个人都有居住空间和居住空间的地址,xx省xx市xx区xx街道xx小区xx楼xx单元xx号。
计算机世界的地址没那么复杂,就是一个简单整形数,比如,0x0000 0004,0x8000 0000等等,地址是多少字节是编译器根据计算机体系结构确定的,这个内容后续章节再讲解。
下面我们以实际的程序来讲解,
int a ;
int b ;
int *p;
a、b、p都是变量,编译链接后或者运行时,就会分配一个存储空间,具体会分配到哪个地址是由编译链接时和运行时确定,暂且先不管,比如分配如下:
这个时候变量 a、b、p都被分配了自己的存储空间和对应的空间地址,但是,存储空间没有指定存储什么内容,一般默认0或者是随机值,这个后续再深入讲解。现在可以理解成什么都没有存,是空的。
现在对a、b、p空间写内容,在C语言中称之为赋值
a = 10;
b = 20;
很简单,对不对,赋值后就成为上图所示。**但这个时候p还是一个空的,我们称之为空指针。**接下来,有小伙伴按耐不住了,给p指针也赋值呗,通过前面的介绍,p指针也称之为地址变量,它的内容应该是一个地址,那么,我们把a变量的地址,赋值给p指针,有小伙伴可能这么写了,
*p = 0x2000;
一运行程序,就崩溃了,为啥?为啥!说明之前对于指针定义的理解,还是错误的。那我们把之前对指针的定义,再般过来,认认真真看一遍。
p才是真正意义的指针变量,在指针定义的时候,是指针变量的类型。类似,我们不能已经定义了变量a后,给a赋值时,再带上变量类型,就是错误的了。
int a = 10; // 错误的写法
同理,指针变量的赋值,也不能带上*变量类型,
p = 0x2000; // 这样就对了。
可能有很多小伙伴,就不服气了,经常在代码里面见到,指针变量*p = 0x2000;的这种写法啊,这个咱们后续文章讲解指针操作的时候在详细讲解。
按照上面的步骤给指针变量赋值后,指针的地址是0x1000,指针的内容是0x2000,地址变量p的地址是0x1000,地址变量的值是0x2000,这样指针p就指向了变量a,如下图。
四、C语言指针的操作符号
int *p;
*p=0x2000; //错误的赋值,会导致程序崩溃或者跑飞
p=0x2000; //正确的赋值
int *p; // 定义指针变量p
p=0x2000; // 对指针p赋值
*p=0x2000; // 这里可以对*p赋值了
为什么经过改写后的程序就可以对p赋值了呢?这里就需要理解号在指针的定义和指针操作上的区别。
int *p; // 定义指针变量p
p=0x2000; // 对指针p赋值
*p=0x2000; // 这里可以对*p赋值了
*在指针定义时,是一种变量类型,表明变量是一个指针类型的变量
*在指针操作时,是一种指针运算/操作符,它代表指针所指向地址的值,或者说代表指针所指向地址的内容,又或者说代表指针所指向地址的对象。
类似现实生活中每个人都有居住空间和居住空间的地址,xx省xx市xx区xx街道xx小区xx楼xx单元xx号。指针p代表的是地址xx省xx市xx区xx街道xx小区xx楼xx单元xx号;*p代表的是地址xx省xx市xx区xx街道xx小区xx楼xx单元xx号所对应的房屋空间,地址是不能住人的,但地址对应的房屋空间是可以住人的。
嵌入式底层驱动最常用就是对MCU、单片机的寄存器进行读写操作,而MCU、单片机的寄存器就是固定的地址,通过指针能够对地址直接读写操作,简单又效率高的特性,就是为什么嵌入式底层驱动都是C语言编程的主要原因,简直就是嵌入式底层驱动的设计的利器。
五、C语言指针的操作符*和&
在C语言中,&有三种作用,分别如下:
- 取地址操作符,单目运算符。用来取一个变量的地址。比如
int i, *p;
p = &i; //这里的&作用是取变量i的地址
- 位与操作运算符,也就是常说的and操作,双目运算符。计算的时候按位计算,&两边操作数按位(bit)做与操作。比如,
0x12&0x23 转为二进制(bit)为:
B00010010&B00100011,按位计算结果为B00000010,即结果为0x02。
- 当两个&&一起用的时候,表示为逻辑与运算。
逻辑运算结果只有0和1两种结果。一般在&&两边是两个逻辑表达式。比如,
2>1 && 0<1 这个的值为1&&1 = 1。
1>2 && 2>0 这个值为0&&1 = 0。
1>2 && 2>3 这个值为0&&0=0
有了取地址符&,那么我们把之前程序改一下,同样也可以达到对变量a和b赋值操作。
修改前:
int *p;
p=0x2000;// 第一步,将指针变量赋值成变量a的地址,指针p就指向了变量a
*p=10; // 第二步,对指针所指向的地址空间(就是变量a空间)进行赋值
p=0x3000; // 第三步,将指针变量赋值成b的地址,指针p就指向了变量b
*p=20; // 第四步,对指针所指向的地址空间(就是变量b空间)进行赋值
修改后
int *p,a,b;
p=&a; // 第一步,将指针变量赋值成变量a的地址,指针p就指向了变量a
*p=10; // 第二步,对指针所指向的地址空间(就是变量a空间)进行赋值
p=&b; // 第三步,将指针变量赋值成b的地址,指针p就指向了变量b
*p=20; // 第四步,对指针所指向的地址空间(就是变量b空间)进行赋值
这样心情是不是瞬间就舒畅多了。
上面程序中的p指针(地址变量),也有存储空间,那么对应的也有存储地址。p指针的地址同样也可以通过取p指针的地址来获得,也就是&p。
拓展一下,除了变量有地址,代码也有地址,变量对应的空间一般为堆或者栈数据空间(有些常量也可能放到代码空间),代码对应的是指令空间。函数调用,其实就是调用函数的入口代码的地址(指令),在汇编语言里面称之为call或者跳转。
假设,p、a、b变量的地址和内容,如下图示,
那么,再看看下面代码的意义
&p; // 指针(地址变量)p的地址0x1000
p; // 指针(地址变量)p的值0x2000,是一个地址,也就是a的地址&a。
*p; // 指针(地址变量)p所指向的内容10,就是0x2000地址对应的值(对象),
// 也就是变量a所对应的值。