C++ Primer 第三章 标准库类型
3.1 命名空间的using声明
using声明是对某个命名空间做引入。主要作用是简化代码编写。
3.2 标准string类型
首先要明确类型是类类型,意味着它有构造函数,也类似我们自定义的类一样的其他类对象。
它有几种初始化方式如下
string s1 ; // 调用默认构造函数初始化对象
strng s2(s1) ; // 将S2初始化为S1的一个副本
string s3("value") ; // 用一个字符串值初始化对象
string s4(n,'c') ; // 用N个字符‘C’组成字符串作为初始化s4的值
特别要注意的是第一种初始化方式,虽然默认构造函数是没有参数的但是不能因此就写成 string s1()
我们可以复习一下内置类型的默认初始化方式和类类型做个比较:
string s1 ; // 调用默认构造函数初始化
int i ; // 要根据定义位置来确定初始化值,全局变量一律初始化为0, 局部变量是一个随机数,称为未初始化
string类型可以用于标准输入输出。
while(getline(cin, line))
{
cout << line << endl;
}
cin >> line 输入内容并保存到变量line,输入时会忽略输入左面的的空格直到非空格字符才开始读取,直到再次读到空格输入结束。
例如如果你输入" zhang san " 实际line保存的是"zhang" 。
getline(cin,line) 是一个系统函数,可以输入标准行内容,这个函数不会忽略任何内容一直读取用户输入并保存到line,直到用户输入换行函数才结束,结束时函数会返回cin的引用。 如果用户刚开始就输入换行符那么line的内容就是"" 。
上文说过string是类类型所有有很多类成员(属性和成员函数),下面就是一些常用的操作
s.empty() ; // 判断s是否为空,相当于s.size()==0
s.size() ; // s的长度
s[n] ; // n位置的字符(左值返回)
s1+s2 ; // 返回s1和s2连接的串
s1=s2 ; // 把s1替换为s2的副本
s1==s2 ; // 判断s1,s2是否相等
!=,<,<=,>,>= // 按字典顺序比较
s.insert(...) ; // 插入字符操作,有多个重载可用
s.size()函数返回一个表示字符串长度大小的值,其类型并不是我们认为的int类型,而是一个叫string::size_type的类型,为什么不用INT而新创造一个类型呢,原因有如下两点:
1. 取值范围不同,int有固定的取值范围,并且可以取负数,但字符串长度是不可能为负的,并且长度的大小很可能会超过int的范围而导致溢出
2. int的范围大小与机器相关,有的机器上范围大些,有点机器小一些。但是字符串长度应该是个不能随机器发生大小改变的值,所以即使用无符号int来表示串大小也是不合适的
s[n]可以作为左值操作,也就是说改操作既可以返回N位置的字符也可以替换N位置的字符。 n值一定要在有效范围内,负值或者超过串大小会引发严重异常
a[1] = 'b' ; // 字符一定要用单引号,双引号表示字符串
cout << a << endl; // 输出 "abaa"
cctype 头文件所包含的函数主要用来测试字符值,以下是一个列表,但是对于初学者来说自己上机操作一下,后两个返回的是int型,确实很意外,强制转换一下,很简单。
isalnum(c) ; // 假如c是字母或数字,则为true
isalpah(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是大写字母,则返回小写字母形式(对应的int值),否则返回c。
toupper(c) ;// 假如c是小写字母,则返回大些字母形式(对应的int值),,否则返回c。
可以举个简单的例子
isalpah(line[0]); // true
isdigit(line[1]); // true
ispunct(line[4]); // true
tolower(line[0]); // 返回A对应的ascii 65
3.3 标准vector类型
C++标准库容器有好几类,后面会详细介绍。为什么在这里单单要先介绍vector容器呢?这个容器最常用。对于大部分应用来说用它足以满足你的要求。
vector是个类模板,如果你了解JAVA或C#范型编程的话可以理解为范型类。泛型最大的好处是只需定义一个类或函数就可以提供不同类型版本的操作。
它的初始化有如下几种方式:
Vector<T> v1 ; // 默认构造函数v1为空
Vector<T> v2(v1) ; // v2是v1的一个副本
Vector<T> v3(n, i) ; // v3包含n个值为i的元素 参数 T 如果是类类型则一定要有拷贝构造函数(未定义的情况下系统会自动分配一个)
Vector<T> v4(n) ; // v4含值初始化的元素个副本 参数 T 如果是类类型则一定要有默认构造函数(未定义的情况下系统会自动分配一个) 如果是内置类型则分配n个0
对于类类型如果不能满足红色标示的要求编译会失败。 关于类类型的拷贝构造函数和默认构造函数后续章节有介绍
Vector对象有几种最重要的操作
v.push_back(t) ; // 在数组的最后添加一个值为t的数据
v.size() ; // 当前使用数据的大小 返回vector<T>::size_type类型的长度值,其意义类似上面讲过的string::size_type
v.empty() ; // 判断vector是否为空
v[n] ; // 返回v中位置为n的元素 和string类型下标操作类似 是个左值操作
v1=v2 ; // 把v1的元素替换为v2元素的副本
v1==v2 ; // 判断v1与v2是否相等
!=、<、<=、>、>= ; // 保持这些操作符惯有含义
关于vector需要注意的是下标不能用来添加元素操作。
list[0] = 1; // 错误 list没有任何数据,下标操作只针对存在的元素
list.push_back(2) ; // 增加了一个值为2的元素
list[0] = 1 ; // ok 将第一个元素由2更改成1
3.4 迭代器简介
迭代器是用于对容器做遍历操作的类型。 迭代器和后面要说到的指针非常类似。但它侧重列表的迭代遍历,所有有一些快捷属性可用。
list.begin() // 表示容器的第一项迭代器,如果容器有值指向list[0]
list.end() // 表示容器的哨兵位,也就是最后一项后面的一项,只是用来表示迭代器已遍历到容器末端
iter++ // 迭代器自增表示向后移一位,指向下一个项
vector<string> list(10,"value");
for(vector<string>::iterator iter = list.begin(); iter != list.end(); iter++)
{
cout << *iter << endl; // 要访问迭代器当前值必须解引 *
*iter = "new value"; // 也可以解引后更改其值,可以看出解引是左值操作
}
// 如果想遍历容器又不想使用迭代器可以用下标操作
for(vector<string>::size_type ix = 0; ix != list.size(); ix)
{
cout << list[ix] << endl;
list[ix] = "new value";
}
当容器为空时 list.begin() == list.end()
上面定义的是常规迭代器,还有一种叫常量迭代器的迭代器类型。 我们也能定义迭代器常量,要弄清楚这些拗口的概念可以看下面代码示例
*iter = "new value"; // 这是不允许的,因为const_iterator告诉编译器我代表的是一个常量,所以不能通过任何手段改变其值
const vector<string>::iterator iter // 定义一个常迭代器,迭代器代表的变量,但迭代器本身是常量,所以可以更改代表的内容但无法更改迭代器
*iter = "new value"; // 没问题
iter ++ // 不允许,迭代器是常量所以无法让他指向其他项
const vector<string>::const_iterator iter // 这样定义的迭代器只能读取初始化指向的列表项内容,既无法向后移动迭代器也无法更改项值内容
最后一个定义很有意思:迭代器指向了常量,所以不能通过解引更改常量值,同时迭代器本省也是常量所以无法更改迭代器的指向
迭代器不是每次只能向后移动一位,可以通过迭代器与一个整形字面值相加向后移动多位
iter + n // 迭代器向后移动n位并产生一个指向移位后新位置的迭代器
两个迭代器可以做相减运算结果是类型为difference_type的两个迭代器之间的距离(两个操作数一定要指向同一容器否则报错)
3.5 标准bitset类型
标准库中bitset类型用来处理二进制位的有序集,bitset类型简化了位集的处理,使用bitset时需要包含头文件#include<bitset>
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中含有的位串的副本,s是01串 |
bitset<n> b(s, pos, n); |
b是s中从位置pos开始的n个位的副本 |
用unsigned long值初始化bitset对象
用unsigned long值初始化bitset对象的时候,将long值转化为二进制模式,然后拷贝到bitset的右方(bitset右边为低阶位,左边为高阶位),string位数多了将被截断,少了bitset将在前面补零。
用string对象初始化bitset对象
从string对象初始化bitset对象,需要注意的是,复制拷贝相当于从string位模式平移到了bitset。
例如:
string str(”11001010”);
bitset<32> bitvec(str);
这个时候,bitvec是这样的:0000 0000 0000 0000 0000 0000 1100 1010最右边是bitset的低阶位,即bitvec[0],bitset[1] …
bitset对象上的操作
操作调用方式 |
操作说明 |
b.any() |
测试b中是否有存在1的位 |
b.none() |
测试b中是否全0 |
b.count() |
测试b中置1的位个数 |
b.size() |
b中所有二进制位个数 |
b[pos] |
访问下标为pos位置的位值 |
b.test(pos) |
测试pos位置的二进制位是否为1 |
b.set() |
将b所有位置1 |
b.set(pos) |
将b中pos位置的位置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流 |
注:
Ø b.cout和b.size()返回的是size_t类型,该类型定义在cstddef头文件中(C标准头文件stddef.h的C++版本)
Ø b[pos]可以作为左值,即可以用来改变pos位置的值