自定义内存管理(五十七)-创新互联
一个笔试题:编写能统计对象中某个成员变量的访问次数的程序。我们在类中定义一个私有成员变量,在构造函数中初始化为 0,在进行读写操作时都 ++,那么就达到我们的目的了,下面我们看看程序是怎样写的
创新互联长期为近千家客户提供的网站建设服务,团队从业经验10年,关注不同地域、不同群体,并针对不同对象提供差异化的产品和服务;打造开放共赢平台,与合作伙伴共同营造健康的互联网生态环境。为隆化企业提供专业的做网站、成都做网站,隆化网站改版等技术服务。拥有10年丰富建站经验和众多成功案例,为您定制开发。#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。
另外有需要云服务器可以了解下创新互联scvps.cn,海内外云服务器15元起步,三天无理由+7*72小时售后在线,公司持有idc许可证,提供“云服务器、裸金属服务器、高防服务器、香港服务器、美国服务器、虚拟主机、免备案服务器”等云主机租用服务以及企业上云的综合解决方案,具有“安全稳定、简单易用、服务可用性高、性价比高”等特点与优势,专为企业上云打造定制,能够满足用户丰富、多元化的应用场景需求。
名称栏目:自定义内存管理(五十七)-创新互联
链接地址:http://pwwzsj.com/article/cecece.html