1、关键字 mutable
这个代码会出错,因为p没有初始化
#include <iostream>
using namespace std;
int main()
{
int* p;
(*p) = 1;
cout << *p << endl;
return 0;
}
- 笔试题
统计对象中某个成员变量的访问次数
#include <iostream>
#include <string>
using namespace std;
class Test
{
int m_value;
int m_count;
public:
Test(int value = 0)
{
m_value = value;
m_count = 0;
}
int getValue()const
{
m_count++;
return m_value;
}
void setValue(int value)
{
m_value = value;
m_count++;
}
int getCount()const
{
return m_count;
}
};
int main()
{
Test t(1);
t.setValue(100);
cout << "t.m_value = " << t.getValue() << endl;
cout << t.getCount() << endl;
const Test t2(20);
cout << "t2.m_value = " << t2.getValue() << endl;
cout << t2.getCount() << endl;
return 0;
}
这个程序的问题在于可以解决普通对象中某个成员变量的访问次数问题,但是无法解决只读对象中成员变量的访问次数问题。在代码的第 19 行,我们在只读成员函数里面是不能修改成员变量的值的,于是,这样的写法肯定是不对的。
- 遗失的关键字
— mutable 是为了突破 const 函数的限制而设计的
— mutable 成员变量将永远处于可改变的状态
— mutable 在实际的项目开发中被严禁滥用 - mutable 的深入分析
— mutable 成员变量破坏了只读对象的内部状态
— const 成员函数保证只读对象的状态不变性
— mutable 成员变量的出现无法保证状态不变性
#include <iostream>
#include <string>
using namespace std;
class Test
{
int m_value;
mutable int m_count;
public:
Test(int value = 0)
{
m_value = value;
m_count = 0;
}
int getValue()const
{
m_count++;
return m_value;
}
void setValue(int value)
{
m_value = value;
m_count++;
}
int getCount()const
{
return m_count;
}
};
int main()
{
Test t(1);
t.setValue(100);
cout << "t.m_value = " << t.getValue() << endl;
cout << t.getCount() << endl;
const Test t2(20);
cout << "t2.m_value = " << t2.getValue() << endl;
cout << t2.getCount() << endl;
return 0;
}
使用 mutable 可以解决我们的疑惑,但是建议不要这样用。
建议的用法:满足普通对象和只读对象的用法(这个代码存在问题,有一个函数没写成const)
#include <iostream>
#include <string>
using namespace std;
class Test
{
int m_value;
int* const m_Pcount;
//mutable int m_count;
public:
Test(int value = 0):m_Pcount(new int(0))
{
m_value = value;
//m_count = 0;
}
int getValue()const
{
(*m_Pcount)++;
//m_count++;
return m_value;
}
void setValue(int value)
{
m_value = value;
//m_count++;
(*m_Pcount)++;
}
int getCount()const
{
//return m_count;
return *m_Pcount;
}
};
int main()
{
Test t(1);
t.setValue(100);
cout << "t.m_value = " << t.getValue() << endl;
cout << t.getCount() << endl;
const Test t2(20);
cout << "t2.m_value = " << t2.getValue() << endl;
cout << t2.getCount() << endl;
return 0;
}
或者这种写法:
#include <iostream>
using namespace std;
class Test
{
private:
int m_value;
int* p_cnt;
public:
Test()
{
m_value = 0;
p_cnt = new int(0);
}
void setValue(int value)
{
m_value = value;
(*p_cnt)++;
}
int getValue()const
{
(*p_cnt)++;
return m_value;
}
int getCnt()const
{
return *p_cnt;
}
};
int main()
{
Test t;
t.setValue(10);
cout << t.getValue() << endl;
cout << t.getCnt() << endl;
cout << endl;
const Test tt;
cout << tt.getValue() << endl;
cout << tt.getCnt() << endl;
return 0;
}
#include <iostream>
using namespace std;
#include <iostream>
#include <string>
using namespace std;
class Test
{
int m_value;
mutable int m_count;
public:
Test(int value = 0)
{
m_value = value;
m_count = 0;
}
int getValue()const
{
m_count++;
return m_value;
}
void setValue(int value)const
{
int* p = const_cast<int*>(&m_value);
*p = value;
m_count++;
}
int getCount()const
{
return m_count;
}
};
int main()
{
Test t(1);
t.setValue(100);
cout << "t.m_value = " << t.getValue() << endl;
cout << t.getCount() << endl;
const Test t2(20);
cout << "t2.m_value = " << t2.getValue() << endl;
cout << t2.getCount() << endl;
return 0;
}
2、面试题2
new 关键字创建出来的对象位于什么地方?一定只是在堆空间上吗?
- 被忽略的事实
—new/delete
的本质是C++预定义操作符(那么就可以进行操作符重载)
— C++ 对这两个操作符做了严格的行为定义 - new
1、获取足够大的内存空间(默认为堆空间)
2、在获取的空间中调用构造函数创建对象 - delete
1、调用析构函数销毁对象
2、归还对象所占用的空间(默认为堆空间) - 在C++中能够重载
new/delete
操作符
— 全局重载(不推荐)
— 局部重载(针对具体类进行重载)
重载new/delete
的意义在于改变动态对象创建时的内存分配方式 new/delete
的重载方式
这两个重载的函数默认都是静态成员函数,无论你加不加static,它都是静态的。
全局数据区和栈的数据分布不一样。栈是向下生长,下一个地址值比上一个地址值小;全局数据区向上,下一个地址值比上一个地址值大。
#include <iostream>
#include <string>
using namespace std;
class Test
{
static const unsigned int COUNT = 4;
static char c_buf[];
static char c_map[];
public:
void* operator new(unsigned int size)
{
void* ret = NULL;
for (int i = 0; i < COUNT; i++)
{
if (!c_map[i])
{
c_map[i] = 1;
ret = c_buf + i*sizeof(Test);
cout << "succeed to allocate memory: " << ret << endl;
break;
}
}
return ret;
}
void operator delete(void* p)
{
char* mem = reinterpret_cast<char*>(p);
int index = (mem - c_buf) / sizeof(Test);
int flag = (mem - c_buf) % sizeof(Test);
if ((flag == 0) && (index >= 0) && (index < COUNT))
{
c_map[index] = 0;
cout << "succeed to free memory: " << p << endl;
//遇到的问题:直接打印mem为什么打印不出来,因为char* 在cout 下会从该地址打印字符串,如果是输出指针的地址统一转换成 void*是最好的。
}
}
};
char Test::c_buf[sizeof(Test) * Test::COUNT] = {0};
char Test::c_map[COUNT] = {0};
int main()
{
cout << "===== Test Object Array =====" << 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 << "] = " << pa[i] << endl;
}
for (int i = 0; i < 5; i++)
{
cout << "delete " << pa[i] << endl;
delete pa[i];
}
return 0;
}
这个程序告诉我们new
出来的对象不一定是在堆空间上,在上面这个程序,new
出来的对象就是在c_buf
这个静态数组里面。
其实这个程序我一开始理解起来是非常困难的,还好我大哥一直在指点我。我不理解的地方主要是不知道为什么要定义c_map
,c_map
的本质就是作为标记使用。c_buf
的本质就是存储内存空间。重载的new
这个重载函数的 void
指针指向c_buf
的首地址。c_map
如果存满了,那么c_buf
肯定就存满了,那么这个标志可以让c_buf
不存储了。
重载的delete
这个重载函数,因为c_buf
里面共有 COUNT * sizeof(Test)
个元素,所以在计算个数index 的时候要除以 sizeof(Test)
,判断他在 new 第几个对象。delete
完成的标志就是把 c_map
这个标志设置为 0。
这个方法 + 二阶构造 = n例模式,使得我们最多只能存在n个对象。
3、面试题3
如何在指定的地址上创建 C++ 对象?
- 解决方案
— 在类中重载new/delete
操作符
— 在new
的操作符重载函数中返回指定的地址
— 在delete
操作符重载中标记对于的地址可用
程序:在栈上动态创建对象
#include <iostream>
#include <string>
#include <cstdlib>
using namespace std;
class Test
{
static unsigned int c_count;
static char* c_buf;
static char* c_map;
int m_value;
public:
static bool SetMemorySource(char* memory, unsigned int size)
{
bool ret = false;
c_count = size / sizeof(Test);
ret = (c_count && (c_map = reinterpret_cast<char*>(calloc(c_count, sizeof(char)))));
if (ret)
{
c_buf = memory;
}
else
{
free(c_map);
c_map = NULL;
c_buf = NULL;
c_count = 0;
}
return ret;
}
void* operator new(unsigned int size)
{
void* ret = NULL;
if (c_count > 0)
{
for (int i = 0; i < c_count; i++)
{
if (!c_map[i])
{
c_map[i] = 1;
ret = c_buf + i*sizeof(Test);
cout << "succeed to allocate memory: " << ret << endl;
break;
}
}
}
else
{
ret = malloc(size);
}
return ret;
}
void operator delete(void* p)
{
if (c_count > 0)
{
if (p != NULL)
{
char* mem = reinterpret_cast<char*>(p);
int index = (mem - c_buf) / sizeof(Test);
int flag = (mem - c_buf) % sizeof(Test);
if ((flag == 0) && (index >= 0) && (index < c_count))
{
c_map[index] = 0;
cout << "succeed to free memory: " << p << endl;
}
}
}
else
{
free(p);
}
}
};
unsigned int Test::c_count = 0;
char* Test::c_buf = NULL;
char* Test::c_map = NULL;
int main()
{
char buffer[12] = { 0 };
Test::SetMemorySource(buffer, sizeof(buffer));
cout << "===== Test Object Array =====" << 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 << "] = " << pa[i] << endl;
}
for (int i = 0; i < 5; i++)
{
cout << "delete " << pa[i] << endl;
delete pa[i];
}
return 0;
}
解析:这个程序也很经典,可以使动态空间在栈上创建。并且限定个数。
主要有两点:
1、 SetMemorySource(char* memory, unsigned int size)
这个函数使得 c_buf 和 c_map 重新有了作用。c_map
的本质就是作为标记使用,c_buf
的本质就是存储内存空间。c_map
这个指针是指向我们 realloc 的一段空间,c_buf
这个指针是指向我们指定的空间,c_buf
指向的空间大于等于c_map
指向的空间,之间的大小差距小于或等于一个类类型的大小。本质等同于上一段程序。
如果我们没有指定内存,就按默认的在堆上创建空间。
2、new 和 delete 的重载。
4、被忽略的事实4
new[]/delete[]
与new/delete
完全不同
— 动态数组创建通过new[]
完成
— 动态数组的销毁通过delete[]
完成
—new/delete
能够被重载,进而改变内存管理方式new/delete
的重载方式
- 注意事项
—new[]
实际需要返回的内存空间肯比期望的要多
— 对象数组占用的内存中需要保存数组信息
— 数组信息用于确定构造函数和析构函数的调用次数
#include <iostream>
#include <string>
#include <cstdlib>
using namespace std;
class Test
{
int m_value;
public:
Test(int value = 0)
{
m_value = value;
}
~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 = new Test(1);
delete pt;
Test* pa = new Test[5];
delete[] pa;
return 0;
}
4*5 = 20
,按理说是20个字节,系统多给4个字节保存长度信息。所以new[]
的空间只能delete[]
,如果delete
并不能释放完全。
小结:
new/delete
的本质为操作符- 可以通过全局函数重载
new/delete
- 可以针对具体的类重载
new/delete
new[]/delete[]
与new/delete
完全不同new[]/delete[]
也是可以被重载的操作符new[]
返回的内存空间可能比期望的要多