指向类的指针

来源:互联网 发布:什么软件可以赚钱 编辑:程序博客网 时间:2024/06/05 06:37

引用:
定义某个类时,它在内存中是没有被分配空间的,也就是说不存在,只有建立了实例才为这个类分配空间(是这样吧?)。

是这样的,但不能说它是不存在的,类的定义是向编译器提供一个类结构说明,如果是不存在,编译器如何知道是怎样的呢...说它没有被分配空间是因为它是给编译器看的,不是给CPU看的(CPU只知道简单类型)。


引用:
那么,这个实例(或者说,它的类结构)在内存中是怎么被分配的?

编译器就按照你的类的定义(数据成员)来分配相应的存储空间(只分配存储空间,如果数据成员需要进行初始化,你的类应该提供构造函数)。


引用:
如果定义了一个指向该实例(类)的指针,这个指针是保存了什么的内存地址的?

以后要注意不要说成“定义了一个指向该实例(类)的指针”这样了,应该说成“定义了一个指向该类的指针”。类和实例是不同的东西,类是给编译器看的,而实例是给CPU看的。如果是定义了一个实例(对象)的指针,就说明已经构造出一个对象了,比如:
A* pa = new A;
要注意,这里进行了两次的内存分配,一个是指针pa的存储分配,另一个是实例化A对象的存储分配(由new来完成和返回)。

但如果是定义一个类的指针,如:
A* pa;
这时编译器是没有分配一个类A的对象的存储空间的,它只是给指针pa分配一个指针的存储空间,你可以把这个指针理解为是没有值的(因为这个指针的存储空间里的值是原来内存中遗留下来的值,对于这个指针来说是没有意义的)。


引用:
另外,比如定义了一个类如class mycls,那么在函数设置参数时,参数用这个这个类的类型,即声明函数参数格式为:void functionName ( mycls t )就行了吗?(我好像有看见有时用到 * 或 & 的,这个不太清楚怎么弄)

这个只是值传递和地址传递的区别而已,自己找本书看看函数的参数传递的说明吧,一般都会有值传递和地址传递的区别的说明。
&是C++比C新增的一种指针(叫做引用),它在参数的传递中与指针都是类似的,只是在书写的表示上有不同而已。

 

c++的中的指向类的指针和引用的比较 收藏
首先考虑下面这道题:
A* pr = new B();
delete pr;

a) 运行正常,没有错误
b) 运行正常,但出现内存泄漏
c) 出现保护错,程序退出

注: 很多C++教科书都强调在析构函数不是虚函数的情况下,不要以基类指针去
delete一个对象,因此这两条语句是很不标准的作法. 我的意图是想弄明白这
样作到底会产生什么后果,以便更牢固地记住这条规则.
《-----------------------------------------------》
C++中"delete pr"这条语句大致作了两件事:
1 调用pr所指对象的析构函数(再调用基类的析构函数,etc)
2 释放pr所指对象本身所在的内存.(用operator delete, 最终好象是调用free()来完成)

在我们的程序中, 因为pr的类型是A*(虽然它所指的对象实际上是B类型), 第1步只调用A的析构函数,B的析构函数没有被执行. 不过正如楼上所说,在我们的例子中这不会有什么问题,因为~B()啥也没干.
麻烦出在第2步. 由于虚函数表的存在, 在 "A* pr = new B()" 后, pr实际上没有指向所生成的B对象的开始,而是在其后4字节(这是所有问题的关键,我以后会解释.记得我们第一题中pa2!=pb2吗? 事实上pa2==pb2+4). 因此free()在释放内存时,它的指针参数并不是程序之前申请的空间的开始位置, 而是其后4字节! 你说free()会如何是好? 可以预料只可能有两种结果: 或者立即出保护错, 或者, 如果free()啥也不作,这段内存就泄漏了.
 
符合如下两个条件下这么作是完全可以使用 基类指针去 delete一个对象:
(1)派生类的析构函数中没有释放资源语句
(2)基类和派生类要么都不包含虚函数,要么都包含虚函数

再看下面一道题:

#include <stdio.h>

class A
{
public:
int m_a;
};

class B : public A
{
public:
int m_b;
void virtual fun() {}
};

int main()
{
B b;
B* pb = &b;
A* pa = pb;
void *pb2 = (void *)pb;
void *pa2 = (void *)pa;

const char *str1 = (pa==pb) ? "yes" : "no";
const char *str2 = (pa2==pb2) ? "yes" : "no";

printf("%s %s\n", str1, str2);
return 0;
}

你能预测程序的结果吗? 是 "yes yes" 还是 "yes no", 抑或其它值?

分析:
1)基类和派生类的内存映象
(2)虚函数的实现原理--虚函数表
可当你象我一样遇到上面这样莫名其妙的保护错时,肯定也想探个究竟. 很多C++书籍对这两点有很精彩的描述(例如<<Inside the C++ Object Model>>)
(1)在内存布局中,基类与派生类是"头对齐"的.派生类的前半部分就是基类.(声明: 不考虑多重继承)
在我们的例子中(没有虚函数的情况下), b对象的内存映象大概是这样的
   -----------------------
   | A  |
   -----------------------
   |   B   |
   -----------------------
   | m_a | m_b |

因此,指向派生类的指针(B*)同时又是指向基类的指针(A*)

(2)有虚函数的对象在最前面有4字节是指向虚函数表的指针, 因此B类型对象在内存中实际应该是这样的:
   -----------------------
   | vptr | m_a | m_b  |

高手们可能会觉得可笑,有一天我想到例子中的这种情况时突然纳闷不已: 这两点根本就是相互矛盾的! 因为A没有vptr而B有. 怎么可能作到"头对齐"而且B的开头是vptr? 我兴冲冲地用VC跟了一遍看看它怎么处理的.结果让我颇为惊叹. VC的处理方法是照顾虚函数表而牺牲"头对齐"原则, 真正的内存映象是这样的:
   --------------------------
        | A  |
   --------------------------
   |      B      |
   --------------------------
   | vptr | m_a | m_b |

在我们的例子中, "B* pb = &b;" 使得pb指向B的开头,也就是vptr的位置. 接下来"A* pa = pb;"就很有意思了.编译器让pa指向哪呢? 总不能也指向vptr的位置吧? 结果你可能猜到了,编译器把pa向后挪了4字节,使其依然指向A(也就是m_a)的位置. "等等",反应快的同学会问了,"这样一来pa与pb指的就不是同一个位置了.那(pa==pb)为何还为true呢?". 呵呵,这就是编译器的聪明之处,它分析这两个指针指向的是基类/派生类的关系,就会考虑进去这4字节偏差而返回true. 而当我们把二者cast成void *类型(pa2与pb2)后,编译器就啥也不知道了.对(pa2==pb2)老老实实地返回false. 第二题的保护错也是同样道理,我在上面已解释过.总之,这件事给我最深刻的教训就是:一个指针cast成另一种类型指针后,可能已指向不同的内存地址!
btw,我还没说C++Builder中的情况.与VC不同,在BCB中程序的结果是"yes yes". 这是因为BCB不管你类中有没有虚函数,上来就给一个虚函数表指针,以便往其中塞一些运行时类型识别之类的私活.因此就不存在例子程序的这种问题了.真个 是一劳永逸,呵呵(代价是所有对象都无缘无故增大了4字节).我手头没有gcc环境,但我想gcc的结果应该与VC一样.

0 0
原创粉丝点击