标准库类型string
标准库类型string表示可变长的字符序列。
使用string
类型需要先添加头文件#include<string>
,并且由于其定义在命名空间std
中,所以还要添加using std::string;
。
string初始化的方式有下面几种方式:
其中使用等号的初始化执行的是拷贝初始化,是将等号右侧的初始值拷贝到新创建的对象中;而不使用等号,执行的就是直接初始化。
下面给出了string的大多数操作:
读取操作中,string对象会自动忽略开头的空白(即空格符、换行符、制表符等)并从第一个真正的字符开始读起,直到遇到下一处空白为止。
而如果需要读取一行,则可以使用函数getline,它的参数是一个输入流和string对象,从给定的输入流中读取内容,直到遇到换行符为止,注意它会将换行符也读取进来,但是并不保存到string对象中。
size()函数会返回string对象的长度,其返回值的类型是string::size_type
,它是一个无符号类型的值,而且能够存放任何string对象的大小。
当把string对象和字符字面值及字符串字面值混在一条语句中使用时,必须确保每个加法运算符两侧运算对象至少有一个是string。此外,字符串字面值和string是不同的类型。
在cctype头文件中定义了一组标准库函数用于改变某个字符的特性,具体如下所示:
基于范围的for
语句形式如下所示:
string str("string");
for(auto c : str)cout << c << endl;
上面是利用这个for
语句来输出字符串str
中的每个字符,而如果要改变字符串中的内容,则需要将循环变量定义成引用类型,如下所示:
string str("string");
for(auto &c : str)c = toupper(c);
cout << str <<endl;
上述代码就是将str
中的字符串都变成大写字母,其中关键之处就是auto &c
中使用引用类型。
标准库类型vector
标准库类型vector表示对象的集合,其中所有对象的类型都相同。
要使用vector
,都必须做如下using
声明和添加头文件:
#include<vector>
using std::vector;
C++语言既有类模板,也有函数模板,而vector
是一个类模板。
模板本身不是类或函数,可以将模板看作是编译器生成类或函数编写的一份说明。编译器根据模板创建类或函数的过程称为实例化,当使用模板时,需要指出编译器应把类或函数实例化成何种类型。
对于类模板,需要提供一些额外信息来指定模板到底实例化成什么类,提供的信息总是这样:即在模板名字后面跟一对尖括号,在括号内放上信息。例如,vector<int> ivec;
则是定义了一个保存int
类型的对象ivec
。
vector
可以容纳大多数类型的对象作为其元素,但是因为引用不是对象,所以不存在包含引用的vector
。
初始化vector
的方法如下:
一般初始化的时候,使用的是圆括号,提供的值是用来构建vector
,如上述例子的v3,v4
;而如果使用的是花括号,则一般是表示我们想使用列表初始化vector
对象,但如果提供的值不能进行列表初始化,如vector<string> v{10};
这表示v
有10个默认初始化的元素,或者如vector<string> v{10, "hi"};
这表示v
有10个值为”hi”的元素。
注意,如果循环体内部包含有向vector
对象添加元素的语句,则不能使用范围for
语句,范围for
语句体内不应改变其所遍历序列的大小。
迭代器介绍
迭代器也可以实现下标运算符的功能,即能够访问string
对象的字符或者vector
对象的元素。
迭代器提供 了对对象的间接访问,跟指针不一样的是,获取迭代器不是使用取地址符,有迭代器的类型同时拥有返回迭代器的成员。这些类型都拥有名为begin和end的成员,其中begin成员负责返回指向第一个元素的迭代器,而end成员负责返回指向容器或string
对象“尾元素的下一位置”的迭代器,即其指示的是容器的一个本不存在的尾后元素,因此它也常被称为尾后迭代器。如果容器为空,则begin和end返回的是同一个迭代器。
迭代器的运算符如下:
实际上,跟不知道string
和vector
的size_type
成员到底是什么类型一样,一般来说我们也不知,或者也无需知道迭代器的精确类型。实际上,拥有迭代器的标准库类型使用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只能读字符,不能写字符
const_iterator和常量指针差不多,能读取但不能修改它所指的元素值,如果vector
或string
对象是常量,则只能使用const_iterator;相反,iterator的对象可读可写,只有vector
或string
对象不是常量,则这两者都可以使用。
解引用迭代器可获得迭代器所指的对象,如果该对象的类型恰好是类,就有可能希望进一步访问它的成员。如(*it).empty()
,it
是一个vector
对象的迭代器,这句代码就是调用vector
的empty
函数,但是注意这里必须有圆括号,否则点运算符是指向it
,而不是it
解引用的结果。可以使用箭头运算符->实现同样的效果,即it->empty()
,它就是等价于(*it).empty()
。
注意,但凡是使用了迭代器的循环体,都不要向迭代器所属的容器添加元素。
数组
数组是一种复合类型,其声明形如a[d]
,a
是数组名,而d
是数组的维度,维度必须大于0,且必须是一个常量表达式,即编译的时候维度应该是已知的。
和内置类型变量一样,在函数内部定义了某种内置类型的数组,那么默认初始化会令数组含有未定义的值。
不能将数组的内容拷贝给其他数组作为初始值,也不能用数组为其他数组赋值,即下列行为是错误的:
int a[] = {0,1,2}; // 含有3个整数的数组
int a2[] = a; // 错误:不允许使用一个数组初始化另一个数组
a2 = a; // 错误:不能把一个数组直接赋值给另一个数组
在C++中,指针和数组由非常紧密的联系,在使用数组的时候,编译器一般会把它转换成指针。
通常情况下,使用取地址符来获取执行某个对象的指针,取地址符可以用于任何对象,数组的元素也是对象,因此对数组的元素使用取地址符就能得到指向该元素的指针。
此外,数组还有一个特性:在很多用到数组名字的地方,编译器都会自动地将其替换为一个指向数组首元素的指针。如string *p2 = nums;
等价于p2 = &nums[0];
。所以大多数表达式中,使用数组类型的对象其实是使用一个指向该数组首元素的指针。
当使用数组作为一个auto
变量的初始值时,推断得到的类型是指针而非数组。
C++11新标准引入了两个名为begin
和end
的函数,这两个函数与容器中两个同名成员功能类型,不过数组不是类类型,因此这两个函数不是成员函数,正确的使用形式是将数组作为它们的参数。
指向数组元素的指针可以执行一些如解引用、递增、比较、与整数相加、两个指针相减等的迭代器运算。
其中,两个指针相减的结果是它们之间的距离,但两个指针必须指向同一个数组中的元素;而对于比较运算,也要求两个指针指向同一个数组的元素或者该数组的尾元素的下一位置,否则就不能比较;
可以使用数组来初始化vector
对象,只需指明要拷贝区域的首元素地址和尾后地址,如:
int int_arr[] = {0, 1, 2, 3, 4, 5};
// ivec有6个元素,分别是int_arr中对应元素的副本
vector<int> ivec(begin(int_arr), end(int_arr));
// 也可以是数组的一部分,下面是拷贝三个元素:int_arr[1], int_arr[2], int_arr[3]
vector<int> subVec(int_arr+1, int_arr+4);
多维数组其实就是数组的数组。
使用范围for
语句处理多维数组,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型。