Pointer types, void pointer, pointer arithmetic
到目前为止我们在前面的课程中已经看到了如何使用指针, 对指针有了基本的了解. 本节我们会使用指针编写更多代码, 然后通过几个例子来理解一些概念.
指针类型 pointer types
-
指针是强类型的, 这意味着你需要一个特定类型的指针变量来存放特定类型变量的地址. 例如我们需要
int *p
存储int
类型变量的地址. -
为什么指针需要是强类型的?
指针变量不就是存放一个变量的地址吗?为什么我们不用个$8 bytes$的通用类型来存储所有类型变量的地址?
$\;\;$
答案是我们不仅仅使用指针来存储内存地址, 同时我们也用它来解引用那些地址的内容 — 以此来访问和修改这些地址对应不同类型变量的值. 正如我们通过基础介绍所知道的, 不同的数据类型有着不同的字节大小, 可能还会有不同的编码方式.
示例
例如我们有一个int
类型的变量a
:
int a = 1205;//二进制:00000000 00000000 00000100 00000001
我们知道每个字节在内存中都有地址. 假设变量a
的起始地址为200
, 则其字节对应地址为:
a: 00000000 00000000 00000100 00000001
地址: 203 202 201 200
我们有一个指向a
的int
类型指针变量:
int a = 1205;
int *p = &a;
Print p;
Print *p;
打印p
的值, 我们会得到a
的地址$200$; 而当我们打印*p
也就是解引用p
时, 机器根据p
的值知道p
指向变量的起始地址为$200$;根据p
的类型int
机器知道需要看从起始地址$200$开始的$4\;Bytes$, 并用int
类型的编码方式得到字节对应的值. 如果p
是一个char *
,那么当机器看到*p
时会从起始地址$200$开始读取$1\;Byte$. 我们可以用代码来验证这一点.
int a = 1025;
int *p = &a;
printf("size of integer is %d bytes\n", sizeof (int));
printf("address = %p, value = %d\n\n", p, *p);
char *p0;
p0 = (char *)p;//类型转换 typecasting
printf("size of char is %d bytes\n", sizeof (char));
printf("address = %p, value = %d\n\n", p0, *p0);
我们声明了熟悉的int *p
, 打印p
的值并对p
解引用; 接着我们声明一个char
类型的*p0
, 将p
的值赋给p0
(指针是强引用的, 不同类型的指针在赋值时需要类型转换, 否则在某些编译器上会报错).输出p0
的值, 同样会输出a
的起始地址, 但当我们对*p0
解引用时, 输出的值为$1$:这是因为机器看到对p0
的解引用时, 根据p0
的值机器知道起始地址在$200$,通过p0
的char
类型机器知道需要从起始地址读取$1\;Byte$内容, 所以输出的值为1025
的最低字节.
为进一步理解, 在上代码的基础上再加上两个打印语句:
printf("address = %p, value = %d\n\n", p + 1, *(p + 1));
printf("address = %p, value = %d\n\n", p0 + 1, *(p0 + 1));
回忆上一节的指针运算, (p + 1)
会得到下一个int
型数的地址, 因此它会跳过4
个字节(地址值+ 4
); 因为我们没有对这个特定地址写过任何东西, 所以里面的值是一个随机值. 而(p0 + 1)
会得到下一个char
型变量的地址, 因为它会跳过1
个字节(地址值+1
), 对应字节的值为00000100
即4
.
$\;\;\;\;$
这里我们向你展示了在解引用一个指针变量的时侯内存中会发生什么; 以及对指针进行特定类型的算术操作会发生什么. 上面指针类型转换的一些场景会在后面讨论. 现在我们来讨论一种指针类型: 通用指针类型.
通用指针 generic pointer type
通用指针类型不针对某一特定的数据类型, 在C
中我们使用void *
声明这种指针类型.
int a = 1025;
int *p = &a;
printf("size of integer is %d bytes\n", sizeof (int) );
printf("address = %p, value = %d\n", p, *p);
//Void pointer - generic pointer
void *p0;
p0 = p; //这里不需要显式类型转换
printf("Adress = %p", p0);
printf("Value = %d\n", *p0); //error
void *p0
是通用类型指针, 所以用p
对其赋值时不需要类型转换. 而由于p0
没有映射到任何特定数据类型, 我们不能直接对它解引用, 同样的原因我们也不能对p0
进行指针运算. 之后我们会介绍void
指针的使用场景. 本节void
指针可以告诉我们指针类型在解引用和指针运算时的作用.