第三章:字符串、向量和数组
3.1 命名空间的using声明
1.using声明:通过简单的途径也能使用到命名空间中的成员,有了using声明就无需专门的前缀(形如命名空间::)也能使用所需的名字。
格式:using namespace::name;
using std::cin;
//每个需要用到的名字都必须有自己的声明语句,且每句话都以分号结束
using std::cout; using std::endl;
注:位于头文件中的代码一般不应使用using声明,因为头文件的内容会拷贝到所有引用它的文件中,若头文件中有using声明,那么使用了该头文件的文件就会有该using声明,而可能导致不经意间包含了一些名字产生始料未及的冲突
3.2 标准库类型string
1.标准库类型string:表示可变长的字符序列,使用string类型必须包含string头文件,而string定义在命名空间std中
#include <string>
using std::string;
2.定义和初始化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")
string s4(n, 'c'); //直接初始化,将s4初始化为n个字符c组成的串
3.string对象上的操作(p.77)
1)使用 getline 读取一整行:getline(is, s) 函数参数包括一个输入流和一个 string 对象,函数从给定输入流中读入内容直到换行符为止,只要一遇到换行符就结束读取操作并返回结果,若一开始遇到换行符则读入空 string
注:getline 和输入参数符一样,也会返回其流参数
2)string::size_type: s.size()函数返回 string 对象的长度,其返回值是一个 string::size_type 类型的值
注:可以确定size_type是个无符号数,因此若表达式中已经有了 size() 函数就不要再使用 int 了,否则可能会出错
//可通过 auto 或者 decltype 来推断变量的类型
auto len = line.size();
3)字面值和string对象相加:标准库允许将字符字面值和字符串字面值转换成 string 对象,当把 string 对象和字符字面值即字符串字面值混在一条语句中使用时,必须确保每个加法运算符两侧的运算对象至少有一个是 string 类型
string s4 = s1 + ", "; //正确
string s5 = "hello" + ", " //错误,两个运算符都不是string
string s6 = s1 + ", " + "world"; //正确,每个加法运算符都有一个运算对象是string
string s7 = "hello" + ", " + s2; //错误,不能把字面值直接相加
4.处理 string 对象中的字符
1)处理每个字符:使用基于范围的for语句,范围for(range for)语句
//每行输出str中的一个字符
string str("some string");
for(auto c : str)
cout << c << endl;
//改变字符串中的字符
string s("Hello World!!!");
for(auto &c : str)
c = toupper(c); //c是一个引用,每个字符都转换成大写形式
cout << s << endl;
2)处理一部分字符:一种是使用下标,另一种是使用迭代器(位于3.4)
下标运算符([])接收的输入参数是 string::size_type 类型的值,这个参数表示访问字符的位置,返回值是该位置上字符的引用
若索引是该符号类型的值将自动转换成由 string::size_type 表达的无符号类型
string s("some string");
//依次处理s中的字符知道我们处理完全部字符或遇到一个空白
for(decltype(s.size()) index = 0; index != s.size() && !isspace(s[index]); ++index)
s[index] = toupper(s[index]);
//输出得到 SOME stirng
3.3 标准库类型vector
1.标准库类型 vector 表示对象的集合,其中所有对象的类型都相同。集合中每个对象都有着一个与之对应的索引。
因为 vector 容纳着其他对象,所以也被称为容器
#include <vector>
using std::vector;
2.c++语言既有类模板,也有函数模板,vector就是一个类模板而非类型。对于类模板来说,通过提供一些额外信息来指定模板到底实例化为什么类,需要提供的信息由模板来决定,但一般是在模板名字后面跟一对尖括号并在括号内放上信息。
vector<int> ivec; //ivec保存int类型的对象
vector<Sales_item> Sales_vec; //保存Sales_item类型的对象
vector<vector<string>> file; //该向量的元素是vector对象
3.定义和初始化 vector 对象(p.87)
1)值初始化:通常情况,vector 的初始化可以只提供 vector 对象容纳的元素数量而略去初始值,此时库会创建一个值初始化的元素初值,并把它赋给容器中所有元素,这个初始由 vector 对象中元素类型决定
若vector对象的元素为内置类型,则元素初始值自动设为0
若元素是某种类类型,则元素由类默认初始化
若 vector 对象的元素不支持默认初始化,就必须提供初始的元素值,若只提供元素数量而不设置初始值则无法初始化
vector<int> ivec(10); //10个元素,每个都初始化为0
vector<string> svec(10); //10个元素,每个都是空string对象
2)若使用圆括号,则是用来构造 vector 对象的;若使用的是花括号,则表述成想列表初始化 vector 对象
但若初始化时使用了花括号的形式但是提供的值无法用来列表初始化,则要考虑用这样的值构造 vector 对象了
vector<int> v1(10); //v1有10个元素,每个的值都是0
vector<int> v1{10}; //列表初始化,v2有1个元素,该元素的值是10
vector<int> v3(10, 1); //v3有10个元素,每个的值都为1
vector<int> v4{10, 1}; //列表初始化,v4有两个元素,值分别为10和1
vector<string> v5{"hi"}; //列表初始化,v5有一个元素
vector<string> v7{10}; //int无法初始化string对象,无法执行列表初始化,编译器尝试使用默认初始化,因此v7有10个默认初始化元素
vector<string> v8{10, "hi"};//无法执行列表初始化,v8有10个值为"hi"的元素
4.向 vector 对象中添加元素:若创建一个 vector 时并不清楚所需元素个数,元素的值也经常无法确定,亦或者值得总量较大且各不相同,可以考虑创建一个空 vector,然后在运行时再利用 vector 的成员函数 push_back 向其中添加元素,push_back负责把一个值当成 vector对象的尾元素添加到 vector 对象的末尾
vector<int> v2; //空vector对象
for(int i = 0; i != 100; ++i)
v2.push_back(i); //依次将整数值放到v2末尾
注:若循环体内部包含 vector 对象添加或删除元素的语句,则不能使用范围for循环,因为一旦在序列中添加或删除元素,end函数的值就会无效。即范围for语句体内不应改变 vector 对象容量
vector<int> v = {0,1,2,3,4,5,6,7,8,9};
//范围变量必须是引用类型,才能对元素执行写操作
for(auto &r : v) //对于v中的每个元素
r *= 2; //将v中每个元素值翻倍
//上面的范围for语句的定义与下面的传统for语句等价
for(auto beg = v.begin(), end = v.end(); beg != end; ++beg){
auto &r = *beg; //r必须为引用类型,才能对元素执行写操作
r *= 2; //将v中每个元素值翻倍
}
4.其他vector操作(p.91)
1)vector 的 empty 和 size 两个成员函数与 string 的同名成员功能完全一致
size 成员函数返回 vector 对象中元素的个数,返回值是由 vector 定义的 size_type 类型
//要使用size_type,首先需指定由哪种类型定义的,vector对象的类型总是包含着元素类型
vector<int>::size_type //正确
vector::size_type //错误
2)vector 对象的比较:只有元素的值可以比较时,vector 对象才能被比较
3)下标运算符的索引:vector 对象可以使用下标运算符获取指定的元素,下标的类型是相应的 size_type 类型
vector 对象(以及 string 对象)的下标运算符可用于访问已经存在的元素,但不能用于添加元素,因为尚未添加的元素尚不存在相应的下标,使用下标运算符会导致内存访问越界的严重问题
vector<int> ivec; //空 vector 对象
for(decltype(ivec.size()) ix = 0; ix != 0; ix != 10; ++ix)
ivec[ix] = ix; //严重错误,ivec 不包含任何元素
3.4 迭代器介绍
1.迭代器:在3.2中可以使用下标运算符访问string对象的字符或vector对象的元素,而另一种方法则是迭代器;
所有标准库容器都可以使用迭代器,但只有少数几种可以支持下标运算符;
string 对象并不属于容器类型,但是 string 支持很多与容器类型相似的操作;
vector 支持下标运算符,string也支持;string 支持迭代器,vector 也可以
2.使用迭代器:与指针不同,获取迭代器不是使用取地址符,有迭代器的类型同时拥有返回迭代器的成员。
比如这些类型都拥有名为 begin 和 end 的成员,begin 成员返回指向第一个元素(字符)的迭代器,end成员放回指向容器(或string对象)尾元素的下一个位置的迭代器,即该迭代器指示的是容器的一个本不存在的尾后元素,没有实际含义,只是个标记,因此蹦年对 end 返回的迭代器进行递增或解引用的操作
若容器(或string对象)为空,则 begin 和 end 返回的是同一个迭代器;
*iter //返回迭代器 iter 所指元素的引用;
++iter //表示从一个元素移动到下一个元素,即向前移动一个位;
3.迭代器类型:一般来说,不清楚(不在意)迭代器准确类型,可以使用 auto 关键字定义迭代器变量;
但实际上,那些拥有迭代器的标准库类型使用 iterator 和 const_iterator 来表示迭代器的类型;
若对象是常量,则只能使用const_iterator;
若对象不是常量,则既能使用 iterator 也能使用 const_iterator;
vector<int>::iterator it; //it能读写vector<int>的元素
string::iterator it2; //it2能读写string对象中的字符
vector<int>::const_iterator it3;//it3只能读元素,不能写元素
string::iterator it4; //it4只能读字符,不能写字符
4.begin 和 end 运算符:begin 和 end 返回的具体类型由对象是否为常量决定,若对象为常量,则返回 const_iterator,若对象不是常量,则返回 iterator;若对象只需读操作而无需写操作的话,最好使用常量类型,因此c++11新标准提供了两个新函数,cbegin 和 cend,不论对象本身是否为常量,返回值均为 const_iterator
vector<int> v;
const vector<int> cv;
auto it1 = v.begin(); //it1 的类型是 vector<int>::iterator
auto it2 = cv.begin(); //it2 的类型是 vector<int>::const_iterator
auto it3 = v,cbegin(); //it3 的类型是 vector<int>::const_iterator
3.5 数组
1.定义和初始化内置数组
1)数组是一种复合类型,默认情况下,数组的元素被默认初始化,与内置类型一样,若在函数内部定义了某种内置类型的数组,那么默认初始化会令数组含有未定义的值
2)显示初始化数组元素:可以对数组元素进行列表初始化,允许忽略数组维度,若维度比提供的初始值量大,则用提供的初始值初始化靠前的元素,剩下的元素被初始化为默认值(注意:不是未定义的值)
const unsigned sz = 3;
int arr[]; //默认初始化,若在函数内部定义的,则含有未定义的值
int ia1[sz] = {0, 1, 2};
int a2[] = {0, 1, 2}; //该数组维度为3
int a3[5] = {0, 1, 2}; //剩下的值初始化为默认值,等价于 a3[5] = {0, 1, 2,0,0}
string a4[3] = {"hi", "bye"}; //等价于 a4[3] = {"hi", "bye", ""}
unsigned scores[11] = {}; //维度为11,全部初始化为0
3)字符数组的特殊性:字符数组可以使用字符串字面值对此类数组初始化,但使用这种方式时,一定要注意字符串字面值结尾处还有个空字符,这个空字符会像字符串的其他字符一样被拷贝到字符数组中去
char a1[] = {'C', '+', '+'}; //列表初始化,没有空字符,维度是3
char a2[] = {'C', '+', '+', '\0'}; //列表初始化,含有显示空字符,维度是4
char a3[] = "C++"; //字符串字面值初始化,自动添加表示字符串结束的空字符,维度是4
const char a4[6] = "Daniel"; //错误:没有空间存放空字符,因为字符串字面值初始化会自动提那家空字符,因此维度至少为7才行
4)理解复杂的数组声明:想了解数组声明的含义,最好从数组的名字开始由里到外的顺序阅读
int *ptrs[10]; //ptrs是含有10个整型指针的数组
int &refs[10]; //错误,引用不是对象,因此不存在引用的数组
//首先,圆括号括起来的部分 *Parray 意味着 Parray 是个指针
//其次,观察右边,可知 Parray 指向大小为10的数组的指针
//最后,观察左边,知道数组的元素是int
int (*Parray)[10] = &arr; //因此,Parray 是个指向含有10个整数的数组
//首先,&arrRef 表示 arrRef 是个引用
//其次,引用的是一个大小为10的数组
//最后,数组中元素的类型为int
int (&arrRef)[10] = arr; //arrRef 引用一个含有10个整数的数组
int *(&arry)[10] = ptrs; //arry 是数组的引用,该数组含有10个指针
2.访问数组元素:和标准库类型 string 和 vector 一样,数组的元素也能使用范围for语句或下标运算符来访问
使用数组下标时,通常将其定义为 size_t 类型(string、vector是size_type类型),一种机器相关的无符号类型
3.指针和数组(p.105)
4.C风格字符串:尽管C++支持C风格字符串,但最好不要使用,不仅使用不方便而且极易引发程序漏洞
1)字符串字面值是C++由C继承而来的C风格字符串,存放在字符数组中并以空字符结束,以空字符结束的意思是在字符串最后一个字符后面跟着一个空字符(’\0’),一般利用指针来操作这些字符串
//传入C风格字符串的函数的指针必须指向以空字符为结束的数组
char ca[] = {'C', '+', '+'}; //不以空字符结束
cout << strlen(ca) << endl; //严重错误:ca没有以空字符结束,否则strlen函数将沿着ca的内存位置不断寻找直到遇到空字符结束
2)对大多数应用来说,使用标准库 string 要比使用C风格字符串更安全、更高效
3.6 多维数组
1.严格来说,C++语言中没有多维数组,所谓多维数组其实就是数组的数组;
当一个数组的元素仍然是数组时,通常用两个温度定义它,一个温度表示数组本身大小,另一个维度表示其元素(也是数组)大小
对于数组的阅读,依旧是由内到外的顺序进行阅读和理解
对于二维数组,将第一个维度称为行,将第二个维度称为列
//定义的名字是ia,显然ia是个含有3个元素的数组
//向右观察,ia的元素也有自己的维度,所以ia的元素本身是一个含有3个院的的数组
//最后观察左边,真正存储的元素是整数
int ia[3][4]; //大小为3的数组,每个元素是含有4个整数的数组
//首先arr是个大小为10的数组
//每个元素是大小为20的数组
//这些数组的元素又都是含有30个整数的数组
int arr[10][20][30];
2.多维数组的初始化:允许使用花括号括起来的一组值初始化多维数组
而内嵌的花括号并非必须
初始化多维数组时,并非所有元素的值都必须包含在初始化列表中,其他未列出的元素执行默认初始化
int ia[3][4] = { //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},{4},{8}};
//现实的初始化第一行没其他元素执行值初始化为0
int ix[3][4] = {0, 3, 6, 9};
3.使用范围for语句处理多维数组时,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型,避免数组被自动转成指针(p.114)
4.类型别名简化多维数组的指针
using int_array = int[4];
typedef int int_array[4]; //int_array 等同于 int[4]