《C++ Primer 4th》读书摘要
最重要的标准库类型是 string 和 vector,它们分别定义了大小可变的字符串和集合。这些标准库类型是语言组成部分中更基本的那些数据类型(如数组和指针)的抽象。另一种标准库类型 bitset,提供了一种抽象方法来操作位的集合。
标准库string类型
有一种情况下,必须总是使用完全限定的标准库名字:在头文件中。理由是头文件的内容会被预处理器复制到程序中。
#include <string>
using std::string;
几种初始化string 对象的方式,因为历史原因以及为了与 C 语言兼容,字符串字面值与标准库 string 类型不是同一种类型。这一点很容易引起混乱,编程时一定要注意区分字符串字面值和 string 数据类型的使用,这很重要。
string s1; |
默认构造函数 s1 为空串 |
string s2(s1); |
将 s2 初始化为 s1 的一个副本 |
string s3("value"); |
将 s3 初始化为一个字符串字面值副本 |
string s4(n, 'c'); |
将 s4 初始化为字符 'c' 的 n 个副本 |
从标准输入读取 string 并将读入的串存储在 s 中。string 类型的输入操作符:
• 读取并忽略开头所有的空白字符(如空格,换行符,制表符)。
• 读取字符直至再次遇到空白字符,读取终止。
和输入操作符不一样的是,getline 并不忽略行开头的换行符。只要 getline 遇到换行符,即便它是输入的第一个字符,getline 也将停止读入并返回。如果第一个字符就是换行符,则 string 参数将被置为空 string。由于 getline 函数返回时丢弃换行符,换行符将不会存储在 string 对象中。
string 对象的操作
s.empty() |
如果 s 为空串,则返回 true,否则返回 false。 |
s.size() |
返回 s 中字符的个数 |
s[n] |
返回 s 中位置为 n 的字符,位置从 0 开始计数 |
s1 + s2 |
把 s1 和s2 连接成一个新字符串,返回新生成的字符串 |
s1 = s2 |
把 s1 内容替换为 s2 的副本 |
v1 == v2 |
比较 v1 与 v2 的内容,相等则返回 true,否则返 回 false |
!=, <, <=, >, and >= |
保持这些操作符惯有的含义 |
string 类类型和许多其他库类型都定义了一些配套类型(companion type)。通过这些配套类型,库类型的使用就能与机器无关(machine-independent)。任何存储 string 的 size 操作结果的变量必须为 string::size_type 类型。特别重要的是,不要把 size 的返回值赋给一个 int 变量。
大多数 string 库类型的赋值等操作的实现都会遇到一些效率上的问题,但值得注意的是,从概念上讲,赋值操作确实需要做一些工作。它必须先把 st1 占用的相关内存释放掉,然后再分配给 st2 足够存放 st2 副本的内存空间,最后把 st2 中的所有字符复制到新分配的内存空间。
当进行 string 对象和字符串字面值混合连接操作时,+ 操作符的左右操作数必须至少有一个是 string 类型的。
string 类型通过下标操作符([ ])来访问 string 对象中的单个字符。下标操作符需要取一个 size_type 类型的值,来标明要访问字符的位置。
对 string 对象中的单个字符进行处理。下表列出了各种字符操作函数,适用于 string 对象的字符(或其他任何 char 值)。这些函数都在 cctype 头文件中定义。可打印的字符是指那些可以表示的字符,空白字符则是空格、制表符、垂直制表符、回车符、换行符和进纸符中的任意一种;标点符号则是除了数字、字母或(可打印的)空白字符(如空格)以外的其他可打印字符。
isalnum(c) |
如果 c 是字母或数字,则为 True。 |
isalpha(c) |
如果 c 是字母,则为 true。 |
iscntrl(c) |
如果 c 是控制字符,则为 true |
isdigit(c) |
如果 c 是数字,则为 true。 |
isgraph(c) |
如果 c 不是空格,但可打印,则为 true。 |
islower(c) |
如果 c 是小写字母,则为 true。 |
isprint(c) |
如果 c 是可打印的字符,则为 true。 |
ispunct(c) |
如果 c 是标点符号,则 true。 |
isspace(c) |
如果 c 是空白字符,则为 true。 |
isupper(c) |
如果 c 是大写字母,则 true。 |
isxdigit(c) |
如果是 c 十六进制数,则为 true。 |
tolower(c) |
如果 c 大写字母,返回其小写字母形式,否则直接返回 c。 |
toupper(c) |
如果 c 是小写字母,则返回其大写字母形式,否则直接返回 c。 |
C 标准库头文件命名形式为 name.h 而 C++ 版本则命名为 cname ,少了后缀,.h 而在头文件名前加了 c 表示这个头文件源自 C 标准库
标准库 vector 类型
vector 是同一种类型的对象的集合,vector称为容器。vector 不是一种数据类型,而只是一个类模板,可用来定义任
意多种数据类型。vector 类型的每一种都指定了其保存元素的类型。因此,vector<int> 和 vector<string> 都是数据类型。
vector<T> v1; |
vector 保存类型为 T 对象。默认构造函数 v1 为空。 |
vector<T> v2(v1); |
v2 是 v1 的一个副本。 |
vector<T> v3(n, i); |
v3 包含 n 个值为 i 的元素。 |
vector<T> v4(n); |
v4 含有值初始化的元素的 n 个副本 |
vector 对象(以及其他标准库容器对象)的重要属性就在于可以在运行时高效地添加元素。虽然可以对给定元素个数的 vector 对象预先分配内存,但更有效的方法是先初始化一个空 vector 对象,然后再动态地增加元素。
如果没有指定元素的初始化式,那么标准库将自行提供一个元素初始值进行值初始化(value initializationd)。这个由库生成的初始值将用来初始化容器中的每个元素,具体值为何,取决于存储在 vector 中元素的数据类型。元素类型可能是没有定义任何构造函数的类类型。这种情况下,标准库仍产生一个带初始值的对象,这个对象的每个成员进行了值初始化。
vector 操作
v.empty() |
如果 v 为空,则返回 true,否则返回 false。 |
v.size() |
返回 v 中元素的个数。 使用 size_type 类型时,必须指出该类型是在哪里定义的。 vector 类型总是包括总是包括 vector 的元素类型:vector<int>::size_type |
v.empty() |
如果 v 为空,则返回 true,否则返回 false。 |
v.push_back(t) |
在 v 的末尾增加一个值为 t 的元素。 |
v[n] |
返回 v 中位置为 n 的元素。 |
v1 = v2 |
把 v1 的元素替换为 v2 中元素的副本。 |
v1 == v2 |
如果 v1 与 v2 相等,则返回 true。 |
!=, <, <=,>, and >= |
保持这些操作符惯有的含义。 |
C++ 程序员习惯于优先选用 != 而不是 < 来编写循环判断条件,选用或不用某种操作符并没有特别的取舍理由。学习完泛型编程后,你将会明白这种习惯的合理性。
reference:http://www.cnblogs.com/qlee/archive/2011/05/16/2048026.html
vector 的reserve增加了vector的capacity,但是它的size没有改变!而resize改变了vector的capacity同时也增加了它的size!
原因如下: reserve是容器预留空间,但在空间内不真正创建元素对象,所以在没有添加新的对象之前,不能引用容器内的元素。加入新的元素时,要调用push_back()/insert()函数。
resize是改变容器的大小,且在创建对象,因此,调用这个函数之后,就可以引用容器内的对象了,因此当加入新的元素时,用operator[]操作符,或者用迭代器来引用元素对象。此时再调用push_back()函数,是加在这个新的空间后面的。
两个函数的参数形式也有区别的,reserve函数之后一个参数,即需要预留的容器的空间;resize函数可以有两个参数,第一个参数是容器新的大小, 第二个参数是要加入容器中的新元素,如果这个参数被省略,那么就调用元素对象的默认构造函数。下面是这两个函数使用例子:
#include <iostream>
#include <stdio.h>
#include <vector> using namespace std; int vecTest1()
{
vector<int> myVect;
myVect.reserve();
for(int i=;i<;i++)
{
myVect.push_back(i);
}
myVect.resize();
myVect[]=;
myVect[]=; for(vector<int>::iterator iter=myVect.begin(); iter!=myVect.end();++iter)
cout<<*iter<<endl; return ;
} int vecTest2()
{
vector<int> myVect;
myVect.push_back();
myVect.push_back();
myVect.push_back();
myVect.push_back();
myVect.reserve();
cout<<myVect.size()<<endl;
cout<<myVect.capacity()<<endl;
for(vector<int>::size_type i=;i<;++i)
{
cout<<myVect[i]<<endl;
} return ;
} int vecTest3()
{
vector<int> myVect;
myVect.push_back();
myVect.push_back();
myVect.push_back();
myVect.push_back();
myVect.resize();
cout<<myVect.size()<<endl;
cout<<myVect.capacity()<<endl;
for(vector<int>::size_type i=;i<;++i)
{
cout<<myVect[i]<<endl;
} return ;
} int vecTest4()
{
vector<int> myVect;
myVect.resize();
myVect.push_back();
myVect.push_back();
myVect.push_back();
myVect.push_back();
cout<<myVect.size()<<endl;
cout<<myVect.capacity()<<endl;
for(vector<int>::size_type i=;i<;++i)
{
cout<<myVect[i]<<endl;
} return ;
}
Reference:http://www.cnblogs.com/summerRQ/articles/2407974.html
vector内存释放
由于vector的内存占用空间只增不减,比如你首先分配了10,000个字节,然后erase掉后面9,999个,留下一个有效元素,但是内存占用仍为10,000个。所有内存空间是在vector析构时候才能被系统回收。empty()用来检测容器是否为空的,clear()可以清空所有元素。但是即使clear(),vector所占用的内存空间依然如故,无法保证内存的回收。
如果需要空间动态缩小,可以考虑使用deque。如果非vector不可,可以用swap()来帮助你释放内存。具体方法如下:
int freeVec()
{
vector<int> myVect;
myVect.push_back();
myVect.push_back();
myVect.push_back();
myVect.push_back();
vector<int>().swap(myVect); return ;
}
swap()是交换函数,使vector离开其自身的作用域,从而强制释放vector所占的内存空间,总而言之,释放vector内存最简单的方法是vector<int>.swap(nums)。当时如果nums是一个类的成员,不能把vector<int>.swap(nums)写进类的析构函数中,否则会导致double free or corruption (fasttop)的错误,原因可能是重复释放内存。标准解决方法如下:
template < class T >
void ClearVector( vector< T >& vt )
{
vector< T > vtTemp;
veTemp.swap( vt );
}
利用vector释放指针
如果vector中存放的是指针,那么当vector销毁时,这些指针指向的对象不会被销毁,那么内存就不会被释放。如下面这种情况,vector中的元素时由new操作动态申请出来的对象指针:
#include <vector>
using namespace std; vector<void *> v;
每次new之后调用v.push_back()该指针,在程序退出或者根据需要,用以下代码进行内存的释放:
for (vector<void *>::iterator it = v.begin(); it != v.end(); it ++)
if (NULL != *it)
{
delete *it;
*it = NULL;
}
v.clear();
C++中STL的vector容器的析构函数不用自己调用,系统会进行析构,但是vector内元素的清空需要手动进行。
非指针的数据类型,比如 int、string、char ,还包括自定义的数据结构、自定义的类 等等只需要手动调用vector的clesr函数就可以了,空间的释放和析构系统都会自动进行。
-
指针类型的数据,这种情况需要手动进行释放。也就是说new 产生的内存需要手动使用free进行释放。
迭代器是一种检查容器内元素并遍历元素的数据类型。标准库为每一种标准容器(包括 vector)定义了一种迭代器类型,而只有少数的容器支持下标操作。
每种容器类型都定义了自己的迭代器类型,如 vector:
vector<int>::iterator iter;
每种容器都定义了一对命名为 begin 和 end 的函数,用于返回迭代器。由 end 操作返回的迭代器并不指向 vector 中任何实际的元素,相反,它只是起一个哨兵(sentinel)的作用,表示我们已处理完 vector 中所有元素。
迭代器类型可使用解引用操作符(dereference operator)(*操作符)来访问迭代器所指向的元素:
*iter = 0;
迭代器使用自增操作符向前移动迭代器指向容器中下一个元素。因此,如果 iter 指向第一个元素,则 ++iter 指向第二个元素。
由于 end 操作返回的迭代器不指向任何元素,因此不能对它进行解引用或自增操作。
用 == 或 != 操作符来比较两个迭代器,如果两个迭代器对象指向同一个元素,则它们相等,否则就不相等。
每种容器类型还定义了一种名为 const_iterator 的类型,该类型只能用于读取容器内元素,但不能改变其值。
任何改变 vector 长度的操作都会使已存在的迭代器失效。例如,在调用 push_back 之后,就不能再信赖指向 vector 的迭代器的值了。
iter + n
iter - n
可以对迭代器对象加上或减去一个整形值。这样做将产生一个新的迭代器,其位置在 iter 所指元素之前(加)或之后(减) n 个元素的位置。
iter1 - iter2
该表达式用来计算两个迭代器对象的距离,该距离是名为 difference_type 的 signed 类型 size_type 的值,这里的 difference_type 是 signed 类型,因为减法运算可能产生负数的结果。
标准库 bitset
bitset 类是一种类模板;而与 vector 不一样的是 bitset 类型对象的区别仅在其长度而不在其类型。在定义 bitset 时,要明确 bitset 含有多少位,须在尖括号内给出它的长度值:
初始化bitset 对象的方法:
bitset<n> b; |
b 有 n 位,每位都 0 |
bitset<n> b(u); |
b 是 unsigned long 型 u 的一个副本 |
bitset<n> b(s); |
b 是 string 对象 s 中含有的位串的副本 |
bitset<n> b(s, pos, n); |
b 是 s 中从位置 pos 开始的&nbps;n 个位的副本。 |
bitset 操作
b.any() |
b 中是否存在置为 1 的二进制位? |
b.none() |
b 中不存在置为 1 的二进制位吗? |
b.count() |
b 中置为 1 的二进制位的个数 |
b.size() |
b 中二进制位的个数 |
b[pos] |
访问 b 中在 pos 处二进制位 |
b.test(pos) |
b 中在 pos 处的二进制位置为 1 么? |
b.set() |
把 b 中所有二进制位都置为 1 |
b.set(pos) |
把 b 中在 pos 处的二进制位置为 1 |
b.any() |
b 中是否存在置为 1 的二进制位? |
b.reset() |
把 b 中所有二进制位都置为 0 |
b.reset(pos) |
把 b 中在 pos 处的二进制位置为 0 |
b.flip() |
把 b 中所有二进制位逐位取反 |
b.flip(pos) |
把 b 中在 pos 处的二进制位取反 |
b.to_ulong() |
用 b 中同样的二进制位返回一个 unsigned long 值 |
os << b |
把 b 中的位集输出到 os 流 |
count 操作的返回类型是标准库中命名为 size_t 类型。size_t 类型定义在 cstddef 头文件中,该文件是 C 标准库的头文件 stddef.h 的 C++ 版本。