C++ Primer第三版中文版读书笔记
命令行操作:
g++ -c 源代码文件1 源代码文件2 #生成.o目标文件
g++ -c 源代码文件 -L../lua -llua5.1
g++ -o 可执行文件 源代码文件
g++ -o 可执行文件 源代码文件1 源代码文件2
g++ -o -std=c++11 可执行文件 源代码文件
g++ -o 可执行文件 -std=c++11 源代码文件
./可执行文件 参数1 参数2
ar -r 静态库文件1 目标文件1 目标文件2
g++ -fPIC -shared -o 动态库文件 源代码文件
g++ -static -o 可执行文件 源代码文件 -L. -L../lua -l静态库1 -llua5.1 #采用静态库编译命令
g++ -o 可执行文件 源代码文件 -L. -l动态库1 -ldl #采用动态库编译命令
./可执行文件 参数1(./动态库1路径) 参数2 参数3
潘爱民 张丽 译
第二篇基本语言
第三章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的转换则是允许的,结果是空指针。
chap15 C++异常处理和RTTI
健壮性是指软件在异常环境下仍然能够正常运行的能力,健壮性是非常重要的软件质量属性。提高C++应用软件的基本手段之一就是使用异常处理技术。
C++的异常处理技术是一种面向对象的运行时错误处理机制,其思路完全不同于C语言旧式的返回值检查策略。
15.2C++异常处理
15.2.1异常处理的原理
异常处理机制实质上是一种运行时通知机制。
这就需要底层代码通知高层组件“这里发生了XXX错误”,通知的方法就是抛出一个异常对象。C++异常处理技术可以使用户的正常代码与错误处理代码分离开来。
15.2.2异常类型和异常对象
任何一种类型都可以当做异常类型,因此任何一个对象都可以当做异常对象,包括基本数据类型的常量,变量,任何类型的指针,引用,结构等甚至空结构或空类的对象。这是因为异常仅仅通过类型而不是通过值来匹配的,否则就又退回到了传统的错误处理技术上。
15.2.3异常处理的语法结构
异常处理也是一种程序控制结构,它包括4个组成部分:抛出异常,提炼异常,捕获异常及异常对象本身。
它们对应着C++语言的3个关键字及其构造,即throw,try{},catch{}。
throw只是一条语句,而try和catch各自引导了一个程序块,即使它们是空的。
try{}块包含了可能会有异常抛出的代码段,而catch{}块则包含用户定义的异常处理代码,即异常处理器(handler)。一条throw语句只能抛出一个异常,一个catch语句也只能捕获一种异常。
15.3虚函数面临的难题
在介绍RTTI之前先来看一看虚函数的不足之处。
15.4RTTI及其构成
15.4.1起源
异常处理机制在1989年加入C++语言。RTTI后来的实现不仅解决了异常处理机制中的问题,它还能够完成虚函数不能完成的工作。
chap17学习和使用STL
C++标准库不仅仅包含STL,还包括标准C函数库,I/O流,串,异常,数值计算,国际化与本地化等的支持,其中大都用模板技术进行了范型实现。
六大组件:容器,存储分配器,迭代器,泛型算法,函数对象和适配器
17.2.1容器类
17.2.2泛型算法
它们定义在头文件<algorithm>和<utility>中。
17.2.3迭代器
每一种容器都定义了适合自己使用的迭代器,那些具有特殊功能的迭代器,如输入/输出迭代器,插入迭代器,反向迭代器等都是迭代器适配器,定义在头文件<iterator>中。
17.2.4数学运算库
complex复数及其相关操作
valarray数值向量及其相关操作
numerics通用数学运算
limits常用数值类型的极限值和精度等
17.2.5通用工具
utility运算符重载和pair<>定义
functional标准的函数对象及其便捷函数定义
memory存储分配器和auto_ptr<>类
17.2.6其他头文件
17.3容器设计原理
17.3.1内存映像
经过严格测试的容器类几乎不可能存在动态内存管理上的问题,因此可以放心使用。
17.3.2存储方式和访问方式
C++/C的内置数组和vector都是既可以随机访问又可以顺序访问的容器,而list只能顺序访问。
存储方式:连续存储和随机存储(不连续存储)
访问方式:随机访问和顺序访问。
不同的存储方式决定了元素的不同访问方式。
可以这样说,只要底层存储机制采取连续存储方式的容器,就可以随机访问其中任一元素对象,否则只能顺序访问,而任何容器都可以顺序访问,即遍历。
注意:statck,queue及priority_queue在概念和接口上都不支持随机访问和遍历,这是由它们的语义决定的,而不是由底层存储方式决定的,因此没有迭代器(所以它们才被叫做容器适配器而不是归为容器类)
17.3.3顺序容器和关联式容器的比较
在STL中,容器被划分为两类:顺序容器和关联式容器(联合容器)。
这里指的是上层接口表现出来的访问方式,并非底层存储方式。
关联式容器在概念是无序的,在实现上必须是有序和排序的。
关联式容器也能存储值相等的元素,比如multimap和multiset等,但是它们在容器中的位置肯定是相邻的。
附录 泛型算法(按字母排序)
sort利用底层元素的小于操作符,以升序重新排列[first,fast)范围内的元素。第二版本根据comp对元素进行排序。
swap交换数据。
transform转换数据。
merge合并排序。
copy元素复制。
find查找元素是否在此区间内。
find_if查找元素是否在此区间内。
min_element寻找出此区间中的最小的值。
accumulate则对区间内的元素作某种形式的整体求和运算。
partition将区间内的元素分割为满足和不满足某判决条件的两个部分。。
remove将一个指定的值从指定的区间中[first,last)“删除”,这里的“删除”是指:把区间内”指定值”的位置腾出,用copy的方式把后面”非指定值”逐一的往前移动。使用remove删除成员后需要立即调用erase,确保真正的删除。
count统计等于某个值的对象的个数。
for_each()遍历区间。
struct BookInfo
{
BYTE ID;
BYTE fLvl;
WORD score1;
WORD score2;
WORD score12;
BYTE delt;
BYTE weight;
DWORD Num;
};
BYTE bookCnt;
BookInfo bookInfo[255];
// 根据key查找对象
BookInfo *pInfo = std::find_if(bookInfo, bookInfo + bookCnt, finder_t<BookInfo,BYTE>(ftype));
if (!pInfo) {
return false;
}
二分法查找函数
//在 [first, last) 区域内查找不小于 val 的元素
ForwardIterator lower_bound (ForwardIterator first, ForwardIterator last,const T& val);
//在 [first, last) 区域内查找第一个不符合 comp 规则的元素
ForwardIterator lower_bound (ForwardIterator first, ForwardIterator last,const T& val, Compare comp);
//查找[first, last)区域中第一个大于 val 的元素。
ForwardIterator upper_bound (ForwardIterator first, ForwardIterator last,const T& val);
//查找[first, last)区域中第一个不符合 comp 规则的元素
ForwardIterator upper_bound (ForwardIterator first, ForwardIterator last,const T& val, Compare comp);
// 计算等级
unsigned char calcSumLv(long long llScore) {
static long long hscoreX[] = { 2000000000,1000000000,500000000,100000000,50000000,10000000,1000000,100000,10000,0 };
static int s_cnt = sizeof(hscoreX) / sizeof(long long);
// 查找第一个不大于llScore的元素
long long *pX = std::lower_bound(hscoreX, hscoreX + s_cnt, llScore, std::greater<long long>());
if (pX != hscoreX + s_cnt) {
int idx = pX - hscoreX;
return s_cnt - idx;
}
else {
return 1;
}
return 1;
}
附录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//宽字符分类
stdarg.h
标准 C++ (同上的不再注释)
algorithm//STL 通用算法
bitset//STL 位集容器
cctype
cerrno
clocale
cmath
complex//复数类
cstdio
cstdlib
cstring
ctime
cassert
cstdarg
deque//STL 双端队列容器
exception//异常处理类
fstream
functional//STL 定义运算函数(代替运算符)
limits
list//STL 线性列表容器
map//STL 映射容器
iomanip
ios//基本输入/输出支持
iosfwd//输入/输出系统使用的前置声明
iostream
iterator
istream//基本输入流
ostream//基本输出流
queue//STL 队列容器
set//STL 集合容器
sstream//基于字符串的流
stack//STL 堆栈容器
stdexcept//标准异常类
streambuf//底层输入/输出支持
string//字符串类
utility//STL 通用模板类
memory
typeinfo
vector//STL 动态数组容器
cwchar
cwctype
regex//正则表达式
using namespace std;
C99 增加
complex.h//复数处理
fenv.h//浮点环境
inttypes.h//整数格式转换
stdbool.h//布尔环境
stdint.h//整型环境
tgmath.h//通用类型数学宏
附录3 Boost库概览
Any用于存储不同类型的值的安全的泛型容器,仅头文件
Array与STL兼容的常量大小的数组的容器包装类
Asio可移植的网络库,包括Socket、计时器、域名解析和Socket流。
Assign非常方便地使用常数或者生成数据填充容器
Bitmap双向map库:使用Boost.Bitmap,你可以创建两个类型都可以作为键值的关联容器
Bind,boost::bind是标准函数std::bind1st和std::bind2nd的泛化。它支持任意函数对象、函数、函数指针和成员函数指针,也能够用于绑定任何参数到一个指定的值或者路由输入参数到任意位置。
CRC,Boost CRC库提供了CRC(循环冗余校验码)计算对象的两个实现和CRC计算函数的两个实现。实现是基于模板的。
Call Traits,为参数传递定义了类型
Circular Buffer,一个STL兼容的容器,也被广泛称为环形缓冲区或者循环缓冲区。
Compatibility帮助非标准兼容的库。
Compressed Pair空成员优化。
Concept Check泛型编程工具。
Config:帮助Boost库开发人员应付编译器特性,不是为库用户准备的。
Conversion:多态转换和文字转换(译注:lexical cast,指各种类型的数据和字符串形式的相互转换)。
Date Time:一组基于泛型编程概念的日期时间库。
Disjoint Sets,Boost. DisjointSets提供不相交集合操作,并使用union by rank和路径压缩技术加速操作。
boost库命名规则的优点
boost库的命名,涵括了下面几方面信息:
1.功能信息(即是序列化库、正则库还是其它功能的库)
2.静态库还是导入库(静态库以lib开头)
3.构建该库的具体编译器信息
4.线程标签(是多线程还是单线程)
5.影响库与其它编译代码的互操作性的细节的编码(debug版本还是release版本,是否使用了STLPORT)
6.boost库的版本信息
Windows环境下的库文件的命名规则:
静态库命名:lib+工程名_编译器信息_版本信息_互操作性细节信息.lib
这里的互操作细节信息初步分为debug版本、release版本以及多字节版本和unicode版本。
CreateProcess最复杂,一共有十个参数,不过大部分都可以用NULL代替,它可以指定进一个简单的例子就是libpng_vc80_1_3_d.lib。
对应的release版本命名为libpng_vc80_1_3.lib
Unicode版本命名为libpng_vc80_1_3_ud.lib和libpng_vc80_1_3_u.lib
导入库:工程名_编译器信息_版本信息_互操作性细节信息.lib
动态库命名与导入库一样,不过后缀名改为dll。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效