【C++笔记】3. 字符串、向量和数组
3. 字符串、向量和数组
3.1 命名空间的using声明
using std::cin;
,之后便可直接使用cin
。
每个名字都需要独立的using声明。
头文件中不应包含using声明。
3.2 标准库类型string
-
string
表示可变长的字符序列,vector
存放的是某种给定类型对象的可变长序列。 -
标准库类型string,需包含string头文件。
-
初始化string对象的方式:
初始化方法 说明 string s1;
s1是个空字符串 string s2(s1);
s2是s1的副本 string s2 = s1;
同上 string s3("value");
s3是"value"的副本 string s3 = "value";
同上 string s4(n, 'c');
n个字符c组成的字符串 -
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个字符的引用,从0开始 s1+s2
返回s1和s2连接后的结果 s1=s2
用s2的副本代替s1中原来的字符 s1==s2
、s1!=s2
如果s1和s2中所含的字符完全一样,则相等。string对象的相等性判断对字母的大小写敏感 <, <=, >, >=
利用字符在字典中的顺序进行比较,且对大小写敏感。 -
cin
读入字符串时,string对象会自动忽略开头的空白,直到遇到下一处空白开始。 -
getline()
读取一整行:替代>>
运算符,可以保留空白符,遇到换行符结束。
实际上,换行符会被读入内容,但不会被存入对象。
最后换行符会被丢弃,同时在结尾加上\0
。 -
string的empty和size操作。
empty返回一个布尔值,表示是否为空。
size返回一个string::type
类型的值,表示sting对象的长度。
在C++标准中,可以使用auto和decltype来推断变量的类型。
这里知道,size返回值是一个无符号整型数。
因此在做条件判断时,n若小于0,s.size()<n
必成立。
显然,这是错误的。 -
比较string对象:先比长度,再从前向后比字符。
-
为string对象赋值:可以直接用赋值符号
=
。 -
两个string对象相加:字符串相接。
-
字面值和string相加:自左向右结合,要求
+
两侧至少一个string对象。
string s6 = s1 + ", " + "World.";
正确。
string s7 = "Hello" + "," + s2;
错误。 -
建议C++程序使用cname的头文件而非name.h。
标准库中的名字总能在命名空间std中找到。 -
针对单个字符,cctype头文件中定义了一组标准库函数来处理这部分工作。
函数 说明 isalnum© 当c是字母或数字时为真 isalpha© 当c是字母时为真 islower© 当c是小写字母时为真 isupper© 当c是大写字母时为真 isdigit© 当c是数字时为真 isxdigit© 当c是十六进制数字时为真 iscntrl© 当c是控制字符时为真 isgraph© 当c不是空格但可以打印时为真 isprint© 当c是可打印字符时为真(即c是空格或具有可视形式) ispunct© 当c是标点符号时为真(即c不是控制字符、数字、字母、可打印空白) isspace© 当c是空白时为真(即c是空格、横向制表符、纵向制表符、回车符、换行符、进纸符中的一种 tolower© 如果c是大写字母,输出对应的小写字母;否则原样输出c toepper© 如果c是小写字母,输出对应的大写字母;否则原样输出c -
范围for语句
for(declaration:expression){statement};
expression
是一个对象,用于表示一个序列。
declaration
负责定义一个变量,用于访问序列中的基础元素。
每次迭代,declaration
部分的变量会被初始值化为expression
部分的下一个元素值。
循环读作:对于字符串str中的每个字符c。string str("Hello World!!!"); for (auto c : str)cout << c << endl;
-
范围for语句还可以用来改变字符串中的字符。
想要改变str对象中字符的值,必须把循环变量定义成引用。
使用引用作为循环控制变量,使变量依次绑定到序列的每个元素上。
使用这个引用,就能改变它绑定的字符。
如果直接使用c,则只会改变c的值(类比于指针)。string str("Hello World!!!"); for (auto &c : str)c = toupper(c); // c必须是一个引用。 cout << str << endl;
-
只处理一部分字符,可以使用下标,或迭代器。
-
C++中,string可以使用下标访问string中字符。使用下标时须注意下标范围。
-
逻辑运算符
&&
,运算时,会屏蔽掉0后面的值。 -
有一个好习惯是,for循环中的i类型最好设置为
decltype(str.size())
。
3.3 标准库类型vector
-
因为vector“容纳着”其他对象,所以也常被称作容器(container)。
-
标准库类型vector表示对象的集合,其中所有对象的类型都相同。
集合中的每个对象都有一个与之对应的索引,索引用于访问对象。
C++既有类模板,也有函数模板。vector是一个类模板。
使用vector,须包含vector头文件:
include <vector>
using std::vector
-
vector是一种类模板。编译器通过模板创建类或函数的过程称为实例化。
当使用模板时,应指出编译器应把类或函数实例化成何种类型:vector<int> ivec; // ivec保存int对象 vector<Sales_item> Sales_vec; // 保存Sales_item对象 vector<vector<string> > file; // 该向量的元素是vector对象 // 注意:这里最好使用老式语句来声明,即中间加一个空格
-
vector是模板而非类型。
-
vector能容纳大多数类型的对象作为其元素(但不能是引用)
-
初始化vector的方法:
初始化方法 说明 vector<T> v1
v1是个空字符串,默认初始化 vector<T> v2(v1)
v2是v1的副本 vector<T> v2 = v1
同上 vector<T> v3{a,b,c...}
v3是包含了初始值个数的元素,每个元素被赋予相应的初始值 vector<T> v3={a,b,c...}
同上 vector<T> v4(n,val)
v4包含了n个值为val的元素 vector<T> v5(n)
v5包含了n个元素,默认初始化 -
初始化vector时,
()
和{}
是不一样的。
直接初始化、拷贝初始化、列表初始化、值初始化vector<int> v0 = 10; // 错误:不能使用int值构建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个元素,分别是 1和10 vector<string> v5("hi"); // 错误:不能使用字符串字面值构建vector对象。 vector<string> v6{"hi"}; // v6有 1个元素,每个string都是hi vector<string> v7(10); // v7有10个元素,每个string都是空字符串 vector<string> v8{10}; // 同上 vector<string> v9(10, "hi"); // v9有10个元素,每个string都是hi vector<string> vX{10, "hi"}; // 同上
-
向vector中添加元素,须使用成员函数
push_back()
。
通过for循环,依次把整数值放到vector对象的尾端。
如需向已有内容的vector对象添加元素,不建议使用for循环。
基于这种特性,推荐C++在使用vector时不指定长度。 -
其他vector操作:
操作 说明 v.empty()
如果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相等,当且仅当它们的元素数量相同且对应位置的元素值都相同 <, <=, >, >=
按字典顺序进行比较 -
需要注意的是:
(1)size使用时应指明类型:
vector<int>::size_type;
正确。
vector::size_type
错误。
(2)vector对象可以使用范围for循环。
(3)和string一样,不能通过下标形式添加元素。
3.4 迭代器
-
使用下标可以访问string中的字符、vector中的元素。还可以通过迭代器实现。
严格来说,string不属于容器,但string确实支持很多与容器类似的操作。
如:vector和string都支持下标,都支持迭代器。
类似于指针类型,迭代器也提供了对对象的间接访问。
有效的迭代器或者指向某个元素,或者指向容器中尾元素的下一个位置;其他情况都属于无效。 -
拥有迭代器的类型,都拥有begin和end成员。
begin负责返回指向第一个元素的迭代器,end负责返回指向容器不存在的“尾后”元素。
若容器为空,则有begin==end
。 -
迭代器的运算:
运算 说明 *iter
返回迭代器iter所指的元素的引用(下标返回的才是本身) iter->mem
解引用iter并获取该元素的名为mem的成员,等价于(*iter).mem ++iter
令iter指示容器中的下一个元素 --iter
令iter指示容器中的上一个元素 iter1==iter2
、iter1!=iter2
判断两个迭代器是否相等;指向同一个元素或“尾后”元素 -
好的编程习惯:使用迭代器写for循环。这种风格在标准库提供的所有容器上都有效。
养成使用迭代器的习惯,就不用太在意到底是哪种容器类型。即泛型编程。for(auto it = s.begin(); it != s.end() && !isspace(*it); ++it)*it = toupper(*it);
-
迭代器类型为
iterator
或const_iterator
。
前者可读可写,故可进行加、减、相减运算;后者只能读元素。
begin()
和end()
返回的类型为iterator
,除非原容器是const对象。
保险起见,须返回const_iterator
的场合使用cbegin()
和cend()
。 -
解引用迭代器后访问成员,
(*iter).mem
等价于iter->mem
。 -
任何一种可能改变vector对象容量的操作,都会使原vector对象的迭代器会失效。
-
迭代器的算术运算:
迭代器运算的返回类型为difference_type
的带符号整型数,距离可正可负。运算 说明 iter + n
迭代器指示的新位置前进若干元素(或“尾后”元素) iter - n
…后退… iter += n
iter + n 后的结果赋给iter iter -= n
iter - n 后的结果赋给iter iter1 - iter2
计算两元素之间的距离 > >= < <=
判断所指位置谁更靠前或靠后 -
使用迭代器的经典场景是二分搜索。
3.5 数组
-
数组与vector相似,也是一种存放类型相同的对象的容器。
这些对象本身没有名字,需要通过其所在位置访问。
与vector不同的是,数组大小不变,不能随意向数组中增加元素。
因为数组的大小固定,因此对某些特殊程序运行较好,但相应的损失了一些灵活性。 -
定义和初始化数组时,
a[d]
中的d
必须是常量表达式。 -
显式初始化数组,必须使用花括号。
-
字符数组可以按照C风格字符串来进行初始化。
-
不存在引用的数组,但可以引用数组。
-
数组不允许直接拷贝和赋值。
原因:数组名为指针(此处和C语言保持一致) -
复杂的数组声明:
int array1[10]; // 警告:元素的值未初始化 int array2[10] = {}; // array2有10个元素,且元素都为0 int arr[5] = {0, 1, 2, 3, 4}; // 初始化数组只能使用花括号 int *p[5]; // p是1个数组,存放了5个指针,指针指向的都是int int &r[5]; // 错误:不存在存放引用的数组 int (*parr)[5] = &arr; // parr是1个指针,指向了1个有5个int的数组(arr) int (&rarr)[5] = arr; // rarr是1个引用,引用了1个有5个int的数组(arr) int *(&rparr)[5] = p; // rparr是1个引用,引用了1个数组(p)
-
访问数组元素可以使用范围for语句,也可以使用下标运算符。
-
数组下标数据类型为
size_t
,它是一种无符号类型。 -
数组在使用下标时,和vector一样,要注意避免下标越界。
-
使用数组时,编译器会把它转化为指针。实际上,数组名就是指针。
-
因此,当使用数组作为一个auto变量的初始值时,推断的类型是指针!
但decltype(array)
时,返回的类型是数组。int ia[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; // ia是一个含有10个整数的数组 auto ia1 (ia); // ia2是一个整型指针,指向ia的第一个元素 ia1 = 42; // 错误:ia2是一个指针,不能用int值给指针赋值 auto ia2(&ia[0]); // 显然,ia2的类型是int* decltype(ia) ia3 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; // ia3是一个含有10个整数的数组 ia3 = p; // 错误:不能用整型指针给数组赋值 ia3[4] = i; // 正确:把i的值赋给ia3的一个元素
-
数组当作指针使用时,与迭代器有以下异同:
(1)首元素地址:数组名,即迭代器的begin()。
(2)尾后元素地址:int *e = &arr[10];
,其中arr[10]
是不存在的元素。 -
标准库函数begin和end(iterator头文件中)
与迭代器不同的是,这两个函数仅仅是普通函数,并非成员函数。 -
指向数组元素的指针可以进行解引用、递增、递减、比较、与整数相加(减)的运算。
-
两指针相减将得到
ptrdiff_t
类型的值,是带符号类型。
迭代器运算的返回类型为difference_type
的带符号整型数。
数组下标是size_t
类型,是无符号类型。 -
指针和数组下标可以混合使用。
数组下标不能为负数,但指针下标可以。int *p = &a[4]; // p指向a[2] int j = p[1]; // a[3]的值赋给j int k = p[-2]; // a[0]的值赋给k
-
C++支持C风格字符串,但不建议使用。
cstring头文件就是string.h的C++版本。
C风格字符串不是一种独立的类型。
而是一种以\0
为结尾的char型数组。 -
string标准库函数:
strlen(s):返回长度
strcmp(s1, s2):判断是否相等
strcat(s1, s2):s2接在s1后面
strcpy(s1, s2):s2拷贝给s1 -
两个string对象可以直接比较字符串内容。
两个C风格字符串直接比较两个地址,是无意义的。
若想比较C风格字符串的内容,须使用strcmp()
函数。 -
string对象可以用以
\0
为结尾的字符数组来初始化。
string对象加法运算,须保证\0
不会“夹在”字面值的内部。
(也就是只允许最右边的string对象有\0
) -
string对象提供了
c_str()
成员函数,可以用来初始化C风格字符串。 -
数组可以初始化vector对象:
int arr[] = {0, 1, 2, 3, 4, 5};
vector<int> myVec( begin(arr), end(arr) );
3.6 多维数组
-
多维数组就是数组的数组。如
int a[3][4];
:
a是数组,有3行元素,每个元素是个(列)数组。
每一个(列)数组都有4个元素,每个元素是int。 -
多维数组的初始化:
// 这两种方式是等价的 int a[2][3] = { 1, 2, 3, 4, 5, 6 }; int a[2][3] = {{1, 2, 3},{4, 5, 6} }; // 初始化第一行,其余默认 int a[2][3] = { 1, 2, 3, 4 }; // 初始化第一列,其余默认 int a[2][3] = {{1},{4}};
-
多维数组下标引用:
int (&row)[3] = a[1];
表示把row[3]
绑定到a[1]
(三元素的数组)上。 -
多维数组的遍历:
size_t cnt = 0; for (auto &row :ia){ // 对于外层数组的每一个元素for (auto &col : row){ // 对于内层数组的每一个元素col = cnt; // 将下一个值赋给该元素++cnt; // 将cnt加1} } // 由上一节知道,auto初始化一个数组会得到指针 // 因此此处的外层循环row只能是引用。 // 要使用范围for语句处理多维数组,除了最内层循环外, // 所有外层循环的控制变量都应该是引用类型
-
地址和多维数组
因为多维数组是数组的数组,所以由多维数组名转换来的指针,
实际上是指向第一个内层数组的指针。int ia[3][4]; // 大小为3的数组,每个元素是含有4个整数的数组 int (*p)[4] = ia; // p指向含有4个整数的数组 p = &ia[2]; // p指向ia的尾元素 // 上述声明中,圆括号必不可少 int *p [4]; // 整型指针的数组 int (*p)[4]; // 指向含有4个整数的数组
-
C++11新标准中,使用auto或decltype就能使上述代码得到简化:
for (auto p = ia; p != ia + 3; ++p){for (auto q = *p; q != *p + 4; ++q){cout << *q << ' ';}cout << endl; }
-
使用标准库函数begin和end也能实现同样功能。
for (auto p = begin(ia); p != end(ia); ++p){for (auto q = begin(*p); q != end(ia); ++q){cout << *q << ' ';}cout << endl; }
-
使用类型别名简化多维数组的指针:
using int_array = int[4]; // 声明 typedef int int_array[4]; // 等价的typedef声明 for (int_array *p = ia; p != ia + 3; ++p){for (int *q = *p; q != *p +4; ++q){cout << *q << ' ';}cout << endl; }
3.7 补充:size_t和size_type
- 为了使自己的程序有很好的移植性,c++程序员应该尽量使用
size_t
和size_type
而不是int
,unsigned
。 size_t
是全局定义的类型;
size_type
是STL类中定义的类型属性,用以保存任意string和vector类对象的长度。size_t
使用的时候头文件需要<cstddef>
。
size_type
使用的时候需要<string>
或者<vector>
。- 二者联系:在用下标访问元素时,vector使用
vector::size_type
作为下标类型。
而数组下标的正确类型则是size_t
。
发布评论