:
endl;
注意反斜线符号必须是该行的尾字符——不允许有注释或空格符。
同样,后继行行首的任何空格和制表符都是字符串字面值的一部分。
正因如此,长字符串字面值的后继行才不会有正常的缩进。
9.在C++中,操作是否合法是在编译时检查的。
当编写表达式时,编译器检查表达式中的对象是否按该对象的类型定义的使用方式使用。
如果不是的话,那么编译器会提示错误,而不产生可执行文件。
10.C++支持两种初始化变量的形式:
复制初始化和直接初始化。
复制初始化语法用等号(=),直接初始化则是把初始化式放在括号中:
intival(1024);//direct-initialization
intival=1024;//copy-initialization
接初始化语法更灵活且效率更高。
11.extern声明不是定义,也不分配存储空间。
事实上,它只是说明变量定义在程序的其他地方。
程序中变量可以声明多次,但只能定义一次。
externinti;//declaresbutdoesnotdefinei
inti;//declaresanddefinesi
只有当extern声明位于函数外部时,才可以含有初始化式。
在C++语言中,变量必须且仅能定义一次,而且在使用变量之前必须定义或声明变量
解释下列例子中name的意义
externstd:
:
stringname;
std:
:
stringname("exercise3.5a");
externstd:
:
stringname("exercise3.5a");
第一条语句是一个声明,说明std:
:
string变量name在程序的其他地方定义。
第二条语句是一个定义,定义了std:
:
string变量name,并将name初始化为:
“exercise3.5a”
第三条语句也是一个定义,但这个语句只能出现在函数外部(即,name是一个全局变量。
12.在对象第一次被使用的地方定义对象可以提高程序的可读性。
读者不需要返回到代码段的开始位置去寻找某一特殊变量的定义,而且,在此处定义变量,更容易给它赋以有意义的初始值。
13.引用是一种复合类型,通过在变量名前添加“&”符号来定义。
复合类型是指用其他类型定义的类型。
在引用的情况下,每一种引用类型都“关联到”某一其他类型。
不能定义引用类型的引用,但可以定义任何其他类型的引用。
因为引用只是它绑定的对象的另一名字,作用在引用上的所有操作事实上都是作用在该引用绑定的对象上:
可以在一个类型定义行中定义多个引用。
必须在每个引用标识符前添加“&”符号:
inti=1024,i2=2048;
int&r=i,r2=i2;//risareference,r2isanint
inti3=1024,&ri=i3;//definesoneobject,andonereference
int&r3=i3,&r4=i2;//definestworeferences
const引用是指向const对象的引用:
constintival=1024;
constint&refVal=ival;//ok:
bothreferenceandobjectareconst
int&ref2=ival;//error:
nonconstreferencetoaconstobject
将普通的引用绑定到const对象是不合法的。
Const引用可以初始化为不同类型的对象或者初始化为右值。
inti=42;
//legalforconstreferencesonly
constint&r=42;
constint&r2=r+i;
但是这种初始化方式对于非const引用确实不合法的,而且会导致编译时错误。
因为:
doubledval=3.14;
constint&ri=dval;
编译器会把这些代码转换成如以下形式的编码:
inttemp=dval;//createtemporaryintfromthedouble
constint&ri=temp;//bindritothattemporary
如果ri不是const,那么可以给ri赋一新值。
这样做不会修改dval,而是修改了temp。
期望对ri的赋值会修改dval的程序员会发现dval并没有被修改。
仅允许const引用绑定到需要临时使用的值完全避免了这个问题,因为const引用是只读的。
注:
非const引用只能绑定到与该引用同类型的对象
const引用则可以绑定到不同但相关的类型的对象或绑定到右值。
。
。
。
。
。
。
17vector是同一种类型的对象的集合,每个对象都有一个对应的整数索引值。
和string对象一样,标准库将负责管理与存储元素相关的内存。
我们把vector称为容器,是因为它可以包含其他对象。
一个容器中的所有对象都必须是同一种类型的。
我们将在第九章更详细地介绍容器。
使用vector之前,必须包含相应的头文件。
#include
usingstd:
:
vector;
vector是一个类模板(classtemplate)。
使用模板可以编写一个类定义或函数定义,而用于多个不同的数据类型。
因此,我们可以定义保存string对象的vector,或保存int值的vector,又或是保存自定义的类类型对象(如Sales_items对象)的vector。
vectorivec;//ivecholdsobjectsoftypeint
vectorSales_vec;//holdsSales_items
vector不是一种数据类型,而只是一个类模板,可用来定义任意多种数据类型。
vector类型的每一种都指定了其保存元素的类型。
因此,vector和vector都是数据类型。
Vector类定义了好几种构造函数,用来定义和初始化vector对象。
Vectorv1:
vector保存类型为T对象。
默认构造函数v1为空。
Vectorv2(v1):
v2是v1的一个副本。
Vectorv3(n,i)v3包含n个值为i的元素。
Vectorv4(n)v4含有值初始化的元素的n个副本
若要创建非空的vector对象,必须给出初始化元素的值。
当把一个vector对象复制到另一个vector对象时,新复制的vector中每一个元素都初始化为原vectors中相应元素的副本。
但这两个vector对象必须保存同一种元素类型:
vectorivec1;//ivec1holdsobjectsoftypeint
vectorivec2(ivec1);//ok:
copyelementsofivec1intoivec2
vectorsvec(ivec1);//error:
svecholdsstrings,notints
可以用元素个数和元素值对vector对象进行初始化。
构造函数用元素个数来决定vector对象保存元素的个数,元素值指定每个元素的初始值:
vectorivec4(10,-1);//10elements,eachinitializedto-1
vectorsvec(10,"hi!
");//10strings,eachinitializedto"hi!
"
vector对象(以及其他标准库容器对象)的重要属性就在于可以在运行时高效地添加元素。
因为vector增长的效率高,在元素值已知的情况下,最好是动态地添加元素。
虽然可以对给定元素个数的vector对象预先分配内存,但更有效的方法是先初始化一个空vector对象,然后再动态地增加元素
如果没有指定元素的初始化式,那么标准库将自行提供一个元素初始值进行值初始化(valueinitializationd)。
这个由库生成的初始值将用来初始化容器中的每个元素,具体值为何,取决于存储在vector中元素的数据类型
如果vector保存内置类型(如int类型)的元素,那么标准库将用0值创建元素初始化式:
vectorfvec(10);//10elements,eachinitializedto0
如果vector保存的是含有构造函数的类类型(如string)的元素,标准库将用该类型的默认构造函数创建元素初始化式:
vectorsvec(10);//10elements,eachanemptystring
还有第三种可能性:
元素类型可能是没有定义任何构造函数的类类型。
这种情况下,标准库仍产生一个带初始值的对象,这个对象的每个成员进行了值初始化。
Vector对象的操作
v.empty()如果v为空,则返回true,否则返回false。
v.size()返回v中元素的个数。
v.push_back(t)在v的末尾增加一个值为t的元素。
v[n]返回v中位置为n的元素。
v1=v2把v1的元素替换为v2中元素的副本。
v1==v2如果v1与v2相等,则返回true。
!
=,<,<=,>and>=保持这些操作符惯有的含义。
Vector的下标操作
vector中的对象是没有命名的,可以按vector中对象的位置来访问它们。
通常使用下标操作符来获取元素。
vector的下标操作类似于string类型的下标操
vector的下标操作符接受一个值,并返回vector中该对应位置的元素。
vector元素的位置从0开始。
下例使用for循环把vector中的每个元素值都重置为0:
//resettheelementsinthevectortozero
for(vector:
:
size_typeix=0;ix!
=ivec.size();++ix)
ivec[ix]=0;
和string类型的下标操作符一样,vector下标操作的结果为左值,因此可以像循环体中所做的那样实现写入。
另外,和string对象的下标操作类似,这里用size_type类型作为vector下标的类型。
下标操作不添加元素
vectorivec;//emptyvector
for(vector:
:
size_typeix=0;ix!
=10;++ix)
ivec[ix]=ix;//disaster:
ivechasnoelements
上述程序试图在ivec中插入10个新元素,元素值依次为0到9的整数。
但是,这里ivec是空的vector对象,而且下标只能用于获取已存在的元素。
这个循环的正确写法应该是:
for(vector:
:
size_typeix=0;ix!
=10;++ix)
ivec.push_back(ix);//ok:
addsnewelementwithvalueix
注:
必须是已存在的元素才能用下标操作符进行索引。
通过下标操作进行赋值时,不会添加任何元素。
18.除了使用下标来访问 vector 对象的元素外,标准库还提供了另一种访问元素的方法:
使用迭代器(iterator)。
迭代器是一种检查容器内元素并遍历元素的数据类型
标准库为每一种标准容器(包括 vector)定义了一种迭代器类型。
迭代器类型提供了比下标操作更通用化的方法:
所有的标准库容器都定义了相应的迭代器类型,而只有少数的容器支持下标操作。
因为迭代器对所有的容器都适用,现代 C++ 程序更倾向于使用迭代器而不是下标操作访问容器元素,即使对支持下标操作的 vector 类型也是这样。
每种容器类型都定义了自己的迭代器类型,如 vector:
vector:
:
iteratoriter;
这条语句定义了一个名为 iter 的变量,它的数据类型是 vector 定义的 iterator 类型。
每个标准库容器类型都定义了一个名为 iterator 的成员,这里的 iterator 与迭代器实际类型的含义相同。
各容器类都定义了自己的 iterator 类型,用于访问容器内的元素。
换句话说,每个容器都定义了一个名为 iterator 的类型,而这种类型支持(概念上的)迭代器的各种操作。
每种容器都定义了一对命名为 begin 和 end 的函数,用于返回迭代器。
如果容器中有元素的话,由 begin 返回的迭代器指向第一个元素:
vector:
:
iteratoriter=ivec.begin();
述语句把 iter 初始化为由名为 vector 操作返回的值。
假设 vector 不空,初始化后,iter 即指该元素为 ivec[0]。
由 end 操作返回的迭代器指向 vector 的“末端元素的下一个”。
“超出末端迭代器”(off-the-enditerator)。
表明它指向了一个不存在的元素。
如果 vector 为空,begin 返回的迭代器与 end 返回的迭代器相同。
由 end 操作返回的迭代器并不指向 vector 中任何实际的元素,相反,它只是起一个哨兵(sentinel)的作用,表示我们已处理完 vector 中所有元素。
迭代器类型定义了一些操作来获取迭代器所指向的元素,并允许程序员将迭代器从一个元素移动到另一个元素。
解引用操作符返回迭代器当前所指向的元素。
假设 iter 指向 vector 对象 ivec 的第一元素,那么 *iter 和 ivec[0] 就是指向同一个元素。
上面这个语句的效果就是把这个元素的值赋为 0。
代器使用自增操作符(1.4.1 节)向前移动迭代器指向容器中下一个元素。
而对迭代器对象则是把容器中的迭代器“向前移动一个位置”。
因此,如果 iter 指向第一个元素,则 ++iter 指向第二个元素。
由于 end 操作返回的迭代器不指向任何元素,因此不能对它进行解引用或自增操作
另一对可执行于迭代器的操作就是比较:
用 == 或 !
= 操作符来比较两个迭代器,如果两个迭代器对象指向同一个元素,则它们相等,否则就不相等。
每种容器类型还定义了一种名为 const_iterator 的类型,该类型只能用于读取容器内元素,但不能改变其值。
当我们对普通 iterator 类型解引用时,得到对某个元素的非 const(2.5 节)。
而如果我们对 const_iterator 类型解引用时,则可以得到一个指向 const 对象的引用(2.4 节),如同任何常量一样,该对象不能进行重写。
使用 const_iterator 类型时,我们可以得到一个迭代器,它自身的值可以改变,但不能用来改变其所指向的元素的值。
可以对迭代器进行自增以及使用解引用操作符来读取值,但不能对该元素赋值。
不要把 const_iterator 对象与 const 的 iterator 对象混淆起来。
声明一个 const 迭代器时,必须初始化迭代器。
一旦被初始化后,就不能改变它的值:
vectornums(10);//numsisnonconst
constvector:
:
iteratorcit=nums.begin();
*cit=1;//ok:
citcanchangeitsunderlyingelement
++cit;//error:
can'tchangethevalueofcit
何时使用 const 迭代器的?
又在何时使用 const_iterator?
解释两者的区别。
const迭代器是迭代器常量,该迭代器本身的值不能修改,即该迭代器在定义时
需要初始化,而且初始化之后,不能再指向其他元素。
若需要指向固定元素的
迭代器,则可以使用const迭代器。
const_iterator是一种迭代器类型,对这种类型的迭代器解引用会得到一个指
向const对象的引用,即通过这种迭代器访问到的对象是常量。
该对象不能修
改,因此,const_iterator类型只能用于读取容器内的元素,不能修改元素的
值。
若只需遍历容器中的元素而无需修改它们,则可以使用const_iterator。
除了一次移动迭代器的一个元素的增量操作符外,vector 迭代器(其他标准库容器迭代器很少)也支持其他的算术操作。
这些操作称为迭代器算术操作(iteratorarithmetic),包括:
∙iter+n
iter–n
∙可以对迭代器对象加上或减去一个整形值。
这样做将产生一个新的迭代器,其位置在 iter 所指元素之前(加)或之后(减) n 个元素的位置。
加或减之后的结果必须指向 iter 所指 vector 中的某个元素,或者是 vector 末端的后一个元素。
加上或减去的值的类型应该是 vector 的 size_type 或 difference_type 类型(参考下面的解释)。
∙iter1-iter2
∙该表达式用来计算两个迭代器对象的距离,该距离是名为 difference_type 的 signed 类型 size_type 的值,这里的 difference_type 是 signed 类型,因为减法运算可能产生负数的结果。
该类型可以保证足够大以存储任何两个迭代器对象间的距离。
iter1 与 iter2 两者必须都指向同一 vector 中的元素,或者指向 vector 末端之后的下一个元素。
可以用迭代器算术操作来移动迭代器直接指向某个元素,例如,下面语句直接定位于 vector 中间元素:
vector:
:
iteratormid=vi.begin()+vi.size()/2;
上述代码用来初始化 mid 使其指向 vi 中最靠近正中间的元素。
这种直接计算迭代器的方法,与用迭代器逐个元素自增操作到达中间元素的方法是等价的,但前者的效率要高得多。
任何改变 vector 长度的操作都会使已存在的迭代器失效。
例如,在调用 push_back 之后,就不能再信赖指向 vector 的迭代器的值了。
19.有些程序要处理二进制位的有序集,每个位可能包含 0(关)1(开)值。
位是用来保存一组项或条件的 yes/no 信息(有时也称标志)的简洁方法。
标准库提供的 bitset 类简化了位集的处理。
要使用 bitset 类就必须包含相关的头文件。
:
#include
usingstd:
:
bitset;
类似于 vector,bitset 类是一种类模板;而与 vector 不一样的是 bitset 类型对象的区别仅在其长度而不在其类型。
在定义 bitset 时,要明确 bitset 含有多少位,须在尖括号内给出它的长度值:
表 3.6.初始化 bitset 对象的方法
bitsetb;
bhasnbits,eachbitis0
b 有 n 位,每位都 0
bitsetb(u);
bisacopyoftheunsignedlongvalueu
b 是 unsignedlong 型 u 的一个副本
bitsetb(s);
bisacopyofthebitscontainedinstrings
b 是 string 对象 s 中含有的位串的副本
bitsetb(s,pos,n);
bisacopyofthebitsinncharactersfromsstartingfrompositionpos
b 是 s 中从位置 pos 开始的&nbps;n 个位的副本。
bitset<32>bitvec;//32bits,allzero
条语句把 bitvec 定义为含有