一个数据对象的内存位置有两个重要信息:
- 数据对象的首地址。
数据对象占用存储空间大小
指针类型的值存储的是内存地址。内存地址是从0开始,依次加1的整型数据。
指针类型与整型进行加减
我们让指针变量从地址100开始加减,看看能不能成功。如果可以,计算后的结果是什么。
编译后,我们发现编译器提示我们,不能将int类型转换为指针类型。
这个错误应该是意料之中的,指针类型可以记录两种信息,一个是首地址,一个是目标数据空间大小。
而整型数据的值可以被当作首地址,但是目标数据空间大小却无法表示。赋值操作符两边提供的信息不统一,那么肯定无法成功赋值。
既然无法直接赋值,我们将整型转换为对应的指针类型后,再进行赋值。
现在编译可以通过了,让我们仔细观察结果。
所有的指针内保存的首地址一开始均为100,加1后,现在变成了不同的值。与初始值相比,分别移动了:1、2、4、4、8、4、8。
sizeof(char) = 1
sizeof(short) = 2
sizeof(int) = 4
sizeof(long) = 4 sizeof(long long) = 8 sizeof(float) = 4
sizeof(double) =
指针类型加1后,将首地址向后移动了 sizeof(目标数据对象) 字节。我们再让指针加2试试看?
与初始值相比,分别移动了:2、4、8、8、16、8、16。
指针类型加2后,将首地址向后移动了两个 sizeof(目标数据对象) 字节。
pc = pc - 1;
ps = ps - 1;
pn = pn - 1;
pl = pl - 1;
pll = pll - 1;
pf = pf - 1;
pd = pd - 1;
指针类型与整型也可以进行减法运算。指针类型减1后,将首地址向前移动了 sizeof(目标数据对象) 字节。
规律
sizeof(目标数据对象) 被称作步长。
指针类型加 n 后。其首地址向后移动 n * 步长 字节。
指针类型减 n 后。其首地址向前移动 n * 步长 字节。
n1、n2、n3、n4之间的首地址刚好相差 sizeof(int) 。
那么我们是否可以通过指针与整型加减来访问它们呢?
接着,我们使用取值运算符*,将指针指向的目标数据对象进行修改。
*p = 111 ,使得 p 指向的数据对象改为111。
*(p - 1) = 222 ,使得 p - 1 指向的数据对象改为222。
*(p - 2) = 333 ,使得 p - 2 指向的数据对象改为333。
*(p - 3) = 444 ,使得 p - 3 指向的数据对象改为444。
n1到n4被修改为了111到444。
注意,表达式 p - 1 必须先被括号包裹,再使用取值运算符*。这是因为取值运算符*的优先级高于算术运算符。
我们需要先让首地址移动,再进行取值操作。
若不使用括号, *p 会先被取值,之后值再被加1。
不使用括号:
*p 的值为111, *p - 1 的结果为110。
使用括号:
(p - 1) 使得首地址移动到n2, *(p - 1) 得到结果为222。
请格外注意,函数内声明的变量在内存中不一定是首尾相接的。受到内存对齐的影响,它们有可能不连续。这个例子仅仅是为了让读者理解指针与整型加减运算,请勿在实际使用中使用这种写法。
另外,数组可以保证各元素在内存中首尾相接,各元素连续分布在内存上。所以,C语言中可以使用指针与整型加减运算访问和修改数组元素。我们将在下一节中讨论使用指针访问数组。
同类型指针减法运算
第一个元素 arr[0] 的首地址为20970856。第六个元素 arr[5] 的首地址为20970876。
&arr[5] - &arr[0] 进行减法运算后,结果居然是5。
类比于指针类型与整型加减,这里肯定也受到了步长影响。步长为目标类型所占空间大小,步长为 sizeof(int) 。
arr[5] 的首地址减 arr[0] 的首地址为20970876 - 20970856 = 20。两首地址差值除以步长刚好为5。这里的5意义为:第一个元素与第六个元素之间相隔了5个元素。
规律:
sizeof(目标数据对象) 被称作步长。
指针类型与指针类型相减后,其结果为两首地址差值除以步长。
其他类型的指针运算
上面我们介绍了两种有指针类型参数的运算:
- 指针类型与整型加减。
- 同类型的指针相减。
它们的运算结果都在内存上拥有实际的意义。还有另外几种运算:
- 指针类型与整型进行乘除运算。
- 同类型的指针相加。
- 同类型的指针乘除。
这些运算结果都没有实际意义,在C语言中无法通过编译。