第三章 字符串、向量和数组
3.1 命名空间的using声明
-
using声明的形式:
using namespace::name;
每个名字都系要独立的using声明
- 每个using声明引入命名空间的一个成员
头文件不应包含using声明
- 头文件的内容会拷贝到所有引用它的文件中去,容易产生始料未及的名字冲突
3.2 标准库类型string
- 标准库类型string表示可变长的字符序列
- 使用string类型必须包含string头文件
- string定义在命名空间std中
3.2.1 定义和初始化string对象
-
初始化string对象的方式
//1.默认初始化,s1是一个空字符串 string s1 //2.s2是s1的副本 string s2 = s1; //3.s3是字面值value的副本,除了字面值最后的那个空字符外 string s3("value") //4.等价于上者 string s3 = "value" //5.s4初始化为由连续n个字符c组成的串 string s4(n, 'c')
直接初始化和拷贝初始化
- 使用等号为拷贝初始化(copy initialization),不使用等号为直接初始化(direct initialization)
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中所含的字符完全一样,则它们相等,对大小写敏感 s1 != s2 如果s1和s2中所含的字符完全一样,则它们相等,对大小写敏感 <、 <= 、> 、>= 利用字符在字典中的顺序进行比较,对大小写敏感
读写string对象
- 执行读取操作时,string对象会自动忽略开头的空白(即空格符、换行符、制表符等),并从第一个真正的字符开始读起,直到遇见下一处空白为止
- 多个输入输出可以连写在一起
使用getline读取一整行
- getline函数的参数是一个输入流和一个string对象
- 函数从给定的输入流中读入内容,直到遇到换行符为止(换行符也被读取进来了)
- 把所读的内容存入到那个string对象中去(不存换行符)
string的empty和size操作
- empty函数根据string对象是否为空返回一个对应的布尔值
- size函数返回string对象的长度(即string对象中字符的个数)
string::size_type类型
- size函数返回的是一个string::size_type类型的值
- size函数返回的是一个无符号整型数,避免混用带符号数和无符号数
比较string对象
- 相等性运算符(
==和!=):长度相等且包含的字符也全部相等 - 关系运算符(
<、<=、>、>=):- 如果两个string对象长度不同,且较短string对象的每个字符都与较长string对象上的字符相同,则说较短string对象小于较长string对象
- 如果两个string对象在某些对应位置上不一致,则string对象比较的结果是string对象中第一对相异字符比较的结果
为string对象赋值
- 允许把一个对象的值赋给另外一个对象
两个string对象相加
- 两个string对象相加得到一个新的string对象,其内容是把左侧的运算对象与右侧的运算对象串接而成
字面值和string对象相加
- 把string对象和字符字面值及字符串字面值混在一条语句中使用时,必须确保每个加法运算符的两侧运算对象至少有一个是string
- 字符串字面值与string是不同的类型
3.2.3 处理string对象中的字符
-
cctype头文件中定义的部分函数
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为标点符号时为真 isspace(c) c为空白时为真(即c为空格、横向制表符、纵向制表符、回车符、换行符、进纸符中的一种) isupper(c) c为大写字母时为真 isxdigit(c) c为16机制数时为真 tolower(c) 转小写 toupper(c) 转大写 -
C语言的头文件形如
name.h,C++则将这些文件命名为cname -
在名为
cname的头文件中定义的名字从属于命名空间std,而定义在名为.h的头文件中则不然
处理每个字符?使用基于范围的for语句
-
范围for(range for)
//declaration部分负责定义一个变量,该变量将被用于访问序列中的基础元素 //expression部分是一个对象,用于表示一个序列 //每次迭代,declaration部分的变量会被初始化为expression部分的下一个元素值 for (declaration : expression) statement //demo string str("some string"); for (auto c : str) cout << c << endl;
使用范围for语句改变字符串中的字符
-
必须把循环变量定义成引用类型
string s("Hello World!!!"); for (auto &c : s) { c = toupper(c); } -
使用下标进行迭代
-
使用小标执行随机访问
只处理一部分字符?
- 访问string对象中的单个字符的两种方式:
- 使用下标
- 使用迭代器
- 下标运算符接受的参数是string::size_type类型的值,返回值是该位置上字符的引用
- 下标从0开始计算
- 在访问指定字符前,首先要检查字符串是否为空,超出范围或空字符串的索引是不可预知的
3.3 标准库类型vector
-
标准库类型vector表示对象的集合,其中所有对象的类型都相同
-
vector也常被称为“容器(container)”
-
使用vector,必须包含合适的头文件
#include <vector> using std::vector; -
编译器根据模板创建类或函数的过程称为实例化(instantiation)
-
vector能容纳绝大多数类型的对象作为其元素
3.3.1 定义和初始化vector对象
-
初始化vector对象的常用方法
vector<T> v1 v1是一个空vector,其潜在元素是T类型的,执行默认初始化 vector<T> v2(v1) v2中包含有v1所有元素的副本 vector<T> 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, …} 等价于上者
列表初始化vector对象
vector<string> articles = {"a", "an", "the"};
//提供的是初始元素值的列表,则只能把初始值都放在花括号里进行列表初始化,而不能放在圆括号里
vector<string> v1{"a", "an", "the"}; //列表初始化
vector<string> v2("a", "an", "the"); //错误
创建指定数量的元素
vector<int> ivec(10, -1);
vector<string> svec(10, "hi!");
值初始化
- 存在两个特殊限制
- vector对象中元素的类型不支持默认初始化,则该方法无法完成初始化工作
- 吐过只提供元素的数量而没有设定初始值,只能使用直接初始化
vector<int> ivec(10);
vector<string> svec(10);
vector<int> vi = 10; //错误:必须使用直接初始化的形式指定向量大小
列表初始值还是元素数量
- 通过圆括号与花括号区分
- 圆括号:提供的值是用来构造(construct)vector对象的
- 花括号:列表初始化(list initialize)vector对象
vector<int> v1(10); //v1有10个元素,每个值都为0
vector<int> v2{10}; //v2有1个元素,值为10
vector<int> v3(10, 1); //v3有10个元素,每个值为1
vector<int> v4{10, 1}; //v4有2个元素,分别为10和1
vector<string> v5{"hi"}; //列表初始化,v5有一个元素
vector<string> v6("hi"); //错误,用初始元素值的列表,必须放在花括号里
vector<string> v7{10}; //v7有10个默认初始化的元素
vector<string> v8{10, "hi"}; //v8有10个值为“hi”的元素
3.3.2 向vector对象中添加元素
- push_back成员函数:负责将一个值当作vector对象的尾元素“压到(push)”vector对象的“尾端(back)”
- vector对象能高效增长:在定义vector对象时设定其大小没有必要,相反,还可能性能更差
向vector对象添加元素蕴含的编程假定
- 范围for语句体内不应该改变其所遍历序列的大小:如果循环体内部包含有向vector对象添加元素的语句,则不能用范围for循环
3.3.3 其他vector操作
-
vector支持的操作
v.empty() v不含任何元素,返回真;否则返回假 v.size() 返回v中元素的个数 v.push_back() 向v的尾端添加一个值为t的元素 v[n] 返回v中第n个位置上的索引 v1 = v2 用v2中元素的拷贝替换v1中元素 v1 = {a, b, c, …} 用列表中元素的拷贝替换v1中元素 v1 == v2 v1和v2相等:当且仅当两者元素相同且对应位置的元素值都相同 v1 != v2 同上 < <= > >= 以字典顺序进行比较 -
v.size()返回值的类型是由vector定义的size_type类型vector<int>::size_type //正确 vector::size_type //错误 -
关系运算符依照字典顺序进行比较,只有当元素的值可比较时,vector对象才能被比较
-
只能对确知已存在的元素执行下标操作,确保下标合法的一种有效手段是尽可能使用范围for语句
3.4 迭代器介绍
- 迭代器(iterator)
- 迭代器提供了对对象的间接访问
- 有效的迭代器或者指向某个元素,或者指向容器中尾元素的下一位置;其他情况都属于无效
3.4.1 使用迭代器
- 有迭代器的类型同时拥有返回迭代器的成员
- begin成员:返回指向第一个元素的迭代器
- end成员:返回指向容器”尾元素的下一位置(one past the end)“的迭代器
- 指示的是容器的一个本不存在的”尾后(off the end)“元素
- 这样的迭代器没什么实际含义,仅是个标记而已
- end成员返回的迭代器常被称为尾后迭代器(off-the-end iterator)或者尾迭代器(end iterator)
- 如果容器为空,则begin和end返回的是同一个迭代器,都是尾后迭代器
迭代器运算符
-
标准容器迭代器的运算符
*iter 返回迭代器iter所指元素的引用 iter->mem 解引用iter并获取该元素的名为mem的成员,等价于(*iter).mem ++iter 令iter指示容器的下一个元素 --iter 令iter指示容器的上一个元素 iter1 == iter2 如果两个迭代器指示的是同一个元素或者它们是同一个容器的尾后迭代器,则相等;反之,不相等 iter1 != iter2 同上 -
执行解引用的迭代器必须合法并确实指示着某个元素
将迭代器从一个元素移动到另外一个元素
- end返回的迭代器并不实际指示着某个元素,所以不能对其进行递增或者解引用的操作
迭代器类型
-
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只能读元素,不能写元素 -
如果vector对象或string对象是一个常量,只能用const_iterator;如果vector对象或string对象不是常量,那么既能使用iterator也能使用const_iterator
begin和end运算符
- 如果对象是常量,begin和end返回const_iterator;如果对象不是常量,返回iterator
- cbegin和cend:不论vector对象或string对象本身是否是常量,返回值都是const_iterator
结合解引用和成员访问操作
-
箭头运算符(
->)(*it).empty() // 解引用it,然后调用对象的empty()成员 *it.empty() // 错误:试图访问it的empty成员,it为迭代器,无empty()成员 // 两者等效 (*it).empty() it->empty()
某些对vector对象的操作会使迭代器失效
- vector的副作用
- 不能在范围for循环中向vector对象添加元素
- 任何一种可能改变vector对象容量的操作,都会使该vector对象的迭代器失效
- 凡是使用了迭代器的循环体,都不要向迭代器所属的容器添加元素
3.4.2 迭代器运算
-
string和vector的迭代器提供了更多额外的运算符
iter + n 迭代器加上一个整数值仍得一个迭代器,结果迭代器或指示容器内的一个元素或指示容器尾元素的下一位置 iter - n 迭代器减去一个整数值仍得一个迭代器,结果迭代器或指示容器内的一个元素或指示容器尾元素的下一位置 iter1 += n 迭代器加法的复合赋值语句 iter -= n 迭代器减法的复合赋值语句 iter1 - iter2 两个迭代器相减的结果是它们之间的距离,两个迭代器必须指向同一个容器中的元素或者尾元素的下一位置 > >= < <= 指向位置比较,两个迭代器必须指向同一个容器中的元素或者尾元素的下一位置 -
所有这些运算被称作迭代器运算(iterator arithmetic)
迭代器的算术运算
- 只要是两个迭代器指向的是同一个容器中的元素或者尾元素的下一位置,就能将其相减,所得结果是两个迭代器的距离。
- 所谓距离,其类型是名为difference_type的带符号整形数。string和vector都定义了difference_type
3.5 数组
- 一种类似于标准库类型vector的数据结构
- 数组的大小确定不变,不能随意向数组中增加元素
3.5.1 定义和初始化内置数组
- 数组的声明形如
a[d],a为数组名称,d是数组的维度- 维度代表数组中元素的个数,必须大于0
- 维度必须是一个常量表达式
- 定义数组的时候必须指定数组的类型
- 数组的元素应为对象
显式初始化数组元素
-
对数组的元素进行列表初始化时,允许忽略数组的维度
-
提供的初始值优先初始化靠前的元素,剩下的元素被初始化成默认值
const unsigned sz = 3; int ia1[sz] = {0, 1, 2}; int a2[] = {0, 1, 2}; int a3[5] = {0, 1, 2}; string a4[3] = {"hi", "eye"}; int a5[2] = {0, 1, 2} //错误:初始值过多
字符数组的特殊性
-
字符数组可以用字符串字面值初始化
-
字符串字面值的结尾处还有一个空字符
char a1[] = {'C', '+', '+'}; char a2[] = {'C', '+', '+', '\\0'}; // 列表初始化,含有显式的空字符 char a3[] = "C++"; // 自动添加表示字符串结束的空字符 const char a4[6] = "Daniel"; // 错误:没有空间可存放空字符
不允许拷贝和赋值
- 不能将数组的内容拷贝给其他数组作为其初始值
- 不能用数组为其他数组赋值
理解复杂的数组声明
-
从数组的名字开始按照由内向外的顺序阅读
int *ptrs[10]; //ptrs是含有10个整型指针的数组 int &refs[10] = /* ? */; //错误:引用并非对象,不存在引用的数组 int (*Parray)[10] = &arr; // Parray指向一个含有10个整数的数组 int (&arrRef)[10] = arr; // arraRef引用一个含有10个整数的数组
3.5.2 访问数组元素
- 数组下标索引值从0开始
- 在使用数组下标时,通常将其定义为size_t类型,是一种机器相关的无符号类型
- 数组除了大小固定之外,其他用法与vector基本类似
- 可以用范围for语句
- 下标应大于等于0而且小于数组的大小
3.5.3 指针和数组
- 使用数组的时候编译器一般会把它转换成指针
- 对数组的元素使用取地址符就能得到指向该元素的指针
- 在很多用得到数组名字的地方,编译器都会自动地将其替换为一个指向数组首元素的指针
- 当使用数组作为一个auto变量的初始值时,推断得到的类型是指针而非数组
- 当使用decltype关键字时,该转换不会发生
指针也是迭代器
- 通过数组名字或者数组中首元素的地址都能得到指向首元素的指针
- 设法获取数组尾元素之后的那个并不存在的元素的地址
int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int *p = arr; // p指向arr的第一个元素
++p; // p指向arr[1]
int *e = &arr[10]; // 指向arr尾元素的下一位置的指针
标准库函数begin和end
-
数组不是类类型,因此这两个函数不是成员函数
-
正确的使用方式是将数组作为它们的参数
int ia[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; int *beg = begin(ia); //指向ia首元素的指针 int *end = end(ia); //指向arr尾元素的下一位置的指针
指针运算
- 给(从)一个指针加上(减去)某整数值,结果仍是指针
- 两个指针相减的结果是他们的距离,参与运算的两个指针必须指向同一个数组当中的元素
- 相减的结果的类型是一种名为ptrdiff_t的标准库类型,是一种带符号类型
- 只要两个指针指向同一个数组的元素,或者指向该数组的尾元素的下一位置,就能用关系运算符进行比较
- 如果p是空指针,允许给p加上或减去一个值为0的整型常量表达式;两个空指针也允许彼此相减,结果为0
解引用和指针运算的交互
int ia[] = {0, 2, 4, 6, 8};
int last = *(ia + 4); //ia[4]的值
last = *ia + 4; //ia[0]+4的值
下标和指针
-
很多情况下使用数组的名字其实用的是一个指向数组首元素的指针
-
对数组执行下标运算其实是对指向数组元素的指针执行下标运算
-
标准库类型string和vector也能执行下标运算,但是数组有所不同
- 标准库类型限定使用的下标必须是无符号类型,而内置的下标运算无此要求
int ia[] = {0, 2, 4, 6, 8}; int i = ia[2]; int *p = ia; i = *(p+2);//等价于i = ia[2] int *p = &ia[2]; //p指向索引为2的元素 int j = p[1]; //p[1]等价于*(p+1),就是ia[3]表示的元素 int k = p[-2]; //p[-2]是ia[0]的那个元素
3.5.4 C风格字符串
- C++支持C风格字符串,但是在C++中最好不要使用
- C风格字符串使用起来不太方便
- 极易引发程序漏洞
- C风格字符串(C-style character string)
- 存放在字符数组中并以空字符结束(null terminated),字符串的最后一个字符后面跟着一个空字符(’\0’)
- 可以理解为一个字符数组
C标准String函数
- 这些函数可用于操作C风格字符串
- 定义在cstring头文件中
- 传入此类函数的指针必须指向以空字符作为结束的数组
| 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 |
比较字符串
- 使用数组的时候真正使用的是指向数组首元素的指针
//标准库string对象
string s1 = "A string example";
string s2 = "A different string";
if (s1 < s2) //false
//C风格字符串
const char ca1[] = "A string example";
const char ca2[] = "A different string";
if (s1 < s2) //未定义的,试图比较指向不同对象的指针
目标字符串的大小由调用者指定
- 对于C语言风格的字符串,因其存放在数组上,实际操作的是数组的指针
3.5.5 与旧代码的接口
混用string对象和C风格字符串
-
允许使用字符串字面值来初始化string对象
-
任何出现字符串字面值的地方都可以用以空字符结束的字符数组来替代
- 允许使用以空字符结束的字符数组来初始化string对象或为string对象赋值
- 允许使用以空字符结束的字符数组作为其中一个运算对象,不能两个运算对象都是
- 在string对象的复合赋值运算中允许使用以空字符结束的字符数组作为右侧的运算对象
-
不能用string对象直接初始化指向字符的指针,string提供c_str成员函数来完成该功能
- 执行完c_str()函数后程序像一只都能使用其返回的数组,最好将该数组创新拷贝一份
string s("Hello World"); char *str = s; // 错误:不能用string对象初始化char* const char *str = s.c_str(); //正确
使用数组初始化vector对象
- 允许使用数组来初始化vector对象
-
需要指明要拷贝区域的首元素地址和尾后地址即可
int int_arr[] = {0, 1, 2, 3, 4, 5}; // ivec有6个元素,分别是int_arr中对应元素的副本 vector<int> ivec(begin(int_arr), end(int_arr)); // 拷贝3个元素,分别是int_arr[1]、int_arr[2]、int_arr[3] vector<int> subvec(int_arr + 1, int_arr + 4)
-
尽量使用标准库类型而非数组
- 尽量使用vector和迭代器,避免使用内置数组和指针
- 尽量使用string,避免使用C风格的基于数组的字符串
3.6 多维数组
-
多维数组是数组的数组
-
二维:一个维度表示数组本身大小,另外一个维度表示其元素(也是数组)的大小
int ia[3][4]; int arr[10][20][30] = {0}; //所有元素初始化为0 -
二维数组的第一个维度称为行,第二个维度称为列
多维数组的初始化
-
花括号:内层花括号并非必须的
int ia[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11}, } int ia[3][4] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11} // 显示初始化每行的首元素,其他元素默认初始化 int ia[3][4] = {{ 0 }, { 1 }, { 2 }};
多维数组的下标引用
-
数组的每一个维度对应一个下标运算符
int (&row)[4] = ia[1]; //把row定义为一个含有4个整数的数组的引用,然后绑定到ia的第2行
使用范围for语句处理多维数组
- 要使用范围for语句处理多维数组除了最内层的循环外,其他所有循环的控制变量都应该是引用类型
指针和多维数组
-
当程序使用多维数组的名字时,会自动将其转换成指向数组首元素的指针
int ia[3][4]; int (*p)[4] = ia; //p指向含有4个整数的数组 p = &ia[2]; //p指向ia的尾元素 -
多维数组的前n-1层都是数组,最后1层才为元素,在使用指针的时候要注意
类型别名简化多维数组的指针
本文围绕C++中字符串、向量和数组展开。介绍了命名空间using声明,标准库类型string和vector的定义、初始化及操作,迭代器的使用与运算,数组的定义、访问、指针操作,还提及C风格字符串及多维数组相关知识,强调尽量用标准库类型。
252

被折叠的 条评论
为什么被折叠?



