Effective stl读书笔记

关联容器

条款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:确保目标区间足够大

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值