c++ map部分用法关键点的记录

1.原理

1.1.简述

c++中map数据结构为红黑树(平衡二叉树的一种特化),搜索的复杂度为O(logN),其他的操作不在此叙述,读者可以自行在MSDN上查找详细字段和函数的含义及用法示例。

1.2.数据结构

每个节点数据构成如下,

x64环境下,一个空的map大小为24个字节,与上图对应,

一个节点占用内存为24个字节。

1.3.初始化大小

如下图,对于一个初始化的map,其head节点,以及head节点中的left、right、parent节点值是一样的,并且是是递归重复的,由此推断,初始化仅有一个节点(下文的输出信息会佐证),节点中left、right、parent都指向了自己。

1.4.内存管理

map在erase元素或者clear之后,内存大小不会立即释放,因为也因此可以动态的向map中增删元素,重复使用map对象而同时又有较低的内存管理性能消耗,关于这个可以参考如下文章。

关于c++中map的内存占用问题

如果想立即释放map占用空间,可以用swap的方式,交换目标map与临时局部map的内存,这样离开生存范围时,临时map会被释放掉,如下,

map<int, int>().swap(mapTest);

1.5.定制比较器

map在构建的过程中(增删元素),需要对key值做排序,采用的是比较大小的方式,如果需要定制key比较器,有以下两种方法,

  • 重载key类的<操作符
bool operator < (keyTest const& _A) const 
  • 仿函数

例如以字符串char*作为key时,

struct ptrStrLess
{
    bool operator()(char const* a, char const* b) const
    {
        return strcmp(a, b) < 0;
    }
};

1.6.添加元素时的行为

执行如下代码,

class TestData
{
public:
    TestData();
    TestData(int testCode);
    TestData(const TestData& other);
    ~TestData();

    TestData& operator=(const TestData& other);

public:
    int mTestCode;

};

TestData::TestData()
    :mTestCode(-1)
{
    cout << "default structor called." << endl;
}

TestData::TestData(int testCode)
    :mTestCode(testCode)
{
    cout << "parametered structor called." << endl;
}

TestData::TestData(const TestData& other)
    :mTestCode(other.mTestCode)
{
    cout << "copy structor called." << endl;
}

TestData::~TestData()
{
    cout << "destructor called." << endl;
}

TestData& TestData::operator=(const TestData& other)
{
    mTestCode = other.mTestCode;

    cout << "copy assign called." << endl;

    return *this;
}
  • 方式一,map[key] = value;方式(因为相对于insert方式,这种方式更高效些,原因如下),当不存在目标key的元素时添加元素,当存在时更新元素,
void TestFunction()
{
    map<int, TestData> mapTest;

    TestData item0(-1);
    mapTest[0] = item0;
    item0.mTestCode = 1;

    int testCodeGet = mapTest[0].mTestCode;
}

输出内容为,

  • 方式二,insert方式,直接插入元素,当然如果事先已存在key为目标key的元素,insert操作不会产生任何效果(会执行一些行为,比如拷贝构造函数,但是这些行为没有影响map对象的“现状”),
void TestFunction()
{
    map<int, TestData> mapTest;

    TestData item0(-1);
    //mapTest[0] = item0;
    mapTest.insert(pair<int, TestData>(0, item0));
    item0.mTestCode = 1;

    int testCodeGet = mapTest[0].mTestCode;
}

输出内容如下,

  • 组合使用时

在insert操作前,map已存在key为目标key的元素时:

void TestFunction()
{
    map<int, TestData> mapTest;

    TestData item0(-1);
    mapTest[0] = TestData(5);
    mapTest.insert(pair<int, TestData>(0, item0));
    item0.mTestCode = 1;

    int testCodeGet = mapTest[0].mTestCode;
}

输出内容如下,

在insert操作前,map不存在key为目标key的元素时:

void TestFunction()
{
    map<int, TestData> mapTest;

    TestData item0(-1);
    mapTest[1] = TestData(5);
    mapTest.insert(pair<int, TestData>(0, item0));
    item0.mTestCode = 1;

    int testCodeGet = mapTest[0].mTestCode;
}

 

由此可见insert操作时不论事先有没有已存在key为目标key的元素,都会执行两次拷贝构造函数;只不过如果事先已存在时,执行了之后没有改变map的“现状”,即没有产生效果。

值得指出的是无论是debug模式还是release模式(会有代码优化),上述行为都是一致的,因为这是c++语言本身的标准。

  • 其他

上文提到了构造函数、拷贝赋值、析构函数的执行,这里不妨说明下析构函数的调用顺序,由于局部变量存储在栈上,遵循着先进后出的原则:在离开局部作用域时,最先定义的局部变量总是最后被析构。

void TestFunction()
{
    map<int, TestData> mapTest;

    TestData item0(-1);
    TestData item1(3);

    mapTest[1] = TestData(5);
    mapTest.insert(pair<int, TestData>(0, item0));
    item0.mTestCode = 1;

    int testCodeGet = mapTest[0].mTestCode;
}

1.7.构造map的过程中,随着元素的添加和删除,树枝会进行旋转,旋转时元素会被拷贝吗?

答案是否,原因如下,

  • 旋转前

  • 旋转后

2.总结

  • 需要清楚了解map的原理及各种操作的作用和行为,参见本文和MSDN相关文章;
  • 这样在不同的场景下,会根据map的作用长处和当时的场景判断是否可以用map,并且可以用的更好(有效且性能较好);
  • 如后续有补充或新的认识会更新本文,如有错误欢迎指正。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值