指向类的指针

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

是这样的,但不能说它是不存在的,类的定义是向编译器提供一个类结构说明,如果是不存在,编译器如何知道是怎样的呢...说它没有被分配空间是因为它是给编译器看的,不是给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一样.

在C++中,指向指针数组是一种常见的数据结构,用于存储多个指向的对象的指针。这种数组可以动态地管理对象的生命周期,提供灵活的对象访问方式。 ### 指向指针数组的定义 假设我们有一个 `MyClass`,我们可以定义一个指向指针数组如下: ```cpp class MyClass { public: MyClass(int value) : data(value) {} void printData() { std::cout << data << std::endl; } private: int data; }; int main() { int size = 5; MyClass* array[size]; // 定义一个指向MyClass的指针数组 // 初始化数组中的每个指针 for (int i = 0; i < size; ++i) { array[i] = new MyClass(i); } // 使用数组中的指针调用成员函数 for (int i = 0; i < size; ++i) { array[i]->printData(); } // 释放内存 for (int i = 0; i < size; ++i) { delete array[i]; } return 0; } ``` ### 解释 1. **定义指针数组**:`MyClass* array[size];` 定义了一个包含 `size` 个指向 `MyClass` 对象的指针的数组。 2. **初始化指针**:通过循环为数组中的每个指针分配一个新的 `MyClass` 对象。 3. **使用指针**:通过数组中的指针调用对象的成员函数 `printData`。 4. **释放内存**:在程序结束前,通过循环删除每个对象以释放内存。 ### 优点 - **动态管理**:可以动态地管理对象的生命周期,避免内存泄漏。 - **灵活性**:可以方便地访问和操作对象。 ### 注意事项 - **内存管理**:必须确保每个动态分配的对象在使用完毕后被正确删除。 - **空指针检查**:在访问指针之前,确保指针不为 `nullptr`。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值