7. 类层次结构
多继承中的问题:
歧义性解析:
class Task{
// ...
virtual debug_info* get_debug();
};
class Displayed{
// ...
virtual debug_info* get_debug();
};
对于Satelite类:
class Satelite: public Task, public Displayed{};
就必须要消除上述两个类中的函数的歧义性问题。
void f(Satellite * sp)
{
debug_info * dip = sp->get_debug(); // 错误,无法解析调用哪个函数
dip = sp->Task::get_debug(); // OK
dip = sp->Displayed::get_debug(); // OK
}
明确写出类名来调用函数的方法比较混乱,最好的方式是在子类中定义新的同名函数,覆盖基类中的函数。
class Satellite: public Task, public Displayed{
debug_info * get_debug()
{
dip = sp->Task::get_debug(); // OK
dip = sp->Displayed::get_debug(); // OK
return dip1->merge(dip2);
}
};
由于该get_debug覆盖了基类中的两个函数,因此对于Satellite的调用该函数都是调用类本身的函数。
消除继承于不同类的同名函数的重载问题:
class A{
// ...
int f(int );
char f(char );
};
class B{
double debug(double v);
};
class AB: public A, public B{
public:
using A::f;
using A::f;
char f(char); // 隐藏A中的f(char)
AB f(AB);
};
使用声明将基类中的同名函数引入到一个公共作用域中,实现同名函数的重载问题,调用时可以根据参数进行函数选择。
重复基类的问题:
struct Link{
Link * next;
};
class Task: public Link{
//
};
class Displayed: public Link{
//
};
如此没有任何问题,两个类继承于同一个结构体。
Link Link
| |
Task Displayed
| |
\ /
\ /
\ /
Satellite
此时就会出现歧义性的危险。即时是使用消除歧义中 的 类名限定函数的方法也会出现错误,因为有两个Link基类
其中一种方法是覆盖基类中的该函数,使用与基类函数 同名,同参的函数覆盖掉基类中的该函数。
再一种解决方法是,使用虚继承,对于可能出现上述问题的基类Link,在被继承时使用虚继承的方式
class Task: public virtual Link{
//
};
class Displayed: public virtual Link{
//
};
这样再写Satellite类时,其对象中就不会出现其中存在多个Link对象的问题了
访问控制:
对成员的访问控制:类中的一个成员可以是private, protected或 public:
如果一个成员是private,它的名字只能由其声明所在的类的成员函数和友元使用
如果一个成员是protected,他的名字只能由其生命所在的类的成员和友元,以及由该类的派生类的成员和友元使用
如果一个成员是public,它的名字可以由任何的函数使用
对基类的访问控制:类似成员,基类也可以是private,protected或public:
class X : public B{/*...*/};
class Y : protected B{/*...*/};
class Z : private B{/*...*/};
protected 和 private派生用于表示实现细节。
对于基类的访问描述符控制着对基类成员的访问,以及从派生类型到基类类型的指针和引用转换。考虑从基类B派生出类D:
—— 如果B是private基类,那么它的public和protected成员只能由D的成员函数和友元访问。
只有D的成员和友元能将D*转换为B*
—— 如果B是protected基类,那么它的public和protected成员只能由D的成员函数和友元,以及D的派生出的类的成员函数和友元访问
只有D的成员和友元以及由D派生出的类的成员和友元能将D*转换到B*
—— 如果B是public基类,那么它的public成员可以由任何函数使用,它的protected成员能由D的成员函数和友元以及De派生出
的类的成员函数和友元访问。任何函数都能将D*转换为B*。
运行时类型信息:
针对一个对象的任何操作都要求我们有适合这个对象的指针或者引用类型。因此检查独享类型的最明显最有用的操作是类型转换操作。
当对象具有所期望类型时它返回一个合法的指针,如果不是这种类型就返回空指针。dynamic_cast运算符可以实现此功能。
dynamic_cast有两个参数,一个用括号"<"和">"括起来的类型,另一个是用"("和")"括起来的指针或引用。
对指针的强制转换:
dynamic_cast<T*>(p);
将查看被p指向的对象(如果有的话),如果这个对象属于类T,或者有唯一的类型为T的基类,那么dynamic_cast就返回指向该对象的类型为T*的
指针,否则返回0。如果p的值为0,dynamic_cast<T*>(p)也返回0。注:要求p指针为多态的
对引用的强制转换:
在用于引用时,dynamic_cast无法使用0标识操作失败。对于指针可以假设该指针没有指向对象,因此可以用0标识操作失败。
但是对于引用总假定引用着某个对象,因此对于引用r做操作 dynamic_cast<T&>(r);不是提问而是断言:"由r引用的对象的类型是T"
对于引用的dynamic_cast的结果隐式地由dynamic_cast本身的实现去检查,如果引用的dynamic_cast的操作对象不具有所需要的类型
就会抛出一个bad_cast异常。
在动态指针强制和动态引用强制的结果方面的差异,所反应的正是引用和指针之间的根本性差异。
此处需要指针p为多态性的,也正是dynamic_cast要使用虚函数的方式进行对象检查。也即它是以一种类似虚函数的方式维持
灵活性和可扩展性。
静态强制转换:
dynamic_cast能从多态的虚基类强制到某个派生类或兄弟类,static_cast不检查被强制的对象,所以它做不到这些。
由于很多代码是C风格的,使用的是原有的强制类型转换,并且对于某些情况如void*的情况,dynamic_cast需要查看对象,而void*
不对存储提供任何保证,也就意味着dynamic_cast无法查看void*的对象,因此需要使用static_cast
dynamic_cast 和 static_cast都遵从const和访问控制规则。
例如 class Users :private set<person>{};
void f( Users * pu, const Receiver * pcr)
{
static_cast<set<Person>*>(pu); // 错误:访问违规
dynamic_cast<set<Person>*>(pu); // 错误:访问违规
static_cast<Receiver*>(pcr); // 错误:不能强制去掉cosnt
dynamic_cast<Receiver*>(pcr); // 错误:不能强制去掉cosnt
Receiver * pr = const_cast<Receiver*>(pcr); // OK
}
不能强制到某个私用基类,强制去掉const必须使用const_cast。要想安全访问强制的结果,所提供的对象要求原本就不是用const声明。
虚构造函数:
在听了虚构造函数之后,一个明显的问题就是"构造函数可以是虚的吗?"。简单的回答是不可以。
要构造一个对象,构造函数必须掌握所创建的对象的确切类型。因此构造函数不能是虚的。构造函数并不是一个普通的函数。
迂回的方法是,可以定义一个虚函数,在虚函数中构造函数并返回构造起来的对象。
8. 标准库列表容器
vector
为了避免虚函数带来的开销,STL为每种容器提供自己的迭代器,并让这些迭代器都支持同一组标准的迭代器操作
标准容器不是从一个公共基类派生出来的,实际上,是每个容器实现了完整的标准容器界面。同样不存在公共的迭代器基类
迭代器:
template<class T, class A=allocator<T>> class vector}{
public:
//...
// 迭代器
iterator begin(); // 指向首元素
const_iterator begin() const; // const类型的迭代器,不能修改指向的内容
iterator end(); // 指向最后一个元素的下一个位置
const_iterator end() const;
reverse_iterator rbegin(); // 指向反向序列的首元素
const_reverse_iterator rbegin() const; //
reverse_iterator end(); // 指向反向序列的最后一个元素的下一个位置
const_reverse_iterator end() const;
// ...
};
元素访问:
template<class T, class A=allocator<T>> class vector}{
public:
//...
// 元素访问
reference operator[](size_type n); // 不加检查的访问
const_reference operator[](size_type n) const_; //
reference at( size_type n); // 带检查的访问
const_reference at( size_type n) const; //
// ...
};
operator[]();提供的是不加检查的访问,因此程序员必须自己控制下标的有效性。而at()则是要做范围检查,并在下标越界时抛出
out_of_range的异常
成员front()和back()分别返回对首元素和末元素的引用。front返回的是对第一个元素的引用,而begin()返回的是到这个元素的迭代器
因此front()想象为第一个元素,而把begin()想象为指向第一个元素的指针。back()和end()之间的对应就不那么简单了:back()是最后
一个元素,而end()则指向最后元素的下一个位置。
堆栈操作:
template<class T, class A=allocator<T>> class vector}{
public:
//...
// 堆栈操作
void push_back( const T& x); // 在最后加入一个元素
void pop_back(); // 删除最后的元素
// ...
};
每次调用这两个方法时,vector就会增长或减少,都是从最后进行操作。
表操作:
template<class T, class A=allocator<T>> class vector}{
public:
//...
// 表操作
iterator insert( iterator pos, const T& x); // 在pos前加入x
void insert(iterator pos, size_type n, const T& x); // 在pos前加入n个x的副本
template <class In>
void insert(iterator pos, In first, In last); // 插入一批来自序列的元素
iterator erase( iterator pos); // 在pos删除元素
iterator erase( iterator first, iterator last); // 删除一段元素
void clear(); // 删除所有的元素
// ...
};
在insert()和erase()操作之后,原本指向vector中某个元素的迭代器现在可能就指向了另一个元素,甚至指向根本不是vector的元素。
绝对不应该再通过这种非法迭代器访问元素,这样做的结果无定义,或许引起大灾难
大小和容量:
template<class T, class A=allocator<T>> class vector}{
public:
//...
// 大小和容量
size_type size() const; // 元素个数
bool empty() const{return size() == 0};
size_type max_size() const; // vector的最大可能规模
void resize(size_type sz, T val=T()); // 增加元素用val初始化
size_type capacity() const; // 当前已分配存储的大小(可容纳元素个数)
void reserve( size_type n); // 做出总数为n个元素的空位,不初始化。n不能大于max_size()
// ...
};
list 是一种最适合做元素插入和删除的序列。除了下标,capacity()和reserve()方法,其他的方法完全与vector操作相同
粘结,排序和归并:
template <class T, class A=allocator<T>> class list{
// ...
// 表的特殊操作
void splice(iterator pos, list& x); // 将x的所有元素移到
// 本表的pos之前,且做的不是复制,而是拼接过来,原表删除
void splice(iterator pos, list& x, iterator 0); // 将x中的元素*p移到本表的pos之前,且不做复制
void splice(iterator pos, list& x, iterator first, iterator last);
void merge(list&); // 归并排序
template<class Cmp>void merge( list&, Cmp); // 以Cmp类的()方法作为比较函数的归并排序
void sort();
template<class Cmp>void sort( list&, Cmp);
//..
};
对于splice的例子:
fruit:
apple pear
citrus:
orange grapefruit lemon
list<string>::iterator p = find_if(fruit.begin(), fruit.end(), initial('p'));
fruit.splice(p, citrus, citrus.begin());
将orange从citus粘贴接入fruit中,效果是从citrus(citrus.begin())中删除了第一个元素,并将它放到fruit里的第一个名字以p
开始的元素之前,结果是:
fruit:
apple orange pear
citrus:
grapefruit lemon
注意splice不像insert那样复制元素,它是修改引用这些元素的list数据结构。
merge的例子
f1:
apple pear
f2:
orange grapefruit lemon
f1.sort();
f2.sort();
f1.merge(f2);
排序并进行归并排序结果是:
f1:
apple grapefruit lemon orange pear
f2:
<empty>
注:与splice类似,merge也是不进行元素复制,只是简单的list结构操作。
其他操作:
对于list的插入和删除操作效率特别高。
template <class T, class A=allocator<T>> class list{
// ...
void remove(const T& val);
template<class Pred> void remove_if(Pred p); //
void unique(); // 根据==删除重复元素
template <class BinPred> void unique(BinPred b); // 根据b删除重复元素
void reverse(); // 元素反转
//..
};
fruit.remove("orange");
fruit.remove_if(initial('l'));
删除元素的另一种是希望删除列表中的重复元素。由于unique仅能删除连续的重复剩下一个。因此先排序,将所有相同的排到一起。
fruit.sort();
fruit.unique();
序列适配器:
vector,list和deque序列不可能互为实现的基础而同时又不损失效率。但是stack和queue则都可以在这三种基本序列的基础上优雅而高效实现。
因此stack没有定义为独立容器,而是作为基本容器的适配器(adapter)
容器适配器所提供的是原来容器的一个受限的界面,特别是适配器不提供迭代器,提供他们的意图是为了只经由他们的专用界面使用。
堆栈容器的定义:
template <class T, class C=deque<T>> class std::stack{
protected:
C c;
public:
typedef typename C::value_type value_type;
typedef typename C::size_type size_type;
typedef C container_type;
explicit stack(const C&a = C()): c(a){}
bool empty() const{ return c.empty();};
size_type size () const{ return c.size();}
value_type& top() { return c.back();}
const value_type& top() const { return c.back();}
void push(const value_type &x) { c.push_back(x);}
void pop() { c.pop_back();}
};
例如:
stack<char> s1; // 用deque<char>保存char类型的元素
stack<int, vector<int>> s2; // 用vector<int>保存int类型的元素
队列在<queue>中定义,它也是一个容器的界面:
template <class T, class C=deque<T>> class std::queue{
protected:
C c;
public:
typedef typename C::value_type value_type;
typedef typename C::size_type size_type;
typedef C container_type;
explicit queue(const C&a = C()): c(a){}
bool empty() const{ return c.empty();};
size_type size () const{ return c.size();}
value_type& front() { return c.front();}
const value_type& front() const { return c.front();}
value_type& back() { return c.back();}
const value_type& back() const { return c.back();}
void push(const value_type &x) { c.push_back(x);}
void pop() { c.pop_front();}
};
按照默认的规定,queue用deque保存元素。但是任何提供了front(),back(),push_back()和pop_front()的序列都可以。
因为vector没有提供pop_front()操作,因此vector不能作为queue的基础容器
priority_queue的定义:
priority_queue也是一种队列,其中每个元素被定义一个优先级,一次来控制到达top()的顺序。
多继承中的问题:
歧义性解析:
class Task{
// ...
virtual debug_info* get_debug();
};
class Displayed{
// ...
virtual debug_info* get_debug();
};
对于Satelite类:
class Satelite: public Task, public Displayed{};
就必须要消除上述两个类中的函数的歧义性问题。
void f(Satellite * sp)
{
debug_info * dip = sp->get_debug(); // 错误,无法解析调用哪个函数
dip = sp->Task::get_debug(); // OK
dip = sp->Displayed::get_debug(); // OK
}
明确写出类名来调用函数的方法比较混乱,最好的方式是在子类中定义新的同名函数,覆盖基类中的函数。
class Satellite: public Task, public Displayed{
debug_info * get_debug()
{
dip = sp->Task::get_debug(); // OK
dip = sp->Displayed::get_debug(); // OK
return dip1->merge(dip2);
}
};
由于该get_debug覆盖了基类中的两个函数,因此对于Satellite的调用该函数都是调用类本身的函数。
消除继承于不同类的同名函数的重载问题:
class A{
// ...
int f(int );
char f(char );
};
class B{
double debug(double v);
};
class AB: public A, public B{
public:
using A::f;
using A::f;
char f(char); // 隐藏A中的f(char)
AB f(AB);
};
使用声明将基类中的同名函数引入到一个公共作用域中,实现同名函数的重载问题,调用时可以根据参数进行函数选择。
重复基类的问题:
struct Link{
Link * next;
};
class Task: public Link{
//
};
class Displayed: public Link{
//
};
如此没有任何问题,两个类继承于同一个结构体。
Link Link
| |
Task Displayed
| |
\ /
\ /
\ /
Satellite
此时就会出现歧义性的危险。即时是使用消除歧义中 的 类名限定函数的方法也会出现错误,因为有两个Link基类
其中一种方法是覆盖基类中的该函数,使用与基类函数 同名,同参的函数覆盖掉基类中的该函数。
再一种解决方法是,使用虚继承,对于可能出现上述问题的基类Link,在被继承时使用虚继承的方式
class Task: public virtual Link{
//
};
class Displayed: public virtual Link{
//
};
这样再写Satellite类时,其对象中就不会出现其中存在多个Link对象的问题了
访问控制:
对成员的访问控制:类中的一个成员可以是private, protected或 public:
如果一个成员是private,它的名字只能由其声明所在的类的成员函数和友元使用
如果一个成员是protected,他的名字只能由其生命所在的类的成员和友元,以及由该类的派生类的成员和友元使用
如果一个成员是public,它的名字可以由任何的函数使用
对基类的访问控制:类似成员,基类也可以是private,protected或public:
class X : public B{/*...*/};
class Y : protected B{/*...*/};
class Z : private B{/*...*/};
protected 和 private派生用于表示实现细节。
对于基类的访问描述符控制着对基类成员的访问,以及从派生类型到基类类型的指针和引用转换。考虑从基类B派生出类D:
—— 如果B是private基类,那么它的public和protected成员只能由D的成员函数和友元访问。
只有D的成员和友元能将D*转换为B*
—— 如果B是protected基类,那么它的public和protected成员只能由D的成员函数和友元,以及D的派生出的类的成员函数和友元访问
只有D的成员和友元以及由D派生出的类的成员和友元能将D*转换到B*
—— 如果B是public基类,那么它的public成员可以由任何函数使用,它的protected成员能由D的成员函数和友元以及De派生出
的类的成员函数和友元访问。任何函数都能将D*转换为B*。
运行时类型信息:
针对一个对象的任何操作都要求我们有适合这个对象的指针或者引用类型。因此检查独享类型的最明显最有用的操作是类型转换操作。
当对象具有所期望类型时它返回一个合法的指针,如果不是这种类型就返回空指针。dynamic_cast运算符可以实现此功能。
dynamic_cast有两个参数,一个用括号"<"和">"括起来的类型,另一个是用"("和")"括起来的指针或引用。
对指针的强制转换:
dynamic_cast<T*>(p);
将查看被p指向的对象(如果有的话),如果这个对象属于类T,或者有唯一的类型为T的基类,那么dynamic_cast就返回指向该对象的类型为T*的
指针,否则返回0。如果p的值为0,dynamic_cast<T*>(p)也返回0。注:要求p指针为多态的
对引用的强制转换:
在用于引用时,dynamic_cast无法使用0标识操作失败。对于指针可以假设该指针没有指向对象,因此可以用0标识操作失败。
但是对于引用总假定引用着某个对象,因此对于引用r做操作 dynamic_cast<T&>(r);不是提问而是断言:"由r引用的对象的类型是T"
对于引用的dynamic_cast的结果隐式地由dynamic_cast本身的实现去检查,如果引用的dynamic_cast的操作对象不具有所需要的类型
就会抛出一个bad_cast异常。
在动态指针强制和动态引用强制的结果方面的差异,所反应的正是引用和指针之间的根本性差异。
此处需要指针p为多态性的,也正是dynamic_cast要使用虚函数的方式进行对象检查。也即它是以一种类似虚函数的方式维持
灵活性和可扩展性。
静态强制转换:
dynamic_cast能从多态的虚基类强制到某个派生类或兄弟类,static_cast不检查被强制的对象,所以它做不到这些。
由于很多代码是C风格的,使用的是原有的强制类型转换,并且对于某些情况如void*的情况,dynamic_cast需要查看对象,而void*
不对存储提供任何保证,也就意味着dynamic_cast无法查看void*的对象,因此需要使用static_cast
dynamic_cast 和 static_cast都遵从const和访问控制规则。
例如 class Users :private set<person>{};
void f( Users * pu, const Receiver * pcr)
{
static_cast<set<Person>*>(pu); // 错误:访问违规
dynamic_cast<set<Person>*>(pu); // 错误:访问违规
static_cast<Receiver*>(pcr); // 错误:不能强制去掉cosnt
dynamic_cast<Receiver*>(pcr); // 错误:不能强制去掉cosnt
Receiver * pr = const_cast<Receiver*>(pcr); // OK
}
不能强制到某个私用基类,强制去掉const必须使用const_cast。要想安全访问强制的结果,所提供的对象要求原本就不是用const声明。
虚构造函数:
在听了虚构造函数之后,一个明显的问题就是"构造函数可以是虚的吗?"。简单的回答是不可以。
要构造一个对象,构造函数必须掌握所创建的对象的确切类型。因此构造函数不能是虚的。构造函数并不是一个普通的函数。
迂回的方法是,可以定义一个虚函数,在虚函数中构造函数并返回构造起来的对象。
8. 标准库列表容器
vector
为了避免虚函数带来的开销,STL为每种容器提供自己的迭代器,并让这些迭代器都支持同一组标准的迭代器操作
标准容器不是从一个公共基类派生出来的,实际上,是每个容器实现了完整的标准容器界面。同样不存在公共的迭代器基类
迭代器:
template<class T, class A=allocator<T>> class vector}{
public:
//...
// 迭代器
iterator begin(); // 指向首元素
const_iterator begin() const; // const类型的迭代器,不能修改指向的内容
iterator end(); // 指向最后一个元素的下一个位置
const_iterator end() const;
reverse_iterator rbegin(); // 指向反向序列的首元素
const_reverse_iterator rbegin() const; //
reverse_iterator end(); // 指向反向序列的最后一个元素的下一个位置
const_reverse_iterator end() const;
// ...
};
元素访问:
template<class T, class A=allocator<T>> class vector}{
public:
//...
// 元素访问
reference operator[](size_type n); // 不加检查的访问
const_reference operator[](size_type n) const_; //
reference at( size_type n); // 带检查的访问
const_reference at( size_type n) const; //
// ...
};
operator[]();提供的是不加检查的访问,因此程序员必须自己控制下标的有效性。而at()则是要做范围检查,并在下标越界时抛出
out_of_range的异常
成员front()和back()分别返回对首元素和末元素的引用。front返回的是对第一个元素的引用,而begin()返回的是到这个元素的迭代器
因此front()想象为第一个元素,而把begin()想象为指向第一个元素的指针。back()和end()之间的对应就不那么简单了:back()是最后
一个元素,而end()则指向最后元素的下一个位置。
堆栈操作:
template<class T, class A=allocator<T>> class vector}{
public:
//...
// 堆栈操作
void push_back( const T& x); // 在最后加入一个元素
void pop_back(); // 删除最后的元素
// ...
};
每次调用这两个方法时,vector就会增长或减少,都是从最后进行操作。
表操作:
template<class T, class A=allocator<T>> class vector}{
public:
//...
// 表操作
iterator insert( iterator pos, const T& x); // 在pos前加入x
void insert(iterator pos, size_type n, const T& x); // 在pos前加入n个x的副本
template <class In>
void insert(iterator pos, In first, In last); // 插入一批来自序列的元素
iterator erase( iterator pos); // 在pos删除元素
iterator erase( iterator first, iterator last); // 删除一段元素
void clear(); // 删除所有的元素
// ...
};
在insert()和erase()操作之后,原本指向vector中某个元素的迭代器现在可能就指向了另一个元素,甚至指向根本不是vector的元素。
绝对不应该再通过这种非法迭代器访问元素,这样做的结果无定义,或许引起大灾难
大小和容量:
template<class T, class A=allocator<T>> class vector}{
public:
//...
// 大小和容量
size_type size() const; // 元素个数
bool empty() const{return size() == 0};
size_type max_size() const; // vector的最大可能规模
void resize(size_type sz, T val=T()); // 增加元素用val初始化
size_type capacity() const; // 当前已分配存储的大小(可容纳元素个数)
void reserve( size_type n); // 做出总数为n个元素的空位,不初始化。n不能大于max_size()
// ...
};
list 是一种最适合做元素插入和删除的序列。除了下标,capacity()和reserve()方法,其他的方法完全与vector操作相同
粘结,排序和归并:
template <class T, class A=allocator<T>> class list{
// ...
// 表的特殊操作
void splice(iterator pos, list& x); // 将x的所有元素移到
// 本表的pos之前,且做的不是复制,而是拼接过来,原表删除
void splice(iterator pos, list& x, iterator 0); // 将x中的元素*p移到本表的pos之前,且不做复制
void splice(iterator pos, list& x, iterator first, iterator last);
void merge(list&); // 归并排序
template<class Cmp>void merge( list&, Cmp); // 以Cmp类的()方法作为比较函数的归并排序
void sort();
template<class Cmp>void sort( list&, Cmp);
//..
};
对于splice的例子:
fruit:
apple pear
citrus:
orange grapefruit lemon
list<string>::iterator p = find_if(fruit.begin(), fruit.end(), initial('p'));
fruit.splice(p, citrus, citrus.begin());
将orange从citus粘贴接入fruit中,效果是从citrus(citrus.begin())中删除了第一个元素,并将它放到fruit里的第一个名字以p
开始的元素之前,结果是:
fruit:
apple orange pear
citrus:
grapefruit lemon
注意splice不像insert那样复制元素,它是修改引用这些元素的list数据结构。
merge的例子
f1:
apple pear
f2:
orange grapefruit lemon
f1.sort();
f2.sort();
f1.merge(f2);
排序并进行归并排序结果是:
f1:
apple grapefruit lemon orange pear
f2:
<empty>
注:与splice类似,merge也是不进行元素复制,只是简单的list结构操作。
其他操作:
对于list的插入和删除操作效率特别高。
template <class T, class A=allocator<T>> class list{
// ...
void remove(const T& val);
template<class Pred> void remove_if(Pred p); //
void unique(); // 根据==删除重复元素
template <class BinPred> void unique(BinPred b); // 根据b删除重复元素
void reverse(); // 元素反转
//..
};
fruit.remove("orange");
fruit.remove_if(initial('l'));
删除元素的另一种是希望删除列表中的重复元素。由于unique仅能删除连续的重复剩下一个。因此先排序,将所有相同的排到一起。
fruit.sort();
fruit.unique();
序列适配器:
vector,list和deque序列不可能互为实现的基础而同时又不损失效率。但是stack和queue则都可以在这三种基本序列的基础上优雅而高效实现。
因此stack没有定义为独立容器,而是作为基本容器的适配器(adapter)
容器适配器所提供的是原来容器的一个受限的界面,特别是适配器不提供迭代器,提供他们的意图是为了只经由他们的专用界面使用。
堆栈容器的定义:
template <class T, class C=deque<T>> class std::stack{
protected:
C c;
public:
typedef typename C::value_type value_type;
typedef typename C::size_type size_type;
typedef C container_type;
explicit stack(const C&a = C()): c(a){}
bool empty() const{ return c.empty();};
size_type size () const{ return c.size();}
value_type& top() { return c.back();}
const value_type& top() const { return c.back();}
void push(const value_type &x) { c.push_back(x);}
void pop() { c.pop_back();}
};
例如:
stack<char> s1; // 用deque<char>保存char类型的元素
stack<int, vector<int>> s2; // 用vector<int>保存int类型的元素
队列在<queue>中定义,它也是一个容器的界面:
template <class T, class C=deque<T>> class std::queue{
protected:
C c;
public:
typedef typename C::value_type value_type;
typedef typename C::size_type size_type;
typedef C container_type;
explicit queue(const C&a = C()): c(a){}
bool empty() const{ return c.empty();};
size_type size () const{ return c.size();}
value_type& front() { return c.front();}
const value_type& front() const { return c.front();}
value_type& back() { return c.back();}
const value_type& back() const { return c.back();}
void push(const value_type &x) { c.push_back(x);}
void pop() { c.pop_front();}
};
按照默认的规定,queue用deque保存元素。但是任何提供了front(),back(),push_back()和pop_front()的序列都可以。
因为vector没有提供pop_front()操作,因此vector不能作为queue的基础容器
priority_queue的定义:
priority_queue也是一种队列,其中每个元素被定义一个优先级,一次来控制到达top()的顺序。