第3章 字符串、向量和数组
- 抽象数据类型库
- string 支持可变长字符串
- vector 可变长集合
- 迭代器 访问string中字符和vector中的元素
- 内置基础类型 数组 字符
3.1 命名空间的using声明
- using声明 就无需专门的前缀 例如
using std::cout;
- 每个名字都需要独立的using声明 每条声明都需以分号结束
- 头文件不应包含using声明 不经意间包含的名字可能引起冲突
3.2 标准库类型string
- 需要包含string头文件
#include<string>
3.2.1 定义和初始化string对象
#include<string>
using std::string;
string s1;
string s2 = s1;
string s3 = "hiya";
string s4(10, 'c');
|
初始化string对象的方式 |
string s1 |
默认初始化,s1 是个空字符串 |
string s2(s1) |
s2 是s1 的副本 |
string s2 = s1 |
等价于s2(s1) ,s2 是s1 的副本 |
string s3("value") |
s3 是字面值“value”的副本,除了字面值最后的那个空字符外 |
string s3 = "value" |
等价于s3("value") ,s3 是字面值"value"的副本 |
string s4(n, 'c') |
把s4 初始化为由连续n 个字符c 组成的串 |
- 直接初始化和拷贝初始化
- 拷贝初始化 用等号=初始化一个变量
- 直接初始化 不使用等号
- 当要初始化的值有多个时如上方
s4
- 一般只能使用直接初始化
- 用拷贝初始化也行 但需要显示的创建一个临时对象用于拷贝 如
string s8=string(10, 'c');
(不推荐)
3.2.2 string对象上的操作
- 定义通过函数名的操作 定义运算符在该类对象上的新含义
|
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 连接后的结果 |
s1=s2 |
用s2 的副本代替s1 中原来的字符 |
s1==s2 |
如果s1 和s2 中所含的字符完全一样,则它们相等;string 对象的相等性判断对字母的大小写敏感 |
s1!=s2 |
判断s1 和s2 中所含的字符是否不相等 |
< , <= , > , >= |
利用字符在字典中的顺序进行比较,且对字母的大小写敏感 |
- 读写string对象
- 读取未知数量的string对象
while(cin>>word){...}
遇到文件结束标记或非法输入循环结束
- 使用getline读取一整行
- 读入流中内容 遇到换行符为止 换行符也被读入但不存到string对象中
- string的empty和size操作
.empty()
判断字符串是否为空返回布尔值 if(!line.empty()){...}
逻辑非运算符(!)
.size()
返回string对象长度
string::size_type
类型
- 标准库类型与机器无关的特性
- 无符号类型 用于存放string类的size函数返回值的变量的类型 允许使用auto/decltype来推断变量的类型 避免混用带符号和无符号数
- 比较string对象
- 为string对象赋值 将一个对象的值赋给另一个对象
- 两个string对象相加
+
或者+=
(将右侧string对象追加到左侧string对象后面)
- 字面值和string对象相加 必须确保每个加法运算符的两侧运算对象至少有一个是string
- 其中
string s6=s1+","+"world";
也正确等价于string s6=(s1+",")+"world";
(s1+",")
结果是一个sring对象每个加法运算符都有一个string对象
- 字符串字面值与string是不同的类型
3.2.3 处理string对象中的字符
|
cctupe头文件中的函数 |
isalnum(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 是大写字母,输出对应的小写字母;否则原样输出c |
toupper(c) |
当c 是小写字母,输出对应的大写字母;否则原样输出c |
- 建议使用c++版本中的c标准库
- c++兼容c语言标准库 如C语言头文件为name.h,c++则为其命名为cname.h,即在文件名前添加字母c,在cname头文件中定义的名字从属std命名空间
- 处理每个字符 使用基于范围的for语句
for(declaration:expression){...}
expression部分是一个对象用于表示一个序列;declaration部分定义一个变量用于访问序列中的基础元素;每次迭代declaration部分会被初始化为declaration部分的下一个元素值,可使用auto关键字来决定declaration变量的类型
- 使用范围for语句改变字符串中的字符
- 必须把循环变量定义为引用类型如
for(auto &c:s){...}
- 只处理一部分字符
- 需要访问的只是其中一个字符 不需要访问全部(范围for循环)
- 下标运算符(
[]
) 接收输入参数(索引 下标)类型为string::size_type
(带符号类型的值会自动转化为string::size_type
的无符号类型)表示访问字符的位置,返回该位置上字符的引用;超出范围的下标会出错
- 使用下标执行迭代
for(decltype(s.size()) index=0;index !=s.size()&& !isspace(s[index]);++index){...}
使用逻辑与运算符(&&),c++规定只有当左侧运算对象为真时才会检查右侧运算对象的情况,可以确保!isspace(s[index])中index的值不超过s.size()
- 注意检查下标的合法性
0<=index<size()
定义时使index类型为string::size_type
- 使用下标执行随机访问
3.3 标准库类型vector
- vector(也称为容器) 表示对象的集合 所有对象的类型都相同 也有对应索引用于访问对象
- 使用包含头文件
#include <vector>
- vector是一个类模板,模板本身不是类或函数可以看作编译器生成类或函数编写的一份说明,编译器根据类模板创建类或函数的过程称为实例化,使用时须指出实例化类型,可在模板名字后面的尖括号中指定类型;vector并非类型需包含元素的类型如
vector<int>
是一个类型
- 不存在包含引用的vector(引用不是对象)
- 老式编译器右侧两个尖括号处须留有一个空格
vector<vector<int> >
3.3.1 定义和初始化vector对象
|
初始化vector对象的方法 |
vector<T> v1 |
v1 是一个空vector ,它潜在的元素是T 类型的,执行默认初始化 |
vector<T> v2(v1) |
v2 中包含有v1 所有元素的副本 |
vector<T> v2 = v1 |
等价于v2(v1) ,v2 中包含v1 所有元素的副本 |
vector<T> v3(n, val) |
v3 包含了n个重复的元素,每个元素的值都是val |
vector<T> v4(n) |
v4 包含了n个重复地执行了值初始化的对象 |
vector<T> v5{a, b, c...} |
v5 包含了初始值个数的元素,每个元素被赋予相应的初始值 |
vector<T> v5={a, b, c...} |
等价于v5{a, b, c...} |
- 拷贝vector时两个对象的类型必须相同
- 列表初始化vector对象
- 拷贝初始化只能提供一个初始值
- 类内初始化只能使用拷贝初始化或者花括号
- 初始元素值的列表只能把初始值都放在花括号里进行列表初始化 不能放在圆括号里
- 创建指定数量的元素
- 如
vector<int> ivec(10,-1);
//对象元素个数和所有元素的统一初始值来初始化
- 值初始化
- 只提供vector对象元素个数略去初始值,库会创建一个值初始化的元素初值,初值由对象元素类型决定:内置类型如int 初始值为0;类类型如string 则由类默认初始化
- 列表初始化还是元素数量
- 使用圆括号() 值用来构造vector对象
- 使用花括号{} 列表初始化 若使用花括号但提供的值又不能列表初始化要考虑用该值构造vector对象
3.3.2 向vector对象中添加元素
.push_back()
将值当成vector对象尾元素压到push对象尾端back
- 除非所有元素的值都一样 否则更有效的方法是定义一个空vector 再push_back
- 向vector对象添加元素蕴含的编程假定
- 若循环体内部包含向vector对象添加元素的语句则不能使用范围for循环
3.3.3 其他vector操作
|
vector 支持的操作 |
v.emtpy() |
如果v 不含有任何元素,返回真;否则返回假 |
v.size() |
返回v 中元素的个数 |
v.push_back(t) |
向v 的尾端添加一个值为t 的元素 |
v[n] |
返回v 中第n 个位置上元素的引用 |
v1 = v2 |
用v2 中的元素拷贝替换v1 中的元素 |
v1 = {a,b,c...} |
用列表中元素的拷贝替换v1 中的元素 |
v1 == v2 |
v1 和v2 相等当且仅当它们的元素数量相同且对应位置的元素值都相同 |
v1 != v2 |
|
< ,<= ,> , >= |
顾名思义 以字典顺序进行比较 |
- 计算vector内对象的索引
- 不能用下标的形式添加元素
- 可用于访问已存在的元素 不能用于添加元素
- 下标访问不存在的元素会导致缓存区溢出 确保下标合法尽可能使用范围for循环
3.4 迭代器介绍
- 所有标准库容器都可以使用迭代器,但只有少数几种才同时支持下标运算符 (string不属于容器类型 但支持很多类似操作)
- 迭代器可访问某元素 从一个元素移到另一个元素 有有效和无效之分
3.4.1 使用迭代器
auto b = v.begin();
返回指向第一个元素(或第一个字符)的迭代器
auto e = v.end();
返回指向最后一个元素的下一个的迭代器(尾后迭代器)
- 如果容器为空begin和end返回同一个尾后迭代器
|
标准容器迭代器的运算符 |
*iter |
返回迭代器iter 所指向的元素的引用 |
iter->mem |
解引用iter并获取该元素名为mem的成员 等价于(*iter).mem |
++iter |
令iter 指示容器中的下一个元素 |
--iter |
令iter 指示容器中的上一个元素 |
iter1 == iter2 |
判断两个迭代器是否相等 相等指示同一个元素或同一容器的尾后迭代器 |
iter1 != iter2 |
判断两个迭代器是否不相等 |
- 将迭代器从一个元素移动到另外一个元素
- 递增++运算符
- 注意:尾后迭代器不指向某元素 不能进行递增或者解引用操作
- 泛型编程 c++程序员习惯性使用!=而不是<或者下标的原因:这种编程风格在标准库提供的所有容器上都有效
- 迭代器类型
- iterator和const_iterator(能读取但不能修改所指元素的值)
- 迭代器
- 迭代器概念本身
- 容器定义的迭代器类型
- 某个迭代器对象
- begin和end运算符
- 返回的具体类型由对象是否是常量决定
- 如对象只需读无需写 最好使用cbegin和cend返回const_iterator类型
- 结合解引用和成员访问操作
- (*it).empty();检查it迭代器所指字符串是否为空
- 箭头运算符(->)等价于解引用点 如
iter->mem
等价于(*iter).mem
- 某些对vector对象的操作会使迭代器失效
- 范围for循环中向vector对象添加元素
- 任何一种可能改变vector对象容量的操作
- 凡是使用了迭代器的循环体都不要向迭代器所属容器添加元素
3.4.2 迭代器运算
|
vector和string迭代器支持的运算 |
iter + n |
迭代器加上一个整数值仍得到一个迭代器 迭代器指示的新位置和原来相比向前移动了若干个元素 结果迭代器或者指示容器内的一个元素 或者指示容器尾元素的下一位置 |
iter - n |
迭代器减去一个证书仍得到一个迭代器 迭代器指示的新位置比原来向后移动了若干个元素 结果迭代器或者指向容器内的一个元素 或者指示容器尾元素的下一位置 |
iter1 += n |
迭代器加法的复合赋值语句 将iter1 加n的结果赋给iter1 |
iter1 -= n |
迭代器减法的复合赋值语句 将iter2 减n的加过赋给iter1 |
iter1 - iter2 |
两个迭代器相减的结果是它们之间的距离 也就是说 将运算符右侧的迭代器向前移动差值个元素后得到左侧的迭代器 参与运算的两个迭代器必须指向的是同一个容器中的元素或者尾元素的下一位置 |
> 、>= 、< 、<= |
迭代器的关系运算符 迭代器位置比较 |
- 迭代器的算术运算
- 迭代器距离 类型difference_type 带符号整型数 距离可正可负
- 使用迭代器运算
- 二分搜索 有序列表 先从中间元素开始搜索 中间元素小于 则在后半部分继续搜索beg=mid+1(中间的已经验证过) 反之前半部分end=mid
3.5 数组
- 类似vector的数据结构 数组大小固定特殊应用运行性能较好 但损失了一些灵活性
3.5.1 定义和初始化内置数组
- 数组是一种复合类型 如a[d] a为数组名字 d为数组维度(维度说明数组元素个数须大于0)
- 默认情况下数组元素默认初化
- 定义数组时须指定数组类型 不允许使用auto关键字由初始值列表推断类型 ;另外数组元素为对象 故不存在引用的数组
- 显式初始化数组元素
- 列表初始化 如未指定维度 编译器会根据初始值数量计算并推测出来;若指明维度则初始量总数不应超过指定大小,若小于指定大小 则剩下元素被初始化为默认值
- 字符数组的特殊性
- 可用字符串字面值对此类数组初始化 结尾处还有一个空字符
'\0'
(维度记得加上一个空字符)
- 不允许拷贝和赋值
- 不能将数组的内容拷贝给其他数组作为初始值 也不能用数组为其他数组赋值
- 理解复杂的数组声明
- 默认情况下 类型修饰符从右向左依次绑定,但就数组而言 因为维度是紧跟着声明名字的 就从数组的名字开始由内向外阅读(首先是圆括号括起来的部分)
3.5.2 访问数组元素
- 范围for或下标运算符来访问
- 数组下标的类型 size_t 一种机器相关事务无符号类型
- 除了大小固定外 其他用法与vector类似
- 检查下标的值 下标是否在合理范围内 防止缓存区溢出
3.5.3 指针和数组
- 使用数组时编译器一般会把它转换成指针
- 对数组使用下标运算符可以得到该数组指定位置元素,对数组元素用取地址符就能得到指向该元素的指针
- 很多用到数组名的地方 编译器都会自动将其替换为一个指向数组首元素的指针
- 对使用数组作为auto变量初始值时 推断出的类型是指针而非数组
- 指针也是迭代器
- 迭代器支持的运算 数组的指针全部支持
- 递增 遍历等;
- 尾后指针: 最后一个元素的后一个位置 不指向具体的值 不能对其执行解引用或递增操作
- 标准库函数begin和end
- 数组不是类类型 使用方式
int *beg=begin(ia);
- 指针运算
- 解引用 递增 比较 与整数相加 两个指针相减
- 两指针相减结果类型ptrdiff_t 定义在cstddef头文件中的机器相关类型 带符号类型
- 若指向不相关对象则两指针不能比较
- 解引用和指针运算的交互
int last=*(ia+4);
ia前进4个元素后的新地址
int last=*(ia)+4;
等价于int last=ia[0]+4;
- 下标和指针
- 对数组执行下标运算其实是对数组元素的指针执行下标运算
int *p=&ia[2];``int j=p[1];
//p[1]等价于*(p+1) int k=p[-2];
p[-2]等价于*(p-1)即ia[0]表示的元素
- 内置下标运算符所用的索引值不是无符号类型
3.5.4 C风格字符串
- 字符串字面值 c++继承自C的c风格字符串 存放在字符数组中并以空字符
('\0')
结束
|
c风格字符串的函数 |
strlen(p) |
返回p 的长度,空字符不计算在内 |
strcmp(p1, p2) |
比较p1 和p2 的相等性。如果p1==p2 ,返回0;如果p1>p2 ,返回一个正值;如果p1<p2 ,返回一个负值。 |
strcat(p1, p2) |
将p2 附加到p1 之后,返回p1 |
strcpy(p1, p2) |
将p2 拷贝给p1 ,返回p1 |
- 比较字符串strcmp
- 目标字符串的大小由调用者指定
- 建议使用标准库string更为安全 高效
3.5.5 与旧代码接口
- 混用string对象和c风格字符串
- 对string类型字符串使用
s.c_str();
返回一个C风格的字符串(一个指针 指向一个以空字符结束的字符数组 而数组保存的数据恰好与string对象一致 指针类型为const char* 确保我们不会改变字符数组的内容)
- 无法保证c_str()返回的数组一直有效 后续操作改变s的值可能让之前返回的数组失效,所以若后面还要用到该数组则应该提前拷贝一份
- 使用数组初始化vector对象
- 允许使用数组来初初始化vector对象 需要声明要拷贝区域的首元素地址和尾后指针就行
vector<int> ivec(begin(int_arr),end(int_arr));
- 建议尽量使用标准库类型而非指针
3.6 多维数组
- 多维数组其实是数组的数组
- 如 二维数组 两个维度 一个维度表示数组本身大小 另一个维度表示其元素(也是数组)大小
- 多维数组的初始化
- 多维数组的下标引用
- 如果表达式含有的下标运算符数量和数组的维度一样多,该表达式的结果将是给定类型的元素;反之,如果表达式含有的下标运算符数量比数组的维度小,则表达式的结果将是给定索引处的一个内层数组
- 使用范围for语句处理多维数组
- 把管理数组索引的任务交给了系统来完成
- 要使用范围for语句处理多维数组,除了最内层循环 其他层循环的控制变量声明成引用类型(为了避免数组被自动转为指针)
- 指针和多维数组
- 当程序使用多维数组的名字时 编译器会自动将其转换成指向数组首元素的指针(第一个内层数组的指针)
- 类型别名简化多维数组的指针
using int_array=int[4];
typedef int int_array[4];
//与上式等价
小结
- 标准库类型:tring对象是一个可变长的字符序列,vector对象是一组同类型对象的容器
- 迭代器允许容器中的对象进行间接访问,string和vector对象可通过迭代器访问元素或者在元素间移动
- 应优先选用标准库提供的类型,再考虑内置低层的替代品数组或指针
术语表