自定义内存管理(五十七)
一个笔试题:编写能统计对象中某个成员变量的访问次数的程序。我们在类中定义一个私有成员变量,在构造函数中初始化为 0,在进行读写操作时都 ++,那么就达到我们的目的了,下面我们看看程序是怎样写的
目前累计服务客户近1000家,积累了丰富的产品开发及服务经验。以网站设计水平和技术实力,树立企业形象,为客户提供成都做网站、网站设计、外贸营销网站建设、网站策划、网页设计、网络营销、VI设计、网站改版、漏洞修补等服务。创新互联建站始终以务实、诚信为根本,不断创新和提高建站品质,通过对领先技术的掌握、对创意设计的研究、对客户形象的视觉传递、对应用系统的结合,为客户提供更好的一站式互联网解决方案,携手广大客户,共同发展进步。
#include#include using namespace std; class Test { int m_Value; int m_count; public: Test(int value = 0) { m_Value = value; m_count = 0; } int getValue() { m_count++; return m_Value; } int setValue(int value) { m_count++; m_Value = value; } int getCount() { return m_count; } ~Test() { } }; int main() { Test t; t.setValue(100); cout << "t.m_value = " << t.getValue() << endl; cout << "t.m_count = " << t.getCount() << endl; Test ct(200); cout << "ct.m_value = " << ct.getValue() << endl; cout << "ct.m_count = " << ct.getCount() << endl; return 0; }
我们来编译看看结果
我们看到已经正确实现功能了哈,类对象也有可能是 const 的,我们来试试 const 类型的呢
const 对象只能调用 const 成员函数,我们将 getCount 和 getValue 改为 const 成员函数。
我们看到又说 m_count 是在 const 成员函数中不能被改变。那么问题来了,怎样才能改变 const 成员函数中的限制呢?很幸运,在 C++ 中有一个关键字 mutable。mutable 是为了突破 const 函数的限制而设计的,mutable 成员变量将永远处于可改变的状态,它在实际的项目开发中被严禁滥用。我们先来试试,在 m_count 定义前加上 mutable 。
我们看到编译通过,并且成功运行。我们再来看看 mutable 关键字有什么特性,mutable 成员变量破坏了只读对象的内部状态,const 成员函数保证只读对象的状态不变性,mutable 成员变量的出现无法保证状态不变性。我们再次进行改写,程序如下
#include#include using namespace std; class Test { int m_Value; int * const m_pCount; public: Test(int value = 0) : m_pCount(new int(0)) { m_Value = value; } int getValue() const { *m_pCount = *m_pCount + 1; return m_Value; } int setValue(int value) { *m_pCount = *m_pCount + 1; m_Value = value; } int getCount() const { return *m_pCount; } ~Test() { delete m_pCount; } }; int main() { Test t; t.setValue(100); cout << "t.m_value = " << t.getValue() << endl; cout << "t.m_count = " << t.getCount() << endl; const Test ct(200); cout << "ct.m_value = " << ct.getValue() << endl; cout << "ct.m_count = " << ct.getCount() << endl; return 0; }
我们定义一个 int* const 类型的指针,然后在构造函数中进行初始化,再利用它进行 ++操作。我们看看编译结果
已经正确实现了哈。下面又是一个很有意思的面试题:new 关键字创建出来的对象位于什么地方?我们大多数人的第一反应是肯定是堆嘛,new 出来的对象肯定在堆上嘛。其实不一定哦。new/delete 的本质是 C++ 预定义的操作符,C++ 对这两个操作符做了严格的行为定义。new:1.获取足够大的内存空间(默认为堆空间);2、在获取的空间中调用构造函数创建对象。delete:1、调用析构函数销毁对象;2、归还对象所占用的空间(默认为堆空间)。那么在 C++ 中是能够重载 new/delete 操作符的,全局重载(不推荐)和局部重载(针对具体类型进行重载)。重载 new/delete 的意义在于改变动态对象创建时的内存分配方式。
下来我们就来利用 new/delete 的重载在静态存储区中创建动态对象。
#include#include using namespace std; class Test { static const unsigned int COUNT = 4; static char c_buffer[]; static char c_map[]; public: void* operator new (unsigned int size) { void* ret = NULL; for(int i=0; i (p); int index = (mem - c_buffer) / sizeof(Test); int flag = (mem - c_buffer) % sizeof(Test); if( (flag == 0) && (0 <= index) && (index < COUNT) ) { c_map[index] = 0; cout << "succeed to free memory: " << c_map[index] << endl; } } } }; char Test::c_buffer[sizeof(Test) * Test::COUNT]; char Test::c_map[Test::COUNT] = {0}; int main() { cout << "==== Test Single Object ====" << endl; Test* pt = new Test; delete pt; cout << "==== Test Object Array ====" << endl; Test* pa[5] = {0}; for(int i=0; i<5; i++) { pa[i] = new Test; cout << "pa[" << i << "] = " < 我们在全局数据区定义了 4 个数据类型大小的空间,所以只能申请 4 个数据类型大小的空间。在 main 函数中申请了 5 个数据类型,因此编译器只会分配 4 ,最后一个肯定分配不成功。注意:这都是在全局数据区,而不是在堆上。我们来看看编译结果
我们看到的确是只分配了 4 个 int 类型大小的空间,最后一个为 0,没分配成功。我们已经利用重载 new/delete 操作符,将 new 出来的对象位于全局数据区了,所以 new 出来的对象不一定是只在堆空间中,只是默认在堆空间中。
下来我们再来看一个面试题:如何在指定的地址上创建 C++ 对象?解决方案:a> 在类中重载 new/delete 操作符;b> 在 new 的操作符重载函数中返回指定的地址;c> 在 delete 操作符重载中标记对应的地址可用。下来我们来看看程序怎么写
#include#include #include using namespace std; class Test { static unsigned int c_count; static char* c_buffer; static char* c_map; public: static bool SetMemorySource(char* memory, unsigned int size) { bool ret = false; c_count = size / sizeof(Test); ret = (c_count && (c_map = reinterpret_cast (calloc(c_count, sizeof(char))))); if( ret ) { c_buffer = memory; } else { free(c_map); c_map = NULL; c_buffer = NULL; c_count = 0; } return ret; } void* operator new (unsigned int size) { void* ret = NULL; if( c_count > 0 ) { for(int i=0; i 0 ) { char* mem = reinterpret_cast (p); int index = (mem - c_buffer) / sizeof(Test); int flag = (mem - c_buffer) % sizeof(Test); if( (flag == 0) && (0 <= index) && (index < c_count) ) { c_map[index] = 0; cout << "succeed to free memory: " << c_map[index] << endl; } } else { free(p); } } } }; unsigned int Test::c_count = 0; char* Test::c_buffer = NULL; char* Test::c_map = NULL; int main() { char buffer[12] = {0}; Test::SetMemorySource(buffer, sizeof(buffer)); cout << "==== Test Single Object ====" << endl; Test* pt = new Test; delete pt; cout << "==== Test Object Array ====" << endl; Test* pa[5] = {0}; for(int i=0; i<5; i++) { pa[i] = new Test; cout << "pa[" << i << "] = " < 我们在全局数据区自定义数组 buffer,然后再在 buffer 里进行操作。看看编译结果
我们看到已经正确实现了。还有一个:new[] / delete[] 和 new / delete 一样吗?它们是完全不同的。动态对象数组创建通过 new[] 完成,动态对象数组的销毁通过 delete[] 完成。而 new[] / delete[] 能够被重载,进而会改变内存管理方式。通过 new[] 操作,实际返回的内存空间可能比期望的要多。因为对象数组占用的内存中需要保存数组信息,数组信息用于确定构造函数和析构函数的调用次数。
下来我们还是通过示例代码来进行分析
#include#include #include using namespace std; class Test { int m_Value; public: Test() { m_Value = 0; } ~Test() { } void* operator new (unsigned int size) { cout << "operator new: " << size << endl; return malloc(size); } void operator delete (void* p) { cout << "operator delete: " << p << endl; free(p); } void* operator new[] (unsigned int size) { cout << "operator new[]: " << size << endl; return malloc(size); } void operator delete[] (void* p) { cout << "operator delete[]: " << p << endl; free(p); } }; int main() { Test* pt = NULL; pt = new Test; delete pt; pt = new Test[5]; delete[] pt; return 0; } 按照我们之前的想法是 new[5] 肯定是 20了,但是我们今天刚讲了它是需要额外的存储空间来存放用于管理数组信息的空间的,因此申请出来的肯定会比 20 大。我们来看看编译结果
我们看到申请数组申请出来的是 24 个字节的空间。通过对一些经典面试题的学习,总结如下:1、new/delete 的本质为操作符;2、可以通过全局函数重载 new/delete(不推荐),也可以针对具体的类进程重载 new/delete;3、new[] / delete[] 与 new/delete 完全不同;4、new[] / delete[] 也是可以被重载的操作符,new[] 返回的内存空间可能比期望的要多。
欢迎大家一起来学习 C++ 语言,可以加我QQ:243343083。
网站标题:自定义内存管理(五十七)
URL网址:http://azwzsj.com/article/ppodhd.html