c语言:初识指针

目录

目录

目录

CPU与内存协同工作

访问内存中的“房间”

基础数据类型怎样居住“房间”

取地址运算符 &

声明指针类型的变量

定义

指针类型

空间大小

小结 

 使用指针

取值运算符 *

指针类型的大小

强制转换指针类型


CPU与内存协同工作

以整型加法为例,我们来看看在计算机内部,CPU(中央处理器)是如何配合其他硬件进行计算的。

CPU由三部分构成:

  1. 算术、逻辑单元:对数据执行运算(例如加法、减法)的电路。
  2. 控制单元:协调机器活动的电路。
  3. 寄存器组 :数据临时存储。

然而,寄存器能够存储的信息量很少,仅仅是当前工作所必要的。话说鱼的记忆只有7秒,CPU的记忆恐怕是更短。总不能,CPU做一步操作,就把上一步操作的结果给忘了吧。

因此,我们需要内存来配合CPU进行数据操作

数据几乎都存储在内存上,仅有当前正在处理的数据,才放到CPU的寄存器组上,等待CPU进行计算。

CPU将数据计算完毕后,再存储到内存中。数据通过数据总线,在CPU和内存上进行传输。

接下来,我们来看看,将两个数据相加会经历哪些步骤。

  1. 从内存中取出一个加数放到一个寄存器中。
  2. 从内存取出另一个加数,放到另一个寄存器中。
  3. 激活算术、逻辑单元中的加法电路,将前面两步的寄存器作为输入,用另一个寄存器存放结果。
  4. 将结果存放到内存中。


访问内存中的“房间”

只有正在被处理的数据才会被放到CPU的寄存器上,等待CPU进行计算,CPU将数据计算完毕后,再存储到内存中。所以,内存才是数据的大本营。

我们知道,计算机通过晶体管的开关状态来记录数据。它们通常8个编为一组,我们称之为字节。既然内存需要存储数据,内存上必然有非常多的这种8个开关组成的编组。

我们可以把这种编组看作居住数据的房间,而为了方便地找到这些房间,每个房间均有一个编号。第一个房间编号从0开始,此后依次加1

我们把房间号,称之为内存地址。

就像生活中我们所说的:“AA大街,BB公寓,123房间一样。只不过在内存中没有大街和公寓,仅需要编号即可表示地址了。


基础数据类型怎样居住“房间”

 

int为例,我们有以下两种表达方式:

1. 列举所有的房号:301302303304

2. 首房间及房间数:301开始的4个房间。

对于int还好,仅需要4房间。如果数据需要的房间很多,第一种方式需要保存更多的房间号。很显然第二种方式更为灵活。

计算机使用第二种方式记录一个数据对象在内存中的存储位置。

我们把第一个房间房间号,称为这个数据对象的首地址。那么数据对象需要的房间数量,就是它所占用的存储空间大小。

因此,记录一个数据对象在内存中的存储位置,需要两个信息:

  1. 数据对象的首地址。
  2. 数据对象占用存储空间大小。 


取地址运算符 &

现在我们认识一个新的运算符——取地址运算符&

取地址运算符是一个一元运算符,写在一个数据对象的左边,可以获取一个数据对象的首地址和所需存储空间大小

int n;
类型 pn = &n; // 获取数据对象n的首地址和所需空间大小

变量 pn ,存储了变量 n 的首地址和所需空间大小,那么通过变量 pn 可以在内存中找到变量 n 。所以,变量 pn 到底是什么类型呢?


声明指针类型的变量

int n;
int* pn = &n;

char c;
char* pc = &c;

 

int* pn 声明一个保存了 int 类型的首地址大小的变量。

char* pc 声明一个保存了 char 类型的首地址大小的变量。

变量 pn 存储了变量 n 的首地址与大小,变量 pc 存储了变量 c 的首地址与大小。通过 pn pc 可以在内存中找到变量 n c


定义

设一个数据对象为 x ,设另一个数据对象为 p p 存储了 x 的首地址和所占空间大小。那么, p 称之为 x 的指针,或者说 p 指向 x

对于上面的代码:

pn 被称作 n 的指针,或者说 pn 指向 n

pc 被称作 c 的指针,或者说 pc 指向 c

int* pn; // 将空格放在变量旁

int *pn; // 将空格放在类型旁

int*pn; // 不用空格

另外,声明指针变量时,将空格放在变量旁或者将空格放在类型旁,甚至不用空格。这3种写法都是可以的。


指针类型

现在让我们来探究一下,指针类型怎样保存下面两种信息。

  1. 数据对象的首地址。
  2. 数据对象占用存储空间大小。
#include <stdio.h>

int main()

{

int n1;

int n2;

int n3;

int n4;

int *pn1 = &n1;

int *pn2 = &n2;

int *pn3 = &n3;

int *pn4 = &n4;

printf("pn1 = %u\n", pn1);

printf("pn2 = %u\n", pn2);

printf("pn3 = %u\n", pn3);

printf("pn4 = %u\n", pn4);

return 0;

}

我们用 %u 尝试打印指针变量 pn1 pn4 ,看出它们确实为一些数值。

并且,数值之间均相差4。我们有理由怀疑,n1n4它们相邻排布,而这些数值就是n1n4的首地址。事实上,指针类型的值就是目标数据对象的首地址 


空间大小

既然指针类型的值已经被用于存储首地址了,那么目标对象的空间大小存储到哪呢?

#include <stdio.h>
int main()

{
    int a;
    int* pa = &a;

    char b;
    char* pb = &b;

    pa = pb;
    return 0;

}

无法从char转换为int

我们尝试让 pn 赋值为 pc 试试看。

编译出现了错误,无法将 char* 转换为 int*

仅仅看变量的值, pn pc 都是一个整型数据。那么它们理应是可以相互赋值的才对。但是,现在的编译结果告诉我们无法将 char* 转换为 int*

另外,既然变量值已经用于存储首地址了,那么空间大小怎么存储呢?

对于 char* int* 它们的区别就只剩下类型不同且无法直接相互转换了。

我们推测:

指向int的指针类型标识了目标对象的空间大小为 sizeof(int)

指向char的指针类型标识了目标对象的空间大小为 sizeof(char)

没错,C语言中通过不同的指针类型来标记目标数据对象的空间大小

int* pn = &n; 
char* pc = &c;

表达式 &n 的结果为一个类型为 int* 数据对象。它的值为变量 n 的首地址,类型 int* 标记变量 n 占用 sizeof(int) 字节。

表达式 &c 的结果为一个类型为 char* 数据对象。它的值为变量 c 的首地址,类型 char* 标记变量 c 占用 sizeof(char) 字节。

 如果尝试将 pc 赋值给 pn ,虽然首地址可以正确地从 pc 赋值给 pn 。但是指针类型的变化导致数据长度的变化,因此无法进行自动转换。


小结 

指针类型通过值来保存目标数据对象的首地址,类型本身标记目标数据对象的空间大小。 


 使用指针

 既然指针存储了一个数据对象的首地址与大小,并且通过这两个信息可以在内存中找到该数据对象。那么,我们肯定可以使用指针来访问所指向的数据对象。


取值运算符 *

现在我们再认识一个新的运算符——取值运算符*

取值运算符是一个一元运算符,写在一个指针的左边,可以根据指针中存储的首地址和空间大小找到目标数据对象

虽然它和乘法运算符很像,但是,它是一个一元运算符,仅需要一个操作对象。

int n = 123;

int* pn = &n;

printf("%u\n", pn);      // 打印n的首地址

printf("%d\n", *pn);    // 根据`pn`中的首地址与大小,找到的数据对象的值

pn 变量内存储的值,即 n 的首地址。

*pn 表达式结果为,根据 pn 中的首地址与大小,找到的数据对象的值。即 n 的值。

除了通过指针访问所指向的数据对象,也可以通过指针修改所指向的数据对象。



指针类型的大小

charint存储的是数据范围不同的两种数据。char存储小一点的整数范围、int存储大一点的数据范围。所以,char可以占用小一点的空间,而int会占用大一些的空间。

但是, char* int* 存储的均为数据对象的地址,因此它们所占用的空间是相同的。

不同的编译器或编译配置可以让编译器生成32位或64位的程序,有时候被称作x86x64

  1. 32位程序可以访问从0232次方的地址范围。
  2. 64位程序可以访问从0264次方的地址范围。

 

visual studio中,你可以使用上图红框中的下拉列表,切换编译生成的程序是32位或64位。

指针类型必须拥有可以表达所有内存地址的能力。因此在32位程序中,指针类型占用4字节大小。64位程序中,指针类型占用8字节大小。

如果指针类型的大小为4字节,使用 %u 作为printf的占位符是合适的。但是指针类型的大小为8字节时, 使用 %u 有可能不能完全打印地址,你可以使用长度指示符,将长度加长到8字节,例如 %llu 。

 占位符 %p 是指针类型专用的占位符,32位或64位程序使用它均能保证打印的结果正确。不过,它通常是以十六进制显示的。


强制转换指针类型

变量 pn 为指向int类型的指针。变量 pc 为指向char类型的指针。

虽然变量 pn pc 不能在赋值时自动转换。但是,使用强制转换可以将 pn 转换为 char* 后赋值给 pc

赋值后, pn pc 均存储了 n 的首地址,均打印出了同样的首地址。但是,C语言使用不同的指针类型标识目标对象的空间大小。

pn int* 类型, *pn 表达式会从首地址开始处取 sizoef(int) 字节,将其转换成int类型作为表达式结果。因此,结果为1431655765

pc char* 类型, *pc 表达式会从首地址开始处取 sizoef(char) 字节,将其转换成char类型作为表达式结果。因此,结果为85

相关推荐

  1. C#语言

    2023-12-13 13:12:01       36 阅读

最近更新

  1. TCP协议是安全的吗?

    2023-12-13 13:12:01       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2023-12-13 13:12:01       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2023-12-13 13:12:01       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2023-12-13 13:12:01       20 阅读

热门阅读

  1. pytorch-0.4.0上古版本安装参考

    2023-12-13 13:12:01       44 阅读
  2. 【Python 千题 —— 基础篇】分解数据

    2023-12-13 13:12:01       36 阅读
  3. Mysql的基础语句

    2023-12-13 13:12:01       32 阅读
  4. Linux0.11内核源码解析-string待更新

    2023-12-13 13:12:01       40 阅读
  5. 什么是强缓存和协商缓存?

    2023-12-13 13:12:01       34 阅读
  6. PostgreSQL数据库切换到另一个模式下

    2023-12-13 13:12:01       30 阅读
  7. filebeat 后端运行,自动退出解决

    2023-12-13 13:12:01       37 阅读
  8. C : DS静态查找之顺序索引查找

    2023-12-13 13:12:01       40 阅读
  9. HTB Ouija

    2023-12-13 13:12:01       31 阅读
  10. JRT实现Cache的驱动

    2023-12-13 13:12:01       43 阅读
  11. go标记omitempty的含义

    2023-12-13 13:12:01       38 阅读
  12. c++基于流文件输入输出的综合程序设计

    2023-12-13 13:12:01       39 阅读
  13. 你在地铁上修过bug吗?

    2023-12-13 13:12:01       39 阅读
  14. reactHooks之useDeferredValue

    2023-12-13 13:12:01       44 阅读
  15. 12.12每日一题(备战蓝桥杯循环输出)

    2023-12-13 13:12:01       30 阅读