C++ 标准库类型-String,Vector and Bitset

时间:2024-01-15 21:03:02

《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内元素的清空需要手动进行。

  1. 非指针的数据类型,比如 int、string、char ,还包括自定义的数据结构、自定义的类 等等只需要手动调用vector的clesr函数就可以了,空间的释放和析构系统都会自动进行。

  2. 指针类型的数据,这种情况需要手动进行释放。也就是说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++ 版本。