笔试题 一

统计对象中某个成员变量的访问次数

遗失的关键字

  • mutable 是为了突破 const 函数的限制而设计的
  • mutable 成员变量将永远处于可改变的状态
  • mutable 在实际的项目开发中被严谨滥用

  • mutable 的深入分析

    • mutable 成员变量破坏了只读对象的内部状态
    • const 成员函数保证只读对象的状态不变性
    • mutable 成员变量的出现无法保证状态不变性

编程实验: 成员变量的访问统计

mutable 的实现:

#include <iostream>
#include <string>

using namespace std;

class Test
{
private:
    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_count ++;
        
        m_value = value;
    }
    
    int getCount() const
    {
        return m_count;
    }
};

int main()
{
    Test t;
    
    t.setValue(10);
    
    cout << "t.m_value = " << t.getValue() << endl;
    cout << "t.m_vount = " << t.getCount() << endl;
    
    const Test ct(200);
    
    cout << "ct.m_value = " << ct.getValue() << endl;
    cout << "ct.m_vount = " << ct.getCount() << endl;

    return 0;
}
输出:
t.m_value = 10
t.m_vount = 2
ct.m_value = 200
ct.m_vount = 1

统计方式的改进:

#include <iostream>
#include <string>

using namespace std;

class Test
{
private:
    int m_value;
    int * const m_pCount;          // 注意这里!
public:
    Test(int value = 0) : m_pCount(new int(0))
    {
        m_value = 0;
    }
    
    int getValue() const 
    {
        *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;
    }
};

int main()
{
    Test t;
    
    t.setValue(10);
    
    cout << "t.m_value = " << t.getValue() << endl;
    cout << "t.m_vount = " << t.getCount() << endl;
    
    const Test ct(200);
    
    cout << "ct.m_value = " << ct.getValue() << endl;
    cout << "ct.m_vount = " << ct.getCount() << endl;

    return 0;
}
输出:
t.m_value = 10
t.m_vount = 2
ct.m_value = 0
ct.m_vount = 1

分析:
int * const m_pCount;         ==> 定义指针常量
*m_pCount = *m_pCount + 1;    ==> 修改指针常量指向的内存空间处的值
                              ==> 对象内部状态没有发生改变

面试题 二

new 关键字创建出来的对象位于什么地方呢?
  • 堆(默认)
  • 全局数据区

被忽略的事实

  • new / delete 的本质是 C++ 预定义的操作符
  • C++ 对这两个操作符做了严格的行为定义

    • new :

      1. 获取足够大的内存空间(默认为堆空间)
      2. 在获取的空间中调用构造函数创建对象
    • delete :

      1. 调用析构函数销毁对象
      2. 归还对象所占用的空间(默认为堆空间)

  • 在 C++ 中能够重载 new / delete 操作符

    • 全局重载(不推荐)
    • 局部重载(针对具体类进行重载)

重载 new / delete 的意义在于改变动态对象创建时的内存分布方式

  • new / delete 的重载方式

默认为静态成员函数

// static member function
void* operator new(unsigned int size)
{
    void* ret = NULL;
    
    /** ret point to allocated memory  */
    
    return ret;
}

// static member function
void operator delete(void* p)
{
    /** free the memory which is pointed by p */
}

编程实验: 静态存储区中创建对象

#include <iostream>
#include <string>

using namespace std;

class Test
{
private:
    static const unsigned int COUNT = 4;
    static unsigned char c_buffer[];
    static unsigned char c_map[];
    
    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)
    {
        if( p != NULL )
        {
            unsigned char* mem = reinterpret_cast<unsigned char*>(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: " << p << endl;
            }
        }
    }
};

unsigned char Test::c_buffer[Test::COUNT] = {0};
unsigned 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 << "] = " << pa[i] << endl;
    }
    
    for(int i=0; i<5; i++)
    {
        cout << "delete " << pa[i] << endl;
        
        delete pa[i];
    }

    return 0;
}
输出:
===== Test Single Object ====
Succeed to allocate memory: 0x804a0d4
succeed to free memory: 0x804a0d4
==== Test Object Array ====
Succeed to allocate memory: 0x804a0d4
pa[0] = 0x804a0d4
Succeed to allocate memory: 0x804a0d8
pa[1] = 0x804a0d8
Succeed to allocate memory: 0x804a0dc
pa[2] = 0x804a0dc
Succeed to allocate memory: 0x804a0e0
pa[3] = 0x804a0e0
pa[4] = 0
delete 0x804a0d4
succeed to free memory: 0x804a0d4
delete 0x804a0d8
succeed to free memory: 0x804a0d8
delete 0x804a0dc
succeed to free memory: 0x804a0dc
delete 0x804a0e0
succeed to free memory: 0x804a0e0
delete 0
如何在指定的地址上创建C++对象?

  • 解决方案

    • 在类中重载 new / delete 操作符
    • 在 new 的操作符重载函数中返回指定的地址
    • 在 delete 操作符重载中标记对应的地址可用

编程实验: 自定义动态对象的存储空间

#include <iostream>
#include <string>
#include <cstdlib>

using namespace std;

class Test
{
private:
    static unsigned int c_count;
    static unsigned char* c_buffer;
    static unsigned char* c_map;
    
    int m_value;
public:
    static bool SetMemorySource(unsigned char* memory, unsigned int size)
    {
        bool ret = false;
        
        c_count = size / sizeof(Test);
        
        ret = (c_count && (c_map = reinterpret_cast<unsigned char*>(calloc(c_count, sizeof(unsigned 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 )
            {
                unsigned char* mem = reinterpret_cast<unsigned 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 = NULL;
unsigned char* Test::c_buffer = NULL;
unsigned char* Test::c_map = NULL;

int main()
{
    unsigned 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;
}
输出:
===== Test Single Object ====
Succeed to allocate memory: 0xbfbbacb0
succeed to free memory: 0xbfbbacb0
==== Test Object Array ====
Succeed to allocate memory: 0xbfbbacb0
pa[0] = 0xbfbbacb0
Succeed to allocate memory: 0xbfbbacb4
pa[1] = 0xbfbbacb4
Succeed to allocate memory: 0xbfbbacb8
pa[2] = 0xbfbbacb8
pa[3] = 0
pa[4] = 0
delete 0xbfbbacb0
succeed to free memory: 0xbfbbacb0
delete 0xbfbbacb4
succeed to free memory: 0xbfbbacb4
delete 0xbfbbacb8
succeed to free memory: 0xbfbbacb8
delete 0
delete 0

被忽略的事实

  • new[] / delete[] 与 new / delete 完全不同

    • 动态对象数组创建通过 new[] 完成
    • 动态对象数组的销毁通过 delete[] 完成
    • new[] / delete[] 能够被重载,进而改变内存管理方式

new[] / delete[] 的重载方式

默认为静态成员函数

// static member function
void* operator new[] (unsigned int size)
{
    return malloc(size);
}

// static member function
void operator delete[] (void* p)
{
    free(p);
}
  • 注意事项

    • new[] 实际需要返回的内存空间可能比期望的多
    • 对象数组占用的内存中需要保存数组信息(数组长度)
    • 数组信息用于确定构造函数和析构函数的调用次数

编程实验: 动态数组的内存管理

#include <iostream>
#include <string>
#include <cstdlib>

using namespace std;

class Test
{
private:
    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;
}
输出:
operator new: 4
operator delete: 0x9e3c008
operator new[]: 24               ;注意这里!
operator delete[]: 0x9e3c018

小结

  • new / delete 的本质为操作符
  • 可以通过全局函数重载 new / delete (不推荐)
  • 可以针对具体的类重载 new / delete
  • new[] / delete[] 与 new / delete 完全不同
  • new[] / delete[] 也是可以被重载的操作符
  • new[] 返回的内存空间可能比期望的要多

以上内容参考狄泰软件学院系列课程,请大家保护原创!


TianSong
734 声望138 粉丝

阿里山神木的种子在3000年前已经埋下,今天不过是看到当年注定的结果,为了未来的自己,今天就埋下一颗好种子吧