关联容器
条款24 当关乎效率时应该在map::operator[]和map-insert之间仔细选择
新增元素
class Widget {
public:
Widget() { std::cout << "Widget::Widget()" << std::endl; }
Widget(double val)
{
_val = val;
std::cout << "Widget::Widget(double)" << std::endl;
}
Widget& operator=(double val) {
std::cout << "Widget::operator=()" << std::endl;
_val = val;
}
double _val;
};
int main()
{
std::map<int, Widget> widgetMap;
widgetMap[1] = 1.5;
std::cout << "---------" << std::endl;
widgetMap.insert(std::map<int, Widget>::value_type(2, 1.5));
}
结果是
Widget::Widget()
Widget::operator=()
---------
Widget::Widget(double)
分析:
widgetMap[1] = 1.5
widgetMap[1]是widgetMap.operator[](1)的简化,operator[]返回的是一个Widget对象的引用,一开始map中没有元素,因此会先调用默认构造函数构造一个临时对象Widget,然后调用Widget的赋值运算符进行赋值
widgetMap[1] = 1.5 其实相当于
Widget tmp;
std::pair<std::map<int, Widget>::iterator, bool> cit = widgetMap.insert(std::map<int, Widget>::value_type(1, tmp));
cit.first->second = 1.5;
更新元素:
class Widget {
public:
Widget() { std::cout << "Widget::Widget()" << std::endl; }
Widget(double val)
{
_val = val;
std::cout << "Widget::Widget(double)" << std::endl;
}
Widget(const Widget& widget)
{
_val = widget._val;
std::cout << "Widget::Widget(const Widget& widget)" << std::endl;
}
Widget& operator=(double val) {
std::cout << "Widget::operator=()" << std::endl;
_val = val;
}
double _val;
};
int main()
{
std::map<int, Widget> widgetMap;
widgetMap[1] = 1.5;
std::cout << "---------" << std::endl;
widgetMap.insert(std::map<int, Widget>::value_type(1, 1.5));
}
更新元素时insert相比operator[]会先构造一个临时对象,然后通过复制构造函数插入对象。
而operator[]只需要对map中已存在的对象赋值即可。因此operator[]效率更高。
文中写了一个通用的添加元素的方法
template<typename MapType, typename KeyArgType, typename ValueArgtype>
typename MapType::iterator efficientAddOrUpdate(MapType& m, const KeyArgType& k,
const ValueArgtype& v)
{
typename MapType::iterator Ib = m.lower_bound(k);
if(Ib != m.end() && !(m.key_comp()(k, Ib->first))) {
Ib->second = v;
return Ib;
} else {
typedef typename MapType::value_type MVT;
return m.insert(Ib, MVT(k, v));
}
}
/* *
这里有一个有趣的地方是使用模板类型KeyArgType和ValueArgtype,而不是直接用MapType::
key_type和MapType::mapped_type。
template<typename MapType>
typename MapType::iterator efficientAddOrUpdate(MapType& m, MapType::key_type k, MapType::mapped_type v)
原因是使用MapType::key_type可能强迫发生不必要的类型转换
如:
class Widget {
public:
Widget& operator=(double weight);
};
std::map<int, Widget> m;
efficientAddOrUpdate(m, 10, 1.5);
假设是更新元素,不用MapType::key_type时可以直接通过Widget& operator=(double weight)赋值;
但是用MapType::key_type,需要先将1.5构造成一个Widget对象再进行赋值,又多了一次临时对象构造析构的开销
**/
总结:
新增元素用insert,更新元素用operator[]
条款25:熟悉非标准散列容器
这个条例当前已经过时了,C++11后就提供了散列容器unordered_set、unordered_map等等
迭代器
条款26:尽量用iterator代替const_iterator,reverse_iterator和
const_reverse_iterator
这个条例当前已经过时了,文中是由于早期对const_iterator的使用有诸多限制,容器insert和erase不接受const_iterator类型;另外iterator和const_iterator之间混用时,他们之间比较、加减等运算甚至都无法编译。因此综合考虑推荐使用iterator
条款27:用distance和advance把const_iterator转化成iterator
iterator可以通过隐式转换成const_iterator,但是const_iterator不能转换成iterator,即使是通过const_cast也是不行的,iterator和const_iterator不是通常意义上的T和const T,他们是完全不同的两个类。
正确的方式是:std::advance(iterator, std::distance<const_iterator>(iterator, const_iterator));
std::advance(iterator, n)是将迭代器iterator前进或后退 n 个位置
std::distance(first, last)是计算迭代器first和迭代器last之间的偏移
typedef std::deque<int> IntDeque;
typedef IntDeque::iterator Iter;
typedef IntDeque::const_iterator ConstIter;
int main()
{
IntDeque deque{
1,
2
};
Iter it = deque.begin();
ConstIter cit = --deque.cend(); // 将const iterator指向最后一个元素
std::advance(it, std::distance<ConstIter>(it, cit)); // 将iteraor指向与const iterator相同的位置
std::cout << *it << std::endl;
}
注意:
std::advance(iterator, std::distance<const_iterator>(iterator, const_iterator));这里的<const_iterator>必须要指定。
从下面distance的实现可以看到两个入参都需要是同一个类型,而我们的是iterator和const_iterator两种不同的类型,推导InputIterator类型的时候编译器无法识别。
template<typename InputIterator>
typename iterator_traits<InputIterator>::difference_type
distance(InputIterator first, InputIterator last);
条款28:了解如何通过reverse_iterator的base得到iterator
https://en.cppreference.com/w/cpp/iterator/reverse_iterator/base
容器的insert、erase操作不支持reverse_iterator,比如vector,因此当你有一个reverse_iterator时需要先通过base函数转换成iterator再完成工作
插入:
reverse_iterator.base() 和reverse_iterator是等价的
vector<int> v{1, 2, 3, 4, 5};
vector<int>::reverse_iterator ri = v.rbegin();
v.insert(ri.base());
删除:
reverse_iterator.base()的前一个元素才是和reverse_iterator等价
vector<int> v{1, 2, 3, 4, 5};
vector<int>::reverse_iterator ri = v.rbegin();
// v.erase(--ri.base()); 编译不过??
v.erase((++ri).base());
条款29:需要一个一个字符输入时考虑使用istreambuf_iterator
std::ifstream inputFile("interestingData.txt");
inputFile.unset(ios::skipws); // 关闭inputFile的忽略空格标志
std::string fileData((std::istream_iterator<char>(inputFile)), std::istream_iterator<char>());
std::ifstream inputFile("interestingData.txt");
std::string fileData((std::istreambuf_iterator<char>(inputFile)), std::istreambuf_iterator<char>());
两者区别:
1、istream_iterator使用operator>>函数来进行真的读取,而operator>>函数在默认情况下忽略空格,因此你想保留空格要清除输入流的skipws标志;而istreambuf_iterator不忽略任何字符,它istreambuf_iterator对象进入流的缓冲区并直接读取下一个字符。(更明确地说,一个istreambuf_iterator 对象从一个istream s中读取会调用s.rdbuf()->sgetc()来读s的下一个字符。)
2、istreambuf_iterator比istream_iterators更高效,因为istream_iterators所依靠的operator>>函数进行的是格式化输入
总结:对于无格式的一个一个字符输入,总是应该考虑使用istreambuf_iterato;对于无格式输出也可以考虑把ostreambuf_iterator代替ostream_iterator
todo:看看istream_iterator和istreambuf_iterator的实现,看看格式化输入怎么做的
算法
条款30:确保目标区间足够大