一个笔试题:编写能统计对象中某个成员变量的访问次数的程序。我们在类中定义一个私有成员变量,在构造函数中初始化为 0,在进行读写操作时都 ++,那么就达到我们的目的了,下面我们看看程序是怎样写的

#include<iostream>#include<string>usingnamespacestd;classTest{intm_Value;intm_count;public:Test(intvalue=0){m_Value=value;m_count=0;}intgetValue(){m_count++;returnm_Value;}intsetValue(intvalue){m_count++;m_Value=value;}intgetCount(){returnm_count;}~Test(){}};intmain(){Testt;t.setValue(100);cout<<"t.m_value="<<t.getValue()<<endl;cout<<"t.m_count="<<t.getCount()<<endl;Testct(200);cout<<"ct.m_value="<<ct.getValue()<<endl;cout<<"ct.m_count="<<ct.getCount()<<endl;return0;}

我们来编译看看结果

我们看到已经正确实现功能了哈,类对象也有可能是 const 的,我们来试试 const 类型的呢

const 对象只能调用 const 成员函数,我们将 getCount 和 getValue 改为 const 成员函数。

我们看到又说 m_count 是在 const 成员函数中不能被改变。那么问题来了,怎样才能改变 const 成员函数中的限制呢?很幸运,在 C++ 中有一个关键字 mutable。mutable 是为了突破 const 函数的限制而设计的,mutable 成员变量将永远处于可改变的状态,它在实际的项目开发中被严禁滥用。我们先来试试,在 m_count 定义前加上 mutable 。

我们看到编译通过,并且成功运行。我们再来看看 mutable 关键字有什么特性,mutable 成员变量破坏了只读对象的内部状态,const 成员函数保证只读对象的状态不变性,mutable 成员变量的出现无法保证状态不变性。我们再次进行改写,程序如下

#include<iostream>#include<string>usingnamespacestd;classTest{intm_Value;int*constm_pCount;public:Test(intvalue=0):m_pCount(newint(0)){m_Value=value;}intgetValue()const{*m_pCount=*m_pCount+1;returnm_Value;}intsetValue(intvalue){*m_pCount=*m_pCount+1;m_Value=value;}intgetCount()const{return*m_pCount;}~Test(){deletem_pCount;}};intmain(){Testt;t.setValue(100);cout<<"t.m_value="<<t.getValue()<<endl;cout<<"t.m_count="<<t.getCount()<<endl;constTestct(200);cout<<"ct.m_value="<<ct.getValue()<<endl;cout<<"ct.m_count="<<ct.getCount()<<endl;return0;}

我们定义一个 int* const 类型的指针,然后在构造函数中进行初始化,再利用它进行 ++操作。我们看看编译结果

已经正确实现了哈。下面又是一个很有意思的面试题:new 关键字创建出来的对象位于什么地方?我们大多数人的第一反应是肯定是堆嘛,new 出来的对象肯定在堆上嘛。其实不一定哦。new/delete 的本质是 C++ 预定义的操作符,C++ 对这两个操作符做了严格的行为定义。new:1.获取足够大的内存空间(默认为堆空间);2、在获取的空间中调用构造函数创建对象。delete:1、调用析构函数销毁对象;2、归还对象所占用的空间(默认为堆空间)。那么在 C++ 中是能够重载 new/delete 操作符的,全局重载(不推荐)和局部重载(针对具体类型进行重载)。重载 new/delete 的意义在于改变动态对象创建时的内存分配方式。

下来我们就来利用 new/delete 的重载在静态存储区中创建动态对象。

#include<iostream>#include<string>usingnamespacestd;classTest{staticconstunsignedintCOUNT=4;staticcharc_buffer[];staticcharc_map[];public:void*operatornew(unsignedintsize){void*ret=NULL;for(inti=0;i<COUNT;i++){if(!c_map[i]){c_map[i]=1;ret=c_buffer+i*sizeof(Test);cout<<"succeedtoallocatememory:"<<ret<<endl;break;}}returnret;}voidoperatordelete(void*p){if(p!=NULL){char*mem=reinterpret_cast<char*>(p);intindex=(mem-c_buffer)/sizeof(Test);intflag=(mem-c_buffer)%sizeof(Test);if((flag==0)&&(0<=index)&&(index<COUNT)){c_map[index]=0;cout<<"succeedtofreememory:"<<c_map[index]<<endl;}}}};charTest::c_buffer[sizeof(Test)*Test::COUNT];charTest::c_map[Test::COUNT]={0};intmain(){cout<<"====TestSingleObject===="<<endl;Test*pt=newTest;deletept;cout<<"====TestObjectArray===="<<endl;Test*pa[5]={0};for(inti=0;i<5;i++){pa[i]=newTest;cout<<"pa["<<i<<"]="<<pa[i]<<endl;}for(inti=0;i<5;i++){cout<<"delet"<<pa[i]<<endl;deletepa[i];}return0;}

我们在全局数据区定义了 4 个数据类型大小的空间,所以只能申请 4 个数据类型大小的空间。在 main 函数中申请了 5 个数据类型,因此编译器只会分配 4 ,最后一个肯定分配不成功。注意:这都是在全局数据区,而不是在堆上。我们来看看编译结果

我们看到的确是只分配了 4 个 int 类型大小的空间,最后一个为 0,没分配成功。我们已经利用重载 new/delete 操作符,将 new 出来的对象位于全局数据区了,所以 new 出来的对象不一定是只在堆空间中,只是默认在堆空间中。

下来我们再来看一个面试题:如何在指定的地址上创建 C++ 对象?解决方案:a> 在类中重载 new/delete 操作符;b> 在 new 的操作符重载函数中返回指定的地址;c> 在 delete 操作符重载中标记对应的地址可用。下来我们来看看程序怎么写

#include<iostream>#include<string>#include<cstdlib>usingnamespacestd;classTest{staticunsignedintc_count;staticchar*c_buffer;staticchar*c_map;public:staticboolSetMemorySource(char*memory,unsignedintsize){boolret=false;c_count=size/sizeof(Test);ret=(c_count&&(c_map=reinterpret_cast<char*>(calloc(c_count,sizeof(char)))));if(ret){c_buffer=memory;}else{free(c_map);c_map=NULL;c_buffer=NULL;c_count=0;}returnret;}void*operatornew(unsignedintsize){void*ret=NULL;if(c_count>0){for(inti=0;i<c_count;i++){if(!c_map[i]){c_map[i]=1;ret=c_buffer+i*sizeof(Test);cout<<"succeedtoallocatememory:"<<ret<<endl;break;}}}else{ret=malloc(size);}returnret;}voidoperatordelete(void*p){if(p!=NULL){if(c_count>0){char*mem=reinterpret_cast<char*>(p);intindex=(mem-c_buffer)/sizeof(Test);intflag=(mem-c_buffer)%sizeof(Test);if((flag==0)&&(0<=index)&&(index<c_count)){c_map[index]=0;cout<<"succeedtofreememory:"<<c_map[index]<<endl;}}else{free(p);}}}};unsignedintTest::c_count=0;char*Test::c_buffer=NULL;char*Test::c_map=NULL;intmain(){charbuffer[12]={0};Test::SetMemorySource(buffer,sizeof(buffer));cout<<"====TestSingleObject===="<<endl;Test*pt=newTest;deletept;cout<<"====TestObjectArray===="<<endl;Test*pa[5]={0};for(inti=0;i<5;i++){pa[i]=newTest;cout<<"pa["<<i<<"]="<<pa[i]<<endl;}for(inti=0;i<5;i++){cout<<"delet"<<pa[i]<<endl;deletepa[i];}return0;}

我们在全局数据区自定义数组 buffer,然后再在 buffer 里进行操作。看看编译结果

我们看到已经正确实现了。还有一个:new[] / delete[] 和 new / delete 一样吗?它们是完全不同的。动态对象数组创建通过 new[] 完成,动态对象数组的销毁通过 delete[] 完成。而 new[] / delete[] 能够被重载,进而会改变内存管理方式。通过 new[] 操作,实际返回的内存空间可能比期望的要多。因为对象数组占用的内存中需要保存数组信息,数组信息用于确定构造函数和析构函数的调用次数。

下来我们还是通过示例代码来进行分析

#include<iostream>#include<string>#include<cstdlib>usingnamespacestd;classTest{intm_Value;public:Test(){m_Value=0;}~Test(){}void*operatornew(unsignedintsize){cout<<"operatornew:"<<size<<endl;returnmalloc(size);}voidoperatordelete(void*p){cout<<"operatordelete:"<<p<<endl;free(p);}void*operatornew[](unsignedintsize){cout<<"operatornew[]:"<<size<<endl;returnmalloc(size);}voidoperatordelete[](void*p){cout<<"operatordelete[]:"<<p<<endl;free(p);}};intmain(){Test*pt=NULL;pt=newTest;deletept;pt=newTest[5];delete[]pt;return0;}

按照我们之前的想法是 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。