1 如何给容器初始化、赋值大量数据?
这个问题,其实解决办法很普通。
比如,重复调用push_back()、insert()等函数,或者使用初值列语法。
但是我们希望有更高效的大量数据初始化的方法
assign下有两个重要的类,分别负责不同的场景
- list_inserter容器赋值(先创建空的,再赋值)
- generic_list容器构造时填充数据
对比上面两种方案,无疑第二种的效率更高,因为它构造对象的时候就已经填充了数据,而第一种先创建一个空的,再对这个赋值。
2 list_inserter容器赋值(先创建空的,再赋值)
下面时list_inserter部分函数,更多函数可以查看源码
template< class Function, class Argument = assign_detail::forward_n_arguments >
class list_inserter {
public:
list_inserter& operator,(T&& r);
list_inserter& operator()();
list_inserter& operator()(const T&);
list_inserter& repeat_fun( std::size_t sz, Function r );
list_inserter& repeat( std::size_t sz, T r )
list_inserter& range( SinglePassIterator first, SinglePassIterator last );
list_inserter& range( const SinglePassRange& r );
private:
Function insert_; // 其实是一个函数对象
}
这里我们可以看出一个特点,
返回类型都是自己的引用<—>list_inserter& ,assign库中的两个类都是这样的
那为什么返回自己的引用?
答:好比cout << ... << ... <<
,<<符号可以一个接一个写下去,返回自己的引用—运行完当前又可以继续调用当前类中函数,反复下去
ostream& print(ostream& os, int xx){
// ...
return os
}
这些函数的返回值都是return *this;
,来保证获得自己的引用,总体来说,很像迭代器,处理完毕,指向末尾,如果操作,继续在末尾继续操作,直到所有操作执行完毕。
template< class T >
list_inserter& operator,(T&& r)
{
// do something
return *this;
}
在正式分析前,我们看看容器的种类,这样你才能理解assign库的兼容性。
- 容器有push_back,push_front函数,如vector,string,list,deque等
- 容器没有push_back,push_front函数,但是有insert,如set、map等
- 既没有push_back,push_front函数,也没有insert,如非标准容器,如stack、queue、priority_queue
不管什么容器,assign都给出了不错的解决方案
2.1 operator,
重载,运算符,不过我也是第一次遇见重载,。这为简化书写提供了有力的帮助
list_inserter& operator,( const T& r )
{
insert_( r );
return *this;
}
很简单,做一个插入的操作,返回自己的引用。
2.2 operator+=
+=和,
运算符保证下面的代码的正确、简单性!
// v = [1,2,3,4,5,36]
std::vector<int> v;
v += 1, 2, 3, 4, 5, 6 * 6;
看看+=的源代码:
template< class V, class A, class V2 >
inline list_inserter< assign_detail::call_push_back< std::vector<V, A> >, V >
operator+=( std::vector<V, A>& c, V2&& v )
{
return push_back( c )( std::forward<V2>(v) );
}
这个函数调用内部的push_back函数,(push_back返回list_inserter对象,当然push_front同理,前提容器支持这种操作,这就是上面对容器分类的原因),下面是具体细节
- push_back( c )返回值是list_inserter,产生一个list_inserter对象;注意c是容器,由容器构建list_inserter对象!!!
- 借此调用operator()(Ts&&… ts),此时完成1的插入,
- 借此调用operator,(T&&),此时完成2的插入,直到6*6
- 最后+=返回list_inserter对象
push_back( c )返回值是list_inserter,继续调用operator()(Ts&&… ts),这就是要返回自己或者自己的引用的原因,表面上看着是两个()。
和上面不同的是,其实内部的push_back、push_front、insert、返回的是list_inserter对象,和上面有些不同。
2.3 不同容器的不同初始化方法
2.3.1 容器有push_back,push_front函数
下面的两种方法都是可以的,回顾上面,其实+=内部就是调用:push_back(v)(...)
。因此我推荐第二种方式,看着简单方便!
// Method1
std::vector<int> v;
boost::assign::push_back(v)(1)(2)(3)(4)(5)(6*6);
// Method2
std::vector<int> v;
v += 1, 2, 3, 4, 5, 6 * 6;
// list等其他容器也是这样的
2.3.2 容器没有push_back,push_front函数,但是有insert
有两种方案,
- 运用+=,但是必须使用make_pair(map的哈)
- 使用insert进行插入
// Method1
std::map<int, std::string> m;
m += std::make_pair(1, "one"), std::make_pair(2, "two");
//注意:set只有一个元素,所有就不用make_pair了哈
std::set<std::string> s;
s += "c", "cpp", "h", "hpp";
// Method2
// 如果容器没有push_back,push_front,如set、map,要用insert
std::map<int, std::string> m;
boost::assign::insert(m)(1, "one")(2, "two");
// 其他也行,如set.map
下面是+=内部代码,可知,两种方式其实是一致的,+=调用:insert(v)(...)
operator+=( std::map<K,V,C,A>& m, const P& p )
{
return insert( m )( p );
}
总之,如果不想写make_pair,那么就用第二种方案,书写简单些
2.3.3 容器没有push_back,push_front函数,也没有insert
此时,其实对应的是非标准容器,也就是stack、queue、priority_queue。这些容器也想使用assign的一些技术,该怎么做呢?
我们首先必须注意的是,assign类有两个类,一个负责赋值,一个负责构造时填充!!!
答:generic_list---->通过内部函数to_adapter()进行转换,list_inserter—>使用+=(内部使用的push)
// list_inserter方案
std::stack<int> stk;
stk += 4, 5, 6;
// 当然也可以调用内部push
boost::assign::push(stk)(7);
// generic_list方案
std::stack<int> stk2 = (boost::assign::list_of(1), 2, 3).to_adapter();
2.4 重复输入和拷贝序列
案例,如果我想给这样一组数
1 2 3 3 3 3 3 3 3 3 3 3 4 5
现有的方法是
std::vector<int> v;
v += 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 5;
这很不明智,使用repeat
来解决,repeat_fun可调用函数
// 下面的代码将10重复3次
std::vector<int> v = boost::assign::list_of(1)(2).repeat(10, 3)(4)(5);
// 当然想用,就需要加大括号
std::vector<int> v = (boost::assign::list_of(1).repeat(3, 2), 4, 5);
repeat_fun案例
std::multiset<int> ms;
boost::assign::insert(ms).repeat_fun(3, &rand).repeat(3, 1), 10;
//output demo
// 1 1 1 10 846930886 1681692777 1804289383
range:构造d的时候用v的部分,
std::deque<int> d;
push_front(d).range(v.begin(), v.begin() + 3);
3 generic_list容器构造时填充数据
generic_list适用于构造时初始化,省略了构造空的代价
generic_list和list_inserter大体一致,返回自己的引用,用法差不多。不过generic_list内部用std::deque来存储元素,大部分上层函数在底部都转换成deque的push_back
typedef std::deque<Ty> impl_type;
为了产生generic_list对象,assign库提供了三个特殊函数,list_of、map_list_of/pair_list_of、tuple_list_of
3.1 list_of
当然,这个函数的基础任是operator,、operator()等,自己看generic_list的源码
有下面两种方案,如果像偷懒用,的话,使用第二种,注意语法格式
- 整体大括号
- 第一个数值括号,
其他的用,就可以了
,当然用括号也可以(也就是2 3 4 5用括号和,都可以)
// v = [1,2,3,4,5]
// 第一种
std::vector<int> v = boost::assign::list_of(1)(2)(3)(4)(5);
// 第二种
std::vector<int> v = (boost::assign::list_of(1), 2, 3, 4, 5);
在C++语法中,用初值列也可以办到
std::vector<int> v = {1,2,3,4,5};
感觉这种含简单些?是的,但是assign具有强大的兼容性,这是它最大的优势。
3.2 map_list_of/pair_list_of
运用list_of在处理map时候很复杂,这时就用map_list_of/pair_list_of,map_list_of和pair_list_of完全等价
// 等价
// list_of配合make_pair一起使用
std::map<int, std::string> m = boost::assign::list_of(std::make_pair(1, "one"))(std::make_pair(2, "two"));
// 使用pair_list_of
std::map<int, std::string> m1 = boost::assign::pair_list_of(1, "one")(2, "two");
总之,既然库提供了更加简便的方法,那就用呗。
3.3 tuple_list_of
tuple是三元组,类似