注:博客中内容主要来自《狄泰软件学院》,博客仅当私人笔记使用。
测试环境: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 objectm_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://从堆空间申请一块内存,内存数据为0Test(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[]返回的内存空间可能比期望的要多