注:博客中内容主要来自《狄泰软件学院》,博客仅当私人笔记使用。
测试环境:Ubuntu 10.10
GCC版本:9.2.0
笔试题一:
统计对象中某个成员变量的访问次数
分析:
1) 考察面向对象相关的知识
2) 考察内容成员变量的访问次数
我的思路:
定义两个成员变量,一个被访问,一个记录次数
缺陷没有考虑种类(只读成员对象)
如果是只读对象,成员变量不能被访问!
遗失的关键字
1) mutable(可变)是为了突破const函数的限制而设计的
2) mutable成员变量将永远处于可改变的状态
3) mutable在实际的项目开发中被严禁滥用
4) mutable的深入分析(遗失的原因)
- mutable成员变量破坏了只读对象的内部状态
- const成员函数保证只读对象的状态不变性
- mutable成员变量的出现的无法保证状态不变性
编程实验
成员变量的访问统计
69-1.cpp
#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()
{
m_count++;
return m_value;
}
void setValue(int value)
{
m_count++;
m_value = value;
}
int getCount()
{
return m_count;
}
};
int main(int argc, char *argv[])
{
Test t;
t.setValue(100); //访问1次
cout << "t.m_value = " << t.getValue() << endl; //访问2次
cout << "t.m_count = " << t.getCount() << endl;
return 0;
}
操作:
1) g++ 69-1.cpp -o 69-1.out编译正确,打印结果:
t.m_value = 100
t.m_count = 2
分析:
只满足了普通对象情形,但是不满足只读对象。
2) 测试只读对象,代码如下:
#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()
{
m_count++;
return m_value;
}
void setValue(int value)
{
m_count++;
m_value = value;
}
int getCount()
{
return m_count;
}
};
int main(int argc, char *argv[])
{
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;
}
g++ 69-1.cpp -o 69-1.out编译错误:
69-1.cpp: In function ‘int main(int, char**)’:
69-1.cpp:53:41: error: passing ‘const Test’ as ‘this’ argument of ‘int Test::getValue()’ discards qualifiers [-fpermissive]
cout << "ct.m_value = " << ct.getValue() << endl;
^
69-1.cpp: 在函数'int main(int, char**)':
69-1.cpp:53:41: 错误:通过'const Test',当前对象('this')'int Test::getValue()'需要丢弃限定词(const)
意味着:const修饰的对象只能调用const函数。
69-1.cpp:54:41: error: passing ‘const Test’ as ‘this’ argument of ‘int Test::getCount()’ discards qualifiers [-fpermissive]
cout << "ct.m_count = " << ct.getCount() << endl;
^
69-1.cpp:54:41: 错误:通过'const Test',当前对象('this')'int Test::getValue()'需要丢弃限定词(const)
意味着:const修饰的对象只能调用const函数。
分析:
const对象只能调用const成员函数。解决办法:用const修饰调用的函数。
代码如下:
#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_count++;
m_value = value;
}
int getCount() const
{
return m_count;
}
};
int main(int argc, char *argv[])
{
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;
}
g++ 69-1.cpp -o 69-1.out编译报错:
69-1.cpp: In member function ‘int Test::getValue() const’:
69-1.cpp:19:10: error: increment of member ‘Test::m_count’ in read-only object
m_count++;
^
69-1.cpp: 在成员函数'int Test::getValue() const':
69-1.cpp:19:10: 错误:在只读对象中增加成员变量'Test::m_count'数值
分析:
在只读对象中不能修改成员变量中的值。
3) 怎么解决在只读对象中修改成员变量数值:用mutable关键字
#include <iostream>
#include <string>
using namespace std;
class Test
{
int m_value;
mutable int m_count; //用mutable修饰成员变量
public:
Test(int value = 0)
{
m_value = value;
m_count = 0;
}
int getValue() const
{
m_count++;
return m_value;
}
void setValue(int value)
{
m_count++;
m_value = value;
}
int getCount() const
{
return m_count;
}
};
int main(int argc, char *argv[])
{
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;
}
g++ 69-1.cpp -o 69-1.out编译正确,打印结果:
t.m_value = 100
t.m_value = 2
ct.m_value = 200
ct.m_count = 1
分析:
在很多公司并不希望用mutable。
更好的解决办法:
#include <iostream>
#include <string>
using namespace std;
class Test
{
int m_value;
int* const m_pCount;//指针常量:初始化后指针存储的地址不能改变,但是数据可变
public:
//从堆空间申请一块内存,内存数据为0
Test(int value = 0) : m_pCount(new int(0))
{
m_value = value;
}
int getValue() const
{
//操作的是m_pCount指向内存中的数据,不是m_pCount本身
*m_pCount = *m_pCount + 1;
return m_value;
}
void setValue(int value)
{
*m_pCount = *m_pCount + 1;
m_value = value;
}
int getCount() const
{
return *m_pCount;
}
~Test()
{
delete m_pCount;
}
};
int main(int argc, char *argv[])
{
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;
}
g++ 69-1.cpp -o 69-1.out编译正确,打印结果:
t.m_value = 100
t.m_value = 2
ct.m_value = 200
ct.m_count = 1
分析:
定义指针常量(地址不可变,但是地址中的数据可变)。
面试题二:
new关键字创建出来的对象位于什么地方?默认在堆空间,也可以将动态对象创建在静态存储区
被忽略的事实
1) new/delete的本质是C++预定义的操作符
2) C++对这两个操作符做了严格的行为定义
- new:
1. 获取足够大的内存空间(默认为堆空间)
2. 在获取的空间中调用构造函数创建对象
- delete:
1. 调用析构函数销毁对象
2. 归还对象所占用的空间(默认为堆空间)
3) 在C++中能够重载new/delete操作符
- 全局重载(不推荐)
- 局部重载(针对具体类进行重载)
重载new/delete的意义在于改变动态对象创建时的内存分配方式
4) new/delete的重载方式
//static member function
void* operator new(unsigned int size) //size代表内存大小
{
void* ret = NULL;
/* ret point to allocated memory */
return ret;
}
//static member function
void operaotr delete(void* p)
{
/* free the memory which is pointed by p */
}
编程实验
静态存储区中创建动态对象
69-2.cpp
#include <iostream>
#include <string>
using namespace std;
class Test
{
static const unsigned int COUNT = 4; //放入符号表
static char c_buffer[]; //静态字节数组(用来分配静态内存)
static char c_map[]; //标记每个对象:0-没有被使用 1-被使用
int m_value;
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_buffer + i * sizeof(Test); //数组首地址+偏移量
cout << "succeed to allocate memory: " << ret << endl;
break;
}
}
return ret;
}
void operator delete (void* p) //void*为了兼容更多类型指针
{
if( p != NULL ) //如果是空指针什么都不处理
{
char* mem = reinterpret_cast<char*>(p);
//p是c_buffer中的元素地址,减去数组c_buffer首地址/Test内存大小,得到数组下标
int index = (mem - c_buffer) / sizeof(Test);
//位置为0,证明内存分配大小合适
int flag = (mem - c_buffer) % sizeof(Test);
//flag=0和index满足数组下标,证明找到要释放的内存下标
if( (flag == 0) && (0 <= index) && (index < COUNT) )
{ //释放空间
c_map[index] = 0;
//提示释放成功
cout << "succeed to free memory: " << p << endl;
}
}
}
};
char Test::c_buffer[sizeof(Test) * Test::COUNT] = {0};//定义数组,长度为4个Test内存长度
char Test::c_map[Test::COUNT] = {0}; //标记数组
int main(int argc, char *argv[])
{
cout << "===== Test Single Object =====" << endl;
Test* pt = new Test; //调用重载new函数,在静态存储区释放对象
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) g++ 69-2.cpp -o 69-2.out编译正确,打印结果:
=====Test Single Object=====
succeed to allocate memory: 0x804a0cd
succedd to free memory: 0x804a0cd
=====Test Object Array=====
succeed to allocate memory: 0x804a0cd
pa[0] = 0x804a0cd
succeed to allocate memory: 0x804a0d1
pa[1] = 0x804a0d1
succeed to allocate memory: 0x804a0d5
pa[2] = 0x804a0d5
succeed to allocate memory: 0x804a0d9
pa[3] = 0x804a0d9
pa[4] = 0
delete 0x804a0cd
succeed to free memory: 0x804a0cd
delete 0x804a0d1
succeed to free memory: 0x804a0d1
delete 0x804a0d5
succeed to free memory: 0x804a0d5
delete 0x804a0d9
succeed to free memory: 0x804a0d9
delete 0
分析:
自定义的c_buffer最多存储4个字符,导致pa[4] = 0内存分配失败。
void operator delete (void* p) //void*为了兼容更多类型指针
{
if( p != NULL ) //如果是空指针什么都不处理
{
char* mem = reinterpret_cast<char*>(p);
//p是c_buffer中的元素地址,减去数组c_buffer首地址/Test内存大小,得到数组下标
int index = (mem - c_buffer) / sizeof(Test);
//位置为0,证明内存分配大小合适
int flag = (mem - c_buffer) % sizeof(Test);
//flag=0和index满足数组下标,证明找到要释放的内存下标
if( (flag == 0) && (0 <= index) && (index < COUNT) )
{ //释放空间
c_map[index] = 0;
//提示释放成功
cout << "succeed to free memory: " << p << endl;
}
}
}
index = (mem - c_buffer) / sizeof(Test) 确定c_map[]的索引值
flag = (mem - c_buffer) % sizeof(Test) 确定内存分配为完整对象大小,检查是否有内存碎片
面试题三:
如何在指定的地址上创建C++对象?
参照面试题二
设计思路
1) 解决方案
- 在类中重载new/delete操作符
- 在new的操作符重载函数中返回指定的地址
- 在delete操作符重载中标记对应的地址可用
编程实验
自定义动态对象的存储空间
69-3.cpp
#include <iostream>
#include <string>
#include <cstdlib>
using namespace std;
class Test
{
static unsigned int c_count; //对比面试题二
static char* c_buffer; //对比面试题二
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_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<c_count; i++)
{
if( !c_map[i] )
{
c_map[i] = 1;
ret = c_buffer + i * sizeof(Test);
cout << "succeed to allocate memory: " << ret << endl;
break;
}
}
}
else
{
ret = malloc(size); //对比面试题二
}
return ret;
}
void operator delete (void* p)
{
if( p != NULL )
{
if( c_count > 0 )
{
char* mem = reinterpret_cast<char*>(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: " << p << endl;
}
}
else
{
free(p); //对比面试题二
}
}
}
};
unsigned int Test::c_count = 0;
char* Test::c_buffer = NULL; //对比面试题二
char* Test::c_map = NULL; //对比面试题二
int main(int argc, char *argv[])
{
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 << "] = " << pa[i] << endl;
}
for(int i=0; i<5; i++)
{
cout << "delete " << pa[i] << endl;
delete pa[i];
}
return 0;
}
操作:
1) g++ 69-3.cpp -o 69-3.out编译正确,打印结果:
===== Test Single Object =====
succeed to allocate memory: 0xbfc56a80
pa[0] = 0xbfc56a80
succeed to allocate memory: 0xbfc56a84
pa[1] = 0xbfc56a84
succeed to allocate memory: 0xbfc56a88
pa[2] = 0xbfc56a88
pa[3] = 0
pa[4] = 0
delete 0xbfc56a80
succeed to free memory: 0xbfc56a80
delete 0xbfc56a84
succeed to free memory: 0xbfc56a84
delete 0xbfc56a88
succeed to free memory: 0xbfc56a88
delete 0
delete 0
分析:
以上内容能让我们更好了解new是如何分配内存的。
被忽略的事实
1) new[]/delete[]与new/delete完全不同
- 动态对象数组创建通过new[]完成
- 动态对象数组的销毁通过delete[]完成
- new[]/delete[]能够被重载,进而改变内存管理方式
2) new[]/delete[]的重载方式
//static member function
void* operator new[](unsigned int size)
{
return malloc(size);
}
//static member function
void operaotr delete[](void* p)
{
free(p);
}
3) 注意事项
- new[]实际需要返回的内存空间可能比期望的要多
- 对象数组占用的内存中需要保存数组信息(多在这)
- 数组信息用于确定构造函数和析构函数的调用次数(保存信息的目的)
编程实验
动态数组的内存管理
69-4.cpp
#include <iostream>
#include <string>
#include <cstdlib>
using namespace std;
class Test
{
int m_value;
public:
Test()
{
m_value = 0;
cout << "Test()" << endl;
}
~Test()
{
cout << "~Test()" << endl;
}
void* operator new (unsigned int size)
{
cout << "operator new: " << size << endl;
return malloc(size); //malloc返回值为void*
}
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(int argc, char *argv[])
{
Test* pt = NULL;
pt = new Test;
delete pt;
pt = new Test[5];
delete[] pt;
return 0;
}
操作:
1) g++ 69-4.cpp -o 69-4.out编译正确,打印结果:
operator new: 4
Test()
~Test()
operator delete: 0x8e5d008
operator new[]: 24
Test()
Test()
Test()
Test()
Test()
~Test()
~Test()
~Test()
~Test()
~Test()
operator delete[]: 0x8e5d018
分析:
编译器根据size大小,调用多少次构造函数。这就是为什么new和delete成对出现,new[]和delete[]成对出现。对于数组内存分配其实是给每个元素一一分配空间。
小结
1) new/delete的本质为操作符
2) 可以通过全局函数重载new/delete(不推荐)
3) 可以针对具体的类重载new/delete
4) new[]/delete[]与new/delete完全不同
5) new[]/delete[]也是可以被重载的操作符
6) new[]返回的内存空间可能比期望的要多