2.3 指针运算
C和C++代码精粹
当一个指针指向一个数组元素时,可以在指针上加一个整数或减一个整数来使它指向同一数组的元素。在这样的指针上加1是通过它所引用类型的数据的字节数来增加它的值,因此,它就指向下一个数组元素。程序清单2.3中的程序实现了在一个浮点型数组内完成整数的运算。数组看起来像:
在我的系统平台上,浮点型占4个字节。在p上加1实际上等于在它的值上加4。对两个指向数组元素的指针进行相减可以得到在两个地址之间数组元素个数。换句话说,如果p和q是指向相同类型的指针,那么语句q=p+n意味着q-p==n,或相反。存储两个指针差的便捷方法是把这个差存储在ptrdiff_t中,它在stddef.h中定义,包含在中。
指针运算规则可以归纳为下面的公式:
`1.p±n==(char)p±nsizeof(*p)
程序清单2.3 说明指针运算
/* arith.cpp: 举例说明指针运算*/
#include <iostream>
using namespace std;
main()
{
float a[] = {1.0, 2.0, 3.0};
//增加一个指针
cout << "sizeof(float) == " << sizeof(float) << endl;
float* p = &a[0];
cout << "p == " << p << ", *p == " << *p << endl;
++p;
cout << "p == " << p << ", *p == " << *p << endl;
//减去两个指针
ptrdiff_t diff = (p+1) - p;
cout << "diff == " << diff << endl;
diff = (char *)(p+1) - (char *)p;
cout << "diff == " << diff << endl;
}
//输出:
sizeof(float) == 4
p == 0x0012ff80, *p == 1
p == 0x0012ff84, *p == 2
diff == 1
diff == 4
这也就是说“在(从)一个指针上加(减)一个整数n时,指针将在内存中向上(下)移动其所指向的类型的n个单元”。
2.p-q==±n
这里n是p和q之间元素的个数。注意,记住公式2中的n是一个特殊的类型(ptrdiff_t),在一些结构中,你只可以在指针运算中把它作为补充使用,而不能以其他的方式使用(例如,甚至不能打印它)。
因为公式假设一组有序等大小的对象,所以指针运算只在数组内有意义。然而,可以把任何单一的对象解释成字节数组。程序清单2.4中的程序通过把整数的地址存放在一个指向字符型的指针中来详细研究了整数,然后通过指针运算来访问每个字节。注意在cp初始化时的强制类型转换,在给不同类型的指针赋值时,需要有强制类型转换以使编译器确认你知道自己在做什么,否则编译器将怀疑你不知道自己在做什么,因此给出警告消息“可疑的指针转换”,然而,当转换成void指针时则不需要强制类型转换(参见2.5节“普通指针”)。
程序清单2.4 说明指针转换
// convert.cpp: char* 和指针映射
#include <iostream>
using namespace std;
main()
{
int i = 7;
char* cp = (char*) &i;
cout << "The integer at " << &i
<< " == " << i << endl;
//分别打印每个字节的值:
for (int n = 0; n < sizeof i; ++n)
cout << "The byte at " << (void*)(cp + n)
<< " == " << int(*(cp+n)) << endl;
}
//输出:
The integer at 0x0012ff88 == 7
The byte at 0x0012ff88 == 7
The byte at 0x0012ff89 == 0
The byte at 0x0012ff8a == 0
The byte at 0x0012ff8b == 0
程序清单2.4的输出揭示了一个有趣的事实:我的PC的Intel处理器是“从后”存储的,在这种方式下一个对象最没有意义的值被存储在内存地址较低的单元中。这种存储机制叫做“不重要的值结尾”,因为在内存中向上移动时,首先遇到的是一个多字节整数的“小的结尾”。VAX机器同样是“不重要的值结尾”。但是IBM机器是“重要的值结尾”。这在通常的数据处理中并不值得关注的,但是有些时候它将起很大作用。
例如,假设你想有效地存储一个世纪内的日期,需要这样存储:
Year(0-99) 7 bits
Month(1-12) 4 bits
Day(1-31) 5 bits
幸运的是,合在一起是16位,刚好是PC机中一个短整型数的大小。那么,一个将日期存储在短整型中的明显方式是用位段操作如下:
/ bit1.cpp: 向整数中压缩数据
#include <iostream>
#include <iomanip>
using namespace std;
main()
{
unsigned short date, year = 92, mon = 8, day = 2;
date = (year << 9) | (mon << 5) | day;
cout << hex << date << endl;
}
// 输出:
b902
日期1992年8月2日(b902)的位逻辑排列的理论期望是:
但是,“不重要的值结尾”机器物理上从后向前存取数据,即;
用下面的位域结构可得到一个可读性更强的程序(参见程序清单2.5)。
struct Date
{
unsigned day:5;
unsigned mon:4;
unsigned year:7;
};
这个结构反映了相反的排列。要用位域结构来表示一个整型,只需简单地将指向整型的指针强制转换成指向Date的指针。现在可以不用移位和屏蔽而用名字来访日期成员。为了通过指针访问结构成员,需要先对指针进行复引用,然后再命名成员:
(*dp).mon
由于这是个繁锁的句法,其简化的句法为:
dp->mon
程序清单2.5 通过一个位域结构封装短整型数据
// bit2.cpp: 用一个位域结构覆盖一个整数
#include <iostream>
#include <iomanip>
struct Date
{
unsigned day: 5;
unsigned mon: 4;
unsigned year: 7;
};
main()
{
unsigned short date, year = 92, mon = 8, day = 2;
Date* dp = (Date*) &date;
dp->mon = mon;
dp->day = day;
dp->year = year;
cout << hex << date << endl;
}
//输出:
b902
我听过的读法有“dp箭头mon”、“dp指向mon”以及一些其他的读法,在此不再提及。
本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。