vector变长机制、string的其他构造方法,添加、替换和搜索操作,string比较和数值转换,最后是容器适配器。
vector对象是如何增长的
vector和string类型提供了一些成员函数,允许我们与它实现中内存分配的部分互动:capacity()操作告诉我们容器在不扩张内存空间的情况下可以容纳多少个元素,reserve()操作允许我们通知容器它应该准备保存多少个元素。
// shrink_to_fit只适用于vector、string和deque // capacity和reserve只适用于vector和string c.shrink_to_fit; 请将capacity()减少为size()相同大小 c.capacity(); 不重新分配内存空间的话,c可以保存多少元素 c.reserve(n); 分配至少能容纳n个元素的内存空间 // reserve并不改变容器中元素的数量,它仅影响vector预先分配多大的内存空间
只有当需要的内存空间超过当前容量时,reserve调用才会改变vector的容量。如果需要大小大于当前容量,reserve至少分配与需求一样大的内存空间。(一般是2倍)
如果需求大小小于或等于当前容量,reserve什么也不做,特别是,当需求大小小于当前容量时,容器并不会退回内存空间。因此在调用reserve之后,capacity()将会大于或等于传递给reserve的参数!
在新的标准里,我们可以通过shrink_to_fit来要求deque、vector或string退回不需要的内存空间,此函数指出我们不再需要多余的内存空间,但是实际上,会忽略此请求,调用shrink_to_fit并不保证一定会退回内存空间。
capacity和size
capacity指在不重新分配内存空间的情况下,容器最多可以保存多少个元素,而size指的是当前容器已经保存的元素数量。
vector<int> ivec; // size应该为0,而capacity依赖具体实现 cout << "ivec : size : " << ivec.size() << "capacity : " << ivec.capacity() << endl; // 添加24个元素 for(vector<int>::size_type i = 0; i < 24; ++i) ivec.push_back(i); // capacity应该大于等于24,依赖具体实现 cout << "ivec : size : " << ivec.size() << "capacity : " << ivec.capacity() << endl;
// 我们可以预先分配一些资源: ivec.reserve(50); cout << "ivec : size : " << ivec.size() << "capacity : " << ivec.capacity() << endl;
这样,capacity就和reserve的参数一样大了:
我们可以将空余的26个位置填满:
while(ivec.size() != ivec.capacity()) ivec.push_back(0); cout << "ivec : size : " << ivec.size() << "capacity : " << ivec.capacity() << endl;
这证明,我们没有重新分配内存空间,就不会调用reserve,capacity也就不变。
继续添加一个元素后,超出了预留空间,vector就要重新分配内存空间了:
ivec.push_back(0); std::cout << "ivec : size : " << ivec.size () << " capacity : " << ivec.capacity () << std::endl;
输出:ivec : size : 51 capacity : 75
我们可以用shrink_to_fit来要求退回不需要的内存空间:
ivec.shrink_to_fit(); std::cout << "ivec : size : " << ivec.size () << " capacity : " << ivec.capacity () << std::endl;
输出: ivec : size : 51 capacity : 51
shrink_to_fit依赖于具体的实现,有些机器可能不会理会此操作!
额外的string操作
构造string的其他方法:
// n、len2和pos2都是无符号值 string s(cp, n); s是cp指向的数组中前n个字符的拷贝,数组至少包含n个元素 string s(cp, pos2); s是string s2从下标pos2开始的字符的拷贝,若pos2>s2.size(),该行为未定义 string s(cp, pos2, len2); s是string s2从下标pos2开始的len2个字符的拷贝,若pos2>s2.size(),该行为未定义,不管len2多大,最多拷贝s2.size() - pos2个字符
特别的是,当我们从一个const char*创建string时,指针指向的数组必须以空字符' '结尾,拷贝操作遇到空字符停止。当从一个string拷贝字符时,如果位置大于size,则构造函数抛出一个out_of_range异常
substr操作:返回一个string,它是原始string的一部分或全部拷贝。
s.substr(pos, n); 返回一个string,包含s从pos开始的n个字符的拷贝,pos默认为0,n默认为s.size() - pos
改变string的其他方法:
除了接受迭代器的insert和erase版本外,string还提供了下标的版本:
s.insert(s.size(), 5, '!'); // 在s的末尾插入5个'!' s.erase(s.size() - 5, 5); // 删除s的最后5个字符
还有接受C风格字符数组的insert和assign版本:
const char* cp = "Stately, plump Buck"; s.assign(cp, 7); // s == "Stately" s.insert(s.size(), cp + 7); // s == Stately, plump Buck
也可以指定将来自其他string或子字符串插入到当前的string中:
string s = "some string", s2 = "some other string"; s.insert(0, s2); // 在s开始的位置之前插入s2的拷贝 s.insert(0, s2, 0, s2.size()); // 在s开始的位置插入从s2[0]开始的s2.size()个字符
append和replace函数:
string定义了额外的这两个函数来改变string:
string s("C++ Primer"); s2 = s; s.insert(s.size(), " 4th Ed."); // s == "C++ Primer 4th Ed." s2.append(" 4th Ed."); // 等价方法,将" 4th Ed."追加到s2的末尾
replace是调用erase和insert的一种简写形式:
s.erase(11, 3); // s == "C++ Primer Ed." s.insert(11, "5th"); // s == "C++ Primer 5th Ed." // 等价方法,s2 == "C++ Primer 5th Ed." s2.replace(11, 3, "5th");
replace插入的文本不一定要和替换的文本一样长。
s2.replace(11, 3, "Fifth"); // s2 == "C++ Primer Fifth Ed."
string的assign和append函数,assign函数总是替换string中的内容,append总是添加到末尾,replace有两个版本,除了下标,还有接受迭代器的版本。
string的搜索操作:
string提供了6中不同的搜索函数,每个函数都返回一个string::size_type值,表示匹配的下标,如果没有找到,返回一个名为string::npos的static成员,它是const string::size_type类型,并初始化为-1,由于npos是usigned类型,意味着npos等于任何string最大的可能大小。
s.find(args); 查找s中args第一次出现的位置 s.rfind(args); 查找s中args最后一次出现的位置 s.find_first_of(args); 在s中查找args中任何一个字符第一次出现的位置 s.find_last_of(args); 在s中查找args中任何一个字符最后一次出现的位置 s.find_first_not_of(args); 在s中查找第一个不在args中的字符 s.find_last_not_of(args); 在s中查找最后一个不在args中的字符
我们可以给这些操作传递一个可选的开始位置,默认情况下,这个位置被置为0;
string::size_type pos = 0; while((pos = name.find_first_of(numbers, 0)) != string::npos) cout << "found number at index " << pos << " element is " << name[pos++] << endl;
string的数值转换:
to_string(val); 返回数值val的string表示,val可以是任何算数类型。对每个浮点类型和int或更大的整形,都有相应版本的to_string,与往常一样,小整形会被提升。 stoi(s,p,b); 返回s的起始子串的数值,类型分别是int、long、unsigned long、long long、unsigned long long。 stol(s,p,b); b表示转换的基数,默认是10,p是size_t指针,用来保存s中第一个非数值字符的下标,p默认为0,即 函数不保存下标。 stoul(s,p,b); stoll(s,p,b); stoull(s,p,b); stof(s,p); 返回s的起始子串的数值,返回值分别是float、double和long double stod(s,p); stold(s,p);
如果string不能转换为一个数值,这些函数会抛出一个invalid_argument异常,如果转换得到的数值无法用于任何类型来表示,则抛出一个out_of_range异常。
容器适配器:
标准库定义了三个容器适配器: stack、queue和priority_queue。
所有容器适配器都支持的操作和类型:
size_type 一种类型,足以保存当前类型的最大对象的大小 valua_type 元素类型 container_type 实现适配器的底层容器类型 // 所有容器都支持的关系运算符: == != < <= > >= a.empty(); 判断a是否为空 a.size(); 返回a的大小 swap(a,b); 交换a和b的元素 a.swap(b); a和b的类型必须相同,而且它们底层的容器类型也必须相同
默认情况下,stack和queue是基于deque实现的,priority_queue是在vector上实现的。我们可以在创建一个适配器时将一个命名的顺序容器做为第二个类型参数,来重载默认容器类型:
stack<string, vector<string>> stk;
所有适配器都要求具有添加和删除元素的能力,所以底层容器不能是array,类似的,也不能用forward_list来构造适配器。
除过array和forward_list之外的任何顺序容器类型都可以构造stack
queue要求back、push_back、front和push_front,因此queue可以构造在list或deque之上,但不能基于vector。
priority_queue除了front、push_back和pop_back操作之外还要求随机访问能力,因此它可以构造于vector和deque之上,但不能基于list
一些栈操作:
// 栈默认deque实现,也可以构造于list或vector s.pop(); 删除栈顶元素,但不返回该元素值 s.push(item); 拷贝item元素到栈顶 s.emplace(args); 由args构造一个元素添加到栈顶 s.top(); 返回栈顶元素,但不弹出
虽然stack默认deque实现,但不能再一个stack上使用deque的操作!
队列操作:
// queue默认基于deque实现,priority_queue默认基于vector实现 q.pop(); 删除队首元素,但不返回它 q.front(); 返回首元素,但不删除它 q.back(); 返回尾元素,但不删除它 q.top(); 返回最高优先级的元素,但不删除它,只适用于priority_queue q.push(item); 拷贝item到queue末尾或priority_queue中恰当的位置 q.emplace(args); 由args构造
priority_queue允许我们为队列中的元素建立优先级,新加入的元素会排在所有优先级比它低的已有的元素之前。默认情况下,标准库在元素类型上使用 < 来确定相对优先级。