关于类模板
类模板的定义以关键字template 开始,后面是用尖括号括起来的参数表,类型参数由typename 或class 加上一个标识符构成 例如
template<class elemType>
class list_item;
关键字typename 与class 可以互换。typename 是标准C++中新引入的 这种写法更利于记忆,但是在本书写作时 对typename 的支持没有class
广泛,由于这个原因 我们使用关键字class关于命名空间
更通用的解决方案是使用C++名字空间机制,名字空间使得库厂商可以封装全局名字以防止名字冲突。另外,名字空间也提供了访问符号,从而允许在我们的程序中使用这些名字,例如 C++标准库被包装在名字空间std 中 本书第二版的代码也被放到一个唯一的名字空间中。
namespace Primer_Third_Edition
{
template<typename elemType>
class list_item{ ... };
template<typename elemType>
class list{ ... };
// ...
}
如果用户希望练习我们的list 类 那么他可以这样写
// 我们的 list类头文件
#include "list.h"
// 使定义对程序可见
using namespace Primer_Third_Edition;
// ok: 访问我们的 list
list< int > ilist;
// ...
抽象容器
顺序容器[sequence container] 拥有由单一类型元素组成的一个有序集合,两个主要的顺序容器是list 和vector(第三个顺序容器为双端队列deque,发音为 deck。它提供了与vector 相同的行为,但是对于首元素的有效插入和删除提供了特殊的支持。)
关联容器[associative container]支持查询一个元素是否存在,并且可以有效地获取元素两个基本的关联容器类型是map[映射]和set[集合]。map 是一个键/值[key/value] 对。键[key] 用于查询,而值[value]包含我们希望使用的数据。
vector,deque与list
vector 表示一段连续的内存区域 每个元素被顺序存储在这段内存中
deque 也表示一段连续的内存区域,但是与vector 不同的是,它支持高效地在其首部插入和删除元素。它通过两级数组结构来实现,一级表示实际的容器,第二级指向容器的首和尾。
list 表示非连续的内存区域,并通过一对指向首尾元素的指针双向链接起来,从而允许向前和向后两个方向进行遍历。在list 的任意位置插入和删除元素的效率都很高,指针必须被重新赋值,但是不需要用拷贝元素来实现移动;另一方面,它对随机访问的支持并不好访问一个元素需要遍历中间的元素。另外,每个元素还有两个指针的额外空间开销 。
vector如何增长的
#include <vector>
#include <iostream>
int main()
{
vector< int > ivec;
cout << "ivec: size: " << ivec.size()
<< " capacity: " << ivec.capacity() << endl;
for ( int ix = 0; ix < 24; ++ix ) {
ivec.push_back( ix );
cout << "ivec: size: " << ivec.size()
<< " capacity: " << ivec.capacity() << endl;
}
}
在ivec的定义之后,它的长度和容量都是0。但是在插入第一个元素之后,ivec的容量是256,长度为1。这意味着在ivec 下一次需要增长之前,我们可以向它加入256 个元素,当我们插入第256 个元素时,vector以下列方式重新自我增长:它分配双倍于当前容量的存储区,把当前的值拷贝到新分配的内存中,并释放原来的内存。
容量是指在容器下一次需要增长自己之前能够被加入到容器中的元素的总数。容量只与连续存储的容器相关。例如vector,deque 或string。list 不要求容量。
正如稍后我们将要看到的 同list 相比 数据类型越大越复杂 则vector 的效率也就越低
下表 各种数据类型 它们的长度 及其相关vector 的初始容量
数据类型 | 长度(字节) | 初始插入后的容量 |
int | 4 | 256 |
double | 8 | 128 |
simple class | 12 | 85 |
String | 12 | 85 |
large simple class | 8000 | 1 |
large complex class | 8000 | 1 |
无论是list 还是vector,对于已定义拷贝构造函数的类来说,插入这样的类的元素都需要调用拷贝构造函数。拷贝构造函数用该类型的一个对象初始化该类型的另一个对象——这正说明了在简单类和string 的链表之间插入代价的区别。简单类对象和大型简单类对象通过按位拷贝插入,一个对象的所有位被拷贝到第二个对象的位中;而string 类对象和大型复杂类对象通过调用拷贝构造函数来插入。
另外 随着每次重新分配内存 vector 必须为每个元素调用拷贝构造函数,而且在释放原来的内存时,它要为每个元素调用其相关类型的析构函数。vector 的动态自我增长越频繁,元素插入的开销就越大。
reserve()操作允许程序员将容器的容量设置成一个显式指定的值
vector< string > svec;
svec.reserve( 32 ); // 把容量设置为 32
但根据经验发现,用一个非1 的缺省值来调整vector 的容量看起来总会引起性能退化。例如,对于string 和double 型的vector,通过reserve()增加容量导致很差的性能;另一方面,加大型复杂类的容量会大大改善性能。(注,非简单类 8000字节大小,并且带有构造函数和析构函数 )