《C和C++代码精粹》——2.3 指针运算

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”以及一些其他的读法,在此不再提及。

本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。

时间: 2024-10-03 21:26:45

《C和C++代码精粹》——2.3 指针运算的相关文章

《C和C++代码精粹》——2.7 指针和一维数组

2.7 指针和一维数组 C和C++代码精粹 在程序清单2.7中,会注意到在传递数组 s 时并没有使用它的地址,这是因为C和C++在大多数表达式中把数组名转换成指向它第一个元素的指针.自1984年以来,我已经向成百上千的学生讲授了C和C++,我注意到了指针和数组,特别是指针和多维数组之间的关系造成很多迷惑. 这样说似乎很奇怪,但是C++确实不支持数组,至少C++不像支持第一类数据类型如整型或者甚至结构体那样支持数组.考虑以下的语句: int i=1,j; int a[4]={0,1,2,3},b[

《C和C++代码精粹》——2.5 普通指针

2.5 普通指针 C和C++代码精粹 通常编写能接收指向任意类型参数的函数是很方便的.这是很有必要的,例如,用标准的库函数memcpy,能够从一个地址向另一个地址拷贝一块内存.你也可能想调用memcpy来拷贝自己创建的结构: struct mystruct a,b; /.../ memcpy(&a,&b,sizeof(struct mystruct)); 为了操作任意类型的指针,memcpy把它头两个参数声明为void型指针.可以不需要强制类型转换将任何类型的指针赋予void类型.也可以在

《C和C++代码精粹》——2.12 指向函数的指针

2.12 指向函数的指针 C和C++代码精粹 一个指针可以指向函数也可以指向存储的对象.下面的语句声明fp是一个指向返回值为整型(int)的函数的指针: int(*fp)( ); *ftp的圆括号是必需的,没有它的语句 int *fp( ); 将fp声明为一个返回指向整型(int)指针的函数.这就是将星号与类型声明紧密相连的方式成为逐渐受人们欢迎的方式的原因之一. int fp(); //方式说明fp()返回一个指向整型的指针(int ) 当然,这种方式建议你通常应该每条语句只声明一个实体,否则

《C和C++代码精粹》——第 2 章 指针2.1 容易出错的编程

第 2 章 指针 C和C++代码精粹本文仅用于学习和交流目的,不代表异步社区观点.非商业转载请注明作译者.出处,并保留本文的原始链接. 2.1 容易出错的编程 C和C++代码精粹"分割违规" "访问违规" "可疑的指针转换" "不可移植的指针转换" "空指针赋值" 这些消息听起来熟悉吗?指针出错是C++程序员必须应付的最令人厌恶的错误.实际上,长时间以来指针和它所提供给开发者的原始功能已经成为人们对C主要的

《C和C++代码精粹》——2.6 const指针

2.6 const指针 C和C++代码精粹注意memcpy函数第二个参数中的const关键字.这个关键字告诉编译器此函数将不会改变source指向的任何值(除了强制类型转换).当把指针作为参数传递时,总是合适地使用const限定符是一个很好的习惯,它不仅可以防止你无意中错误的赋值,而且还可以防止在作为参数将指针传递给函数时可能会修改了本不想改变的指针所指向的对象的值.例如,如果在程序清单2.6中的声明是: const int i=7,j=8; 有可能因为下面这条语句而得到警告: swap(&i,

《C和C++代码精粹》——2.10 指针和多维数组

2.10 指针和多维数组 C和C++代码精粹实际上,在C++中没有多维数组!至少对多维数组没有直接的支持.人们通常把一个一维数组看作一个向量,把一个二维数组看作一个表或者矩阵,把一个三维数组看作一个长方体.然而,数组的几何模型使明智地使用高维数组变得很困难,取而代之的是C++支持"数组的数组"的概念.例如,如果一个一维的整型数组为 int a[4]={0,1,2,3}; 它是一个有索引的整数集合: 我们通常把它描述成一个向量: 对一个二维整型数组,如: int a[3][4]={{0,

《C和C++代码精粹》——2.13 指向成员函数的指针

2.13 指向成员函数的指针 C和C++代码精粹如果返调函数是某个类的成员函数将会怎样?获得指向类成员的指针与获得指向非成员实体的指针的方式相似,只存在很小的语法变化.例如,考虑下面类的定义: class C { public: void f ( ) {cout << "C::f\n";} void g( ) {cout << "C::g\n";} }; 可以这样定义一个指向C类成员函数的指针: void (C::*pmf) ( ); //

《C和C++代码精粹》——2.11 更高深的内容

2.11 更高深的内容 C和C++代码精粹 我们可以很自然地得出以下结论:一个三维数组是二维数组的集合. int a[2] [3] [4]={{{0,1,2,3},{4,5,6,7},{8,9,0,1}}, {{2,3,4,5},{6,7,8,9},{0,1,2,3}}}; 这个数组的第一个元素是一个"二维数组" a[0](从技术上来说,a[0]是一个由3个含有4个整数的数组的数组),为了使指针与数组名a一致,定义: int (*p) [3] [4] = a; 程序清单2.16是一个应

《C和C++代码精粹》导读

前言 C和C++代码精粹 本书适合于那些C和C++的职业程序员.假如你已熟悉这两种语言的语法和基本结构,这本书能够为你创建有效的.实用的程序提供实践性的指导.每一个代码范例或程序范例均标明行之有效的用法和技术,这些用法和技术对C/C++这两种重要编程语言的性能发挥起着重要的作用. 对于那些希望在工作中加强自身技术和提高效率的人来说,本书可以算是一本经验之谈.尽管目前人们对面向对象模式的推崇到了白热状态(本书也包括这方面的丰富内容),可是我没有理由不对C++的基础-C表示尊崇.我发现太多的程序开发