查了下书,指针是这样定义的:“指向某种类型对象的复合数据类型”。“复合数据类型”这句说得很好,因为一般说道都会想到“就是地址”,说开无非是存着所指向对象的地址呗?但是如果单单说是一个存有地址的类型这显然不够称得上“复合类型”。
不妨再加上一条:1.存着所指对象地址。2.存着所指对象类型和类型大小。虽然指针远没这么简单(并且一个指针变量4个字节存的仅仅是32位的地址,本篇为了给你一种感觉暂且说同时存了“类型和类型大小”,下篇会给出另外的解释),但是我们看看这两条都能做些什么。
第一条应该没什么好解释的了,第二条有必要说说,因为看似很显然,但是可以明白好多事情。
试想我们定义一个某类型指针p,后可以p++,p--,*p这些都与“指针记录了所指对象类型大小”有关,不然p++和p-- 一次跳了多远?还有*p一次取了多长,都是由记录的类型大小决定的:如int *p=&i;那么++ 一次至少会跳int那么远即4个字节(连续存储下),*p一次也会从p所指向地址为起点取出int那么长(4字节)空间的二进制信息,并将这些二进制翻译成“指针内部存着的其所指向的类型”的类型。记住这种“感觉”:初始化指针就是:(1)记下所指对象的地址(2)同时记下所指类型和类型大小。(3)对一个指针解引用(*p)就是从指针所指地址为起点,读出所指类型大小那么大的空间的二进制,然后将二进制翻译成所指类型。(4)而++,——操作就是向前或向后跳所指类型那么大小的空间。
下面强化一下。
- int t = 65<<8;
- char *p = (char*)&t+1;
- cout<<(int)*p<<endl;
65左移了8位然后被给一个被赋值给整型t,然后定义了char*指针p,而后的强制转换就有些意思了,按照前面的感觉也就不难理解了:取了t的地址然后由于遇见(char*),p存储的类型原本应该是t的类型int,然而由于(char*)的出现p中存储的类型将变成char类型,大小也为char那么大即一个字节,而后+1,这时这一跳将因为目前对象是char类型,而只跳一个字节(8位),也就是说这时p将指向之前左移8位的65这个数所在内存那么下面的cout将如期望的一样输出65这个数。
我们有了感觉之后似乎对指针间的强制类型转换也很清楚了:(1)转换后的指针中记录的地址依旧是转换前的地址(2)转换后的指针记下的所指类型和类型大小,为强制转换的类型和强制转换类型的大小。(3)*操作会依旧会以指针所指地址为起点,却取出转换后的指针所记录的类型大小那么大的空间的二进制,翻译成指针转换后的所指类型。(4)转换后的++,和--跳的距离以转换后的指针中存放的类型大小为依据。
那么我们更有感觉了:只要指针间在“能转换的情况下”我们就可以转来转去,只要保证不对指针进行移位就可以保证指针所指向地址一直不变,转换仅仅转的是里面存的类型和类型大小,只要我们最后能转回原来的类型还可以如同最初时一样按原类型操作它。
- typedef struct
- {
- int a;
- }test;
- test t;
- t.a=100;
- long* t1=(long*)&t;
- char* t2=(char*)t1;
- test * t3=(test*)t2;
- cout<<t3->a<<endl;//output:100
转来转去又转回初始类型,如我们所愿的输出100。
最后确认一下感觉。
- int i=*(int *)"ABC";
- cout<<i<<endl;//output:4407873
4407873是什么看不清楚吗?转成二进制10000110100001001000001,还看不清?补齐四个字节,再分割一下,00000000'01000011'01000010'01000001。不知道是什么了吗?对照下表:
二进制数 ASCII(也是%d输出的整型值) 字符
01000001 65 A
01000010 66 B
01000011 67 C
00000000 0 /0
又很有感觉了吧?对于上面的程序中原本ABC/0(字符串隐含/0下一篇会解释)是一坨二进制,生成后返回地址char*类型然后遇到 (int *)被转成了int*,就等于告诉程序:以后对待存ABC和/0这坨内存别再看成是char*的了,而是我int*的了,要读的话别读char*那样的一个字节了,要给我读满4个字节(int是4个字节),也就是后再遇到*解引用,取出4个字节的东西赋值给i,而i是int型,00000000'01000011'01000010'01000001这串二进制自然翻译成int型的即4407873。
最后,忘了这一切吧,记住一句话“永远别玩指针!玩不好容易被指针玩”,以上的一切代码都不是好习惯,只是为了给你一种感觉,记住那句话,再记住感觉(下一篇还要用到这种感觉)就足够了。