字符串、向量和数组
3.1 命名空间的using声明
,使用命名空间可以避免名字冲突,就是用using声明命名空间,如:
using namespace std;
另外头文件不应包含using声明,因为头文件中的内容会被拷贝到所有引用它的文件中,导致这些文件都会有了using的声明,可能会产生名字冲突。
3.2 标准库类型string
定义和初始化string 对象
#include<string>
string s1;//默认初始化。s1为空串
string s2(s1);
string s2=s1;//类似于s2(s1)
string s3("hello");//直接初始化
string s3="hello";//拷贝初始化
string s4(n,'s');//直接初始化,s4由n个字符‘s’组成
string对象上可以进行如下操作
s.empty();//判断字符串是否为空
s.sze();//返回s中字符的个数
s[n];//返回s中第n个字符的引用
s1+s2;//拼接两个字符串
s1==s2,s1<s2,s1>s2;//对两个字符串进行大小比较
需要注意的是:s.size()返回的结果类型为无符号的size_type类型,由于size_type是无符号类型,因此如果表达式中存在有符号类型,那么可能会发生错误,如:
int n=-10;
string s="hello";
s.size()<n;//结果为true,因为负值的int型转换为无符号类型会得到一个很大的数(参见第二章)
另外需要注意的是:两个string对象可以相加,一个string对象和一个字符字面值(注:字符字面值与string是不同的类型)也可以相加,但是两个字符字面值不能相加。
string s1="hello";
string s2="world';
string s3=s1+s2;//正确
string s4=s1+"ok";//正确
string s5="IG"+"niubi";、、错误,两个字符字面值不能相加
string s6="theshy"+"ning"+s1;//错误,该表达式先执行第一个加号,即"theshy"+"ning",因此错误
字符串的读入
使用cin读入字符串时,忽略开头部分的空白(空格、换行、制表符),而从真正第一个字符开始读入,直到遇到下一处空白位置。
string s;
cin>>s; //如果输入为“ hello world ”,则得到的s为:hello。结果中不含空白
而使用getline读取字符串,若开头有空白,则将会包含开头的空白,遇到换行符时才结束读取并返回结果,且换行符不会出现在结果中,如果一开始就是换行符,那么结果将是一个空串
string s;
getline(cin,s);//遇到换行符结束读取,
string对象的比较
其实就是按位对每个字符进行比较,也就是比较字符的ASCII值。(z>a>Z>A),‘z’最大,'A’最小。
处理string对象中的字符
在cctype头文件中定义许多库函数来处理字符相关的内容。如:tolower©,将字符c转换为小写,toupper©将字符c装换为大写等等,详见《c++primer》第82页。(貌似不用包含cctype文件,只包含string文件也能调用这些函数),另外可以通过基于范围的for循环语句,对字符串的每一个字符进行处理,如:
string s="hello";
for(auto c : s) //这里c为char类型,相当于遍历字符串s的每一个字符然后输出
cout<<c<<endl;
值得注意的是:如果需要改变原字符串中的字符,则需要使用引用,如:
for(auto &c : s)
c=toupper(c);//使用引用从而改变原字符串中的字符。
另外还可以通过下标运算符([])对字符串中的某一个字符进行访问,返回值是该字符的引用,但是注意保证下标处于合理的范围之类(0~s.size()-1)。
使用范围for循环不应该改变所遍历序列的大小,比如不能再对vector范围for循环中,xiangvector中添加元素。
3.2 标准库类型vector
vector是模板而非类型
vector的初始化
vector<T> v1;
vector<T> v2(v1);
vector<T> v2=v1;
vector<int> v3(n,1);//v3包含了n个1
vector<int> v4(n);//v4中含有n个值,每个值都为0
vector<T> v5={a,b,c....};//列表初始化
vector<T> v5{a,b,c,d,...};//等价于上一种
另外也可以用数组初始化vector
int a[]={1,2,3,4,5};
vector<int> v(a.begin(),a.end());//数组初始化vector
由于vector能够自动增长容量,因此一般都是定义一个空的vector然后往里面添加元素,对于vector的初始化,如果使用的是圆括号一般表示数量,如果使用花括号一般表示值,如:
vector<int> v1(10);//v1含有10个元素,由于保存的是Int型,因此每个值默认初始化为0
vector<int> v2{10};//v2包含一个元素,值为10
vector<int> v3(10,1);//v3包含10个元素,且每一个值都为1;
vector<int> v4{10,1};//v4只包含两个元素,即10和1;
但是也有特殊情况,一般初始化过程都会尽可能把花括号内的值当作为元素初始值,但是如果花括号内的元素不能初始化对象,则表示数量了,如:
vector<string> s1{"hello"};//s1中只包含一个元素“hello”
vector<string> s2{10};//由于int型的10不能初始化string类型,因此该初始化表示包含10个默认值
vector<string> s3{10,"hello"};//由于使用花括号,但是10并不能初始化string对象,因此该初始化表示初始化10个"hello"。
向vector中添加元素:push_back();
vector的其他操作:v.empty(0;v.size();v[n]通过下标访问元素等等,
值得注意的是:不能通过下标的形式添加元素,只能通过push_back()。另外vector以及string的下标运算符只能访问已经存在的元素,因此确保下标处于一个合理的值
3.4 迭代器
迭代器类似于指针,提供了容器与算法之间的桥梁,使得容器和算法得以独立开来。
begin()表示指向第一个元素的迭代器。end()表示指向容器尾元素下一位置迭代器,及end()实际上指向一个不存在的值,仅仅是作为标记而已。迭代器的操作如下:
*iter;//返回迭代器所指元素的引用
iter->mem;解引用并获取该元素的成员,等价于(*iter).mem;
++iter;//是迭代器指向下一位,
--iter;//使迭代器指向前一位
迭代器可以与整数相加减,表示迭代器向前或向后移动,两个迭代器相减表示两个迭代器之间的距离,但是两个迭代器不能进行相加操作。
值得注意的是:由于end()指向一个不存在的值,因此不能对end进行递增操作以及解引用操作。
类似于常量指针,迭代器也有常量迭代器,即只能读不能修改指向元素的值。用const_iterator表示,如果vector或者string 是常量,那么迭代器只能使用const_iterator,如果vector或者string不是常量的话,那么迭代器即可以使用iterator,又能使用const_iterator,只是使用const_oterator不能修改值而已。
vector<int>::iterator it;
string::const_iterator it1;//两种类型的迭代器
另外,由bengin和end返回的迭代器类型由对象是否是常量决定,若对象是常量则返回const_iterator,或者返回iterator。若对于非常量的对象也想返回const_iterator,则可以使用c++11的新标准,cbegin和cend。
vector<int> v1;
auto it1=v1.begin();//此时it1类型为vector<int>::iterator
const vector<int> v2;
auto it2=v2.begin();//此时it2的类型为vector<int>::const_iterator
auto it3=v1.cbegin();//此时it3的类型同样为vector<int>::const_iterator
3.5 数组
数组声明是必须确定其大小即维度,并且不能改变。注意:维度是一个常量表达式(constexpr:元素的值在编译阶段就能确定且不能改变),如果采用列表初始化则可以不用指明维度,编译器会感觉初始化列表的个数推断维度
int a=10;
const int b=20;
int array[a];//错误,a不是一个常量表达式
int array1[b];//正确
int array2[]={1,2,3};//列表初始化,维度为3
int array3[5]={1,2};//数组前两个元素为1和2 ,剩下3个默认为0
初始化列表中元素的个数必须小于等于所给出的维度。另外数组与数组之间不能进行拷贝和赋值
字符数组的特殊性:当用一个字符串字面值对一个字符数组初始化时,别忘了字符串字面值结尾还隐藏了一个空字符,该空字符也会被拷贝到字符数组中去。
char c[]="hello";//此时c的维度为6
char c1[2]="IG";//错误,c1空间不够,忽略了末尾的空字符
对于复杂的数组声明,从数组名字开始,从内到外,从右向左的顺序阅读,如:
int *p[10];//p是含有10元素的数组,元素类型为int型指针
int (*p)[10];//首先p是一个指针,指向的是一个含有10个int型元素的数组
int *(&p)[10];//首先p是一个引用,然后引用的是一个含有10个元素数组,数组里存放的是int型指针
指针与数组
c++中数组与指针有着密切的关系,使用数组的时候,编译器会把数组转化为指针。
数组名称就是数组第一个元素的地址,因此可以直接将数组名称赋值给一个指针,此时就可以对指针进行下标运算
int a[5]={1,2,3,4,5};
int *p=a;//等价于int *p=&a[0];
p[2]=10;//等价于a[2]=10;
值得注意的是;尽管数组名称表示数组首地址,使用auto会推断出指针类型,而使用decltye关键字则返回的仍然是数组
int a[5];
auto p(a);//此时auto推断出为int型指针
decltype(a) a1={1,2,3,4,5};//decltype返回的类型为包含5个int型的数组,而不是指针
通过标准库函数begin和end可以得到数组的首指针和尾指针(最后一个元素的下一个地址,不指向任何一个元素,类似于尾迭代器)
int a[5]={1,2,3,4,5};
int *p1=begin(a); //指向第一个元素的指针
int *p2=end(a); //指向最后一个元素的下一位的指针
值得注意的是:内置的下标运算符的索引值不是无符号类型,(vector和string是无符号类型),如:
int a[5]={1,2,3,4,5};
int *p=&a[2];//p为指向第3个元素的指针
int j=p[1];//等价于j=*(p+1),即a[3]的元素
int k=p[-2];//等价于k=*(p-2),即a[0]元素
c风格的字符串是以空字符(’\0’)结束的字符数组表示
3.6 多维数组
实际上c++没有多维数组[,],所谓的多维数组其实是数组的数组([][])。
多维数组的初始化:可以使用花括号进行列表初始化
int a[3][4]={{0,1,2,3},{4,5,6,7},{8,9,10,11}};
int a[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};//内层花括号可以省略
int a[3][4]={{0},{4},{8}};//可以个别元素,其他元素执行默认初始化
int a[3][4]={0,1,2,3,4};//按顺序从多元化第一个初始化到最后一行最后一个,因此初始化了第一行的4个元素,以及第二行的第一个元素,其他元素执行默认初始化。
对多维数组进行遍历时,可以使用for循环,也可以使用基于范围的for语句处理,不过控制变量必须使用引用类型。
int a[3][4];
for(int i=0;i<3;i++)
{
for(int j=0;j<4;j++)
{
a[i][j]=i+j;
}
}
使用基于范围的for循环时,除了最内层循环外,其余循环层的控制变量必须为引用类型,为了避免数组自动转化为指针,因为当程序使用多维数组的名字时,会自动将其转化为指向数组首元素的 指针,内层循环对指针进行遍历是无意义的。
int a[3][4];
for(auto &r : a )//,此时r表示一维数组,如果不声明引用,则auto会把数组名称a解析成指针
{
for(auto c : r)//内层循环如果是不改变元素的操作,可以不用声明为引用
{
cout<<c<<endl;
{
}
使用类型别名可以简化多维数组的指针
using in_array=int[4];//类型别名
int a[3][4];
for(int_array *p=a;p!=a+3;p++)//p表示指向一个包含4个整型元素的指针,数组名称a表示第一个数组的地址(总共有3个数组)。
{
for(int *q=*p;q!=*p+4;q++) //对p解指针得到每个数组中首元素的地址
cout <<*q<<endl;
}
二维数组的指针有点复杂,因此画了一张示意图来理解每一层指针的所指向的内容
图中最后一个公式有点小错误:应该是*q=*a1=*(*p)=星星
好了,第二章基本结束,该去吃饭了。