3.1 命名空间的using声明
- 位于头文件的代码一般来说不应该使用
using声明
,这是因为头文件的内容会拷贝到所有引用它的文件中去,如果头文件里有某个using声明
,那么每个使用了该头文件的文件就都会有这个声明.
3.2 标准库类型string
- 标准库类型
string
表示可变长的字符序列,使用string
类型必须首先包含string
头文件,作为标准库的一部分,string
定义在命名空间std
中(见例子一) - c++中
string
的初始化有多种方式,如果使用等号(=)初始化一个变量,实际上执行的是拷贝初始化(copy initialization
,编译器把等号右侧的初始值拷贝到新创建的对象中去。与之相反,如果不使用等号,则执行的是直接初始化(direct initialization)
.(string的初始化见例子二) - 使用
getline(cin,line)
函数从给定的输入流中读入内容,直到遇到换行符为止(注意换行符也被读进来了),然后把所读的内容存入到string
对象line中去(注意不存换行符) string
的size
函数返回一个string::size_type
类型值,它是一个无符号类型的值,而且能足够存放下任何string
对象的大小- 当把
string
对象和字符字面值及字符串字面值混在一条语句中使用时,必须确保每个加法运算符的两侧的运算对象至少有一个是string
- 在
cctype
头文件中定义了一组标准库函数用来处理string对象中的字符
//例子一:使用string
#include<string>
using std::string;
//例子二:string的初始化
string s1; //默认初始化,s1是一个空串
string s2(s1); //s2是s1的副本
string s3=s1;
string s4("value"); //s4是字面值"value"的副本,除了字面值最后的那个空字符外
string s5="value";
string s6(n,'c'); //把s6初始化为由连续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个字符的引用,位置n从0计起
//例子四:字面值和string对象相加
string s1="hello";
string s2=s1+" "; //正确
string s3="hello"+" "; //错误:两个运算对象都不是string
string s4=s1+", "+"world"; //正确
//例子五:处理string对象中的字符
isalnum(c); //当c是字母或数字时为真
isalpha(c); //当c是字母时为真
isdigit(c); //当c是数字时微针
islower(c); //当c是小写字母时为真
isupper(c); //当c是大写字母时为真
isspace(c); //当c是空白时为真(即c是空格、横向制表符、纵向制表符、回车符、换行符、进纸符中的一种)
tolower(c); //如果c是大写字母,输出对应的小写字母;否则原样输出c
toupper(c); //如果c是小写字母,输出对应的大写字母;否则原样输出c
注意:如果一条表达式中已经有了
size()
函数就不要再使用int
了,这样就可以避免混用int
和unsigned
可能带来的问题.
3.3 标准库类型vector
- 要想使用
vector
,同样必须包含适当的头文件 - c++有多种定义
vector
对象的常用方法(见例子二)- 如果
vector
对象的元素是内置类型,比如int
,则元素初始值自动设为0,如果元素是某种类类型,比如string
,则元素由类默认初始化
- 如果
- 对
vector
使用关系运算符比较时:- 如果两个
vector
对象的容量不同,但是在相同位置上的元素值都一样,则元素较少的vector
对象小于元素较多的vector
对象; - 如果元素的值有所区别,则
vector
对象的大小关系由第一对相异的元素值的大小关系决定.
- 如果两个
//例子一:
#include<vector>
using std::vector
//例子二:定义vector的多种方式
vector<T> v1; //v1是一个空vector
vector<T> v2(v1); //v2是v1的拷贝副本
vector<T> v3=v1; //v3也是v1的拷贝副本
vector<T> v4(n,val); //v4包含了n个重复的元素,每个元素的值都是val
vector<T> v5(n); //v5包含了n个重复地执行值初始化的对象
vector<T> v6{a,b,c...}; //v6包含了初始值个数的元素,每个元素被赋予相应的初始值
vector<T> v6={a,b,c...}; //等价于v6{a,b,c...}
//例子三:其他vector操作
v.empty(); //判断v是否为空
v.size(); //返回v中元素的个数
v.push_back(t); //向v的尾端添加一个值为t的元素
v[n]; //返回v中第n个位置上元素的引用
v1=v2;
v1={a,b,c,...}; //用列表中元素的拷贝替换v1中的元素!!!!
3.4 迭代器介绍
- 类似于指针类型,
迭代器
提供了对对象的间接访问vector
和string
中都拥有名为begin
和end
的成员,其中begin
成员负返回指向第一个元素的迭代器,end
成员则负责返回指向容器"尾元素的下一位置"(这个迭代器被称为尾后迭代器
)(例子一给出了标准容器迭代器的运算符)
- 任何一种可能改变
vector
对象容量的操作,比如push_back
,都会使该vector对象的迭代器失效- 谨记,但凡是使用了迭代器的循环体,都不要向迭代器所属的容器添加元素
//例子一:标准容器迭代器的运算符
*iter //返回迭代器iter所指元素的引用
iter->mem //解引用iter并获取该元素的名为mem的成员
++iter //令iter指示容器中的下一个元素
--iter //令iter指示容器中的上一个元素
iter1==iter2 //判断两个迭代器是否相等(不相等),如果两个迭代器所指示的是同一个元素
iter1!=iter2 //或者它们是同一个容器的尾后迭代器,则相等;反之,不相等
//例子二:迭代器的类型
vector<int>::iterator it; //it能读写vector<int>的元素
string::iterator it2; //it2能读写string对象中的字符
vector<int>::const_iterator it3; //it3只能读元素,不能写元素
string::const_iterator it4; //it4只能读字符,不能写字符
//例子三:vector和string迭代器支持的运算
//注意是vector和string支持的,其他标准库中的容器不一定有
iter+n //迭代器向后(向前)移动若干个元素
iter-n
iter+=n
iter-=n
iter1-iter2 //两个迭代器相减的结果是它们之间的距离
>,>=,<,<= //迭代器的关系运算符,如果某迭代器指向的容器位置在另一个迭代器所指位置之前,则说前者小于后者
注意:所有标准库容器的迭代器都定义了
==
和!=
。因此,只要我们养成使用迭代器和!=
的习惯,就不用太在意用的到底是哪种容器类型
3.5 数组
- 与
vector
不同的地方是,数组的大小确定不变,不能随意向数组中增加元素,不允许拷贝和赋值 - 数组中元素的个数也属于数组类型的一部分,编译的时候维度应该是已知的,也就是说,维度必须是一个
常量表达式
- 和内置类型的变量一样,如果在函数内部定义了某种内置类型的数组,那么默认初始化会令数组含有未定义的值
- 不能将数组的内容拷贝给其他数组作为其初始值,也不能用数组为其他数组赋值
- 为了理解复杂的数组声明,采用由内向外的顺序可以更好地理解数组的含义(见例子二)
- 在使用数组下标的时候,通常将其定义为
size_t
类型,size_t
是一种机器相关的无符号类型,它被设计得足够大以便能表示内存中任意对象的大小,在cstddef
头文件中定义了size_t
类型 - 数组有一个特性:在很多用到数组名字的地方,编译器都会自动地将其替换为一个指向数组首元素的指针
- 需要指出的是,当使用
decltype
关键字时数组名向指针的转换不会发生(可以看出decltype比较"呆板",不会对类型进行转换,比如顶层const和引用都会保留)
- 需要指出的是,当使用
- c++11新标准引入了两个名为
begin
和end
的函数,begin
函数返回指向数组首元素的指针,end
函数返回指向数组尾元素下一位置的指针- 特别需要注意,尾后指针不能执行解引用和递增操作
- c风格字符串以
\0
结尾,在cstring
头文件中提供了对c风格字符串操作的函数(见例子五)
//例子一:数组不允许拷贝和赋值
int a[]={0,1,2};
int a2[]=a; //错误:不允许使用一个数组初始化另一个数组
a2=a; //错误:不能把一个数组直接赋值给另一个数组
//例子二:理解复杂的数组声明
int *ptrs[10]; //ptrs是含有10个整型指针的数组
int &refs[10]=/* ? */; //错误:不存在引用的数组
int (*parray)[10]=&arr; //parray是一个指针,parray指向一个含有10个整数的数组
int (&arrRef)[10]=arr; //arrRef是一个引用,arrRef引用一个含有10个整数的数组
int *(&arry)[10]=pgrs; //arry是一个引用,arry引用一个含有10个整型指针的数组
//例子三:指针和数组
int ia[]={0,1,2,3,4,5,6,7,8,9};
auto ia2(ia); //ia2是一个整型指针,指向ia的第一个元素
auto ia2(&ia[0]); //显然ia2的类型是int*,等价于auto ia2(ia);
decltype(ia) ia3={0,1,2,3,4,5,6,7,8,9};
ia3=p; //错误:不能用整型指针给数组赋值
ia3[4]=i; //正确:把i的值赋给ia3的一个元素
//例子四:标准库函数begin和end
int ia[]={0,1,2,3,4,5,6,7,8,9}; //ia是一个含有10个整数的数组
int *beg=begin(ia); //指向ia首元素的指针
int *last=end(ia); //指向arr尾元素的下一位置的指针
//例子五:C风格字符串的函数
//传入此类函数的指针必须指向以空字符作为结束的数组
strlen(p); //返回p的长度,空字符不计算在内
strcmp(p1,p2); //比较p1和p2的相等性。如果p1==p2,返回0;如果p1>p2,返回一个正值;如果p1<p2,返回一个负值
strcat(p1,p2); //将p2附加到p1之后,返回p1
strcpy(p1,p2); //将p2拷贝给p1,返回p1
char ca[{'C','+','+'}; //不以空字符结束
cout<<strlen(ca)<<endl; //严重错误:ca没有以空字符结束
3.6 多维数组
- 允许使用花括号括起来的一组值初始化多维数组,这点和普通的数组一样(见例子一)
- 要使用范围
for
语句处理多维数组,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型
- 因为多维数组实际上是数组的数组,所以由多维数组名转换得来的指针实际上是指向第一个内层数组的指针(见例子三)
//例子一:多维数组的初始化
int ia[3][4]={
{0,1,2,3},
{4,5,6,7},
{8,9,10,11}
};
//例子二:范围for遍历多维数组
int ia[3][4];
for(auto row:ia) //错误:外循环中row得到的是指向该数组内部首元素的指针
for(auto col:row)
...
for(const auto &row:ia)
for(auto col:row)
cout<<col<<endl;
//例子三:指针和多维数组
int ia[3][4];
int (*p)[4]=ia; //p指向含有4个整数的数组
p=&ia[2]; //p指向ia的尾元素
//例子四:类型别名简化多维数组的指针
using int_array=int[4]; //新标准下类别别名的声明
typedef int int_array[4]; //等价的typedef声明
for(int_array *p=ia; p!=ia+3; ++p) //这里p是指向指针的指针 指向关系:p->多维数组的内层数组的指针->内侧数组的首元素!!!
for(int *q=*p;q!=*p+4;++q){ //q是多维数组的内层数组的指针
cout<<*q<<" ";
}
cout<<endl;