!给定两个vector,v1和v2,使v1的内容和v2的后半部分一样的最简单方式是什么
v1.assign(v2.begin() + v2.size() / 2, v2.end());
。它对于所有标准序列容器(vector,string,deque和list)都有效
v1.clear();
copy(v2.begin() + v2.size() / 2, v2.end(), back_inserter(v1));写这些仍然比写assign的调用要做更多的工作。此外,虽然在这段代码中没有表现出循环,在copy中的确存在一个循环
太多的STL程序员过度使用copy,所以我重复一遍我的建议:几乎所有目标区间被插入迭代器指定的copy的使用都可以
用调用的区间成员函数的来代替。
,这个copy的调用可以用一个insert的区间版本代替:
v1.insert(v1.end(), v2.begin() + v2.size() / 2, v2.end());
比如,假设你要把一个int数组拷贝到vector前端。(数据可能首先存放在一个数组而不是vector中,因为数据来自于遗留
的C API。对于混合使用STL容器和C API的问题的讨论,参见条款16。)使用vector区间insert函数,真的微不足道:
int data[numValues]; // 假设numValues在
// 其他地方定义
vector<int> v;
...
v.insert(v.begin(), data, data + numValues); // 把data中的int
// 插入v前部
,用调用copy来代替循环,我们会得出像这样的东西:
copy(data, data + numValues, inserter(v, v.begin()));
尽量选择区间成员函数。
区间插入。所有标准序列容器都提供这种形式的insert:
void container::insert(iterator position, // 区间插入的位置
InputIterator begin, // 插入区间的起点
InputIterator end); // 插入区间的终点
关联容器使用它们的比较函数来决定元素要放在哪里,所以它们了省略position参数。
void container::insert(lnputIterator begin, InputIterator end);
● 区间删除。每个标准容器都提供了一个区间形式的erase,但是序列和关联容器的返回类型不同。序列容器提供
了这个:
iterator container::erase(iterator begin, iterator end);
而关联容器提供这个:
void container::erase(iterator begin, iterator end);
区间赋值。就像我在这个条款的一开始提到的,所有标准列容器都提供了区间形式的assign:
void container::assign(InputIterator begin, InputIterator end);
list<int> data(istream_iterator<int>(dataFile), istream_iterator<int>());
打起精神,这声明了一个函数data,它的返回类型是list<int>。这个函数data带有两个参数:
● 第一个参数叫做dataFile。它的类型是istream_iterator<int>。dataFile左右的括号是多余的而且被忽略。
● 第二个参数没有名字。它的类型是指向一个没有参数而且返回istream_iterator<int>的函数的指针。
用括号包围一个
实参的声明是不合法的,但用括号包围一个函数调用的观点是合法的,所以通过增加一对括号,我们强迫编
译器以我们的方式看事情:
list<int> data((istream_iterator<int>(dataFile)), // 注意在list构造函数
istream_iterator<int>()); // 的第一个实参左右的
// 新括号
一个更好的解决办法是在数据声明中从时髦地使用匿名istream_iterator对象后退一步,仅仅给那些迭代器名
字。以下代码到哪里都能工作:
ifstream dataFile("ints.dat");
istream_iterator<int> dataBegin(dataFile);
istream_iterator<int> dataEnd;
list<int> data(dataBegin, dataEnd);
命名迭代器对象的使用和普通的STL编程风格相反,但是你得判断这种方法对编译器和必须使用编译器的人
都模棱两可的代码是一个值得付出的代价。
结果,下面代码直接导致一个内存泄漏:
void doSomething()
{
vector<Widget*> vwp;
for (int i = 0; i < SOME_MAGIC_NUMBER; ++i)
vwp.push_back(new Widget);
... // 使用vwp
} // Widgets在这里泄漏!
当vwp除了生存域后,vwp的每个元素都被销毁,
通常,你需要它们被删除。当情况如此时,可以很简单地实现:
void doSomething()
{
vector<Widget*> vwp;
... // 同上
for (vector<Widget*>::iterator i = vwp.begin();
i != vwp.end(),
条款7:当使用new得指针的容器时,记得在销毁容器前delete那些指针
++i) {
delete *i;
}
}
这可以工作,除非你不是对你“工作”的意思很吹毛求疵
要把你的类似for_each的循环转化为真正使用for_each,你需要把delete转入一个函数对象中。这像儿戏般简
单,假设你有一个喜欢和STL一起玩的孩子:
template<typename T>
struct DeleteObject : // 条款40描述了为什么
public unary_function<const T*, void> { // 这里有这个继承
void operator()(const T* ptr) const
{
delete ptr;
}
};
现在你可以这么做:
void doSomething()
{
... // 同上
for_each(vwp.begin(), vwp.end(), DeleteObject<Widget>);
}
不幸的是,这让你指定了DeleteObject将会删除的对象的类型(在本例中是Widget)。那是很讨厌的,vwp是
一个vector<Widget*>,所以当然DeleteObject会删除Widget*指针!咄!这种冗余就不光是讨厌了,因为它会导
致很难跟踪到的bug。假设,比如,有的人恶意地故意从string继承:
条款9:在删除选项中仔细选择:
如果你有一个连续内存容器(vector、deque或string——参见条款1),最好的方法是erase-remove惯用法(参:
c.erase(remove(c.begin(), c.end(), 1963), // 当c是vector、string
c.end()); // 或deque时,
// erase-remove惯用法
// 是去除特定值的元素
// 的最佳方法
这方法也适合于list,但是,正如条款44解释的,list的成员函数remove更高效:
c.remove(1963); // 当c是list时,
// remove成员函数是去除
// 特定值的元素的最佳方法
当c是标准关联容器(即,set、multiset、map或multimap)时,使用任何叫做remove的东西都是完全错误的。
这样的容器没有叫做remove的成员函数,而且使用remove算法可能覆盖容器值(参见条款32),潜在地破坏
容器。(关于这样的破坏的细节,参考条款22,那个条款也解释了为什么试图在map和multimap上使用remove
肯定不能编译,而试图在set和multiset上使用可能不能编译。)
不,对于关联容器,解决问题的适当方法是调用erase:
条款9:在删除选项中仔细选择
c.erase(1963); // 当c是标准关联容器时
// erase成员函数是去除
// 特定值的元素的最佳方法
这不仅是正确的,而且很高效,只花费对数时间
看起来,这个任务很简单,而且实际上,代码也很简单。不幸的是,那些正确工作的代码很少是跃出脑海的
代码。例如,这是很多程序员首先想到的:
AssocContainer<int> c;
...
for (AssocContainer<int>::iterator i = c.begin(); // 清晰,直截了当
i!= c.end(); // 而漏洞百出的用于
++i) { // 删除c中badValue返回真
if (badValue(*i)) c.erase(i); // 的每个元素的代码
} // 不要这么做!
唉,这有未定义的行为。当容器的一个元素被删时,指向那个元素的所有迭代器都失效了。当c.erase(i)返回
时,i已经失效。那对于这个循环是个坏消息,因为在erase返回后,i通过for循环的++i部分自增。
为了避免这个问题,我们必须保证在调用erase之前就得到了c中下一元素的迭代器。最容易的方法是当我们调
用时在i上使用后置递增:
AssocContainer<int> c;
...
for (AssocContainer<int>::iterator i = c.begin(); // for循环的第三部分
i != c.end(); // 是空的;i现在在下面
/*nothing*/ ){ // 自增
if (badValue(*i)) c.erase(i++); // 对于坏的值,把当前的
else ++i; // i传给erase,然后
} // 作为副作用增加i;
// 对于好的值,
// 只增加i
这种调用erase的解决方法可以工作,因为表达式i++的值是i的旧值,但作为副作用,i增加了。因此,我们把i
的旧值(没增加的)传给erase,但在erase开始执行前i已经自增了。那正好是我们想要的
我们必须对vector、string和deque采用不同的战略。特别是,我们必须利用erase的返回值。那个返回值正是我
们需要的:一旦删除完成,它就是指向紧接在被删元素之后的元素的有效迭代器。换句话说,我们这么写:
for (SeqContainer<int>::iterator i = c.begin();
i != c.end();){
if (badValue(*i)){
logFile << "Erasing " << *i << '/n';
i = c.erase(i); // 通过把erase的返回值
} // 赋给i来保持i有效
else
++i;
}