指针的引入
指针是什么呢?指针就是地址的别名,所谓指针==地址
,所以指针就是地址,地址就是指针。我们找到一个数据的方式有两种方式,比如你去女朋友定的酒店,一种方式是找五洲大酒店森林主题房,这种方式就好比我们通过变量名访问一个变量;另一种方式是找湖北省武汉市南李路22号3栋301房,这种方式就是通过地址去访问一个变量。
那么怎么去访问一个变量中的值呢?很容易想到,我们可以通过变量名去访问,如下:
int a = 10;
printf("a=%d\n",a);
当然,除了a的值,a所在的内存还对应一个地址,我们怎么把这个地址打印出来呢?如下:
int a = 10;
printf("a=0x%p\n",&a);
其中,&的意思式取变量a的地址,所以它被称作取地址符号,把它放在一个变量的前面就可以将这个变量的地址取出来。以上是我们通过取地址符号将一个变量的地址取了出来,下面我们如何通过这个地址去访问到a变量的值呢?
在这里,我们提到了一个运算符叫 * 运算符,它又叫取值运算符,把它放在一个地址的前面,就可以将一个地址所在内存的变量的值取出来。具体代码如下:
int a = 10;
printf("a=%d\n",*(&a));
指针变量的引入
什么是指针变量?指针变量就是存放地址的变量,即指针变量==存放地址的变量
,上面我们介绍了,&的意思式取变量a的地址,那当我们把这个地址取出来以后放在哪里呢?所以我们就需要一种变量去承接这个地址,那么这个变量就叫做指针变量。变量的格式很简单,就是在一般变量的基础上在前面加上一个 * 符号,例如:*p ,这里的 * 是一个标识符,告诉系统我是一个指针变量,是用来保存别人地址的。和刚刚的 * 不同,上面提到的 * 的意思是取地址中的变量的值,我们这里的 *p 相当于上面的 * (&a) ,它们的值都是一个地址。
int a = 10;
int *p = &a;
printf("指针变量访问a=0x%p\n",p);
指针变量为什么要要求类型?
既然指针变量是存放别人地址的变量,那为什么要区分类型呢?
下面我们看这段代码:
#include<stdio.h>
int main()
{
int a = 0x1234;
int *p = &a;
char *c = &a;
printf("a = %x\n",*p);
printf("a = %x\n",*c);
printf("p = %p\n",p);
printf("c = %p\n",c);
printf("++p = %p\n",++p);
printf("++c = %p\n",++c);
return 0;
}
这段代码的运行结果为:
首先我们定义了一个整型变量
a
=
10
a=10
a=10,然后用两种指针变量存储
a
a
a 的地址,分别为
i
n
t
∗
int*
int∗ 和
c
h
a
r
∗
char*
char∗,但我们知到
i
n
t
int
int 型数据大小为4个字节,
c
h
a
r
char
char 型数据大小为1个字节,所以用 *p 和 *c 的方式访问变量
a
a
a 的值就会不同,p 就会连续从低位到高位访问完整型变量
a
a
a的值,而c只会访问最低位的一个字节,8位的两个16进制数,所以输出
a
a
a结果不同。
再看++p和++c,因为不同类型数据的大小不同,所以自增的量也不同,指针变量 p p p 每次自增就会加4,而指针变量 c c c 每次自增就会加1,所以 p p p 的地址最低位偏移了4位,而 c c c 的地址最低位偏移了1位。所以打印的结果如上。
通过以上分析,我们明白了指针变量必须分类型的原因,而且在以后的编程中,我们定义的指针变量所承接地址的变量的类型必须与该变量类型一致,否则将会出现不必要的错误。
指针的应用场景
指针的应用场景一:
既然出现了指针这种东西,想必一定有它的应用场景,首先,我们看一段代码:
#include<stdio.h>
int main()
{
int data = 10;
int data2 = 20;
int temp;
printf("交换前,data = %d\n",data);
printf("交换前,data2 = %d\n",data2);
temp = data;
data = data2;
data2 = temp;
printf("交换后,data = %d\n",data);
printf("交换后,data2 = %d\n",data2);
return 0;
}
这是一段我们熟知的类似于茶杯换水的数值交换的简单算法,通过第三个变量的引入,我们可以将两个变量的值置换,运行这段代码,我们可以得到如下结果:
下面我们将置换部分进行函数封装,代码如下:
#include<stdio.h>
void changeData(int data,int data2)
{
int temp;
temp = data;
data = data2;
data2 = temp;
}
int main()
{
int data = 10;
int data2 = 20;
printf("交换前,data = %d\n",data);
printf("交换前,data2 = %d\n",data2);
changeData(data,data2);
printf("交换后,data = %d\n",data);
printf("交换后,data2 = %d\n",data2);
return 0;
}
运行结果如图:
可以看出,值并没有发生改变,这是为什么呢?
下面我们去分析一下,当我们进行函数封装的时候,函数内的参数 d a t a data data 和 d a t a 2 data2 data2 和我们 m a i n ( ) main() main() 函数内的 d a t a data data 和 d a t a 2 data2 data2 并不是同一个变量,在调用函数的时候,发生函数实参到形参数值拷贝的同时,函数又在内存给自己开辟了一片空间用来存放传进来的 d a t a data data 和 d a t a 2 data2 data2 ,所以在函数内部进行的一系列操作只会影响函数自己的参数,而对主函数内变量丝毫没有影响,所以最终主函数内 d a t a data data 和 d a t a 2 data2 data2 的值并没有发生交换。
那么,如何才能通过函数封装的形式,将两个值进行交换呢?我们引入了指针来解决这个问题,看如下代码:
#include<stdio.h>
#include<stdio.h>
void changeData(int *pdata,int *pdata2)
{
int temp;
temp = *pdata;
*pdata = *pdata2;
*pdata2 = temp;
}
int main()
{
int data = 10;
int data2 = 20;
printf("交换前,data = %d\n",data);
printf("交换前,data2 = %d\n",data2);
changeData(&data,&data2);
printf("交换后,data = %d\n",data);
printf("交换后,data2 = %d\n",data2);
return 0;
}
}
该代码的运行结果为:
下面我们分析一下为什么引入指针就能真正的实现内存的交换。如图:
在void changeData()函数中,我们将两个形参变量改成了指针,比如在调用函数的时候传进去两个地址,分别为
d
a
t
a
data
data的地址
∗
d
a
t
a
* data
∗data,值为0x1234和
d
a
t
a
2
data2
data2的地址
∗
d
a
t
a
2
*data2
∗data2,值为0x1238,这样我们通过函数传进去地址就能直接访问到
m
a
i
n
(
)
main()
main()函数中的data和data2,以这样的形式在函数中对这两个值进行操作,这样我们就可以真正地将它们两个数据进行交换,由此可见指针的强大之处。同时这也是指针的应用场景之一:即通过地址访问到变量真正的内存空间。
指针的应用场景二:
除了以上介绍的第一种应用场景,指针还有第二种应用场景:指向固定的内存地址。下面我们看一段代码:
#include<stdio.h>
int main()
{
int a = 10;
printf("address of a is %p\n",&a);
volatile unsigned int *p = (volatile unsigned int *)0x000000000061FE33;
printf("p = 0x%p\n",p);
return 0;
}
运行结果为:
在本段代码中,我们首先定义了一个
a
a
a 变量,并且打印了
a
a
a 的地址,其次我们将
0
x
000000000061
F
E
33
0x000000000061FE33
0x000000000061FE33 转化为一个无符号整型指针变量存入指针变量
∗
p
*p
∗p 中,这样我们就实现了定义一个指针指向固定的地址,其中关键字
v
o
l
a
t
i
l
e
volatile
volatile 的含义是防止编译器优化使地址值发生改变。该应用在单片机的开发和armbootloader开发的时候代码编写中可能设计到类似的操作。