string
表示可变长的字符数组。
vector
存放的是某种给定类型对象的可变长序列。
数组和其他内置类型一样,实现与硬件密切相关,在灵活性上稍显不足。
命名空间using
声明:using namespace::name;
例如:using std::cin;
。
头文件不应包含using
声明,防止名字冲突。
3.1 标准库类型 string
3.1.1 头文件和声明
#include <string>
using std::string;
3.1.2 定义和初始化
string s1; //默认初始化,空字符串
string s2 = s1; //s2是s1的副本,等价于 s2(s1)
string s3 = "hiya"; //s3是字符串字面值的副本,等价于s3("hiya")
string s4(10, 'c'); //s4的内容是cccccccccc
3.1.3 string 对象上的操作
操作 | 意义 |
---|---|
os << s | 将 s 写到输出流 os 中,返回 os |
is >> s | 从 is 中读取字符串赋给 s,字符串以空白分隔,返回 is |
getline(is, s) | 从 is 中读取一行赋给 s,返回 is |
s.empty() | s 为空返回 true,否则返回 false |
s.size() | 返回 s 中字符的个数 |
s[n] | 返回 s 中第 n 个字符的引用,位置 n 从0记起 |
s1 + s2 | 返回连接后的结果 |
s1 = s2 | 用 s2 的副本代替 s1 |
s1 == s2 | 如果 s1 和 s2 中所含的字符完全一样,则他们相等 |
s1 != s2 | string 对象的相等性判断对大小写敏感 |
<, <=, >, >= | 利用字符在字典里的顺序进行比较,大小写敏感 |
3.1.4 读取未知数量的 string 对象
int main()
{
string word;
while (cin >> word)
cout << word << endl;
return 0;
}
直到遇到结束标记或者非法输入才会停止
3.1.5 使用 getline 读取一整行
int main()
{
string line;
while (getline(cin,line))
cout << line << endl;
return 0;
}
getline
函数的参数是一个输入流和一个string
对象,getline
从给定的输入流中读入内容,直到遇到换行符为止(注意换行符也被读进来了),然后把所读的内容存入string
对象中(注意不存换行符)。如果输入一开始就是换行符,那么所得的结果是个空string
。
3.1.6 string::size_type 类型
是一个无符号类型的值。如果一个表达式中已经有size
函数就不要再使用int
了,这样可以避免混用int
和unsigned
可能带来的问题。
string
对象可以和字符字符串字面值相加,只要保证 + 号两侧的运算对象至少有一个string
对象即可。
由于某些历史原因,也为了和 C 兼容,所以 C++ 中的字符串字面值并不是标准库string
类型。
3.1.7 处理 string 对象里的字符
包含在cctype
头文件中:
函数 | 意义 |
---|---|
isalunm(c) | 当 c 是字母或数字时为真 |
isalpha(c) | 当 c 是字母时为真 |
iscntrl(c) | 当 c 是控制字符时为真 |
isdigit(c) | 当 c 是数字时为真 |
isgraph(c) | 当 c 不是空格但可打印时为真 |
islower(c) | 当 c 是小写字母时为真 |
isprint(c) | 当 c 是可打印字符时为真(即 c 是空格或 c 具有可视形式) |
ispunct(c) | 当 c 是标点符号时为真(即 c 不是控制字符、数字、字母、可打印空白中的一种) |
isspace(c) | 当 c 是空白时为真(即 c 是空格、横向制表符、纵向制表符、回车符、换行符、进纸符中的一种) |
isupper(c) | 当 c 是大写字母时为真 |
isxdigit(c) | 当 c 是十六进制数字时为真 |
tolower(c) | 输出 c 的小写 |
toupper(c) | 输出 c 的大写 |
C++ 标准库除了定义 C++ 语言特有的功能外,也兼容了 C 语言的标准库。C 语言的头文件形如 name.h,C++ 则将这些文件命名为 cname。因此cctype
头文件和ctype.h
头文件的内容是一样的,只不过从命名规范上来讲更符合 C++ 语言的要求。特别地,在名为 cname 的头文件中定义的名字从属于命名空间std
。
3.1.8 范围 for
for (declaration: expression)
statement
expression 是一个对象,用于表示一个序列;declaration 负责定义一个变量,该变量将被用于访问序列中的基础元素,每次迭代,declaration 部分的变量会被初始化为 expression 部分的下一个元素值。
使用范围for输出
string str("some string");
for (auto c : str)
cout << c << endl;
使用范围for修改
string s("Hello World!");
for (auto &c : s)
c = toupper(c);
cout << s << endl;
3.1.9 下标运算符 [ ]
[ ]接收的输入参数是string:size_type
类型的值,这个参数表示要访问的字符的位置,返回值是该位置上字符的引用。string
对象的下标必须大于等于0而小于s.size()
。
3.2 标准库类型 vector
vector
表示对象的集合,其中所有对象的类型都相同,也常被称作容器,是一种类模板
#include <vector>
using std::vector;
编译器根据模板创建类或函数的过程称为实例化,对于类模板,我们通过提供一些额外信息来指定模板到底实例化成什么样的类:在模板名字后面跟一对尖括号,在括号内放上信息
vector<int> ivec;
vector<Sales_item> Sales_vec;
vector<vector<string>> file;
在早期版本的 C++ 标准中如果vector
的元素还是vector
(或者其他模板类型),则其定义的形式与现在的 C++11 新标准略有不同。过去,必须在外层vector
对象的右尖括号和其元素类型之间添加一个空格,如应写成
vector<vector<int> >
3.2.1 初始化
默认初始化。
列表初始化。
值初始化:通常情况下,可以只提供vector对象容纳的元素数量而略去初始值。此时库会创建一个值初始化的元素初值,并把它赋给容器中的所有元素。这个初值由vector对象中元素的类型决定。内置类型:0;类类型:由类默认初始化。
3.2.2 添加元素
push_back
:负责把一个值当成vector
对象的尾元素压到vector
对象的尾端
vector<int> v2;
for (int i = 0; i != 100; ++i)
v2.push_back(i);
如果循环内部包含有向vector
对象添加元素的语句,则不能使用范围for
循环。
3.2.3 其他vector操作
函数 | 含义 |
---|---|
v.empty() | 如果 v 不含有任何元素,返回真;否则返回假 |
v.size() | 返回 v 中元素的个数 |
v.push_back(t) | 向 v 的尾端添加一个值为 t 的元素 |
v[n] | 返回 v 中第 n 个位置上元素的引用 |
v1 = v2 | 用 v2 中的元素拷贝替换 v1 中的元素 |
v1 = | 用列表中元素的拷贝替换 v1 中的元素 |
v1 == v2 | v1 和 v2 相等当且仅当它们的元素数量相同且对应位置的元素值都相同 |
v1 != v2 | |
<,<=,>,>= | 以字典顺序进行比较 |
3.2.4 vector 的 size_type
vector<int>::size_type //正确
vector::size_type //错误
和string
一样,vector
对象的下标也是从0开始计算,vector
对象(以及string
对象)的下标运算符可用于访问已存在的元素,而不能用于添加元素。
3.3 迭代器
所有标准库容器都可以使用迭代器,但只有少数的几种才同时支持下标运算符。
严格来讲,string
不属于容器类型,但是string
支持很多与容器类型相似的操作。vector
支持下标运算符,这点和string
一样。
和指针不一样,获取迭代器不是使用取地址符,有迭代器的类型同时拥有返回迭代器的成员。begin
和end
:auto b = v.begin(), e = v.end();
。
end
成员负责返回指向容器“尾元素的下一位置”的迭代器。常被称作尾后迭代器或尾迭代器。
如果容器为空,则begin
和end
返回的是同一个迭代器,都是尾后迭代器。
3.3.1 标准容器迭代器的运算符
符号 | 含义 |
---|---|
*iter | 返回迭代器 iter 所指元素的引用 |
iter->mem | 解引用 iter 并获取该元素的名为 mem 的成员,等价于(*iter).mem |
++iter | 令 iter 指向容器中的下一个元素 |
--iter | 令 iter 指向容器中的上一个元素 |
iter1 == iter2 | 判断两个迭代器是否相等 |
iter1 != iter2 | 如果指向同一元素或者他们是同一容器的尾后迭代器 |
因为end
返回的迭代器并不实际指向某个元素,所以不能对其进行递增或解引用操作。C++ 程序员习惯性地使用!=
,其原因和他们更愿意使用迭代器而非下标的原因一样:这种编程风格在标准库提供的所有容器上都有效,因为所有标准库容器都定义了==
和!=
。
3.3.2 迭代器类型
拥有迭代器的标准库类型使用iterator
和const_iterator
来表示迭代器的类型
vector<int>::iterator it; //it 能读写 vector<int> 的元素
string::iterator it2; //it2 能读写 string 对象中的元素
vector<int>const_iterator it3; //it3 只能读元素,不能写元素
string::const_iterator it4; //it4 只能读字符,不能写字符
begin
和end
返回的具体类型由对象是否是常量决定,如果对象是常量,begin
和end
返回const_iterator
;如果对象不是常量,返回iterator
。如果对象只需读操作而无需写操作的话最好使用常量类型迭代器。为了专门得到const_iterator
,C++11 标准引入两个新函数:cbegin
和cend
。任何一种可能改变容器容量的操作,都会是迭代器失效。
3.3.3 迭代器运算
string
和vector
的迭代器提供了更多额外的运算符:
iter + n
:迭代器加上一个整数值仍得到一个迭代器,迭代器指示的新位置与原来相比向前移动了若干个元素。结果迭代器或者指示容器内的一个元素,或者指示容器尾元素的下一位置。
iter - n
:迭代器减去一个整数值仍得一个迭代器,迭代器指示的新位置与原来相比向后移动了若干位置。结果迭代器或者指示容器内的一个元素,或者指示容器尾元素的下一位置。
iter1 - iter2
:两个迭代器相减的结果是它们之间的距离,也就是说,将运算符右侧的迭代器向前移动差值个元素后将得到左侧的迭代器。参与运算的两个迭代器必须指向的是同一容器中的元素或者尾元素的下一位置。
>, >=, <, <=
:迭代器的关系运算符,如果迭代器指向的容器位置在另一个迭代器所指位置之前,则说前者小于后者。
3.4 数组
因为数组的大小固定,因此在对某些特殊的应用来说程序的运行时性能较好,但是相应的也损失了一些灵活性。
数组的维度必须是一个常量表达式。
默认情况下数组的元素被默认初始化。
和内置类型的变量一样,如果在函数内部定义了某种内置类型的数组,那么默认初始化会令数组含有未定义的值。
定义数组的时候必须指定数组的类型,不允许用auto关键字由初始值的列表推断类型。
字符数组可以利用字符串字面值初始化,此时注意字符串字面值末尾的空字符,也会被拷贝到字符数组中。
不允许拷贝和赋值。一些编译器支持数组的赋值,这就是所谓的编译器扩展,但一般来说,最好避免使用非标准特性,因为可能在其他编译器上无法正常工作。
理解复杂的数组声明:由内向外阅读
int *ptrs[10]; //ptrs 是含有10个整型指针的数组
int (*Parray)[10] = &arr; //Parray 是指针,指向一个含有10个整数的数组
int (&arrRef)[10] = arr; //arrRef 是引用,引用一个含有10个整数的数组
int *(&arry)[10] = ptrs; //arry 是引用,引用一个含有10个整型指针的数组
3.4.1 访问数组元素
数组的索引从0开始。
在使用数组下标的时候,通常将其定义为size_t
类型。size_t
是一种机器相关的无符号类型,它被设计的足够大以便能表示内存中任意对象的大小。
在 cstddef 头文件中定义了size_t
类型,这个文件是 C 标准库 stddef.h 头文件的 C++ 版本。
使用数组的时候编译器一般会把它转换成指针。
通常情况下,使用取地址符来获取指向某个对象的指针,取地址符可以用于任何对象。数组的元素也是对象,对数组使用下标运算符得到该数组指定位置的元素,对数组元素应用取地址符就能得到指向该元素的指针。
数组还有一个特性:在很多时候用到数组名字的地方,编译器都会自动地将其替换为一个指向数组首元素的指针
string *p2 = nums; //等价于 p2 = &nums[0]
所以如果使用数组作为一个auto变量的初始值时,推断的类型是指针而非数组
int ia[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9,};
auto ia2(ia); // ia2 是一个整型指针,指向 ia 的第一个元素
ia2 = 42; //错误,ia2 是一个指针,不能用 int 赋值
当使用decltype
关键字时上述转换不会发生,decltype(ia)
返回的类型是由10个整数构成的数组
decltype(ia) ia3 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
ia3 = p; //错误,不能用整型指针给数组赋值
ia3[4] = i; //正确:把 i 的值赋给 ia3 的一个元素
为了让指针的使用更简单、更安全,C++11 新标准引入两个名为begin
和end
的函数,不过数组毕竟不是类类型,因此这两个函数不是成员函数。正确的使用形式是将数组作为它们的参数
int ia[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int *beg = begin(ia);
int *last = end(ia);
begin
函数返回指向 ia 首元素的指针,end
函数返回指向 ia 尾元素下一位置的指针,这两个函数定义在 iterator 头文件中
两个指针相减结果的类型是一种名为ptrdiff_t
的类型,和size_t
一样,它也是定义在 cstddef 头文件中的机器相关的类型。因为差值可能是负值,所以它是一种带符号类型。
内置的下标运算符所用的索引值不是无符号类型,这一点和vector
和string
不一样
int *p = &ia[2];
int k = p[-2]; //k 是 ia[0] 那个元素
3.4.2 C 风格字符串
按此习惯书写的字符串存放在字符数组中并以空字符结束。
C 语言标准库提供了一组函数用于操作 C 风格字符串,它们定义在 cstring 头文件中,cstring 是 C 语言头文件 string.h 的 C++ 版本。
3.4.3 与旧代码的接口
任何出现字符串字面值的地方都可以用空字符结尾的字符数组来代替。不能用string对象直接初始化指向字符的指针,为了完成该功能,string专门提供了一个名为c_str的成员函数
char *str = s; //错误,不能用 string 对象初始化 char*
const char *str = s.c_str(); //正确
我们无法保证 c_str 函数返回的数组一直有效,事实上,如果后续的操作改变了 s 的值就可能让之前返回的数组失去效用
使用数组初始化vector
对象
int int_arr[] = {0, 1, 2, 3, 4, 5};
vector<int> ivec(begin(int_arr),end(int_arr));
现代的 C++ 程序员应当尽量使用vector
和迭代器,避免使用内置的数组和指针,应该尽量使用string
,避免使用 C 风格的基于数组的字符串。
多维数组:要使用范围 for 语句处理多维数组,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型。
缓冲区溢出:主要原因是试图通过一个越界的索引访问容器内容。
difference_type
:由string
和vector
定义的一种带符号整数类型,表示两个迭代器之间的距离。