第三章 字符串 向量和数组
第二章介绍的内置类型是c++直接定义的,标准库定义了一组具有更高级性质的类型,例如string和vector。
1.命名空间的using声明
目前为止,我们用到的所有库函数基本都属于命名空间std。
格式:using 命名空间 :: 名字,如using std :: cin。
头文件最好不要包含using声明,因为头文件内容会被拷贝到所有引用它的文件中,如果头文件有个using声明,那么所有使用了该头文件的文件都会有这个声明。不经意间可能会产生问题。
2.标准库类型string
表示可变字符序列。必须包含string头文件和命名空间声明。
#include <string>
using std::string
1.定义和初始化string 对象
string s1;
string s2=s1;string s3="value";
//
string s2(s1);string s3(value);string s4(n,'c')
//
2.string对象的操作
1.相加
可以相加s1+s2,可以和字符字面值(单引号’\n‘)和字符串字面值”,“相加,但是确保+两侧至少有一个对象是string;
注意:字符串字面值不同于string
2.比较
可以比较相等或者不相等s1==s2还可以比较大小(大小写敏感)
3.读写
可以用<< >>读写,自动忽略开头的空白,直到遇到下一个空白
可以用getline读取一行,直到遇到换行符
4.s.size(),s.empty()
s.size()返回的不是int类型,而是size_type类型,是无符号整型数,所以一个表达式不要同时包含s.size()和int这种有符号的
5.处理每个字符:范围for语句
string s("hello");
for (auto c:s)
cout<<c<<endl
for(auto &c:s)
c=toupper(c);
6.处理一部分字符:下标索引
使用下标/索引,s[0]代表第一个字符。[ ]接受的输入参数是string::size_type类型。
3.标准库类型vector(容器)
表示对象的集合,所有对象类型相同,每个对象有一个索引。必须包含头文件和using声明。
#include<vector>
using std::vector
vector是一个类模板。模板不是类或者函数,是编译器生成类或者函数的一份声明。编译器根据模板创建类或者函数的对象称为实例化。使用模板时,需要指出编译器应把类或者函数实例化为何种类型。
vector<int> ivec;
vector的元素(对象)可以是很多类型,甚至可以是vector,但是不能是引用,因为引用不是对象。
1.初始化
常用默认初始化,也可以是拷贝初始化,直接初始化,列表初始化
vector<int> v1(10); //v1有10个元素,每个的值都是0
vector<int> {10}, //v1有1个元素10
列表初始化{ }里面的值类型要和元素类型一样,不然编译器会用默认值初始化。
vector<string> v1{10}; //v1有10个默认元素
vector<string> v1{"hi"}; //正确
通常先创建一个空的,再向里面添加元素。
2.向vector对象添加元素
使用成员函数push_back,把值添加到vector对象的尾端。
string word;
vector<string> text;
while(cin>>word)
text.push_back(word);
范围for语句不应改变其遍历序列的大小,所以循环体内包含有向vector对象添加元素的语句时不能使用范围for循环。**
3.其它vector操作
也可以比较
与string类似,size返回的是size_type类型
使用下标索引,返回的也是size_type类型。
缓冲区溢出(buffer flow)——通过下标访问不存在的元素
4.迭代器(iterator)介绍
迭代器可以用来访问容器对象的元素。所有标准库容器都可以使用迭代器,但只有少数同时支持下标访问元素。
严格说,string不属于容器,但是支持迭代器。
迭代器类似于指针,也提供对对象的间接访问。
1.使用迭代器
获取迭代器不是通过取址运算符,支持迭代器的类型都有返回迭代器的成员函数,如begin(返回第一个元素或字符)和end(返回最后一个元素或字符的下一个位置,尾后迭代器)。
cbegin和cend返回的是常量迭代器。只能读不能写。
如果容器为空,两个返回的是同一个迭代器。
迭代器类型一般使用自动推导auto。实际上iterator和const_iterator通常表示迭代器类型。
vector<int> v;
const vector<int> cv;
auto it1=v.begin();
auto it2=cv.begin();
迭代器运算符
1.解引用
获得迭代器指向的元素。执行解引用的迭代器必须合法指向某个元素。
string s("hello");
if(s.begin()!=s.end())
//s不为空
auto it=s.begin();
*it=toupper(*it)
2.移动迭代器
递增运算符++。下面使用!=而不是用<,因为c++中素有标准库容器的迭代器都定义了==和!=,但不是所有的都有<。
for(auto it=s.begin();it!=s.end()&&!=isspace(*it);++it)
*it=toupper(*it);
3.解引用的成员访问
如果容器对象是类,可以进一步访问成员函数。字符串组成vector对象,要检查其元素是否为空:
(*it).empty();
也可以写成it->empty();
向迭代器所属容器中添加元素会使迭代器失效。
2.迭代器运算
string和vector的迭代器提供了更多额外的运算符。可以相加,相减,比较大小。
二分搜索
//text必须有序
auto beg=text.begin(),end=text.end();
auto mid=text.begin()+(end-beg)/2;
while(mid!=end&&*mid!=sought)
if(*mid>sought)
end=mid;
else
beg=mid+1;
mid=beg+(end-beg)/2;
5.数组
数组与vector类似,但是大小固定,程序运行性能较好,但不灵活。
1.定义和初始化
a[d],a是名字,d是维度。
不允许使用auto由初始值列表推断类型;数组元素为对象因此不存在引用的数组。
利用字符串字面值初始化数组,注意结尾有个空字符。
char a[]="c++";
不允许将一个数组的值赋给另一个数组。
可以定义存放指针的数组, 但是不存在引用的数组。int *ptr[10]
可以定义数组的指针和数组的引用。
int (*parry)[10] = &arr;
parry指向一个含有10个整数的数组
int (&arref)[10] = arr;
arref引用一个含有10和整数的数组
int *(&arry)[10] =ptrs;
arry是数组的引用,该数组含有10个指针。
2.访问数组元素
范围for语句或者下标。
3.数组和指针
使用数组的时候编译器一般会将其转换为指针,大多数表达式中,使用数组类型的对象其实是使用一个指向该数组首元素的指针。
string *p=&num[0];
等价于string *p=num;
自动类型推导时,如果ia是数组
auto ia2(ia); 等价于auto ia2(&ia[0]) 所以ia2的类型时指针
decltype(ia) ia3;不会发生上述情况,ia3是数组。
指针也是迭代器
获取指向数组首元素的指针:int *p=arr;
或者int *beg=begin(arr)
获取指向数组尾元素下一个位置的指针:int *p=&arr[最后一个下标+1];
或者int *last=end(arr)
指针不是类类型,所以begin和end函数不是成员函数,只是标准库函数。
指针运算
解引用,递增,比较,相加,但要指向同一个对象。
int a[]={0,2,4,6,8};
int p=*(a+4);
下标
很多时候使用数组的名字其实使用的是指向数组首元素的指针。
a[2]等效于*(a+2)
与标准库类型string和vector的下标区别是:标准库类型限定下标必须为无符号类型,数组的内置下标运算可以处理负值。
4.c风格字符串
字符串字面值就是C风格字符串,即按此习惯书写的字符串存**存放在字符数组中并以 ‘\0’ 结束。**而字符数组的名字实际上代表的是指向数组首元素的指针。
比较两个标准库string对象用的是==,<这些,而比较C风格字符串需要调用函数。
连接两个标准库string对象用+,而连接C风格字符串需要调用函数且容易出错。
标准库string比C风格字符串更安全高效。
5.与旧代码接口
1.string对象和C风格字符串
允许以字符串字面值初始化string,string对象的运算中允许出现字符串字面值。
但是string对象不能初始化C风格字符串,需要c_str的成员函数。
string s("frghj");
char *str=s;
错误
const char *str=s.c_str();
正确
2.使用数组初始化vector对象
不允许使用内置类型的数组或者vector对象初始化数组,但是数组可以用来初始化vector对象。指明要拷贝区域的首元素地址和尾后地址就可以了。
vector<int> ivec(begin(arr),end(arr));
vector<int> sub(arr+1,arr+4);
尽量使用标准库类型而非数组。
6.多维数组
严格来说,c++中没有数组,多维数组其实是数组的数组。
1.初始化
采用花括号。
2.下标引用
int ia[3][4]
;
int (&row)[4]=ia[1];
3.范围for语句
int ia[3][4]
;
for (auto &row:ia)
for(auto &col:row)
{col=cnt;
++cnt;}
上面,为了改变元素的值我们使用了引用。
但是还有一个原因是避免数组被自动给转换为指针,所以范围for语句处理多维数组除了最内层的循环,其它循环的控制变量都应该是引用类型。
4.指针和多维数组
记住,多为数组是数组的数组。所以多维数组的名字是指向第一个内层数组的指针。
int *p[4];
整型指针的数组
int (*p)[4];
指向含有4个整数的数组
C++11中可以通过自动类型推导避免在数组前面加一个指针类型。
for(auto p=begin(ia);p!=end(ia);++p)
for(auto q=begin(*p);q!=end(*p);++q)
cout<<*q<<endl;