接手一段C++代码,看到map初始化写了七行insert,每行还调一次find去重。其实C++11以后列表初始化一行就够。std::map的初始化方式这几年变化挺大,写不对会在编译期或者运行期冷不丁扎你一下。
默认构造加逐个插入
声明一张空的map,键默认按升序排。插入可以直接用下标运算符赋值,但键不存在时会先默认构造一个值再赋值,值类型没有默认构造函数就编译不过。只想插入不覆盖,用insert加一对花括号,键已存在时这条不生效。emplace在构造上少一次临时对象拷贝,语义也比insert清楚。
列表初始化一步到位
C++11起可以一次填满整张表,大括号里每项是一对花括号包起来的键值。带等号那行是拷贝初始化,不带等号那行是直接初始化,效果一样。C++17起还能省掉模板参数,靠类模板推导自动识别类型。注意嵌套的map或者map里放vector,花括号层数要对,多一层少一层编译器给的报错都很难看懂,遇到就一层层拆开写。
std::map m = {{1, 100}, {2, 200}}; std::map m2{{3, 300}, {4, 400}};自定义排序规则
map的第二个模板参数是比较器,默认用less,也就是升序。要降序就把greater传进去。要按自定义结构体的某个字段排,写一个重载了括号运算符的仿函数。构造时也可以直接传一个比较器对象进去。比较器必须是严格弱序,写成小于等于会让红黑树行为异常,插入重复键时甚至可能崩。
常量表和值语义的坑
用初始化列表填map时,如果值类型是自定义类,每一项都会先构造临时对象再拷贝进去。C++17的try_emplace能就地构造,避免这次拷贝。跨文件共享的只读字典,直接写成const的全局map,或者用C++17的inline变量放在头文件里。别在头文件里定义普通全局map,多个cpp包含会重复定义,链接时报multiple definition。
- 只读字典:直接列表初始化成const,最省事。
- 要顺序:用map;只在乎查找速度,用unordered_map。
- 频繁插入:用emplace或try_emplace,别用下标运算符触发默认构造。