字符串、向量和数组
第2章介绍的内置类型是由C++语言直接定义的。这些类型,比如数字和字符,体现了大多数计算机硬件本身具备的能力。标准库定义了另外一组具有更高级性质的类型,它们尚未直接实现到计算机硬件中。
本章将介绍两种最重要的标准库类型: string 和 vector。string表示可变长的字符序列,vector存放的是某种给定类型对象的可变长序列。本章还将介绍内置数组类型,和其他内置类型一样,数组的实现与硬件密切相关。因此相较于标准库类型string和 vector,数组在灵活性上稍显不足。
命名空间的using声明
有了 using声明就无须专门的前缀(形如std :)也能使用所需的名字。声明如下:using namespace::name ;
例如:using std::cin; using std::cout;
头文件不应包含using声明,以免产生名字的冲突。
标准库类型string
使用前需包含
1 |
|
定义和初始化
1 | string sl;//默认初始化,s1是一个空字符串 |
直接初始化与拷贝初始化
区分:使用等号的都是拷贝初始化,其余是直接初始化。
string对象上的操作
读写string对象
1 | 1nt main( |
输入过程中自动忽略开头空白,直到下一处空白,如:“ Hello World! ”则只会输出”Hello”,输出结果没有任何空格。
1 | string sl, s2; |
这样输入上面的语句则会输出”HelloWorld!”。
读取未知数量的string对象
1 | int main (){ |
在遇到文件结束符或非法输入就结束了
getline读取一整行
getline函数的参数是一个输入流和一个string对象,函数从给定的输入流中读入内容,直到遇到换行符为止(注意换行符也被读进来了),然后把所读的内容存入到那个string对象中去(注意不存换行符)。getline只要一遇到换行符就结束读取操作并返回结果,哪怕输入的一开始就是换行符也是如此。
1 | int main ( ){ |
string的empty和size操作
empty函数根据string对象是否为空返回对应的布尔值。
size函数返回string对象的长度,其类型为string::size_type类型,它是一个无符号类型的值,所以尽量避免size()和int混用。
比较string对象
- 两个string对象长度相同,所包含字符必须一模一样才算相等。
- 长度不同,而且较短string对象的每个字符都与较长string对象对应位置上的字符相同,就说较短string对象小于较长string对象。
- 如果两个string 对象在某些对应的位置上不一致,则string对象比较的结果
其实是string对象中第一对相异字符比较的结果。
为string对象赋值
1 | string st1(10, 'c'), st2; |
两个string对象相加
1 | string s1 = "hello, ", s2 = "world\n"; |
字面值和string对象相加
1 | string s4 = s1 + ", " + s2; //可以,依次运算中至少保证有一个string对象 |
处理string对象中的字符
函数名称 返回值
isalnum() 如果参数是字母数字,即字母或者数字,函数返回true
isalpha() 如果参数是字母,函数返回true
iscntrl() 如果参数是控制字符,函数返回true
isdigit() 如果参数是数字(0-9),函数返回true
isgraph() 如果参数是除空格之外的打印字符,函数返回true
islower() 如果参数是小写字母,函数返回true
isprint() 如果参数是打印字符(包括空格),函数返回true
ispunct() 如果参数是标点符号,函数返回true
isspace() 如果参数是标准空白字符,如空格、换行符、水平或垂直制表符,函数返回true
isupper() 如果参数是大写字母,函数返回true
isxdigit() 如果参数是十六进制数字,即0-9、a-f、A-F,函数返回true
tolower() 如果参数是大写字符,返回其小写,否则返回该参数
toupper() 如果参数是小写字符,返回其大写,否则返回该参数
C++版本的标准库头文件为形如cname,而C头文件形如name.h,这里建议使用C++版本。
for处理每个字符
1 | string str ( "some string" ); |
如果需要改变str中的字符,则在c前面加上&。
处理部分字符
1 | //依次处理s中的字符直至我们处理完全部字符或者遇到一个空白 |
这里主要注意一点,index必须大于等于0,小于size()。
标准库类型vector
头文件:
1 |
|
vector是一个类模板,模板本身不是类或函数,相反可以将模板看作为编译器生成类或函数编写的一份说明。编译器根据模板创建类或函数的过程称为**实例化(instantiation)**,当使用模板时,需要指出编译器应把类或函数实例化成何种类型。
例如vector<int> ivec; vector<Sales_item> Sales_vec;
定义和初始化vector对象
1 | vector<T> v1 v1是一个空vector,它潜在的元素是T类型的,执行默认初始化 |
拷贝构造(类型必须相同)
1 | vector<int> ivec; //初始状态为空 |
列表初始化vector对象
1 | vector<string> articles = { "a", "an" , "the" }; |
值初始化
使用vector<T> v(n);
需注意 T 支不支持默认初始化。
vector<int> vi = 10;//错误:必须使用直接初始化的形式指定向量大小
注意区分花括号和圆括号
1 | vector<int> v1(10) ; // v1有10个元素,每个的值都是0 |
1 | vector<string> v5 { "hi"};//列表初始化:v5有一个元素 |
上面的代码中,只有v5为列表初始化,使用花括号时,若提供的对象不可以作为对象的初始值,则编译器会尝试默认值初始化。
向vector对象中添加元素
1 | vector<int> v2;//空vector对象 |
其他vector操作
1 | v.empty () //如果v不含有任何元素,返回真;否则返回假 |
vector容器的size类型一定要包含元素类型
1 | veetor<int>: :size_type //正确 |
vector容器的比较大小参照string。
迭代器
使用迭代器
1 | //由编译器决定b和e的类型 |
迭代器运算符
1 | * iter //返回迭代器iter所指兀素的引用 |
迭代器的移动
采用++或–操作改变其位置
迭代器用法:
1 | //依次处理s 的字符直至我们处理完全部字符或者遇到空白 |
迭代器类型
1 | vector<int> : :iterator it; // it能读写vector<int>的元素 |
begin和end
1 | vector<int> v; |
如果对象只需读操作而无须写操作的话最好使用常量类型(比如 const_iterator)。为了便于专门得到const_iterator类型的返回值,C++11新标准引入了两个新函数,分别是cbegin和cend,无论对象是什么,返回值都是const_iterator。
结合解引用和成员访问操作
1 | ( *it) .empty() //解引用it,然后调用结果对象的empty成员 |
为了简化上述表达式,C++语言定义了箭头运算符(->)。箭头运算符把解引用和成员访问两个操作结合在一起,也就是说,it->mem和(*it) .mem表达的意思相同。
迭代器失效
迭代器运算
迭代器可以进行+- 等运算使它一次移动多个位置,大于小于操作则判断迭代器的相对位置,但必须在同一个容器里。
迭代器相减得到所得结果是两个迭代器的距离。所谓距离指的是右侧的迭代器向前移动多少位置就能追上左侧的迭代器,其类型是名为difference_type 的带符号整型数。string 和vector都定义了difference_type ,因为这个距离可正可负,所以difference_type是带符号类型的。
使用迭代器运算
1 | // text必须是有序的 |
数组
与vector对比:
- 数组也是存放类型相同的对象的容器,这些对象本身没有名字,需要通过其所在位置访问。
- 与vector不同的地方是,数组的大小确定不变,不能随意向数组中增加元素。因为数组的大小固定,因此对某些特殊的应用来说程序的运行时性能较好,但是相应地也损失了一些灵活性。
定义和初始化
1 | unsigned cnt = 42; //不是常量表达式 |
定义数组的时候必须指定数组的类型,不允许用auto关键字由初始值的列表推断类型。另外和 vector一样,数组的元素应为对象,因此不存在引用的数组。
显示初始化数组元素
1 | const unsigned sz = 3 ; |
字符数组的特殊性
1 | char al[]= {'c' , '+', '+'}; //列表初始化,没有空字符 |
拷贝与赋值
1 | int a[] = {0,1,2}; //含有3个整数的数组 |
一些编译器支持数组的赋值,这就是所谓的编译器扩展(compiler extension)。但一般来说,最好避免使用非标准特性,因为含有非标准特性的程序很可能在其他编译器上无法正常工作。
复杂数组的声明
1 | int *ptrs[10] ; //ptrs是含有10个整型指针的数组 |
这里重点在于顺序,
- 对于无括号情况,从右往左,例如ptrs,我们先看到的是[10],表明这是一个数组。
- 对于右括号,从内向外,例如parray,先看到是一个指针,表明是一个指针,它指向了数组。
- 对于arry,先从内向外,再从右往左。
访问数组元素
在使用数组下标的时候,通常将其定义为size_t类型。size_t是一种机器相关的无符号类型,它被设计得足够大以便能表示内存中任意对象的大小。在cstddef头文件中定义了size_t类型,这个文件是C标准库 stddef.h头文件的C++语言版本。
1 | for (auto i : scores) //对于scores中的每个计数值 |
指针和数组
在大多数表达式中,使用数组类型的对象其实是使用一个指向该数组首元素的指针。
1 | int ia[] = {0,1,2,3,4,5,6,7,8,9}; // ia是一个含有10个整数的数组 |
指针也是迭代器
指针可以做与迭代器同样的操作(我认为本质上没有区别),尾指针可以通过int *e = &arr[arr.len];
的方法获取。
标准函数begin和end
1 | int ia[ ] = {0,1,2,3,4,5,6,7,8,9); //ia是一个含有10个整数的数组 |
C++11新标准引入了两个名为begin和 end 的函数。这两个函数与容器中的两个同名成员,功能类似,不过数组毕竟不是类类型,因此这两个函数不是成员函数。正确的使用形式是将数组作为它们的参数:
指针运算
这里与容器中的迭代器除了两个指针相减是ptrdiff_t类型基本一致,该类型也是带符号类型。
指针运算同样适用于空指针和所指对象并非数组的指针。在后一种情况下,两个指针必须指向同一个对象或该对象的下一位置。如果p是空指针,允许给p加上或减去一个值为0的整型常量表达式。两个空指针也允许彼此相减,结果当然是0。
1 | int ia[] = {5,4,2,2,1,3} |
下标和指针
只要指针指向的是数组中的元素(或者数组中尾元素的下一位置),都可以执行下标运算:
1 | int i = ia[2]; // ia转换成指向数组首元素的指针ll ia [2]得到(ia + 2)所指的元素 |
数组下标类型是带符号类型,这与vector和string不一致。
C风格字符串
风险大,不推荐使用,故暂时跳过
多维数组
严格来说,C++语言中没有多维数组,通常所说的多维数组其实是数组的数组。谨记这一点,对今后理解和使用多维数组大有益处。
多维数组初始化
1 | int ia[ 3][4]= { //三个元素,每个元素都是大小为4的数组 |
多维数组的下标引用
1 | //用arr的首元素为ia最后一行的最后一个元素赋值 |
for循环遍历
1 | size_t cnt = 0; |
指针和多维数组
1 | int*ip[4]; //整型指针的数组 |
声明指针注意区分以上区别,C++ 11推荐使用auto或者decltype也可避免加指针类型。
1 | int ia [ 3][4]; //大小为3的数组,每个元素是含有4个整数的数组 |
auto p = begin(ia)
以可以更加简洁。
类型别名简化多维数组
1 | using int_array = int [4]; //新标准下类型别名的声明,参见2.5.1节(第60页) |
程序将类型“4个整数组成的数组”命名为 int_array,用类型名int_array定义外层循环的控制变量让程序显得简洁明了。