潘爱民 张丽 译
第二篇基本语言
第三章C++数据类型
3.2.1什么是变量?
变量为我们提供了一个有名字的内存存储区,可以通过程序对其进行读、写和处理。
变量和文字常量都有存储区,并且有相关的类型。区别在于变量是可寻址的。对于每一个变量,都有两个值与其关联。
3.2.2变量名
变量名,即变量的标识符,可以由字母、数字以及下划线字符组成。它必须以字母或下划线开头,并且区分大写字母和小写字母。
C++保留了一些词作关键字。关键字标识符不能再作为程序的标识符使用。
3.2.3对象的定义
一个简单的定义指定了变量的类型和标识符,它并不提供初始值。如果一个变量是在全局域内定义的,那么系统自动会保证给它提供初始值0。如果变量是在局部域内定义的,或是通过new表达式动态分配的,则系统不会向它提供初始值0。这些对象被称为是未初始化的。未初始化的对象不是没有值,而是它的值是未定义的。(与它相关联的内存区中含有一个随机的位串,可能是以前使用的结果)
第6章抽象容器类型
顺序容器拥有由单一类型元素组成的一个有序集合。两个主要的顺序容器是list和vector。第三个顺序容器为双端队列deque,发音为“deck”,它提供了与vector相同的行为,但是对于首元素的有效插入和删除提供了特殊的支持。例如,在实现队列时,deque比vector更为合适。
关联容器支持查询一个元素是否存在,并且可以有效地获取元素。两个基本的关联容器是map(映射)和set(集合)。map是一个键/值对:键用于查询,而值包含我们希望使用的数据。例如,map可以很好地支持电话目录,键是人名,值是相关联的电话号码。
set包含一个单一键值,有效支持关于元素是否存在的查询。
map和set都只包含每个键的惟一出现,即每个键只允许出现一次。
multimap(多映射)和multiset(多集合)支持同一个键的多次出现。
例如,我们的电话目录可能需要为单个用户支持多个列表,一种实现方法是使用multimap。
vector,deque以及list都是动态增长的。在这三者之中选择的准则主要是关注插入特性以及对元素的后续访问要求。
vector 表示一段连续的内存区域,每个元素被顺序存储在这段内存中。对vector的随机访问效率很高。但是,在任意位置,插入或删除的效率很低。
deque也表示一段连续的内存区域,但它支持高效地在其首部插入和删除元素。list表示非连续的内存区域,并通过一对指向首尾元素的指针双向链接起来,从而允许向前和向后两个方向进行遍历。在list的任意位置插入和删除元素的效率都很高:它对随机访问的支持并不好。
容量是指在容器下一次需要增长自己之前能够被加入到容器中的元素的总数(容量指与连续存储的容器相关:例如vector,deque或string。list不要求容量)。
而长度(size)是指容器当前拥有元素的个数。为了调获得容器的当前长度,我们调用它的size()操作。
迭代器(iterator)提供了一种一般化的方法,对顺序或关联容器类型中的每个元素进行连续访问。
例如,假设iter为任意容器类型的一个iterator,则++iter;向前移动迭代器,使其指向容器的下一个元素,而*iter;返回iterator指向元素的值。
每种容器类型都提供一个begin()和一个end()成员函数。
begin()返回一个iterator,它指向容器的第一个元素。
end()返回一个iterator,它指向容器的末元素的下一个位置。
除了iterator类型,每个容器还定义了一个const_iterator类型,后者对于遍历const容器是必须的。
iterator算术运算只适用于vector或deque,而不适用于list,因为list元素在内存中不是连续存储的。
20131210
17.9 STL容器特征总结
STL中顺序容器类和关联式容器类的主要特征如下:
(1)vector
内部数据结构:连续存储,如数组。
随机访问每个元素,所需要的时间为常量。
在末尾增加或删除元素所需时间与元素数目无关,在中间或开头增加或删除元素所需时间随元素数目呈线性变化。
可动态增加或减少元素,内存管理自动完成,但程序员可以使用reserve()成员函数来管理内存。
vector的迭代器在内存重新分配时将失效(它所指向的元素在该操作的前后不再相同)。当把超过capacity()-size()个元素插入vector中时,内存会重新分配,所有的迭代器都将失效;否则,
指向当前元素以后的任何元素的迭代器都将失效。当删除元素时,指向被删除元素以后的任何元素的迭代器都将失效。
【建议17-8】:使用vector时,用reserve()成员函数预先分配需要的内存空间,它既可以保护迭代器使之不会失效,又可以提高运行效率。
(12)map
键唯一。
元素默认按键的升序排列。
如果迭代器所指向的元素被删除,则该迭代器失效。其他任何增加、删除元素的操作都不会使迭代器失效。
(13)multimap
键可以不唯一。
其他特点与map相同。
(14)hash_map
与map相比较,它里面的元素不一定是按键值排序的,而可能是按照所用的hash函数分派的,它能提供更快的搜索速度(当然也跟hash函数有关)。
其他特点与map相同。
【建议17-11】当元素的有序搜索速度更重要时,应选用set、multiset、map或multimap。否则,选用hash_set、hash_multiset、hash_map或hash_multimap。
【建议17-12】往容器中插入数据时,若元素在容器中的顺序无关紧要,请尽量加载最后面。若经常需要在序列容器的开头或中间增加或删除元素时,应选用list。
【建议17-13】对关联式容器而言,尽量不要使用C风格的字符串(即字符指针)作为键值。
【建议17-14】当容器作为参数传递时,请采用引用传递方式。否则将调用容器的拷贝构造函数,其开销是难以想象的。
第三篇 基于过程的程序设计
第7章函数
第8章域和生命期
8.1.1局部域
局部域是包含在函数定义(或函数块)中的程序文本区。每一个函数都有一个独立的局部域。在函数中,每个复合语句(或块)也有它自己的局部域。局部域可以被嵌套。
8.2全局对象和函数
全局域内的函数声明将引入全局函数,而在全局域内的变量声明将引入全局对象。
全局对象和非inline全局函数在一个程序内只能被定义一次,而只要给出的定义完全相同即可,inline全局函数可以在一个程序中被定义多次。这样的要求被称为“一次定义法则”。
第19章 C/C++继承的用法
19.1运行时刻类型识别RTTI(Run-Time type Identification)
在C++中,为了支持RTTI提供了两个操作符:
1.dynamic_cast操作符
2.typeid操作符
2013-12-2添加:POD对象、位域、4个cast类型转换符
在C++环境中,我们把C风格的struct叫做POD(Plain Old Data)对象。
任何POD对象的初始化都可以使用memset函数或者其他类似的内存初始化函数。
C风格的构造类型对象也可以在定义的时候指定初始值。
所谓对象之间的包含是指一个类型的对象充当了另一个类型定义的数据成员,从而也就充当了它的对象的成员,即两个对象之间存在has-a关系。
一个对象不能自包含。
虽然对象不能只包含,但可以自引用,而且两个类型可以交叉引用,这种关系称为holds-a关系。
8.1.3位域
#include<iostream>
using namespace std;
struct DataTime_old
{
unsigned int year;
unsigned int month;
unsigned int day;
unsigned int hour;
unsigned int minute;
unsigned int second;
};
struct DataTime_Bad
{
unsigned int year;
unsigned int month:4;
unsigned int day:5;
unsigned int hour:5;
unsigned int minute:6;
unsigned int second:6;
};
struct DataTime_Good
{
unsigned int year;
unsigned int month:8;
unsigned int day:8;
unsigned int hour:8;
unsigned int minute:8;
unsigned int second:8;
};
int main()
{
cout<<sizeof(DataTime_old)<<endl;//24
cout<<sizeof(DataTime_Bad)<<endl;//8
cout<<sizeof(DataTime_Good)<<endl;//12
system("pause");
return 0;
}
位域以单个的位为单位来设计一个struct所需要的存储空间,因此你可以根据数据成员的有效取值范围来仔细规划它们各自所需要的位数。
不要定义超越类型最大位数的位域成员。
使用位域节省存储空间会导致程序运行速度的下降。
c++新增了4个类型转换符,它们是:
static_cast<dest_type>(src_obj),作用相当于C风格的强制转换。
const_cast<dest_type>(src_obj) ,用于去除一个对象的const/volatile属性
reinterpret_cast<dest_type>(src_obj)
dynamic_cast<dest_type>(src_obj)
你也可以使用typeid()来检索非多态类型对象和基本数据类型对象的类型信息,只不过此时它不会去检索对象的vptr甚至vtable(它们根本就没有这些设施),其结果仍然是操作数静态类型对应的type_info对象。
#include<typeinfo>
#include<iostream>
#include<string>
using namespace std;
typedef unsigned int UINT;
int main()
{
cout<<typeid(UINT).name()<<endl;//unsigned int
cout<<typeid(string).name()<<endl;//class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >
system("pause");
return 0;
}
可以看出,typeid()不具备可扩展性,因为它返回一个对象的确切类型而不是基类型。
仅有虚函数和typeid()还是不足以解决问题。
C++的dynamic_cast<>就是解决这个问题的运算符,用来执行运行时类型识别和转换,其语法如下:
dynamic_cast<type-id>(expression)
type-id就是转换的目标类型[类的指针、类的引用或者void *],而expression是被转换的对象。该运算符把expression转换成type-id类型的对象。其行为可以这样描述:如果运行时expression和type-id确实存在is-a关系,则转换可进行,否则转换失败。
dynamic_cast<>可以用来转换指针和引用,但是不能转换对象。当目标类型是某种类型的指针(包括void*)时,如果转换成功则返回目标类型的指针,否则返回NULL;当目标类型为某种类型的引用时,如果成功则返回目标类型的引用,否则抛出std::bad_cast异常,因为不存在NULL引用。
如果type-id是类指针类型,那么expression也必须是一个指针,如果type-id是一个引用,那么expression也必须是一个引用。
dynamic_cast<>只能用于多态类型对象(拥有虚函数或虚拟继承),否则将导致编译失败。
dynamic_cast<>可以实现两个方向的转换:upcast和downcast。还可以用于类之间的交叉转换。
upcast:派生类->基类,类层次间的上行转换
downcast:基类->派生类,类层次间的下行转换
在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;
在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
class B
{
public:
int m_iNum;
virtual void foo();
};
class D:public B
{
public:
char *m_szName[100];
};
void func(B *pb)
{
//下行转换
D *pd1 = static_cast<D *>(pb);
D *pd2 = dynamic_cast<D *>(pb);
}
在上面的代码段中,如果pb指向一个D类型的对象,pd1和pd2是一样的,并且对这两个指针执行D类型的任何操作都是安全的;
但是,如果pb指向的是一个B类型的对象,那么pd1将是一个指向该对象的指针,对它进行D类型的操作将是不安全的(如访问m_szName),而pd2将是一个空指针。
另外要注意:B要有虚函数,否则会编译出错;static_cast则没有这个限制。
这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表中,只有定义了虚函数的类才有虚函数表,没有定义虚函数的类是没有虚函数表的。
另外,dynamic_cast还支持交叉转换(cross cast)。如下代码所示。
class A
{
public:
int m_iNum;
virtual void f(){}
};
class B:public A
{
};
class D:public A
{
};
void foo()
{
B *pb = new B;
pb->m_iNum = 100;
D *pd1 = static_cast<D *>(pb); //compile error
D *pd2 = dynamic_cast<D *>(pb); //pd2 is NULL
delete pb;
}
在函数foo中,使用static_cast进行转换是不被允许的,将在编译时出错;而使用 dynamic_cast的转换则是允许的,结果是空指针。
附录 泛型算法(按字母排序)
sort()利用底层元素的小于操作符,以升序重新排列[first,fast)范围内的元素。第二版本根据comp对元素进行排序。
附录2 C++库
静态库
C、传统 C++
assert.h//设定插入点
ctype.h//字符处理
errno.h//定义错误码
float.h//浮点数处理
fstream.h//文件输入/输出
iomanip.h//参数化输入/输出
iostream.h//数据流输入/输出
limits.h//定义各种数据类型最值常量
locale.h//定义本地化函数
math.h//定义数学函数
stdio.h//定义输入/输出函数
stdlib.h//定义杂项函数及内存分配函数
string.h//字符串处理
strstrea.h//基于数组的输入/输出
time.h//定义关于时间的函数
wchar.h//宽字符处理及输入/输出
wctype.h//宽字符分类
标准 C++ (同上的不再注释)
algorithm//STL 通用算法
bitset//STL 位集容器
cctype
cerrno
clocale
cmath
complex//复数类
cstdio
cstdlib
cstring
ctime
deque//STL 双端队列容器
exception//异常处理类
fstream
functional//STL 定义运算函数(代替运算符)
limits
list//STL 线性列表容器
map//STL 映射容器
iomanip
ios//基本输入/输出支持
iosfwd//输入/输出系统使用的前置声明
iostream
istream//基本输入流
ostream//基本输出流
queue//STL 队列容器
set//STL 集合容器
sstream//基于字符串的流
stack//STL 堆栈容器
stdexcept//标准异常类
streambuf//底层输入/输出支持
string//字符串类
utility//STL 通用模板类
vector//STL 动态数组容器
cwchar
cwctype
using namespace std;
C99 增加
complex.h//复数处理
fenv.h//浮点环境
inttypes.h//整数格式转换
stdbool.h//布尔环境
stdint.h//整型环境
tgmath.h//通用类型数学宏