Chinaunix首页 | 论坛 | 博客
  • 博客访问: 7894026
  • 博文数量: 701
  • 博客积分: 2150
  • 博客等级: 上尉
  • 技术积分: 13233
  • 用 户 组: 普通用户
  • 注册时间: 2011-06-29 16:28
个人简介

天行健,君子以自强不息!

文章分类

全部博文(701)

文章存档

2019年(2)

2018年(12)

2017年(76)

2016年(120)

2015年(178)

2014年(129)

2013年(123)

2012年(61)

分类: C/C++

2013-06-20 11:12:51

C++ 定义了内容丰富的抽象数据类型标准库,
这些标准库类型是语言组成部分中更基本的那些数据类型(如数组和指针)的抽象,
其中最重要的标准库类型是 string 和 vector,bitset;
string :  支持大小可变的字符串;
vector : 支持大小可变的集合,可用于保存一组指定类型的对象;
             它们往往将迭代器用作配套类型(companion type),用于访问 string 中的字符,或者 vector 中的元素。
bitset :  提供了一种抽象方法来操作位的集合。
             与整型值上的内置位操作符相比,bitset 类类型提供了一种更方便的处理位的方式。

3.1. 命名空间的 using 声明
使用 using 声明可以在不需要加前缀 namespace_name:: 的情况下访问命名空间中的名字。
using 声明的形式如下:
  using namespace::name;

一旦使用了 using 声明,我们就可以直接引用名字,而不需要再引用该名字的命名空间,
且 每个名字都需要一个 using 声明; 
如下所示:

#include

#include

// using declarations states our intent to use these names from the namespace std
using std::cin;
using std::string;

int main()
{
  string s;          // ok: string is now a synonym for std::string
  cin >> s;          // ok: cin is now a synonym for std::cin
  cout << s;         // error: no using declaration; we must use full name
  std::cout << s;    // ok: explicitly use cout from namepsace std
}

3.2. 标准库 string 类型
string 类型支持长度可变的字符串,
C++ 标准库将负责管理与存储字符相关的内存,以及提供各种有用的操作;

using 声明:
#include
using std::string;


3.2.1. string 对象的定义和初始化
几种初始化 string 对象的方式
string s1;                // 默认构造函数 s1 为空串
string s2(s1);         // 将 s2 初始化为 s1 的一个副本
string s3("value");  // 将 s3 初始化为一个字符串字面值副本
string s4(n, 'c');     // 将 s4 初始化为字符 'c' 的 n 个副本

3.2.2. string 对象的读写
1. 使用标准输入输出操作符来读写 string 对象:
// Note: #include and using declarations must be added to compile this code
int main()
{
  string s; // empty string
  cin >> s; // read whitespace-separated string into s
  cout << s << endl; // write s to the output
  return 0;
}

2. 读入未知数目的 string 对象
string word;
// read until end-of-file, writing each word to a new line
while (cin >> word)
  cout << word << endl;


3. 使用 getline 读取整行文本
getline 函数将 istream 参数作为返回值,和输入操作符一样也把它用作判断条件。
例如,重写前面那段程序,把每行输出一个单词改为每次输出一行文本:
int main()
{
  string line;
  // read line at time until end-of-file
  while (getline(cin, line))
    cout << line << endl;
  
  return 0;
}

3.2.3. string 对象的操作
1. 
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 >= :  保持这些操作符惯有的含义


2. string::size_type 类型
任何存储 string 的 size 操作结果的变量必须为 string::size_type 类型。
特别重要的是,还要把 size 的返回值赋给一个 int 变量。


3.2.4. string 对象中字符的处理
1. 表 3.3. 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。

3.3. 标准库 vector 类型
vector 是同一种类型的对象的集合,每个对象都有一个对应的整数索引值;
和 string 对象一样,标准库将负责管理与存储元素相关的内存;
我们把 vector称为容器,是因为它可以包含其他对象;
一个容器中的所有对象都必须是同一种类型的;

使用 vector 之前,必须包含相应的头文件:
#include
using std::vector;

vector 是一个类模板(class template)。
使用模板可以编写一个类定义或函数定义,而用于多个不同的数据类型。
因此,我们可以定义保存 string 对象的 vector,或保存 int 值的 vector,
又或是保存自定义的类类型对象(如Sales_items 对象)的 vector。

声明从类模板产生的某种类型的对象,需要提供附加信息,信息的种类取决于模板。
以 vector 为例,必须说明 vector 保存何种对象的类型,
通过将类型放在类型放在类模板名称后面的尖括号中来指定类型:
vector ivec;                          // ivec holds objects of type int
vector Sales_vec;  // holds Sales_items


3.3.1. vector 对象的定义和初始化
1. vector 类定义了好几种构造函数(2.3.3 节),用来定义和初始化 vector对象。
表 3.4 列出了这些构造函数。
表 3.4. 初始化vector
vector v1;         vector 保存类型为 T 对象。默认构造函数 v1 为空。
vector v2(v1);   v2 是 v1 的一个副本。
vector v3(n, i);  v3 包含 n 个值为 i 的元素。
vector v4(n);    v4 含有值初始化的元素的 n 个副本。

2. 创建确定个数的元素
若要创建非空的 vector 对象,必须给出初始化元素的值。
当把一个 vector对象复制到另一个 vector 对象时,
新复制的 vector 中每一个元素都初始化为原 vectors 中相应元素的副本。

但这两个 vector 对象必须保存同一种元素类型:
vector ivec1;                 // ivec1 holds objects of type int
vector ivec2(ivec1);      // ok: copy elements of ivec1 into ivec2
vector svec(ivec1);  // error: svec holds strings, not ints


可以用元素个数和元素值对 vector 对象进行初始化。
构造函数用元素个数来决定 vector 对象保存元素的个数,元素值指定每个元素的初始值:
vector ivec4(10, -1);          // 10 elements, each initialized to -1
vector svec(10, "hi!");  // 10 strings, each initialized to "hi!"

3. 值初始化
如果没有指定元素的初始化式,那么标准库将自行提供一个元素初始值进行值初始化(value initializationd)。
这个由库生成的初始值将用来初始化容器中的每个元素,具体值为何,取决于存储在 vector 中元素的数据类型。

如果 vector 保存内置类型(如 int 类型)的元素,那么标准库将用 0 值创建元素初始化式:
vector fvec(10);   // 10 elements, each initialized to 0


如果 vector 保存的是含有构造函数的类类型(如 string)的元素,
标准库将用该类型的默认构造函数创建元素初始化式:
vector svec(10);   // 10 elements, each an empty string

3.3.2. vector 对象的操作
表 3.5. vector 操作
v.empty()            :   如果 v 为空,则返回 true,否则返回 false。
v.size()                :   返回 v 中元素的个数。
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 >=

1. 向 vector 添加元素
push_back 操作接受一个元素值,并将它作为一个新的元素添加到 vector对象的后面,
也就是“插入(push)”到 vector 对象的“后面(back)”:

// read words from the standard input and store them as elements in a vector
string word;
vector text;     // empty vector
while (cin >> word) {
  text.push_back(word);  // append word to text
}

2. vector 的下标操作
vector 中的对象是没有命名的,可以按 vector 中对象的位置来访问它们。
通常使用下标操作符来获取元素。vector 的下标操作类似于 string 类型的下标操作(3.2.3 节)。
vector 的下标操作符接受一个值,并返回 vector 中该对应位置的元素。
vector 元素的位置从 0 开始。

下例使用 for 循环把 vector 中的每个元素值都重置为 0:
// reset the elements in the vector to zero
for (vector::size_type ix = 0; ix != ivec.size(); ++ix)
  ivec[ix] = 0;

3. 下标操作不能添加元素
初学 C++ 的程序员可能会认为 vector 的下标操作可以添加元素,其实不然:
vector ivec;     // empty vector

for (vector::size_type ix = 0; ix != 10; ++ix)
  ivec[ix] = ix;      // disaster: ivec has no elements


上述程序试图在 ivec 中插入 10 个新元素,元素值依次为 0 到 9 的整数。
但是,这里 ivec 是空的 vector 对象,而且下标只能用于获取已存在的元素。

这个循环的正确写法应该是:
for (vector::size_type ix = 0; ix != 10; ++ix)
  ivec.push_back(ix);    // ok: adds new element with value ix

关键概念:安全的泛型编程
习惯于 C 或 Java 编程的 C++ 程序员可能会觉得难以理解,
for 循环的判断条件用 != 而不是用 < 来测试 vector 下标值是否越界。
C 程序员难以理解的还有,上例中没有在 for 循环之前就调用 size 成员函数并保存其返回的值,
而是在 for 语句头中调用 size 成员函数。C++ 程序员习惯于优先选用 != 而不是 < 来编写循环判断条件。

调用 size 成员函数而不保存它返回的值,在这个例子中同样不是必需的,
但这反映了一种良好的编程习惯。
在 C++ 中,有些数据结构(如vector)可以动态增长
上例中循环仅需要读取元素,而不需要增加新的元素。
但是,循环可以容易地增加新元素,如果确实增加了新元素的话,
那么测试已保存的 size 值作为循环的结束条件就会有问题,因为没有将新加入的元素计算在内。
所以我们倾向于在每次循环中测试 size的当前值,而不是在进入循环前,存储 size 值的副本

4. :仅能对确知已存在的元素进行下标操作
对于下标操作符([] 操作符)的使用有一点非常重要,就是仅能提取确实已存在的元素,
例如:
vector ivec;         // empty vector
cout << ivec[0];          // Error: ivec has no elements!
vector ivec2(10);    // vector with 10 elements
cout << ivec[10];         // Error: ivec has elements 0...9

3.4. 迭代器简介
迭代器(iterator):是一种检查容器内元素并遍历元素的数据类型。

标准库为每一种标准容器(包括 vector)定义了一种迭代器类型。
迭代器类型提供了比下标操作更通用化的方法:
  所有的标准库容器都定义了相应的迭代器类型,而只有少数的容器支持下标操作。

因为迭代器对所有的容器都适用,现代 C++ 程序更倾向于使用迭代器而不是下标操作访问容器元素,
即使对支持下标操作的 vector 类型也是这样;

1. 容器的 iterator 类型
每种容器类型都定义了自己的 迭代器类型,如 vector:

vector::iterator iter;
这符语句定义了一个名为 iter 的变量,它的数据类型是 vector 定义的 iterator 类型。
每个标准库容器类型都定义了一个名为 iterator 的成员,这里的 iterator 与迭代器实际类型的含义相同。

2. begin 和 end 操作
每种容器都定义了一对命名为 begin 和 end 的函数,用于返回迭代器。
如果容器中有元素的话,由 begin 返回的迭代器指向第一个元素:

vector::iterator iter = ivec.begin();
上述语句把 iter 初始化为由名为 vector 操作返回的值。
假设 vector 不空,初始化后,iter 即指该元素为 ivec[0]。
由 end 操作返回的迭代器指向 vector 的“末端元素的下一个”。“超出末端迭代器”(off-the-end iterator)。
表明它指向了一个不存在的元素。如果 vector 为空,begin 返回的迭代器与 end 返回的迭代器相同。

由于 end 操作返回的迭代器并不指向 vector 中任何实际的元素,
相反,它只是起一个哨兵(sentinel)的作用,表示我们已处理完 vector 中所有元素。

3. vector 迭代器的自增和解引用运算
迭代器类型定义了一些操作来获取迭代器所指向的元素,并允许程序员将迭代器从一个元素移动到另一个元素。
迭代器类型可使用解引用操作符(dereference operator)(*)来访问迭代器所指向的元素:

  *iter = 0;

解引用操作符返回迭代器当前所指向的元素。
假设 iter 指向 vector 对象 ivec 的第一元素,那么 *iter 和 ivec[0] 就是指向同一个元素。
上面这个语句的效果就是把这个元素的值赋为 0。

4. 迭代器的其他操作
另一对可执行于迭代器的操作就是比较:用 == 或 != 操作符来比较两个迭代器,
如果两个迭代器对象指向同一个元素,则它们相等,否则就不相等。

5. 迭代器应用的程序示例
假设已声明了一个 vector 型的 ivec 变量,要把它所有元素值重置为 0,可以用下标操作来完成:

// reset all the elements in ivec to 0
for (vector::size_type ix = 0; ix != ivec.size(); ++ix)
  ivec[ix] = 0;

上述程序用 for 循环遍历 ivec 的元素,for 循环定义了一个索引 ix ,每循环迭代一次 ix 就自增 1。
for 循环体将 ivec 的每个元素赋值为 0。

更典型的做法是用迭代器来编写循环:
// equivalent loop using iterators to reset all the elements in ivec to 0
for (vector::iterator iter = ivec.begin(); iter != ivec.end(); ++iter)
  *iter = 0;   // set element to which iter refers to 0

6. const_iterator
每种容器类型还定义了一种名为 const_iterator 的类型,该类型只能用于读取容器内元素,但不能改变其值。

如果 text 是 vector 类型,程序员想要遍历它,输出每个元素,可以这样编写程序:

// use const_iterator because we won't change the elements
for (vector::const_iterator iter = text.begin(); iter != text.end(); ++iter)
  cout << *iter << endl;      // print each element in text

3.4.1. 迭代器的算术操作
除了一次移动迭代器的一个元素的增量操作符外,
vector 迭代器(其他标准库容器迭代器很少)也支持其他的算术操作。
这些操作称为迭代器算术操作(iterator arithmetic),包括:

iter + n  ;   
iter - n  ;
可以对迭代器对象加上或减去一个整形值。
这样做将产生一个新的迭代器,其位置在 iter 所指元素之前(加)或之后(减) n 个元素的位置。
加或减之后的结果必须指向 iter 所指 vector 中的某个元素,或者是 vector 末端的后一个元素。
加上或减去的值的类型应该是 vector 的 size_type 或 difference_type 类型(参考下面的解释)。

iter1 - iter2
该表达式用来计算两个迭代器对象的距离,
该距离是名为 difference_type 的 signed 类型 size_type 的值,这里的 difference_type 是 signed 类型,
因为减法运算可能产生负数的结果。该类型可以保证足够大以存储任何两个迭代器对象间的距离。
iter1 与 iter2 两者必须都指向同一 vector 中的元素,或者指向 vector 末端之后的下一个元素。

3.5. 标准库 bitset
有些程序要处理二进制位的有序集,每个位可能包含 0(关)1(开)值。
位是用来保存一组项或条件的 yes/no 信息(有时也称标志)的简洁方法。
标准库提供的 bitset 类简化了位集的处理。

要使用 bitset 类就必须包含相关的头文件:
#include
using std::bitset;

3.5.1. bitset 对象的定义和初始化
表 3.6. 初始化 bitset 对象的方法
bitset b;            // b 有 n 位,每位都 0
bitset b(u);         // b 是 unsigned long 型 u 的一个副本
bitset b(s);         // b 是 string 对象 s 中含有的位串的副本
bitset b(s, pos, n); // b 是 s 中从位置 pos 开始的&nbps;n 个位的副本。

bitset<32> bitvec;      // 32 bits, all zero
这条语句把 bitvec 定义为含有 32 个位的 bitset 对象。
和 vector 的元素一样,bitset 中的位是没有命名的,程序员只能按位置来访问。
位集合的位置编号从 0 开始,因此,bitvec 的位序是从 0 到 31。
以 0 位开始的位串是低阶位(low-order),以 31 位结束的位串是高阶位(high-order)。

1. 用 unsigned 值初始化 bitset 对象
当用 unsigned long 值作为 bitset 对象的初始值时,该值将转化为二进制的位模式。
而 bitset 对象中的位集作为这种位模式的副本。
如果 bitset 类型长度大于 unsigned long 值的二进制位数,
  则其余的高阶位将置为 0;
如果 bitset 类型长度小于 unsigned long 值的二进制位数,
  则只使用 unsigned 值中的低阶位,超过 bistset 类型长度的高阶位将被丢弃。

2. 用 string 对象初始化 bitset 对象
当用 string 对象初始化 bitset 对象时,string 对象直接表示为位模式。
从 string 对象读入位集的顺序是从右向左(from right to left):

string strval("1100");
bitset<32> bitvec4(strval);

bitvec4 的位模式中第 2 和 3 的位置为 1,其余位置都为 0。
如果 string 对象的字符个数小于 bitset 类型的长度,则高阶位置为 0;

不一定要把整个 string 对象都作为 bitset 对象的初始值。
相反,可以只用某个子串作为初始值:
string str("1111111000000011001101");
bitset<32> bitvec5(str, 5, 4);           // 4 bits starting at str[5], 1100
bitset<32> bitvec6(str, str.size() - 4); // use last 4 characters

3.5.2. bitset 对象上的操作
表 3.7. 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 流

阅读(985) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~