第1章
>> << 输入输出操作符返回 输出流std::cin, std::cout本身
endl输出换行,刷新与设备关联的buffer
augument 实参 paremeter 形参
buit-in type 内置类型
manipulator 操纵符
第2章
C++是静态类型语言,编译时执行类型检查
wchar_t =L'a' 16位
float 6位有效数字
double 至少10位有效数字
long double 至少10位有效数字
赋值:对于unsigned越界赋值,结果等于该值对unsigned可能取值个数求模
例如:unsigned char c=336; //实际c=336%256=80
unsigned char c=-1; //实际c=-1%256=255
对于signed越界赋值,由编译器决定实际值
初始化不是赋值,初始化指创建变量并赋值,赋值是擦出当前值赋新值
内置类型初始化:在函数体外定义的变量初始化为0,函数体内定义的不自动初始化。
定义:分配存储空间,还可以指定初值
声明:向程序表明变量的类型,名字
extern声明:当有初始化式时则为定义
非const变量默认为extern,要使const变量能在其他文件中访问,则需显式指定为extern。const默认为定义它的文件的局部变量。
引用必须在定义时初始化,引用一经初始化,就始终指向同一个特定对象。
const必须在定义时初始化。
非const引用 只能绑定与该引用同类型的对象
const引用:int a=1;
const int &p=a //等价于int temp=a; const int &p==temp;
头文件不应该有定义的例外:1)可定义类 2)值在编译时已知的const对象 3)inline函数
头文件中const应被常量表达式初始化,并extern被多个文件共享
预处理器preprocessor 头文件保护符header guard
#ifndefine
#define
#endif
第3章
#include
using std:string:
string s1;
string s2(s1);
string s3("value");
string s4(n,'c');
s.empty()
s.size() 字符个数 返回string::size_type类型// 配套类型(companion type),使库类型的使用与机器无关machine-independent, unsigned类型,不要赋值给int
s[]的索引变量最好也是string::size_type类型
getline(istream,string);
#include
isalnum(c)字母或数字 isalpha(c) isdigit(c) islower(c) isupper(c)
tolower(c) toupper(c)
类模板vector #include
vector
vector
vector
vector
vector
v.empty(); v.size();//返回vector
v.push_back(i);
迭代器iterator
vector
end()返回指向末端元素的下一个
const_iterator 自身可变,指向的元素只能读不能写
const类型的iterator 自身不能变,指向固定元素
迭代器距离difference_type, signed类型
vector
#include
bitset<位数> bitvec; 0为低阶位
bitset
bitset
bitset
bitset
b.any(); 是否存在为1的二进制位 b.none(); 不存在1
b.count(); 1的个数 b.size();二进制位个数
//都返回cstddef头文件中的size_t类型,与机器相关的unsigned类型
b.test(pos); b[pos]是否为1
b.set();所有都置1 b.set(pos);
b.reset();都置0 b.reset(pos);
b.filp();所有都取反 b.flip(pos); //b[0].flip()等价于b.flip(0);
b.to_ulong(); 返回一个unsigned long值
os <
bitset无iterator
第4章
非const变量及要到运行阶段才知道其值的const变量不能用于定义数组维数。
函数体外定义的内置数组,均初始化为0.
不管在哪里定义,类类型数组,自动调用默认构造函数进行初始化,如果没有默认构造函数需显式初始化。
如果数组维数大于显式初始化列表中的初始值数,则剩下的元素,如是内置类型则初始化为0,类类型则调用默认构造函数。 如int a[5]={1,2}; //对于多维数组同样适用
数组下标类型 size_t
不允许使用void*指针操纵它所指向的对象
指针相减,在cstddef中的ptrdiff_t类型,是signed类型
int *p = &ia[2];
int j = p[1]; //j=ia[3];
int k = p[-2]; // k=ia[0];
C++允许指针计算末端的一个地址,但不允许对此地址进行解引用。
指向const对象的指针: const int *cptr; 定义时不需初始化
const指针: int *const cp; 定义时必须初始化
typedef string *pstring;
const pstring cstr; // 解释为const指针,string *const cstr;
C风格字符串:以null结尾的字符数组
#include
strlen(s); strcmp(s1,s2); strcat(s1,s2);strcpy(s1,s2);strncat(s1,s2,n);strncpy(s1,s2,n); //如果非null结尾则结果不可预料。
new 类类型数组时,使用默认构造函数初始化,
内置类型则无初始化
也可加上()做值初始化,如 int *p=new int[10]();
char a[0]; //error
char *p=new char[0] //ok,返回有效的非0指针,但不能解引用操作,允许比较运算,在指针上加减0或减去本身得0
string str; string加法两个操作数可以有一个C风格字符串
const char *p=str.c_str(); // c_str返回const指针
int a[3][4];
typdef int int_array[4];
for (int_array *p=a; p!=a+3; ++p)
for(int *q=*p; q!=*p+4; ++q)
cout<<*q<
dimension 维数 free store 自用存储区==heap 堆
precedence 优先级
第5章
int_quizl |=1UL<<27; //第27位置1
int_quizl &=~(1UL<<27); // 第27位置0
++,--后置,需要先保存操作数原理的值,对于int和指针编译器可优化这额外的工作,但对于复杂迭代器类型需花费更大代价,因此尽量用前置
*iter++ ; //*(iter++)
求3个变量最大值
int max = i > j
? i > k ? i : k
: j >k ? j : k;
sizeof 返回编译时常量,size_t类型
数组元素个数:
int sz=sizeof(ia)/sizeof(*ia);
逗号操作符,最右端的操作数是左值,则逗号表达式的值也是左值。
delete 值为0的指针合法
delete指针后要将指针置0,悬垂指针指向曾经存放对象的内存,但该对象已不存在。
内存泄露memory leak: 删除动态分配的指针失败。
const int *pci=new const int(1024);
delete pci; // ok.
类型转换istream-》bool
while(cin>>s)
显式转换(强制类型转换): cast_name
static_cast
dynamic_cast 支持运行时识别指针或引用所指向的对象
const_cast 添加或删除const特性
reinterpret_cast:将操作数内容解释为另一种类型
dangling pointer 悬垂指针
implicit conversion 隐式类型转换
integral promotion 整型提升
操作符的优先级
操作符及其结合性 功能 用法
L :: global scope(全局作用域) :: name
L :: class scope(类作用域) class :: name
L :: namespace scope(名字空间作用域) namespace :: name
L . member selectors(成员选择) object . member
L -> member selectors(成员选择) pointer -> member
L [] subscript(下标) variable [ expr ]
L () function call(函数调用) name (expr_list)
L () type construction(类型构造) type (expr_list)
R ++ postfix increment(后自增操作) lvalue++
R -- postfix decrement(后自减操作) lvalue--
R typeid type ID(类型 ID) typeid (type)
R typeid run-time type ID(运行时类型 ID) typeid (expr)
R explicit cast(显式强制类型转换)type conversion(类型转换) cast_name
R sizeof size of object(对象的大小) sizeof expr
R sizeof size of type(类型的大小) sizeof(type)
R ++ prefix increment(前自增操作) ++ lvalue
R -- prefix decrement(前自减操作) -- lvalue
R ~ bitwise NOT(位求反) ~expr
R ! logical NOT(逻辑非) !expr
R - unary minus(一元负号) -expr
R + unary plus(一元正号) +expr
R * dereference(解引用) *expr
R & address-of(取地址) &expr
R () type conversion(类型转换) (type) expr
R new allocate object(创建对象) new type
R delete deallocate object(释放对象) delete expr
R delete[] deallocate array(释放数组) delete[] expr
L ->* ptr to member select(指向成员操作的指针) ptr ->* ptr_to_member
L .* ptr to member select(指向成员操作的指针) obj .*ptr_to_member
L * multiply(乘法) expr * expr
L / divide(除法) expr / expr
L % modulo (remainder)(求模(求余)) expr % expr
L + add(加法) expr + expr
L - subtract(减法) expr - expr
L << bitwise shift left(位左移) expr << expr
L >> bitwise shift right(位右移) expr >> expr
L < less than(小于) expr < expr
L <= less than or equal(小于或等于) expr <= expr
L > greater than(大于) expr > expr
L >= greater than or equal(大于或等于) expr >= expr
L == equality(相等) expr == expr
L != inequality(不等) expr != expr
L & bitwise AND(位与) expr & expr
L ^ bitwise XOR() expr ^ expr
L | bitwise OR(位异或) expr | expr
L && logical AND(逻辑与) expr && expr
L || logical OR(逻辑或) expr || expr
R ?: conditional(条件操作) expr ? expr : expr
R = assignment(赋值操作) lvalue = expr
R *=, /=, %=, compound assign(复合赋值操作) lvalue += expr, etc.
R +=, -=,
R <<=, >>=,
R &=,|=, ^=
R throw throw exception(抛出异常) throw expr
L , comma(逗号) expr , expr
第6章
switch-case 必须是整型,只能在最后一个case标号或default后面定义变量,或者引入块语句{},以保证该变量在使用前被定义和初始化。
在while循环条件中定义的变量,每次循环里都要经历创建和销毁的过程。
stdexpcept头文件中定义几种标准异常类:
运行时错误:exception 最常见问题 runtime_error仅在运行时才能检测到的问题 range_error overflow_error underflow_error
逻辑错误:logic_error 可在运行前检测到的问题 domain_error 参数的结果值不存在 invalid_argument 不合适的参数 length_error 试图生成一个超出该类型最大长度的对象 out_of_range 使用一个超出有效范围的值
第7章
void func(string &s){..} //实参为字符串字面值出错,func("abc"); 编译失败
void fuc(const string &s){...} //func("abc"); ok.
编译器不会检查形参数组关联的实参数组的长度。
形参是数组的引用int (&a)[10], 编译器不会将数组实参转化为指针,这时编译器会检查形参数组和实参数组的长度是否匹配。
不要返回指向函数局部变量的引用或指针!
//ff.h
int ff(int = 0);
//ff.cc
#include "ff.h"
int ff(int i=0){...} // error. 默认实参只能出现一次,通常放头文件里,如果在定义中提供默认实参,则只有在包含该函数定义的源文件中调用该函数,默认实参才有效。
static局部对象:当定义static局部对象的函数结束时,该对象不会撤销,在之后该函数的多次调用中,该对象会持续存在并保持它的值。
编译器隐式地将在类内定义的成员函数作为内联函数。
每个成员函数(除static成员函数外),都有隐含形参this,在调用成员函数时,形参this初始化为调用函数的对象的地址。
const成员函数则是则是使this指针有const特性,因此该函数不能修改调用对象的数据成员。
const对象,指向const对象的指针或引用,只能调用其const成员函数。
如果类没有显式定义任何构造函数,编译器自动生成默认构造函数。
默认构造函数自动调用类类型的成员的默认构造函数,无需显式初始化。
void func(int);
void func(const int); //重载ok
void func(int *);
void func(const int *);重载ok
void func(int *);
void func(int *const); //redeclaration,不能基于指针是否为const实现重载。
function prototype 函数原型==函数声明
synthesized default constructor合成默认构造函数
第8章
ostream istream
ofsream ostringstream iostream istringstream ifstream
stringstream fstream
IO对象不可复制和赋值! 只有支持复制的元素类型可以存在vector等容器中。形参或返回类型也不能为流类型,只能用指针或引用。
strm::iostate 机器相关的整形名,定义条件状态
strm::badbit 指出被破坏的流 ,不可恢复
strm::failbit 指出失败的IO操作,可恢复
strm::eofbit 指出流已到达文件结束符,同时设置failbit
s.eof(); 如设置了s的eofbit值,则返回true
s.fail();如设置了failbit值则返回true
s.bad();入设置badbit置则返回true
s.good();上面3个都不为true
s.clear();将所有状态重置为有效
s.clear(flag); 重置为flag,strm::iostate类型
s.setstate(flag);添加指定条件 //is.setstate(ifstream::badbit | ifstream::failbit);同时置1.
s.rdstate();返回当前条件
每个IO对象管理一个缓冲区,刷新后写入到真实的输出设备或文件。
导致刷新:1)程序正常结束。
2)缓冲区满
3)endl,ends,flush
4)每次输出操纵符执行完后,用unitbuf设置流的内部状态,从而清空buffer
cout<
等价于cout<<"output sth"<
5)将输出流于输入流tie起来,则在读输入流时将刷新关联的输出缓冲区。
cin.tie(&cout);
ostream *old_tie=cin.tie();
cin.tie(0);
cin.tie(old_tie);
string ifile="..";
ifstream infile(ifile.c_str()); //文件名使用C风格字符串
ofstream outfile;
outfile.open("..");
outfile.close(); //重新绑定其他文件前须先关闭
outfile.clear(); //重用已存在的流对象记得clear恢复流状态
文件模式
in out打开后清空
app 在每次写之前找到文件尾 ate打开文件后立即定位到文件尾
trunc打开文件时清空已存在的文件流 binary
fstream inOut("copyOut",fstream::in | fstream::out); //同时in和out不清空
stringstream strm;
stringstream strm(str);
strm.str();
strm.str(s); //返回void
while (getline(cin, line))
{
istringstream stream(line);
while(stream >> word)
{}
}
stringstream 在不同数据类型之间实现自动转换/格式化。
derived class 派生类
object-oriented library 面向对象标准库
第9章 sequential container
顺序容器: vector 快速随机访问 list快速插入删除 deque双端,随机访问
C
C c(c2);
C c(b,e); //迭代器,数组,指针,不要求两个容器类型相同
C
C
char *words[]={"a","b","c"};
list
list
容器元素类型必须满足:1) 支持赋值运算(引用不支持) 2)可复制
除IO标准库类型及auto_ptr类型外,其他所有标准库类型都是有效的容器类型。容器本身也满足。
vector
vector
vecotr
容器的容器 vector
iter->mem等价于 (*iter).mem;
list容器不支持算术运算(iter+n,iter-n,iter+=n,iter-=n), 也不支持关系运算(<=,<,>=,>),
只提供前置后后置自增自减,以及相等不等运算。vector和deque都支持。
list
ilist.begin()+ilist.size()/2; // error:no addition on list iterators
容器定义的类型别名:
size_type
difference_type//迭代器差值的有符号类型
iterator const_iterator
reverse_iterator const_reverse_iterator
value_type
reference //元素的左值类型,value_type&同义词
const_reference //const value_type&同义词
c.begin(); c.end();
c.rbegin(); //返回reverse_iterator,指向c的最后一个元素
c.rend();//返回reverse_iterator,指向第一个元素前面的位置
容器c为const则返回const_reverse_iterator
c.push_back(t);
c.push_front(t); //只适用于list和deque
c.insert(p,t);//迭代器p前面插入t,返回新元素的迭代器
c.insert(p,n,t);//返回void
c.insert(p,b,e);//返回void
容器的关系运算:容器类型和元素类型都完全相同。
容器大小操作:
c.size(); //返回c::size_type
c.max_size();
c.empty();
c.resize(n);//n
c.resize(n,t);//新元素值为t
c.front();c.back();//返回引用,若c为空则未定义
c[n];c.at(n);// vector,deque
c.erase(p);//返回迭代器,指向被删元素后面的元素,若p指向超出末端的下一位置,则未定义。
c.erase(b,e);//返回迭代器,若e指向超出末端的下一位置,则也返回超出末端的下一位置。
c.clear();//void
c.pop_back();//void,若c为空,则未定义
c.pop_front();//void,若c为空,则未定义,只适用于list或deque
c1=c2;//c1和c2类型要完全相同,类型不同则使用assign
c1.swap(c2);//c1和c2类型要完全相同,速度比赋值快,不会使迭代器失效
c.assign(b,e);//b和e必须不指向c中元素,执行后迭代器失效
c.assign(n,t);
v.capacity();//vector在必须分配新的存储空间前可以存储的元素总数。
v.reverse(n);//预留存储空间大小,改变capacity的值
容器的选择:
1.随机访问:vector,deque
2.在中间位置插入 list
3.只在头部或尾部插入,deque
4.只在读取输入时在容器中间插入元素,然后随机访问,则先list,后复制到vector
5.如果既要随机访问,又要在中间位置插入删除元素,则考虑两者的使用的频率
string支持大部分vector操作,除了栈操作:不能使用front,back,pop_back,pop_front
string s1;
string s2(5,’a’);
string s3(s2);
string s4(s3.begin(), s3.end());
string s5(cp);//cp指向以null结尾的C风格字符串
string s6(cp, n);
string s7(s2, pos2);//从pos2开始
string s8(s2, pos2, len2);//从pos2开始的len2个字符,无论len2值多少,最多只能复制s2.size()-pos2个字符
与容器共有的string操作:
s.insert(p, t);
s.insert(p, n, t);
s.insert(p, b, e);
s.assign(b, e);
s.assign(n, t);
s.erase(p);
s.erase(b, e);
string特有的版本:
s.insert(pos, n, c);
s.insert(pos, s2);
s.insert(pos, s2, pos2, len);
s.insert(pos, cp, len);
s.insert(pos, cp);
s.assign(s2);
s.assign(s2, pos2, len);
s.assign(cp, len);
s.assign(cp);
s.erase(pos, len);
string特有操作
s.substr(pos, n);
s.substr(pos);
s.substr();
s.append(args);//追加,返回引用
s.replace(pos, len, args);//删除,用args替换,返回引用
s.replace(b, e, args);
append和replace的参数args
s2
s2, pos2, len2
cp // cp指向以null结束的数组
cp, len2 // cp指向以null结束的数组
n, c
b2, e2
find操作都返回string::size_type,没找到则返回string::npos
s.find(args);
s.rfind(args);
s.find_first_of(args);
s.find_last_of(args);
s.find_first_not_of(args);
s.find_last_not_of(args);
find操作的参数args
c, pos //从下标pos开始查找字符c,pos默认值为0
s2, pos //从下标pos开始查找string对象上,pos默认值为0
cp, pos //cp指向null结尾的C风格字符串,pos默认值为0
cp, pos, n //pos和n无默认值,从pos开始查找cp指向的前n个字符
string::size_type pos=0;
while ((pos = name.find_first_of(numerics, pos)) != string::npos)
{
//…
++pos;}
s.compare(s2);
s.compare(pos1, n1, s2);
s.compare(pos1, n1 ,s2, pos2, n2);
s.compare(cp); //cp指向以null结尾的字符串
s.compare(pos1, n1, cp);
s.compare(pos1, n1, cp, n2);
顺序容器适配器:stack queue priority_queue有优先级管理的队列
适配器通用操作和类型:
size_type
value_type
container_type
A a;
A a(c);
关系操作符: == != < <= > >=
支持关系运算
覆盖基础容器类型:stack,queue都基于deque,priority_queue基于vector
创建适配器时,通过指定适配器的第二个类型实参覆盖基础容器类型
stack可任意,queue的基础容器要有push_front运算,因此不能建立在vector上
priority_queue需要随机访问,因此可建立在vector和deque上
stack适配器操作
s.empty();
s.size();
s.pop();
s.top();
s.push(item);
队列queue和优先级队列priority_queue支持的操作:
q.empty();
q.size();
q.pop();
q.front(); //只适用于queue
q.back(); //只适用于queue
q.top(); //返回最高优先级的元素值,只适用于priority_queue
q.push(item); //对于queue在队尾压入,对于priority_queue放在优先级比它低的元素前面
第10章 associative container 关联容器
map set
multimap//支持同一个键多次出现的map类型
multiset//支持同一个键多次出现的set类型
#inlcude
pair
pair
make_pair(v1,v2);
p1 < p2
p1 == p2
p.firist
p.second
关联容器支持的操作:
1.关联容器共享大部分顺序容器操作,但不支持front,back,push_front,pop_front,push_back,pop_back.
2. C
C
C
3. 关系运算
4.begin,end,rbegin,rend;
5.容器类型别名: map的value_type是pair类型
6.swap和赋值操作,但不支持assign
7.clear和erase,但关联容器的erase返回void
8.size,max_size,empty,但不支持resize
map
map
map
键类型k必须定义<操作符,严格弱排序 strict weak ordering
map
map
map
//值可改,键不可改,对迭代器解引用获得指向value_type的引用
用下标访问不存在的元素将导致在map容器中添加新元素,它的键即为下标值,并对值类型值初始化。
m.insert(e); //若e.first不在m中则插入,反之m不变。返回pair类型对象,包含指向e.first元素的map迭代器,及bool对象表示是否插入了该元素.
m.insert(beg, end);//返回void
m.insert(iter, e); //以iter为起点搜索,查找是否有e.first对应的键元素,如果没有,则插入e。返回一个迭代器,指向具有e.first键的元素
使用insert可避免使用下标操作符带来的副作用:不必要的初始化。
word_count["Anna"]=1; //查找Anna,若没有则创建,值初始化,插入map对象中, 最后读取并将值赋为1
pair
word_cound.insert(make_pair("Anna",1);
查找或读取map中元素,下标会自动插入新元素,考虑使用
m.count(k); //返回k出现的次数,map中只能为0或1
m.find(k); //返回指向k的迭代器,若k不存在则返回超出末端的迭代器
if (word_count.count("foobar")) occurs = word_count["foobar"]; //对元素两次查找
map
if (it != word_count.end()) occurs = it->second; //一次查找
m.erase(k); //返回size_type类型的值,表示删除的元素个数
m.erase(p);//p不能等于m.end(), 返回void
m.erase(b, e);//返回void
set容器支持的操作基本与map容器一致,构造函数,insert count find erase
除了不支持下标操作符,没有定义mapped_type类型,value_type不是pair类型而等价于key_type。
与map一样,带有一个键参数的insert返回pair类型,包含一个指向该键元素的迭代器和是否插入元素的bool。
set的键元素与map一样,不能修改。
mutimap multiset 头文件也是 map和set
带有一个键参数的erase删除拥有该键的所有元素并返回删除的个数。
查找元素:
1)typedef mutimap
sz_type entries = authors.count(k);
mutimap
for (sz_type cnt = 0; cnt != entries; ++cnt, ++iter)
cout <
2)map,set,mutimap,multiset都使用
m.lower_bound(k); //返回指向键不小于k的第一个元素的迭代器
m.upper_bound(k); //返回指向键大于k的第一个元素的迭代器
m.equal_range(k); //返回pair对象,包含两个迭代器,first等价于m.lower_bound(k), second等价于m.upper_bound(k).
typedef multimap
author_it beg = authors.lower_bound(k), end=authors.upper_bound(k);
while (beg != end){cout<
pair
while(p->first != pos->second){cout<
第11章 泛型算法 generic algorithm
#include
#include
accumulate(v.begin(), v.end(),a); //累加初值a与容器元素类型匹配
fill(v.begin(), v.end(), t); //将t的副本分别写入,要保证输入范围有效
fill_n(v.begin(), n, t); //写入n个t的副本,要保证输入范围有效,n个元素必须已经存在
back_inserter迭代器适配器,back_inserter(container);生成一个绑定在容器上的插入迭代器,
试图通过这个迭代器给元素赋值时,赋值运算将调用push_back来添加元素。
vector
fill_n (back_inserter(ivec),10,0); //ok
copy (ilst.begin(), ilst.end(), back_inserter(ivec)); //效率差,一般直接vector
replace(ilst.begin(), ilst.end(), a, b);//将所有等于a的元素替换为b
如果不想改变原序列则用replace_copy
vector
replace_copy(ilst.begin(), ilst.end(), back_inserter(ivec), a, b);
sort(words.begin(), words.end()); //使用<操作符比较,
vector
unique(words.begin(),words.end()); //"删除"相邻的重复元素,把重复的元素移动到序列末尾,返回的迭代器指向超出无重复的元素范围末端的下一位置
words.erase(end_unique,words.end());
stable_sort(words.begin(), words.end(), isShorter); //稳定排序,重复元素相对位置不变,第3个参数使用谓词predicate
// bool isShorter(const string &s1, const string &s2){return s1.size() < s2.size();}
//bool GT6(const string &s){return s.size()>=6;}
vector
插入迭代器
1)back_inserter : push_back实现插入的迭代器
2)front_inserter: push_front实现插入,vector不能用
3)inserter: insert实习插入,第二个实参指向插入起始位置的迭代器
list
for (list
copy (ilst.begin(), ilst.end(), front_inserter(ilst2); // 0123
copy(ilst.begin(),ilst.end(), inserter(ilst3, ilst3.begin()); //3210
iostream 迭代器: 自增,解引用,赋值。 istream提供==,!=运算,ostream不提供比较运算
都是类模板,任何定义<<操作符的类型都可以定义istream_iterator,定义>>的类型都可以定义ostream_iterator
istream_iterator
istream_iterator
ostream_iterator
ostream_iterator
istream_iterator
istream_iterator
while(in_iter != eof) vec.push_back(*in_iter++); //vector
ostream_iterator
istream_iterator
while(in_iter !=eof) *out_iter++ = *in_iter++;
一旦给ostream_iterator对象赋值,写入就提交了,没法改变这个值
ostream_iterator没有->操作符
从标准输入读取一些数,将不重复的数写到标准输出
istream_iterator
vector
sort(ivec.begin(), ivec.end());
ostream_iterator
unique_copy(ivec.begin(), ivec.end(), out_it);
反向迭代器:需要使用自减操作符,流迭代器没有。
sort(vec.rbegin(), vec.rend()); //实现降序
find(vec.rbegin(),vec.rend(),',');//反向开始搜索
const迭代器
使用时注意迭代器范围
find_first_of(it, roster1.end(), roster2.begin(), roster2.end());
//如果it为const_iterator, 而roster为非const容器end()返回非const迭代器,用来指定迭代器范围的两个迭代器类型不同,将无法编译
迭代器种类:低级别迭代器可使用任意更高级迭代器替代
1.输入迭代器 == != ++ * –> find accumulate istream_iterator
2.输出迭代器 ++ * 只能写一次 copy ostream_iterator
3.前向迭代器:读写指定同期,以一个方向遍历序列,支持输入输出迭代器的所有操作,还支持对同一元素的多次读写 replace
4.双向迭代器: -- reverse 标准库容器提供的迭代器至少达到双向迭代器的要求 list map set
5.随机访问迭代器:关系操作符< <= > >= iter+n, iter-n, iter1-iter2,iter[n] sort vector deque string 所以list不能用sort排序
算法的形参模式
alg (beg, end, other parms);
alg (beg, end, dest, other parms);
alg (beg, end, beg2, other parms); //假定以beg2开始的范围与beg end一样大
alg (beg, end, beg2, end2, other parms);
sort (beg, end); // <操作符
sort (beg, end, cmp);
find (beg, end, val); //==操作符 带谓词形参加_if,因形参个数相同导致二义性
find_if (beg, end, pred);
reverse(beg, end);
reverse_copy(beg, end, dest);
list特有操作:对list容器应优先使用特有的成员版本,而不是泛型算法
lst.merge(lst2);
lst.merge(lst2, comp); //两个版本都要先排序, 合并后lst2为空,返回void
lst.remove(val);
lst.remove_if(unaryPred);//调用lst.erase实现,返回void
lst.reverse();
lst.sort();
lst.sort(comp);
lst.splice(iter, lst2);
lst.splice(iter, lst2, iter2);
lst.splice(iter, beg, end);//将lst2的元素移到lst中迭代器iter指向元素的前面,在lst2中删除移出的元素
lst.unique();
lst.unique(binaryPred);//调用erase删除同一个值的连续副本,第一个用==判断,第二个用指定谓词函数判断
list特有版本与其泛型算法两个重要区别:
1.remove和unique真正删除了指定元素
2.merge和splice会破坏实参
使用merge的泛型算法时将合并的序列写入目标迭代器指向的对象,两个输入序列不变。
第12章 类
void fuc() const; //成员函数声明为常量,不能改变其所操作对象的数据成员,const必须同时出现在声明和定义中
可任意在成员函数的声明或定义中指定inline
前向声明,不完全类型,声明未定义的类型后,在类型定义前不能定义该类型的对象,只能定义指针及引用或函数形参或返回类型,
因为在定义对象及用指针或引用访问类成员时需要知道类的相应存储空间的大小。
基于const的重载: const对象只能用const成员函数
1.基于成员函数是否为const,可重载
2.基于一个指针形参是否是指向const,可重载
引用形参是否为const可重载
当形参以副本传递时,不能基于形参是否为const来实现重载。
f(int *);f(int *const); //redeclaration
可变数据成员mutable,放在声明前面,const成员函数也可改变其值。
成员函数类外定义的返回类型不在类的作用域中,此时要使用完全限定的类型名
class Screen {
public:
typedef std::string::size_type index;
index get_cursor() const;};
inline Screen::index Screen:: get_cursor() const{ //
return cursor;}
构造函数不能为const
在构造函数初始化列表中没有显式提及的每个成员,用该类类型的默认构造函数来初始化,内置或复合类型成员的初始值依赖于对象的作用域。
必须用初始化列表:
1.const或引用类型的成员 。 可以初始化const对象或引用类型对象,但不能对它们赋值。
2.没有默认构造函数的类类型成员
构造函数初始化列表中成员被初始化顺序就是成员被定义的次序。
只有当一个类没有定义构造函数时,编译器才会自动生成一个默认构造函数。
隐式类型转换:单个实参来调用的构造函数定义了从形参类型到该类类型的一个隐式转换。
class Fruit //定义一个类,名字叫Fruit
{
string name;//定义一个name成员
string colour;//定义一个colour成员
public:
bool isSame(const Fruit& otherFruit)//期待的形参是另一个Fruit类对象,测试是否同名
{
return name == otherFruit.name;
}
void print()//定义一个输出名字的成员print()
{
cout << colour << " " << name << endl;
}
Fruit(const string& nst, const string& cst = "green"):name(nst), colour(cst){}
Fruit(){}
};
int main()
{
Fruit apple("apple");
Fruit orange("orange");
cout << "apple = orange? : " << apple.isSame(orange) << endl;
//单个实参,隐式转换
cout << "apple = /"apple/"? :" << apple.isSame(string("apple")) << endl;
return 0;
}
单个参数的构造函数前要加explicit,来抑制构造函数定义的隐式转换。
static成员函数没有this指针,可以直接访问所属类的static成员,但不能直接只用非static成员。
static关键字只能用于类定义体内部的声明中,不能用于定义。
static成员函数不能声明为const,因为static成员不是任何对象的组成部分,而const是不修改函数所属对象。
static成员函数不能声明为虚函数。
static数据成员必须在类定义体的外部定义(正好一次)。不在构造函数中初始化,而应该在定义时进行初始化。
static数据成员的类型可以是该成员所属的类类型,非static成员只能声明为自身类对象的指针或引用。
class Bar {
public:
//...
private:
static Bar mem1; //ok
Bar *mem2; //ok
Bar mem3; //error
};
static数据成员可用作默认实参,非static不能用作默认实参
class scope
encapsulation
第13章 复制控制 copy control 复制构造函数 copy constructor 赋值操作符 assignment operator 析构函数 destructor
复制构造函数:单个形参,且是对本类类型对象的引用(常用const修饰)。
作用:
1.根据另一个同类型对象显式或隐式初始化一个对象
2.复制一个对象,将他作为实参传给一个函数, 非引用类型,值传递
3.从函数返回时复制一个对象
4.初始化顺序容器的元素
5.根据元素初始化式列表初始化数组元素
对象定义的初始化形式:直接初始化(直接调用实参匹配的构造函数),复制初始化(先创建临时对象,再调用复制构造函数)。
对于不支持复制的类型,或使用非explicit构造函数的时候,它们有本质区别
ifstream file1("filename"); //ok,direct initialization
ifstream file2 = "filename"; //error,copy initialization,不能复制IO类型
//this initialization is okay only if
//the Sales_item(const string&) constructor is not explicit
Sales_item item = string("9-999-99999-9"); //隐式转换
//default string constructor and five string copy constructor invoked
vector
合成的复制构造函数synthesized copy constructor:将现有对象的每个非static成员,依次复制到正创建的对象。内置类型和数组直接复制,类类型使用该类的复制构造函数。
如果没有定义复制构造函数,编译器就会合成一个,即使已经定义了其他的构造函数。与合成的默认构造函数不同。
合成的复制构造函数完成对象之间的位拷贝。位拷贝又称浅拷贝,两个指针a=b指向同一资源,值拷贝或深拷贝进行资源的重新分配。
值传递,创建对象副本的时候,如果构造函数里为指针分配了内存,那么副本和原对象的指针指向相同的内存空间,当函数返回,副本的析构函数对该指针释放空间后,原对象的指针仍指向释放了的空间。而当原对象被销毁,执行析构函数的时候该内存空间又被释放了一次,又将产生严重错误。
当然我们可以用传地址或传引用,但有时我们不希望函数里面的操作影响原始对象,就可以用复制构造函数,复制构造函数就是在产生对象副本的时候执行的,我们可以定义自己的复制构造函数。在复制构造函数里面我们申请一个新的内存空间来保存构造函数里面的那个指针所指向的内容。这样在执行对象副本的析构函数时,释放的就是复制构造函数里面所申请的那个内存空间。
禁止复制:如IO类。 可以显示声明其复制构造函数为private。
但类的成员和友元仍可复制,可以声明一个private的复制构造函数而不定义。用户代码中的复制尝试编译时报错,成员和友元的复制尝试在链接时报错。
类似的,编译器可合成赋值操作符。
合成的析构函数:不删除指针成员指向的对象。
因为不能指定任何形参,所以不能重载析构函数。
即使我们编写了自己的析构函数,合成析构函数仍然运行。 先运行自己的,再运行合成的以撤销类的成员。
只有删除指向动态分配对象的指针或实际对象(而非对象的引用或指针)超出作用域时,才运行析构函数。
任何时候编译器都会合成析构函数,并且合成的析构函数总会运行。
撤销容器(标准库容器和内置数组)是按逆序进行的。
内存泄漏(Memory leak):删除指向动态分配内存的指针失败,因而无法将该块内存返还给自由存储区,这样的删除动态分配内存失败称为内存泄漏。
三法则(rule of three):如果类需要析构函数,则也需要赋值操作符和复制构造函数。
如果类里面有需要手动释放的资源,比如指针,就需要自己定义析构函数释放,为了防止浅拷贝后多次析构,需要自己定义拷贝构造函数,赋值操作符重载同理。
其他构造函数如果没有显式初始化某个类成员,则那个成员用该成员的默认构造函数初始化。
而显式定义的复制构造函数不会进行任何自动复制。
管理指针成员:
1. 智能指针
class HasPtr {
public:
HasPtr(int *p, int i): ptr(new U_Ptr(p)), val(i){}
HasPtr(const HasPtr &orig):
ptr(orig.ptr), val(orig.val) { ++ptr->use;}
HasPtr& operator=(const HasPtr&);
int *get_ptr() const {return ptr->ip;}
int get_int() const {return val;}
void set_ptr(int *p) { ptr->ip = p;}
void set_int(int i) { val = i;}
int get_ptr_val() const {return *ptr->ip;}
void set_ptr_val(int val) const { *ptr->ip = val;}
~HasPtr() { if (--ptr->use == 0) delete ptr; }
private:
U_Ptr *ptr;
int val;
};
HasPtr& HasPtr::operator=(const HasPtr &rhs)
{
++rhs.ptr->use;
if (--ptr->use == 0)
delete ptr;
ptr = rhs.ptr;
val=rhs.val;
return *this;
}
//引入使用计数
class U_Ptr {
private:
friend class HasPtr;
int *ip;
size_t use;
U_Ptr(int *p):ip(p),use(1) {}
~U_Ptr() {delete ip;}
};
2.定义值型类
class HasPtr {
public:
HasPtr(const int *p, int i): ptr(new int(p)), val(i){}
HasPtr(const HasPtr &orig):
ptr(new int(*orig.ptr)), val(orig.val) {}
HasPtr& operator=(const HasPtr&);
int *get_ptr() const {return ptr;}
int get_int() const {return val;}
void set_ptr(int *p) { ptr = p;}
void set_int(int i) { val = i;}
int get_ptr_val() const {return *ptr;}
void set_ptr_val(int val) const { *ptr = val;}
~HasPtr() { delete ptr; }
private:
int *ptr;
int val;
};
HasPtr& HasPtr::operator=(const HasPtr &rhs)
{
*ptr = *rhs.ptr;
val = rhs.val;
return *this;
}
第14章重载操作符与转换
不能重载的操作符 :: .* . ?:
可重载的:算术运算符,逻辑运算符, << >> <<= >>= [] () –> –>* new new[] delete delete[]
通过连接其他合法符号创建新的操作符,如operator**是合法的。
不能为内置数据类型重定义操作符,如不能定义两个数组类型操作符的operator+
重载操作符必须具有至少一个类类型或枚举类型的操作数。
优先级和结合性不变。
不具备短路求职特性: && || 逗号操作符,两边求值顺序无规定。
除了函数调用操作符operator()外,重载操作符时使用默认实参是非法的。
= [] () –>必须定义为成员函数
一般赋值操作符和复合赋值操作符要返回做操作数*this的引用。
IO操作符重载必须为非成员函数,友元。 如果为成员函数,左操作数为该类类型的对象,与正常相反。
ostream&
operator<<(ostream &os, const ClassType &object) //写入到流,ostream会变,object应不变,且避免复制实参
{
os</...
return os;
}
istream&
operator>>(istream &in, Sales_item &s) //错误处理
{
in>>//...
if (in) //...
else
s=Sales_item(); //input failed:reset object to default state
}
算术操作符和关系操作符一般定义为非成员函数
算术操作符返回新值而非任一操作数的引用,如果定义了该算术操作符的相关复合赋值操作符,则该算术操作符可用复合赋值操作符实现。
inline bool
operator==(const Sales_item &lhs, const Sales_item &rhs) //find函数{
return lhs.units_sold==rhs.units_sold && …;
}
inline bool
operator!=(const Sales_item &lhs, const Sales_item &rhs)
{
return !(lhs == rhs);
}
定义下标操作符一般要定义两个版本,非const成员返回引用,const成员返回const引用。因在用作赋值的左右操作数时都应表现正常。
class Foo {
public:
int& operator[] (const size_t);
const int& operator[] (const size_t) const;
private:
vector
};
int& operator[] (const size_t index)
{
return data[index];
}
const int& operator[] (const size_t) const
{
return data[index];
}
point->action();
1.如果 point 是一个指针,指向具有名为 action 的成员的类对象,则编译器将代码编译为调用该对象的 action 成员。
2.否则,如果 point是定义了 operator-> 操作符的类的一个对象,则 point->action 与 point.operator->()->action 相同。即,执行 point 的 operator->(),然后使用该结果重复这三步。
3.否则,代码出错。
重载箭头操作符必须返回指向类类型的指针,或者返回定义了自己的箭头操作符的类类型对象。
class A{
public:
void action(){
cout << "Action in class A!" << endl;
}
};
class B{
A a;
public:
A* operator->(){
return &a;
}
void action(){
cout << "Action in class B!" << endl;
}
};
class C{
B b;
public:
B operator->(){
return b;
}
void action(){
cout << "Action in class C!" << endl;
}
};
int main(int argc, char *argv[])
{
C* pc = new C;
pc->action();
C c;
c->action(); //等价于c.operator->().operator->()->action();
getchar();
return 0;
}
上面代码输出结果是:
Action in class C!
Action in class A!
常用于智能指针
class ScrPtr {
friend class ScreenPtr;
Screen *sp;
size_t use;
ScrPtr(Screen *p): sp(p), use(1) { }
~ScrPtr() { delete sp; }
};
class ScreenPtr {
public:
ScreenPtr(Screen *p): ptr(new ScrPtr(p)) { }
ScreenPtr(const ScreenPtr &orig):
ptr(orig.ptr) { ++ptr->use; }
ScreenPtr& operator=(const ScreenPtr&);
Screen &operator*() { return *ptr->sp; }
Screen *operator->() { return ptr->sp; }
const Screen &operator*() const { return *ptr->sp; }
const Screen *operator->() const { return ptr->sp; }
~ScreenPtr() { if (--ptr->use == 0) delete ptr; }
private:
ScrPtr *ptr;
};
自增自减操作符
class CheckedPtr {
public:
// no default constructor; CheckedPtrs must be bound to an object
CheckedPtr(int *b, int *e): beg(b), end(e), curr(b) { }
CheckedPtr& operator++(); // prefix operators
CheckedPtr& operator--();
CheckedPtr operator++(int); // postfix operators后置
CheckedPtr operator--(int);
private:
int* beg; // pointer to beginning of the array
int* end; // one past the end of the array
int* curr; // current position within the array
};
CheckedPtr& CheckedPtr::operator++()
{
if (curr == end)
throw out_of_range
("increment past the end of CheckedPtr");
++curr; // advance current state
return *this;
}
CheckedPtr& CheckedPtr::operator--()
{
if (curr == beg)
throw out_of_range
("decrement past the beginning of CheckedPtr");
--curr; // move current state back one element
return *this;
}
CheckedPtr CheckedPtr::operator++(int)
{
// no check needed here, the call to prefix increment will do the check
CheckedPtr ret(*this); // save current value
++*this; // advance one element, checking the increment
return ret; // return saved state
}
CheckedPtr CheckedPtr::operator--(int)
{
// no check needed here, the call to prefix decrement will do the check
CheckedPtr ret(*this); // save current value
--*this; // move backward one element and check
return ret; // return saved state
}
调用操作符和函数对象 Call Operator and Function Objects
struct absInt {
int operator() (int val) {
return val < 0 ? -val : val;
}
};
int i = -42;
absInt absObj; // object that defines function call operator
unsigned int ui = absObj(i); // calls absInt::operator(int)
将函数对象用于标准库算法Using Function Objects with Library Algorithms
bool GT6(const string &s)
{
return s.size() >= 6;
}
vector
count_if(words.begin(), words.end(), GT6);
class GT_cls {
public:
GT_cls(size_t val = 0): bound(val) { }
bool operator()(const string &s)
{ return s.size() >= bound; }
private:
std::string::size_type bound;
};
//计算长度在 1 到 10 个字符的单词数
//for (size_t i = 0; i != 11; ++i)
// cout << count_if(words.begin(), words.end(), GT(i))
// << " words " << i
// << " characters or longer" << endl;
#include
算术函数对象类型 所对应的操作符
plus
minus
multiplies
divides
modulus
negate
关系函数对象类型
equal_to
not_equal_to
greater
greater_equal
less
less_equal
逻辑函数对象类型
logical_and
logical_or
logical_not
sort(svec.begin(), svec.end(), greater
函数对象的函数适配器 Function Adaptors for Function Objects
1.绑定器binder,是一种函数适配器,它通过将一个操作数绑定到给定值而将二元函数对象转换为一元函数对象。
标准库定义了两个绑定器适配器:bind1st 和 bind2nd, bind1st 将给定值绑定到二元函数对象的第一个实参,bind2nd 将给定值绑定到二元函数对象的第二个实参。
//计算一个容器中所有小于或等于 10 的元素的个数,可以这样给 count_if 传递值:
count_if(vec.begin(), vec.end(),
bind2nd(less_equal
2.求反器Negator,是一种函数适配器,它将谓词函数对象的真值求反。
标准库还定义了两个求反器:not1 和 not2。你可能已经想到的,not1 将一元函数对象的真值求反,not2 将二元函数对象的真值求反。
count_if(vec.begin(), vec.end(),
not1(bind2nd(less_equal
转换操作符 // 转换函数必须是成员函数,不能指定返回类型,但每个转换函数必须显式返回一个指定类型的值, 并且形参表必须为空。
operator type(); //type 表示内置类型名、类类型名或由类型别名定义的名字
类类型转换之后不能再跟另一个类类型转换。如果需要多个类类型转换,则代码将出错。
class SmallInt {
public:
SmallInt(int = 0);
SmallInt(double);
operator int() const { return val; }
operator double() const { return val; }
private:
std::size_t val;
};
void compute(int);
void fp_compute(double);
void extended_compute(long double);
SmallInt si;
compute(si); // SmallInt::operator int() const
fp_compute(si); // SmallInt::operator double() const
extended_compute(si); // error: ambiguous
void manip(const SmallInt &);
double d; int i; long l;
manip(d); // ok: use SmallInt(double) to convert the argument
manip(i); // ok: use SmallInt(int) to convert the argument
manip(l); // error: ambiguous
void compute(int);
void compute(double);
void compute(long double);
SmallInt si;
compute(si); // error: ambiguous
//显式强制转换消除二义性
SmallInt si;
compute(static_cast
///
//当两个类定义了相互转换时,很可能存在二义性:
class Integral;
class SmallInt {
public:
SmallInt(Integral); // convert from Integral to SmallInt
// ...
};
class Integral {
public:
operator SmallInt() const;
// ...
};
void compute(SmallInt);
Integral int_val;
compute(int_val); // error: ambiguous
class SmallInt {
public:
SmallInt(int = 0);
};
class Integral {
public:
Integral(int = 0);
};
void manip(const Integral&);
void manip(const SmallInt&);
manip(10); // error: ambiguous
///
class SmallInt {
public:
SmallInt(int = 0); // convert from int to SmallInt
// conversion to int from SmallInt
operator int() const { return val; }
// arithmetic operators
friend SmallInt
operator+(const SmallInt&, const SmallInt&);
private:
std::size_t val;
};
SmallInt s1, s2;
SmallInt s3 = s1 + s2; // ok: uses overloaded operator+
int i = s3 + 0; // error: ambiguous
第15章 object-oriented programming, OOP
面向对象编程 :数据抽象,继承,动态绑定。
通过基类的引用或指针调用虚函数时,发生动态绑定。
除了构造函数之外,任意非 static 成员函数都可以是虚函数。保留字virtual只在类内部的成员函数声明中出现,不能用在类定义体外部出现的函数定义上。
protected不能被外部访问,但可被派生类访问。
private继承:【Derived对象】不能调用Base中的任何东西;所有继承方式【Derived类】都还能调用Base类中的public和protected的东西(成员函数)。Derived类的Derived则都无法调用。
-----------------------------------------------------------
private继承
基类成员 private成员 public成员 protected成员
内部访问 不可访问 可访问 可访问
对象访问 不可访问 不可访问 不可访问
------------------------------------------------------------
public继承
基类成员 private成员 public成员 protected成员
内部访问 不可访问 可访问 可访问
对象访问 不可访问 可访问 类定义中可访问,外部不可访问
------------------------------------------------------------
protected继承
基类成员 private成员 public成员 protected成员
内部访问 不可访问 可访问 可访问
对象访问 不可访问 不可访问 不可访问
------------------------------------------------------------
默认继承方式:
class Base { /* ... */ };
struct D1 : Base { /* ... */ }; // public inheritance by default
class D2 : Base { /* ... */ }; // private inheritance by default
派生类中虚函数的声明必须与基类中的定义方式完全匹配,但有一个例外:返回对基类型的引用(或指针)的虚函数。派生类中的虚函数可以返回基类函数所返回类型的派生类的引用(或指针)。
一旦函数在基类中声明为虚函数,它就一直为虚函数。派生类重定义虚函数时,可以使用 virtual 保留字,但不是必须这样做。
C++ 语言不要求编译器将对象的基类部分和派生部分和派生部分连续排列。
已定义的类才可以用作基类。如果已经声明了 Item_base 类,但没有定义它,则不能用 Item_base 作基类:
class Item_base; // declared but not defined
class Bulk_item : public Item_base { ... }; // error: Item_base must be defined
每个派生类包含并且可以访问其基类的成员,为了使用这些成员,派生类必须知道它们是什么。这一规则暗示着不可能从类自身派生出一个类。
如果一个调用省略了具有默认值的实参,则所用的值由调用该函数的类型定义,与对象的动态类型无关。
通过基类的引用或指针调用虚函数时,默认实参为在基类虚函数声明中指定的值,如果通过派生类的指针或引用调用虚函数,则默认实参是在派生类的版本中声明的值。
友元关系不能继承。
//恢复基类成员的访问级别
class Base {
public:
std::size_t size() const { return n; }
protected:
std::size_t n;
};
class Derived : private Base {
public:
//maintain access levels for members related to the size of the object
using Base::size;
protected:
using Base::n;
};
如果基类定义 static 成员,则整个继承层次中只有一个这样的成员。无论从基类派生出多少个派生类,每个 static 成员只有一个实例。
用派生类对象对基类对象进行初始化或赋值,非基类对象被切除。
如果是 public 继承,则用户代码和后代类都可以使用派生类到基类的转换。如果类是使用 private 或 protected 继承派生的,则用户代码不能将派生类型对象转换为基类对象。
如果是 private 继承,则从 private 继承类派生的类不能转换为基类。如果是 protected 继承,则后续派生类的成员可以转换为基类类型。
无论是什么派生访问标号,派生类本身都可以访问基类的 public 成员,因此,派生类本身的成员和友元总是可以访问派生类到基类的转换。
构造函数和复制控制成员不能继承,每个类定义自己的构造函数和复制控制成员。像任何类一样,如果类不定义自己的默认构造函数和复制控制成员,就将使用合成版本。
某些类需要只希望派生类使用的特殊构造函数,这样的构造函数应定义为protected。
如果派生类定义了自己的赋值操作符,则该操作符必须对基类部分进行显式赋值。
// Base::operator=(const Base&) not invoked automatically
Derived &Derived::operator=(const Derived &rhs)
{
if (this != &rhs) { //防止自身赋值
Base::operator=(rhs); // assigns the base part
// do whatever needed to clean up the old value in the derived part
// assign the members from the derived
}
return *this;
}
析构函数的工作与复制构造函数和赋值操作符不同:派生类析构函数不负责撤销基类对象的成员。
编译器总是显式调用派生类对象基类部分的析构函数。每个析构函数只负责清除自己的成员。
对象的撤销顺序与构造顺序相反。
基类析构函数是三法则的一个重要例外。三法则指出,如果类需要析构函数,则类几乎也确实需要其他复制控制成员。基类几乎总是需要构造函数,从而可以将析构函数设为虚函数。如果基类为了将析构函数设为虚函数则具有空析构函数,那么,类具有析构函数并不表示也需要赋值操作符或复制构造函数。
构造函数不能定义为虚函数。构造函数是在对象完全构造之前运行的,在构造函数运行的时候,对象的动态类型还不完整。
将类的赋值操作符设为虚函数很可能会令人混淆,而且不会有什么用处。因为虚函数必须在基类和派生类中具有同样的形参。
在基类构造函数或析构函数中,将派生类对象当作基类类型对象对待。构造派生类对象时首先运行基类构造函数初始化对象的基类部分。撤销派生类对象时,首先撤销它的派生类部分。
如果在构造函数或析构函数中调用虚函数,则运行的是为构造函数或析构函数自身类型定义的版本。
继承情况下,派生类的作用域嵌套在基类作用域中。如果不能在派生类作用域中确定名字,就在外围基类作用域中查找该名字的定义。
struct Base {
int memfcn();
};
struct Derived : Base {
int memfcn(int); // hides memfcn in the base
};
d.memfcn(); // error: memfcn with no arguments is hidden 找名字memfcn,并在 Derived 类中找到。一旦找到了名字,编译器就不再继续查找了
d.Base::memfcn(); // ok: calls Base::memfcn
局部作用域中声明的函数不会重载全局作用域中定义的函数,同样,派生类中定义的函数也不重载基类中定义的成员。通过派生类对象调用函数时,实参必须与派生类中定义的版本相匹配,只有在派生类根本没有定义该函数时,才考虑基类函数。
如果派生类重定义了重载成员,则通过派生类型只能访问派生类中重定义的那些成员。
如果派生类想通过自身类型使用重载版本,则派生类必须要么重定义所有重载版本,要么一个也不重定义。也可为重载成员提供 using 声明。
class Base {
public:
virtual int fcn();
};
class D1 : public Base {
public:
// hides fcn in the base; this fcn is not virtual
int fcn(int); // parameter list differs from fcn in Base
// D1 inherits definition of Base::fcn()
};
class D2 : public D1 {
public:
int fcn(int); // nonvirtual function hides D1::fcn(int)
int fcn(); // redefines virtual fcn from Base
};
从 Base 继承的虚函数不能通过 D1 对象(或 D1 的引用或指针)调用,因为该函数被 fcn(int) 的定义屏蔽了。
通过基类类型的引用或指针调用函数时,编译器将在基类中查找该函数而忽略派生类:
Base bobj; D1 d1obj; D2 d2obj;
Base *bp1 = &bobj, *bp2 = &d1obj, *bp3 = &d2obj;
bp1->fcn(); // ok: virtual call, will call Base::fcnat run time
bp2->fcn(); // ok: virtual call, will call Base::fcnat run time
bp3->fcn(); // ok: virtual call, will call D2::fcnat run time
//句柄类存储和管理基类指针,使用计数来管理
// use counted handle class for the Item_base hierarchy
class Sales_item {
public:
// default constructor: unbound handle
Sales_item(): p(0), use(new std::size_t(1)) { }
// attaches a handle to a copy of the Item_base object
Sales_item(const Item_base&);
// copy control members to manage the use count and pointers
Sales_item(const Sales_item &i):
p(i.p), use(i.use) { ++*use; }
~Sales_item() { decr_use(); }
Sales_item& operator=(const Sales_item&);
// member access operators
const Item_base *operator->() const { if (p) return p;
else throw std::logic_error("unbound Sales_item"); }
const Item_base &operator*() const { if (p) return *p;
else throw std::logic_error("unbound Sales_item"); }
private:
Item_base *p; // pointer to shared item
std::size_t *use; // 指向使用计数
// called by both destructor and assignment operator to free pointers
void decr_use()
{ if (--*use == 0) { delete p; delete use; } }
};
Sales_item::Sales_item(const Item_base &item):
p(item.clone()), use(new std::size_t(1)) { }
// use-counted assignment operator; use is a pointer to a shared use count
Sales_item&
Sales_item::operator=(const Sales_item &rhs)
{
++*rhs.use;
decr_use();
p = rhs.p;
use = rhs.use;
return *this;
}
class Item_base {
public:
virtual Item_base* clone() const
{ return new Item_base(*this); }
std:string book() const { return isbn;}
virtual double net_price(std:size_t n) const {}
private:
std:string isbn;
};
class Bulk_item : public Item_base {
//若虚函数的基类实例返回类类型的引用或指针,则该派生类可返回派生类的指针或引用
public:
Bulk_item* clone() const
{ return new Bulk_item(*this); }
double net_price(std:size_t n) const {}
};
// compare defines item ordering for the multiset in Basket
inline bool
compare(const Sales_item &lhs, const Sales_item &rhs)
{
return lhs->book() < rhs->book(); //
}
// type of the comparison function used to order the multiset
typedef bool (*Comp)(const Sales_item&, const Sales_item&);
std::multiset
/*items 是一个 multiset,它保存 Sales_item 对象并使用 Comp 类型的对象比较它们。multiset 是空的,但我们的确提供了一个比较函数 compare。当在 items 中增加或查找元素时,将用 compare 函数对 multiset 进行排序。*/
class Basket {
// type of the comparison function used to order the multiset
typedef bool (*Comp)(const Sales_item&, const Sales_item&);
public:
// make it easier to type the type of our set
typedef std::multiset
// typedefs modeled after corresponding container types
typedef set_type::size_type size_type;
typedef set_type::const_iterator const_iter;
Basket(): items(compare) { } // initialze the comparator
void add_item(const Sales_item &item)
{ items.insert(item); }
size_type size(const Sales_item &i) const
{ return items.count(i); }
double total() const; // sum of net prices for all items in the basket
private:
std::multiset
};
double Basket::total() const {
double sum = 0.0; // holds the running total
for (const_iter iter = items.begin();
iter != items.end();
iter = items.upper_bound(*iter))
//返回指向与该键相同的最后一个元素的下一元素
{
sum += (*iter)->net_price(items.count(*iter));
}
return sum;
}
15.9程序在本文最后
函数模板可以用与非模板函数一样的方式声明为 inline。说明符放在模板形参表之后、返回类型之前,不能放在关键字 template 之前。
template
用作模板形参的名字不能在模板内部重用。
template
typedef double T; // error: redeclares template parameter T
// ... }
// error: illegal reuse of template parameter name V
template
正如可以重用函数形参名字一样,模板形参的名字也能在不同模板中重用:
// ok: reuses parameter type name across different templates
template
template
template
Parm fcn(Parm* array, U value) {
Parm::size_type * p; // If Parm::size_type is a type, then a declaration
// If Parm::size_type is an object, then multiplication
}
//我们不知道 size_type 是一个类型成员的名字还是一个数据成员的名字,
//默认情况下,编译器假定这样的名字指定数据成员,而不是类型
//如果希望编译器将 size_type 当作类型,则必须显式告诉编译器这样做:
template
Parm fcn(Parm* array, U value) {
typename Parm::size_type * p; // ok: declares p to be a pointer
}
template
{
for (size_t i = 0; i != N; ++i) {
parm[i] = 0;
}
}
模板非类型形参是模板定义内部的常量值,在需要常量表达式的时候,可使用非类型形参
int x[42];
array_init(x); // instantiates array_init(int(&)[42]
编译器只会执行两种转换:
1.const 转换:接受 const 引用或 const 指针的函数可以分别用非 const 对象的引用或指针来调用,无须产生新的实例化。
2.数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换。数组实参将当作指向其第一个元素的指针,函数实参当作指向函数类型的指针。
template
template
T fref(const T&, const T&); // reference arguments
string s1("a value");
const string s2("another value");
fobj(s1, s2); // ok: calls f(string, string), const is ignored
fref(s1, s2); // ok: non const object s1 converted to const reference
int a[10], b[42];
fobj(a, b); // ok: calls f(int*, int*)
fref(a, b); // error: array types don't match; arguments aren't converted to pointers 当形参为引用时,数组不能转换为指针
可以使用函数模板对函数指针进行初始化或赋值,这样做的时候,编译器使用指针的类型实例化具有适当模板实参的模板版本。
template
// pf1 points to the instantiation int compare (const int&, const int&)
int (*pf1) (const int&, const int&) = compare;
在返回类型中引入第三个模板形参,它必须由调用者显式指定。
template
T1 sum(T2, T3);
long val3 = sum
显式模板实参从左至右对应模板形参相匹配,第一个模板实参与第一个模板形参匹配,第二个实参与第二个形参匹配,以此类推。
template
T3 alternative_sum(T2, T1);
// error: can't infer initial template parameters
long val3 = alternative_sum
// ok: All three parameters explicitly specified
long val2 = alternative_sum
模板编译模型:当编译器看到模板定义的时候,它不立即产生代码。只有在看到用到模板时,如调用了函数模板或调用了类模板的对象的时候,编译器才产生特定类型的模板实例。
一般而言,当调用函数的时候,编译器只需要看到函数的声明。类似地,定义类类型的对象时,类定义必须可用,但成员函数的定义不是必须存在的。因此,应该将类定义和函数声明放在头文件中,而普通函数和类成员函数的定义放在源文件中。模板则不同:要进行实例化,编译器必须能够访问定义模板的源代码。
1.包含编译模型:这一策略使我们能够保持头文件和实现文件的分享,但是需要保证编译器在编译使用模板的代码时能看到两种文件。
//sum.h
#ifndef SUM
#define SUM
template
T1 sum(T1 a,T1 b);
#include "sum.cpp"
#endif
//sum.cpp
template
T1 sum(T1 a,T1 b) {
return a+b;
}
//main.cpp
#include "sum.h"
#include
int main() {
std::cout << sum(1, 2) << std::endl;
return 0;
}
2.分别编译模型: 使用export关键字,必须写在template前面,和inline不能同时使用
VS2010都还不支持。
在类外定义模板类的成员函数
template
何时实例化类和成员:
类模板的成员函数只有为程序所用才进行实例化,类模板的指针定义不会对类进行实例化,只有用到这样的指针时才会对类进行实例化。
template
class Screen {};
友元声明依赖性:
当授予对给定模板的实例的访问权时候,在作用域中不需要存在该类模板或函数模板的声明。
想要限制对特定实例化的友元关系时,必须在可以用于友元声明之前声明类或函数。如果没有事先告诉编译器该友元是一个模板,则编译器将认为该友元是一个普通非模板类或非模板函数。
template
template
public:
friend class A
friend class C; // ok: C must be an ordinary, nontemplate class
template
friend class E
friend class F
};
// declaration that Queue is a template needed for friend declaration in QueueItem
template
template
friend class Queue
// ...
};
成员模板不能为虚.
类模板的 static 成员:一样只在使用时才进行初始化,必须在类外定义。
template
Queue模板:
//Queue.h
#ifndef queue_h
#define queue_h
// declaration that Queue is a template needed for friend declaration in QueueItem
template
// function template declaration must precede friend declaration in QueueItem
template
std::ostream& operator<<(std::ostream&, const Queue
template
friend class Queue
// needs access to item and next
friend std::ostream& operator<<
// private class: no public section
QueueItem(const Type &t): item(t), next(0) {}
Type item; // value stored in this element
QueueItem *next; // pointer to next element in the Queue
};
template
// needs access to head
friend std::ostream& operator<<
public:
// empty Queue
Queue(): head(0), tail(0) { }
// construct a Queue from a pair of iterators on some sequence
template
Queue(It beg, It end):
head(0), tail(0) { copy_elems(beg, end); }
// copy control to manage pointers to QueueItems in the Queue
Queue(const Queue &Q): head(0), tail(0)
{ copy_elems(Q); }
Queue& operator=(const Queue&); // left as exercise for the reader
~Queue() { destroy(); }
// replace current Queue by contents delimited by a pair of iterators
template
// return element from head of Queue
// unchecked operation: front on an empty Queue is undefined
Type& front() { return head->item; }
const Type &front() const { return head->item; }
void push(const Type &);
void pop();
bool empty() const { // true if no elements in the Queue
return head == 0;
}
private:
QueueItem
QueueItem
// utility functions used by copy constructor, assignment, and destructor
void destroy();
void copy_elems(const Queue&);
// version of copy to be used by assign to copy elements from iterator range
template
};
// Inclusion Compilation Model: include member function definitions as well
template
std::ostream& operator<< (std::ostream &os, const Queue
os << "< ";
QueueItem
for (p = q.head; p; p = p->next)
os << p->item << " ";
os <<">";
return os;
}
template
while (!empty())
pop();
}
template
// pop is unchecked: Popping off an empty Queue is undefined
QueueItem
head = head->next; // head now points to next element
delete p; // delete old head element
}
template
// allocate a new QueueItem object
QueueItem
// put item onto existing queue
if (empty())
head = tail = pt; // the queue now has only one element
else {
tail->next = pt; // add new element to end of the queue
tail = pt;
}
}
template
void Queue
while (beg != end) {
push(*beg);
++beg;
}
}
template
void Queue
destroy(); // remove existing elements in this Queue
copy_elems(beg, end); // copy elements from the input range
}
template
void Queue
// copy elements from orig into this Queue
// loop stops when pt == 0, which happens when we reach orig.tail
for (QueueItem
push(pt->item); // copy the element
}
template
Queue
destroy();
head=rhs.head;
tail=rhs.tail;
return *this;
}
#endif
//main.cpp
#include
#include "Queue.h"
int main() {
short a[4] = { 0, 3, 6, 9 };
Queue
std::vector
qi.assign(vi.begin(), vi.end());
std::cout<
return 0;
}
句柄类模板
template
public:
// unbound handle
Handle(T *p = 0): ptr(p), use(new size_t(1)) { }
// overloaded operators to support pointer behavior
T& operator*();
T* operator->();
const T& operator*() const;
const T* operator->() const;
// copy control: normal pointer behavior, but last Handle deletes the object
Handle(const Handle& h): ptr(h.ptr), use(h.use)
{ ++*use; }
Handle& operator=(const Handle&);
~Handle() { rem_ref(); }
private:
T* ptr; // shared object
size_t *use; // count of how many Handle spointto *ptr
void rem_ref()
{ if (--*use == 0) { delete ptr; delete use; } }
};
template
inline Handle
++*rhs.use; // protect against self-assignment
rem_ref(); // decrement use count and delete pointers if needed
ptr = rhs.ptr;
use = rhs.use;
return *this;
}
template
if (ptr) return *ptr;
throw std::runtime_error
("dereference of unbound Handle");
}
template
if (ptr) return ptr;
throw std::runtime_error
("access through unbound Handle");
}
1.函数模板的特化template <> 返回类型 模板名<特化定义的模板形参>(函数形参表){}
函数重载与模板特化:在特化中省略 template<> ,则会声明重载版本。
当特化模板的时候,对实参类型不应用转换。
在模板特化版本的调用中,实参类型必须与特化版本函数的形参类型完全匹配,如果不完全匹配,编译器将为实参从模板定义实例化一个实例。
2.类模板的特化
3.特化成员而不特化类
4.类模板的部分特化
template
class some_template {
// ...
};
// partial specialization: fixes T2 as int and allows T1 to vary
template
class some_template
// ...
};
函数模板可以重载:可以定义有相同名字但形参数目或类型不同的多个函数模板,也可以定义与函数模板有相同名字的普通非模板函数。
//15.9文本查询
class TextQuery {
public:
typedef string::size_type str_size;
typedef vector
void read_file(ifstream &is) {store_file(is); build_map();}
set
string text_line(line_no) const;
line_no size() const;
private:
void store_file(ifstream&);
void build_map();
vector
map< string, set
static string cleanup_str(const string&);
};
void TextQuery::store_file(ifstream &is)
{
string textline;
while (getline(is, textline))
lines_of_text.push_back(textline);
}
void TextQuery::build_map()
{
for (line_no line_num = 0;
line_num != lines_of_text.size();
++line_num)
{
istringstream line(lines_of_text[line_num]);
string word;
while (line >> word)
word_map[cleanup_str(word)].insert(line_num);
}
}
string TextQuery::text_line(line_no line) const
{
if(line < lines_of_text.size())
return lines_of_text[line];
throw out_of_range("line number out of range");
}
string TextQuery::cleanup_str(const string &word)
{
string ret;
for (string::const_iterator it = word.begin();
it != word.end(); ++it)
{
if (!ispunct(*it))
ret += tolower(*it);
}
return ret;
}
TextQuery::line_no TextQuery::size() const
{
return lines_of_text.size();
}
set
{
map
loc=word_map.find(query_word);
if (loc == word_map.end())
return set
else
return loc->second;
}
class Query_base {
friend class Query;
protected:
typedef TextQuery::line_no line_no;
virtual ~Query_base() { }
private:
virtual set
virtual ostream& display(ostream& = cout) const = 0;
};
class WordQuery: public Query_base {
friend class Query; // Query uses the WordQuery constructor
WordQuery(const std::string &s): query_word(s) { }
// concrete class: WordQuery defines all inherited pure virtual functions
set
{ return t.run_query(query_word); }
ostream& display (std::ostream &os) const
{ return os << query_word; }
string query_word; // word for which to search
};
inline ostream& operator<<(std::ostream &os, const Query &q)
{
return q.display(os);
}
// handle class to manage the Query_base inheritance hierarchy
class Query {
// these operators need access to the Query_base* constructor
friend Query operator~(const Query &);
friend Query operator|(const Query&, const Query&);
friend Query operator&(const Query&, const Query&);
public:
Query(const string&); // builds a new WordQuery
// copy control to manage pointers and use counting
Query(const Query &c): q(c.q), use(c.use) { ++*use; }
~Query() { decr_use(); }
Query& operator=(const Query&);
// interface functions: will call corresponding Query_base operations
std::set
eval(const TextQuery &t) const { return q->eval(t); }
std::ostream &display(ostream &os) const
{ return q->display(os); }
private:
Query(Query_base *query): q(query),
use(new size_t(1)) { }
Query_base *q;
size_t *use;
void decr_use()
{
if (--*use == 0) { delete q; delete use; }
}
};
Query::Query(const string &s):q(new WordQuery(s)),
use(new size_t(1)){}
Query& Query::operator=(const Query &rhs)
{
++*rhs.use;
decr_use();
q=rhs.q;
use=rhs.use;
return *this;
}
class BinaryQuery: public Query_base {
protected:
BinaryQuery(Query left, Query right, std::string op):
lhs(left), rhs(right), oper(op) { }
// abstract class: BinaryQuery doesn't define eval
ostream& display(ostream &os) const
{ return os << "(" << lhs << " " << oper << " "<< rhs << ")"; }
const Query lhs, rhs; // right- and left-hand operands
const std::string oper; // name of the operator
};
class AndQuery: public BinaryQuery {
friend Query operator&(const Query&, const Query&);
AndQuery (Query left, Query right):
BinaryQuery(left, right, "&") { }
// concrete class: And Query inherits display and defines remaining pure virtual
std::set
};
class NotQuery: public Query_base {
friend Query operator~(const Query &);
NotQuery(Query q): query(q) { }
// concrete class: NotQuery defines all inherited pure virtual functions
std::set
std::ostream& display(std::ostream &os) const
{ return os << "~(" << query << ")"; }
const Query query;
};
class OrQuery: public BinaryQuery {
friend Query operator|(const Query&, const Query&);
OrQuery(Query left, Query right):
BinaryQuery(left, right, "|") { }
// concrete class: OrQuery inherits display and defines remaining pure virtual
set
};
inline Query operator&(const Query &lhs, const Query &rhs)
{
return new AndQuery(lhs, rhs);
}
inline Query operator|(const Query &lhs, const Query &rhs)
{
return new OrQuery(lhs, rhs);
}
inline Query operator~(const Query &oper)
{
return new NotQuery(oper);
}
// returns union of its operands' result sets
set
{
// virtual calls through the Query handle to get result sets for the operands
set
ret_lines = lhs.eval(file); // destination to hold results
// inserts the lines from right that aren't already in ret_lines
ret_lines.insert(right.begin(), right.end());
return ret_lines;
}
// returns intersection of its operands' result sets
set
{
// virtual calls through the Query handle to get result sets for the operands
set
right = rhs.eval(file);
set
// writes intersection of two ranges to a destination iterator
// destination iterator in this call adds elements to ret
set_intersection(left.begin(), left.end(),
right.begin(), right.end(),
inserter(ret_lines, ret_lines.begin()));
return ret_lines;
}
// returns lines not in its operand's result set
set
{
// virtual call through the Query handle to eval
set
set
// for each line in the input file, check whether that line is in has_val
// if not, add that line number to ret_lines
for (TextQuery::line_no n = 0; n != file.size(); ++n)
if (has_val.find(n) == has_val.end())
ret_lines.insert(n);
return ret_lines;
}
string make_plural(size_t ctr,const string &word, const string &ending)
{
return (ctr==1) ? word : word+ending;
}
ifstream& open_file(ifstream &in, const string &file)
{
in.close(); // close in case it was already open
in.clear(); // clear any existing errors
// if the open fails, the stream will be in an invalid state
in.open(file.c_str()); // open the file we were given
return in; // condition state is good if open succeeded
}
void printf_results(const set
{
typedef set
line_nums::size_type size = locs.size();
cout << "match occurs "
<< size << ""
<
line_nums::const_iterator it = locs.begin();
for(; it != locs.end(); ++it)
{
cout << "/t(line "
<< (*it) + 1 << ") "
<< file.text_line(*it) <
}
}
int main(int argc, char **argv)
{
ifstream infile;
if (argc < 2 || !open_file(infile, argv[1]))
{
cerr << "No input file" <
return EXIT_FAILURE;
}
TextQuery file;
file.read_file(infile);
typedef set
Query q = Query("fiery") & Query("bird") | Query("wind");
const line_nums &locs = q.eval(file);
cout << "/nExecuted Query for: "<< q << endl;
printf_results(locs,file);
return 0;
}
generic handle class 泛型句柄类
inclusion compilation model 包含编译模型
instantiation 实例化
partial specialization 部分特化
第17章 用于大型程序的工具
17.1异常处理
· 异常是通过抛出对象而引发的。该对象的类型决定应该激活哪个处理代码。被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那个.
· 不存在数组或函数类型的异常类型。因为与函数传递实参一样,如果抛出一个数组,被抛出的对象自动转换为指向数组首元素的指针;如果抛出一个函数,函数被转换为指向该函数的指针。
· 因为在处理异常的时候会释放局部存储,所以被抛出的对象就不能再局部存储,而是用 throw 表达式初始化一个称为异常对象的特殊对象。异常对象由编译器管理,而且保证驻留在可能被激活的任意 catch 都可以访问的空间。这个对象由 throw 创建,并被初始化为被抛出的表达式的副本。异常对象将传给对应的 catch,并且在完全处理了异常之后撤销。
· 异常对象通过复制被抛出表达式的结果创建,该结果必须是可以复制的类型。
· 执行 throw 的时候,不会执行跟在 throw 后面的语句,而是将控制从 throw 转移到匹配的 catch,该 catch 可以是同一函数中局部的 catch,也可以在直接或间接调用发生异常的函数的另一个函数中。
· 当抛出一个表达式的时候,被抛出对象的静态编译时类型将决定异常对象的类型。如果该指针是一个指向派生类对象的基类类型指针,则那个对象将被分割,只抛出基类部分.
· 抛出异常的时候,将暂停当前函数的执行,开始查找匹配的 catch 子句。首先检查 throw 本身是否在 try 块内部,如果是,就找与抛出对象相匹配的最近的catch子句。如果找到匹配的 catch,就处理异常;如果找不到,就退出当前函数(释放当前函数的内在并撤销局部对象),并且继续在调用函数中查找。如果对抛出异常的函数的调用是在 try 块中,则检查与该 try 相关的 catch 子句。如果找到匹配的 catch,就处理异常;如果找不到匹配的 catch,调用函数也退出,并且继续在调用这个函数的函数中查找。这个过程,称之为栈展开(stack unwinding),沿嵌套函数调用链继续向上,直到为异常找到一个 catch 子句。只要找到能够处理异常的 catch 子句,就进入该 catch 子句,并在该处理代码中继续执行。当 catch 结束的时候,在紧接在与该 try 块相关的最后一个 catch 子句之后的点继续执行。
· 栈展开期间,释放局部对象所用的内存并运行类类型局部对象的析构函数。
· 析构函数应该从不抛出异常。栈展开期间会经常执行析构函数。在执行析构函数的时候,已经引发了异常但还没有处理它。如果在这个过程中析构函数本身抛出新的异常。该先处理哪个异常呢?答案是:在为某个异常进行栈展开的时候,析构函数如果又抛出自己的未经处理的另一个异常,将会导致调用标准库 terminate 函数。一般而言,terminate 函数将调用 abort 函数,强制从整个程序非正常退出。
· 如果在构造函数对象的时候发生异常,则该对象可能只是部分被构造,它的一些成员可能已经初始化,而另一些成员在异常发生之前还没有初始化。即使对象只是部分被构造了,也要保证将会适当地撤销已构造的成员。
· 不能不处理异常。异常是足够重要的、使程序不能继续正常执行的事件。如果找不到匹配的 catch,程序就调用库函数 terminate。
· catch 子句中的异常说明符的类型决定了处理代码能够捕获的异常种类。类型必须是完全类型,即必须是内置类型或者是已经定义的程序员自定义类型。类型的前向声明不行。当 catch 为了处理异常只需要了解异常的类型的时候,异常说明符可以省略形参名。
· 在查找匹配的 catch 期间,找到的 catch 不必是与异常最匹配的那个catch,相反,将选中第一个找到的可以处理该异常的 catch。因此,在catch 子句列表中,最特殊的 catch 必须最先出现。带有因继承而相关的类型的多个 catch 子句,必须从最低派生类类到最高基类类型排序。
· 异常与 catch 异常说明符匹配的规则比匹配实参和形参类型的规则更严格,大多数转换都不允许。既不允许标准算术转换,也不允许为类类型定义的转换。只允许以下3种转换
1)允许从非 const 到 const 的转换
2)允许从派生类型型到基类类型的转换。
3)将数组转换为指向数组类型的指针,将函数转换为指向函数类型的适当指针。
· 进入 catch 的时候,用异常对象初始化 catch 的形参。像函数形参一样,异常说明符类型可以是引用。异常对象本身是被抛出对象的副本。是否再次将异常对象复制到 catch 位置取决于异常说明符类型。
· 像形参声明一样,基类的异常说明符可以用于捕获派生类型的异常对象,而且,异常说明符的静态类型决定 catch 子句可以执行的动作。如果被抛出的异常对象是派生类类型的,但由接受基类类型的 catch 处理,那么,catch不能使用派生类特有的任何成员。如果 catch 子句处理因继承而相关的类型的异常,它就应该将自己的形参定义为引用。
· 有可能单个 catch 不能完全处理一个异常。在进行了一些校正行动之后,catch 可能确定该异常必须由函数调用链中更上层的函数来处理,catch 可以通过空throw语句重新抛出将异常传递函数调用链中更上层的函数。
· 虽然重新抛出不指定自己的异常,但仍然将一个异常对象沿链向上传递,被抛出的异常是原来的异常对象,而不是 catch 形参。catch 在改变它的形参之后,如果 catch 重新抛出异常,那么,只有当异常说明符是引用的时候,才会传播那些改变。当 catch 形参是基类类型的时候,我们不知道由重新抛出表达式抛出的实际类型,该类型取决于异常对象的动态类型。例如,来自带基类类型形参 catch 的重新抛出,可能实际抛出一个派生类型的对象。
· 捕获所有异常catch (...) { }
· 在进入构造函数函数体之前处理构造函数初始化式,构造函数函数体内部的catch 子句不能处理在处理构造函数初始化时可能发生的异常。为了处理来自构造函数初始化式的异常,必须将构造函数编写为函数 try 块,可以使用函数测试块将一组 catch 子句与函数联成一个整体。
template
try : ptr(p), use(new size_t(1))
{
// empty function body
} catch(const std::bad_alloc &e)
{ handle_out_of_memory(e); }
· exception 类型所定义的唯一操作是一个名为 what 的虚成员,该函数返回const char* 对象,它一般返回用来在抛出位置构造异常对象的信息。因为what 是虚函数,如果捕获了基类类型引用,对 what 函数的调用将执行适合异常对象的动态类型的版本。
//应用程序还经常通过从 exception 类或者中间基类派生附加类型来扩充 exception 层次。这些新派生的类可以表示特定于应用程序领域的异常类型。
class isbn_mismatch: public std::logic_error {
public:
explicit isbn_mismatch(const std::string &s):
std::logic_error(s) { }
isbn_mismatch(const std::string &s,
const std::string &lhs, const std::string &rhs):
std::logic_error(s), left(lhs), right(rhs) { }
const std::string left, right;
virtual ~isbn_mismatch() throw() { }
//合成析构函数调用string析构函数,C++ 标准保证,string 析构函数像任意其他标准库类析构函数一样,不抛出异常。
//但标准库的析构函数没有定义异常说明,我们知道,但编译器不知道,string析构函数将不抛出异常。我们必须定义自己的析构函数来恢复析构函数不抛出异常的承诺。
};
Sales_item operator+(const Sales_item& lhs, const Sales_item& rhs) {
if (!lhs.same_isbn(rhs))
throw isbn_mismatch("isbn mismatch",lhs.book(), rhs.book());
Sales_item ret(lhs); // copy lhs into a local object that we'll return
ret += rhs; // add in the contents of rhs
return ret; // return ret by value
}
// use hypothetical bookstore exceptions
Sales_item item1, item2, sum;
while (cin >> item1 >> item2) { // read two transactions
try {
sum = item1 + item2; // calculate their sum
} catch (const isbn_mismatch &e) {
cerr << e.what() << ": left isbn(" << e.left
<< ") right isbn(" << e.right << ")"
<< endl;}
· 在 new 之后但在 delete 之前发生的异常使得资源没有被撤销,从而引入异常安全的概念保证“如果发生异常,被分配的任何资源都适当地释放”。
通过定义一个类来封闭资源的分配和释放,可以保证正确释放资源。这一技术常称为“资源分配即初始化”,简称 RAII。
应该设计资源管理类,以便构造函数分配资源而析构函数释放资源。想要分配资源的时候,就定义该类类型的对象。如果不发生异常,就在获得资源的对象超出作用域后释放资源。更为重要的是,如果在创建了对象之后但在它超出作用域之前发生异常,那么,编译器保证撤销该对象,作为展开定义对象的作用域的一部分。
class Resource {
public:
Resource(parms p): r(allocate(p)) { }
~Resource() { release(r); }
// also need to define copy and assignment
private:
resource_type *r; // resource managed by this type
resource_type *allocate(parms p); // allocate this resource
void release(resource_type*); // free this resource
};
· auto_ptr 类,就是异常安全的“资源分配即初始化 RAII的例子。它接受一个类型形参的模板,为动态分配的对象提供异常安全。在头文件 memory 中定义。
auto_ptr
auto_ptr
auto_ptr
ap1 = ap2;//将所有权 ap2 转给 ap1。删除 ap1 指向的对象并且使 ap1指向 ap2 指向的对象,使 ap2 成为未绑定的
~ap //析构函数。删除 ap 指向的对象
*ap //返回对 ap 所绑定的对象的引用
ap-> //返回 ap 保存的指针
ap.reset(p);//如果 p 与 ap 的值不同,则删除 ap 指向的对象并且将 ap绑定到 p
ap.release();//返回 ap 所保存的指针并且使 ap 成为未绑定的
ap.get();// 返回 ap 保存的指针
· auto_ptr 只能用于管理从 new 返回的一个对象,它不能管理动态分配的数组.
auto_ptr 对象的复制和赋值是破坏性操作,基础对象的所有权从原来的auto_ptr 对象转给副本,原来的 auto_ptr 对象重置为未绑定状态。因此,赋值的左右操作数必须都是可修改的左值。除了将所有权从右操作数转给左操作数之外,赋值还删除左操作数原来指向的对象。
· 不能将 auto_ptrs 存储在标准库容器类型中。标准库的容器类要求在复制或赋值之后两个对象相等。因为如我们所见,当 auto_ptr 被复制或赋值的时候,有不寻常的行为。
void f() {
auto_ptr
// code that throws an exception that is not caught inside f
}//编译器保证在展开栈越过 f 之前运行 ap 的析构函数。
// error: constructor that takes a pointer is explicit and can't be used implicitly
auto_ptr
auto_ptr
· auto_ptr类型没有定义到可用作条件的类型的转换
if (p_auto) // error: cannot use an auto_ptr as a condition
if (p_auto.get()) //OK
· 应该只用 get 询问 auto_ptr 对象或者使用返回的指针值,不能用 get 作为创建其他 auto_ptr 对象的实参。
使用 get 成员初始化其他 auto_ptr 对象违反 auto_ptr 类设计原则:在任意时刻只有一个 auto_ptrs 对象保存给定指针,如果两个 auto_ptrs 对象保存相同的指针,该指针就会被 delete 两次。
· p_auto = new int(1024); // error: cannot assign a pointer to an auto_ptr
p_auto.reset(new int(1024)); //不能直接将一个地址(或者其他指针)赋给 auto_ptr 对象,必须使用reset。
要复位 auto_ptr 对象,可以将 0 传给 reset 函数。
调用 auto_ptr 对象的 reset 函数时,在将 auto_ptr 对象绑定到其他对象之前,会删除 auto_ptr 对象所指向的对象(如果存在)。但是,正如自身赋值是没有效果的一样,如果调用该 auto_ptr 对象已经保存的同一指针的 reset 函数,也没有效果,不会删除对象。
· 使用auto_ptr要注意
1)不要使用 auto_ptr 对象保存指向静态分配对象的指针
2)永远不要使用两个 auto_ptrs 对象指向同一对象。如用同一指针来初始化或者 reset 两个不同的 auto_ptr 对象,或使用一个 auto_ptr 对象的 get函数的结果来初始化或者 reset 另一个 auto_ptr 对象。
3)不要使用 auto_ptr 对象保存指向动态分配数组的指针。当 auto_ptr对象被删除的时候,它只释放一个对象——它使用 delete 操作符,而不用delete [] 。
4)不要将 auto_ptr 对象存储在容器中
· 定义异常说明
void recoup(int) throw(runtime_error); //指出recoup函数如果抛出一个异常,该异常将是 runtime_error 对象,或者是由 runtime_error 派生的类型的异常。
void no_problem() throw(); // 空说明列表指出函数不抛出任何异常
在编译的时候,编译器不能也不会试图验证异常说明,不可能在编译时知道程序是否抛出异常以及会抛出哪些异常,只有在运行时才能检测是否违反函数异常说明。
如果函数抛出了没有在其异常说明中列出的异常,就调用标准库函数unexpected。默认情况下,unexpected 函数调用 terminate 函数,terminate函数一般会终止程序。
在 const 成员函数声明中,异常说明跟在 const 限定符之后。
确定函数将不抛出任何异常,对函数的用户和编译器都有所帮助:知道函数不抛出异常会简化编写调用该函数的异常安全的代码的工作,我们可以知道在调用函数时不必担心异常,而且,如果编译器知道不会抛出异常,它就可以执行被可能抛出异常的代码所抑制的优化。
· 异常说明与虚函数
基类中虚函数的异常说明,可以与派生类中对应虚函数的异常说明不同。但是,派生类虚函数的异常说明必须与对应基类虚函数的异常说明同样严格,或者比后者更受限。
这个限制保证,当使用指向基类类型的指针调用派生类虚函数的时候,派生类的异常说明不会增加新的可抛出异常。
class Base {
public:
virtual double f1(double) throw ();
virtual int f2(int) throw (std::logic_error);
virtual std::string f3() throw
(std::logic_error, std::runtime_error);
};
class Derived : public Base {
public:
// error: exception specification is less restrictive than Base::f1's
double f1(double) throw (std::underflow_error);
// ok: same exception specification as Base::f2
int f2(int) throw (std::logic_error);
// ok: Derived f3 is more restrictive
std::string f3() throw ();
};
//通过派生类抛出的异常限制为由基类所列出的那些,在编写代码时就可以知道必须处理哪些异常。
//在确定可能需要捕获什么异常的时候compute 函数使用基类中的异常说明
void compute(Base *pb) throw(){
try {
// may throw exception of type std::logic_error
// or std::runtime_error
pb->f3();
} catch (const logic_error &le) { /* ... */ }
catch (const runtime_error &re) { /* ... */ }
}
· 函数指针的异常说明
void (*pf)(int) throw(runtime_error); //pf指向接受int值的函数,该函数返回void对象,该函数只能抛出runtime_error类型的异常. 在用另一指针初始化带异常说明的函数的指针,或者将后者赋值给函数地址的时候,两个指针的异常说明不必相同,但是,源指针的异常说明必须至少与目标指针的一样严格。
void recoup(int) throw(runtime_error);
// ok: recoup is as restrictive as pf1
void (*pf1)(int) throw(runtime_error) = recoup;
// ok: recoup is more restrictive than pf2
void (*pf2)(int) throw(runtime_error, logic_error) = recoup;
// error: recoup is less restrictive than pf3
void (*pf3)(int) throw() = recoup;
// ok: recoup is more restrictive than pf4
void (*pf4)(int) = recoup;
17.2 命名空间命名空间可以在全局作用域或其他作用域内部定义,但不能在函数或类内部定义。命名空间作用域不能以分号结束。既可以定义新的命名空间,也可以添加到现在命名空间中。接口和实现的分离
// ---- Sales_item.h ----
namespace cplusplus_primer {
class Sales_item { /* ... */};
Sales_item operator+(const Sales_item&,
const Sales_item&);
// declarations for remaining functions in the Sales_item interface
}
// ---- Query.h ----
namespace cplusplus_primer {
class Query {
public:
Query(const std::string&);
std::ostream &display(std::ostream&) const;
// ...
};
class Query_base { /* ... */};
}
// ---- Sales_item.cc ----
#include "Sales_item.h"
namespace cplusplus_primer {
// definitions for Sales_item members and overloaded operators
}
// ---- Query.cc ----
#include "Query.h"
namespace cplusplus_primer {
// definitions for Query members and related functions
}
// ---- user.cc ----
// defines the cplusplus_primer::Sales_item class
#include "Sales_item.h"
int main() {
// ...
cplusplus_primer::Sales_item trans1, trans2;
// ...
return 0;
}
全局命名空间(在任意类、函数或命名空间外) ::member_name 引用全局命名空间的成员。
· 未命名的命名空间可以在给定文件中不连续,但不能跨越文件,每个文件有自己的未命名的命名空间。未命名的命名空间用于声明局部于文件的实体。在未命名的命名空间中定义的变量在程序开始时创建,在程序结束之前一直存在。
· 未命名的命名空间中定义的名字可直接使用。不能使用作用域操作符来引用未命名的命名空间的成员。
int i; // global declaration for i
namespace {
int i;
}
// error: ambiguous defined globally and in an unnested, unnamed namespace
i = 10;
· 如果头文件定义了未命名的命名空间,那么,在每个包含该头文件的文件中,该命名空间中的名字将定义不同的局部实体。
· 未命名的命名空间取代文件中的静态声明。
· 除了在函数或其他作用域内部,头文件不应该包含 using 指示或 using 声明。头文件应该只定义作为其接口的一部分的名字,不要定义在其实现中使用的名字。
· 类作用域中的 using 声明局限于被定义类的基类中定义的名字。
· 命名空间别名。
namespace cplusplus_primer { /* ... */ };
namespace primer = cplusplus_primer;
· using 指示(using directive ) using namespace namespace_name; 使得特定命名空间所有名字可见
可以尝试用 using 指示编写程序,但在使用多个库的时候,这样做会重新引入名字冲突的所有问题。
using 指示有用的一种情况是,用在命名空间本身的实现文件中。
namespace blip {
int bi = 16, bj = 15, bk = 23;
// other declarations
}
int bj = 0; // ok: bj inside blip is hidden inside a namespace
void manip(){
using namespace blip;
++bi; // sets blip::bi to 17
++bj; // error: ambiguous
++::bj; // ok: sets global bj to 1
++blip::bj; // ok: sets blip::bj to 16
int bk = 97; // local bk hides blip::bk
++bk; // sets local bk to 98
}
· 屏蔽命名空间名字规则的一个重要例外:
接受类类型形参(或类类型指针及引用形参)的函数(包括重载操作符),以及与类本身定义在同一命名空间中的函数(包括重载操作符),在用类类型对象(或类类型的引用及指针)作为实参的时候是可见的。
std::string s;
// ok: calls std::getline(std::istream&, const std::string&)
getline(std::cin, s); //它在当前作用域,包含调用的作用域以及定义 cin的类型和 string 类型的命名空间中查找匹配的函数。因此,它在命名空间std 中查找并找到由 string 类型定义的 getline 函数。
std::string s;
cin >> s;
//如果没有这个例外
using std::operator>>; // need to allow cin >> s
std::operator>>(std::cin, s); // ok: explicitly use std::>>
· 隐式友元声明与命名空间:如果不存在可见的声明,那么,友元声明具有将该函数或类的声明放入外围作用域的效果。如果类在命名空间内部定义,则没有另外声明的友元函数在同一命名空间中声明。
namespace A {
class C {
friend void f(const C&); // makes f a member of namespace A
};
}
//因为该友元接受类类型实参并与类隐式声明在同一命名空间中,所以使用它时可以无须使用显式命名空间限定符:
// f2 defined at global scope
void f2() {
A::C cobj;
f(cobj); // calls A::f
}
· 命名空间对函数匹配有两个影响。一个影响是明显的:using 声明或 using指示可以将函数加到候选集合。另一个则是如果该函数的形参有类类型则在该类类型(及定义其基类)的命名空间中查找,将匹配的函数加入候选.
namespace NS {
class Item_base { /* ... */ };
void display(const Item_base&) { }
}
// Bulk_item's base class is declared in namespace NS
class Bulk_item : public NS::Item_base { };
int main() {
Bulk_item book1;
display(book1);
return 0;
}
· 如果命名空间内部的函数是重载的,那么,该函数名字的 using 声明声明了所有具有该名字的函数。如果 using 声明在已经有同名且带相同形参表的函数的作用域中引入函数,则 using 声明出错,否则,using 定义给定名字的另一重载实例,效果是增大候选函数集合。
· 命名空间与模板:模板的显式特化必须在定义通用模板的命名空间中声明.
有两种定义特化的方式:一种是重新打开命名空间并加入特化的定义,可以这样做是因为命名空间定义是不连续的;或者使用由命名空间名字限定的模板名定义特化。
17.3. 多重继承与虚继承
· 构造函数初始化式只能控制用于初始化基类的值,不能控制基类的构造次序。基类构造函数按照基类构造函数在类派生列表中的出现次序调用。
· 总是按构造函数运行的逆序调用析构函数。
· 派生类的指针或引用可以转换为其任意基类(直接或间接)的指针或引用。但多重继承情况下,遇到二义性转换的可能性更大。
· 像单继承一样,用基类的指针或引用只能访问基类中定义(或继承)的成员,不能访问派生类中引入的成员或从其他基类继承的成员。
· 多个基类含有同名函数,派生类对象调用该函数时可能导致二义性。即使两个继承的函数有不同的形参表,也会产生错误。类似地,即使函数在一个基类中是私有的而在另一个基类中是公用或受保护的,也是错误的。最后,如果在其中一个基类的基类中定义了该函数而直接基类中没有定义,调用仍是错误的。
因为首先发生名字查找。然后编译器才确定所找到的声明是否合法。
· 每个 IO 库类都继承了一个共同的抽象基类,那个抽象基类管理流的条件状态并保存流所读写的缓冲区。如果 IO 类型使用常规继承,则每个iostream 对象可能包含两个 ios 子对象,通过使用虚继承解决这类问题。
虚继承是一种机制,类通过虚继承指出它希望共享其虚基类的状态。在虚继承下,对给定虚基类,无论该类在派生层次中作为虚基类出现多少次,只继承一个共享的基类子对象。
class istream : public virtual ios { ... };
class ostream : virtual public ios { ... };
// iostream inherits only one copy of its ios base class
class iostream: public istream, public ostream { ... };
· 即使基类是虚基类,也照常可以通过基类类型的指针或引用操纵派生类的对象。
· 特定派生类实例的优先级高于共享虚基类实例
//从 VMI 类内部可以限定地访问哪些继承成员?哪些继承成员需要限定?
class Base {
public:
bar(int);
protected:
int ival;
};
class Derived1 : virtual public Base {
public:
bar(char);
foo(char);
protected:
char cval;
};
class Derived2 : virtual public Base {
public:
foo(int);
protected:
int ival;
char cval;
};
class VMI : public Derived1, public Derived2 { };
//bar和ival不用限定,且特定派生类实例的优先级高于共享虚基类实例
//不加限定访问的分别是Derived1::bar和Derived2::ival
//foo需要限定,不然有二义性
· 派生类对虚基类的初始化。如果使用常规规则,就可能会多次初始化虚基类。
在虚派生中,由最低层派生类的构造函数初始化虚基类。
总是先调用虚基类的构造函数,再调用非虚基类的构造函数,分别都按声明的次序。
Bear::Bear(std::string name, bool onExhibit):
ZooAnimal(name, onExhibit, "Bear") { }
Raccoon::Raccoon(std::string name, bool onExhibit)
: ZooAnimal(name, onExhibit, "Raccoon") { }
Panda::Panda(std::string name, bool onExhibit) //Panda虚派生自Bear和Raccoon
: ZooAnimal(name, onExhibit, "Panda"), //首先使用构造函数初始化列表中指定的初始化式构造ZooAnimal部分
Bear(name, onExhibit), //接下来,构造 Bear部分。忽略Bear的用于ZooAnimal构造函数初始化列表的初始化式。
Raccoon(name, onExhibit),//然后,构造 Raccoon部分,再次忽略ZooAnimal初始化式。
Endangered(Endangered::critical),
sleeping_flag(false) { } //最后,构造 Panda 部分。
class TeddyBear : public BookCharacter, public Bear, public virtual ToyAnimal { /* ... */ };
要创建TeddyBear对象,调用构造函数的次序为ZooAnimal(); ToyAnimal();Character();BookCharacter(); Bear();TeddyBear(); 在这里,由最低层派生类TeddyBear指定用于 ZooAnimal和 ToyAnimal的初始化式
在合成复制构造函数中使用同样的构造次序,在合成赋值操作符中也是按这个次序给基类赋值。保证调用基类析构函数的次序与构造函数的调用次序相反。
第十八章 特殊工具与技术
18.1. 优化内存分配
· new 基于每个对象分配内存的事实可能会对某些类强加不可接受的运行时开销,这样的类可能需要使用用户级的类类型对象分配能够更快一些。这样的类使用的通用策略是,预先分配用于创建新对象的内存,需要时在预先分配的内存中构造每个新对象。如vector
· 分配原始内存时,必须在该内存中构造对象;对未构造的内存中的对象进行赋值而不是初始化,其行为是未定义的。对许多类而言,这样做引起运行时崩溃。赋值涉及删除现存对象,如果没有现存对象,赋值操作符中的动作就会有灾难性效果。
· C++ 提供下面两种方法分配和释放未构造的原始内存。
1)allocator 类,它提供可感知类型的内存分配。这个类支持一个抽象接口,以分配内存并随后使用该内存保存对象。
2)标准库中的 operator new 和 operator delete,它们分配和释放需要大小的原始的、未类型化的内存。
· C++ 还提供不同的方法在原始内存中构造和撤销对象。
1)allocator 类定义了名为 construct 和 destroy 的成员。construct成员在未构造内存中初始化对象,destroy 成员在对象上运行适当的析构函数。
2) 定位 new 表达式接受指向未构造内存的指针,并在该空间中初始化一个对象或一个数组。
3)可以直接调用对象的析构函数来撤销对象。运行析构函数并不释放对象所在的内存。
4)算法 uninitialized_fill 和 uninitialized_copy 像 fill 和 copy算法一样执行,除了它们的目的地构造对象而不是给对象赋值之外。
· allocator 类是一个模板,allocator 类将内存分配和对象构造分开。当allocator 对象分配内存的时候,它分配适当大小并排列成保存给定类型对象的空间。但是,它分配的内存是未构造的,allocator 的用户必须分别construct 和 destroy 放置在该内存中的对象。
allocator
a.allocate(n)//分配原始的未构造内存以保存 T 类型的 n 个对象
a.deallocate(p, n)//释放内存,在名为 p 的 T* 指针中包含的地址处保存 T 类型的 n 个对象。运行调用 deallocate 之前在该内存中构造的任意对象的destroy 是用户的责任
a.construct(p, t)//在 T* 指针 p 所指内存中构造一个新元素。运行 T 类型的复制构造函数用 t 初始化该对象
a.destroy(p)//运行 T* 指针 p 所指对象的析构函数
uninitialized_copy(b, e, b2)//从迭代器 b 和 e 指出的输入范围将元素复制到从迭代器 b2 开始的未构造的原始内存中。该函数在目的地构造元素,而不是给它们赋值。假定由 b2 指出的目的地足以保存输入范围中元素的副本
uninitialized_fill(b, e, t)//将由迭代器 b 和 e 指出的范围中的对象初始化为 t 的副本。假定该范围是未构造的原始内存。使用复制构造函数构造对象
uninitialized_fill_n(b, e, t, n)//将由迭代器 b 和 e 指出的范围中至多 n个对象初始化为 t 的副本。假定范围至少为 n 个元素大小。使用复制构造函数构造对象
// pseudo-implementation of memory allocation strategy for a vector-like class
template
public:
Vector(): elements(0), first_free(0), end(0) { }
void push_back(const T&);
// ...
private:
static std::allocator
void reallocate(); // get more space and copy existing elements
T* elements; // pointer to first element in the array
T* first_free; // pointer to first free element in the array
T* end; // pointer to one past the end of the array
// ...
};
template
// are we out of space?
if (first_free == end)
reallocate(); // gets more space and copies existing elements to it
alloc.construct(first_free, t); // new (first_free) T(t);
++first_free;
}
template
// compute size of current array and allocate space for twice as many elements
std::ptrdiff_t size = first_free - elements;
std::ptrdiff_t newcapacity = 2 * max(size, 1);
// allocate space to hold newcapacity number of elements of type T
T* newelements = alloc.allocate(newcapacity); //T* newelements = static_cast
// construct copies of the existing elements in the new space
uninitialized_copy(elements, first_free, newelements);
// destroy the old elements in reverse order
for (T *p = first_free; p != elements; /* empty */ )
alloc.destroy(—p); // p->~T();
// deallocate cannot be called on a 0 pointer
if (elements) //传给 deallocate 一个零指针是不合法的。
// return the memory that held the elements
alloc.deallocate(elements, end - elements); // operator delete[](elements);
// make our data structure point to the new elements
elements = newelements;
first_free = elements + size;
end = elements + newcapacity;
}
· new表达式调用名为 operator new的标准库函数,分配足够大的原始的未类型化的内存,以保存指定类型的一个对象;接下来,运行该类型的一个构造函数,用指定初始化式构造对象;最后,返回指向新分配并构造的对象的指针。
delete表达式对指向的对象运行适当的析构函数;然后,通过调用名为operator delete 的标准库函数释放该对象所用内存。
调用 operator delete 函数不会运行析构函数,它只释放指定的内存
· 标准库函数operator new和operator delete是 allocator 的 allocate和deallocate成员的低级版本,它们都分配但不初始化内存.
void *operator new(size_t); // allocate an object
void *operator new[](size_t); // allocate an array
void *operator delete(void*); // free an object
void *operator delete[](void*); // free an array
· allocator 的成员 construct 和 destroy 也有两个低级选择
定位 new 表达式在已分配的原始内存中初始化一个对象,它不分配内存。比construct 更灵活,它可以使用任何构造函数。construct 函数总是使用复制构造函数。
使用析构函数的显式调用作为调用 destroy 函数的低级选择
new (place_address) type
new (place_address) type (initializer-list)
allocator
string *sp = alloc.allocate(2); // allocate space to hold 2 strings
// two ways to construct a string from a pair of iterators
new (sp) string(b, e); // construct directly in place
alloc.construct(sp + 1, string(b, e)); // build and copy a temporary
· 相对都使用低级版本而言,使用allocate类更好,因为operator new分配空间时要对指针强制类型转换,而且allocate类提供可感知类型的内存管理更安全灵活,但也要注意 定位new表达式比construct要灵活。
· 编译器看到类类型的 new 或 delete 表达式的时候,它查看该类是否有operator new 或 operator delete 成员,如果类定义(或继承)了自己的成员 new 和 delete 函数,则使用那些函数为对象分配和释放内存;否则,调用这些函数的标准库版本。
· 成员 new 和 delete 函数:如果类定义了这两个成员中的一个,它也应该定义另一个。
这些函数隐式地为静态static函数.因为它们要么在构造对象之前使用operator new,要么在撤销对象之后使用(operator delete),
void* operator new(size_t); //类成员 operator new必须返回类型void* 并接受 size_t 类型的形参。用以字节计算的分配内存量初始化函数的size_t 形参。
void operator delete(void*);//必须具有返回类型void。它可以定义为接受单个void*类型形参,也可以定义为接受两个形参
void operator delete(void*,size_t);//void*可以是空指针。若提供了size_t 形参,就由编译器用第一个形参所指对象的字节大小自动初始化size_t
当基类有virtual析构函数,size_t则传给 operator delete 的大小将根据被删除指针所指对象的动态类型而变化.
类成员 operator new[] 必须具有返回类型 void*,并且接受的第一个形参类型为 size_t。用表示存储特定类型给定数目元素的数组的字节数值自动初始化操作符的 size_t 形参。
成员操作符 operator delete[] 必须具有返回类型 void,并且第一个形参为 void* 类型。用表示数组存储起始位置的值自动初始化操作符的 void*形参。也可以有两个形参,第二个形参为 size_t。如果提供了附加形参,由编译器用数组所需存储量的字节数自动初始化这个形参。
· 覆盖类特定的内存分配: 如果类定义了自己的成员 new 和 delete,类的用户就可以通过使用全局作用域确定操作符,用::new 或 ::delete 表达式使用全局的库函数。
如果用 new 表达式调用全局 operator new 函数分配内存,则 delete 表达式也应该调用全局 operator delete 函数。
· 一个内存分配器基类
改进内置库的 new 和 delete 函数。一个通用策略是预先分配一场原始内存来保存未构造的对象,创建新元素的时候,可以在一个预先分配的对象中构造;释放元素的时候,将它们放回预先分配对象的块中,而不是将内存实际返还给系统。这种策略常被称为维持一个自由列表freelist。可以将自由列表实现为已分配但未构造的对象的链表。
template
class QueueItem: public CachedObj< QueueItem
// remainder of class declaration and all member definitions unchanged
};
template
public:
void *operator new(std::size_t);
void operator delete(void *, std::size_t);
virtual ~CachedObj() { }
protected:
T *next;
private:
static void add_to_freelist(T*);
static std::allocator
static T *freeStore;
static const std::size_t chunk;
};
template
void *CachedObj
{
// new should only be asked to build a T, not an object
// derived from T; check that right size is requested
if (sz != sizeof(T))
throw std::runtime_error
("CachedObj: wrong size object in operator new");
if (!freeStore) {
// the list is empty: grab a new chunk of memory
// allocate allocates chunk number of objects of type T
T * array = alloc_mem.allocate(chunk);
// now set the next pointers in each object in the allocated memory
for (size_t i = 0; i != chunk; ++i)
add_to_freelist(&array[i]);
}
T *p = freeStore;
freeStore = freeStore->CachedObj
return p; // constructor of T will construct the T part of the object
}
template
void CachedObj
{
if (p != 0)
// put the "deleted" object back at head of freelist
add_to_freelist(static_cast
}
template
void CachedObj
{
p->CachedObj
freeStore = p;
}
template
template
template
QueueItem
CachedObj 只能用于不包含在继承层次中类型。与成员 new 和 delete 操作不同,CachedObj 类没有办法根据对象的实际类型分配不同大小的对象:它的自由列表保存单一大小的对象。因此,它只能用于不作基类使用的类,如 QueueItem类。
18.2. 运行时类型识别RTTI (Run-time Type Identification): 程序能够使用基类的指针或引用来检索这些指针或引用所指对象的实际派生类型。
· 通过下面两个操作符提供 RTTI: 对于带虚函数的类,在运行时执行 RTTI 操作符,但对于其他类型,在编译时计算 RTTI 操作符。
1) typeid 操作符,返回指针或引用所指对象的实际类型。
2) dynamic_cast 操作符,将基类类型的指针或引用安全地转换为派生类型的指针或引用。
· dynamic_cast 操作符: 可将基类类型对象的引用或指针转换为同一继承层次中其他类型的引用或指针。
与其他强制类型转换不同,dynamic_cast 涉及运行时类型检查。如果绑定到引用或指针的对象不是目标类型的对象,则 dynamic_cast 失败。如果转换到指针类型的 dynamic_cast 失败,则 dynamic_cast 的结果是 0 值;如果转换到引用类型的 dynamic_cast 失败,则抛出一个 bad_cast 类型的异常。
//假定 Base 是至少带一个虚函数的类,Derived 类派生于 Base 类。basePtr是指向 Base的指针
if (Derived *derivedPtr = dynamic_cast
{
// use the Derived object to which derivedPtr points
} else { // BasePtr points at a Base object
// use the Base object to which basePtr points
}
因为不存在空引用,所以不可能对引用使用用于指针强制类型转换的检查策略,相反,当转换失败的时候,它抛出一个 std::bad_cast 异常,该异常在库头文件typeinfo 中定义。
void f(const Base &b){
try {
const Derived &d = dynamic_cast
// use the Derived object to which b referred
} catch (std::bad_cast) {
// handle the fact that the cast failed
}
}
· dynamic_cast失败条件:如果运行时实际绑定到引用或指针的对象不是目标类型的对象(或其派生类的对象),则dynamic_cast失败。
· typeid 操作符:如果操作数是定义了至少一个虚函数的类类型,则在运行时计算类型
typeid(e) //e 是任意表达式或者是类型名
//结果为std::type_info& 在头文件typeinfo中
typeid 最常见的用途是比较两个表达式的类型,或者将表达式的类型与特定类型相比较
Base *bp;
Derived *dp;
// compare type at run time of two objects
if (typeid(*bp) == typeid(*dp)) {
// bp and dp point to objects of the same type
}
// test whether run time type is a specific type
if (typeid(*bp) == typeid(Derived)) {
// bp actually points to a Derived
}
· 如果指针 p 的值是 0,那么,如果 p 的类型是带虚函数的类型,则typeid(*p) 抛出一个 bad_typeid 异常;如果 p 的类型没有定义任何虚函数,则结果与 p 的值是不相关的。
· RTTI 的使用
//比较基类和派生,3个类型就要9个操作符重载,4个类型要16个,太多。。。
bool operator==(const Base&, const Base&)
bool operator==(const Derived&, const Derived&)
bool operator==(const Derived&, const Base&);
bool operator==(const Base&, const Derived&);
//考虑用虚函数解决时,无法访问派生类特有成员
//使用RTTI dynamic_cast来访问运行时动态绑定的派生类私有成员
class Base {
friend bool operator==(const Base&, const Base&);
public:
// interface members for Base
protected:
virtual bool equal(const Base&) const;
// data and other implementation members of Base
};
class Derived: public Base {
friend bool operator==(const Base&, const Base&);
public:
// other interface members for Derived
private:
bool equal(const Base&) const;
// data and other implementation members of Derived
};
bool operator==(const Base &lhs, const Base &rhs)
{
// returns false if typeids are different otherwise
// returns lhs.equal(rhs)
return typeid(lhs) == typeid(rhs) && lhs.equal(rhs);
}
bool Derived::equal(const Base &rhs) const
{
if (const Derived *dp
= dynamic_cast
// do work to compare two Derived objects and return result
} else
return false;
}
bool Base::equal(const Base &rhs) const
{
// do whatever is required to compare to Base objects
}
· type_info 类:确切定义随编译器而变化,但是,标准保证所有的实现将至少提供以下操作
t1 == t2 如果两个对象 t1 和 t2 类型相同,就返回 true;否则,返回 false
t1 != t2 如果两个对象 t1 和 t2 类型不同,就返回 true;否则,返回 false
t.name() 返回 C 风格字符串,这是类型名字的可显示版本。类型名字用系统相关的方法产生
t1.before(t2) 返回指出 t1 是否出现在 t2 之前的 bool 值。before 强制的次序与编译器有关
· 默认构造函数和复制构造函数以及赋值操作符都定义为 private,所以不能定义或复制 type_info 类型的对象。程序中创建 type_info 对象的唯一方法是使用 typeid 操作符。
18.3. 类成员的指针:只应用于类的非 static 成员
class Screen {
public:
typedef std::string::size_type index;
char get() const;
char get(index ht, index wd) const;
private:
std::string contents;
index cursor;
index height, width;
};
//定义数据成员的指针
Screen::index Screen::*pindex = &Screen::width;
//定义成员函数的指针:所属类的类型,返回类型,函数形参的类型和数目,包括成员是否为 const
char (Screen::*pmf2)(Screen::index, Screen::index) const = &Screen::get;
//为成员指针使用类型别名
typedef
char (Screen::*Action)(Screen::index, Screen::index) const;
Action get = &Screen::get;
//使用类成员指针
//成员指针解引用操作符(.*)从对象或引用获取成员。
//成员指针箭头操作符(->*)通过对象的指针获取成员。
// pmf points to the Screen get member that takes no arguments
char (Screen::*pmf)() const = &Screen::get;
Screen myScreen;
char c1 = myScreen.get(); // call get on myScreen
char c2 = (myScreen.*pmf)(); // equivalent call to get
Screen *pScreen = &myScreen;
c1 = pScreen->get(); // call get on object to which pScreen points
c2 = (pScreen->*pmf)(); // equivalent call to get
char (Screen::*pmf2)(Screen::index, Screen::index) const = &Screen::get;
char c3 = myScreen.get(0,0); // call two-parameter version of get
char c4 = (myScreen.*pmf2)(0,0); // equivalent call to get
//使用数据成员的指针
Screen::index Screen::*pindex = &Screen::width;
Screen myScreen;
Screen::index ind1 = myScreen.width; // directly
Screen::index ind2 = myScreen.*pindex; // dereference to get width
Screen *pScreen;
ind1 = pScreen->width; // directly
ind2 = pScreen->*pindex; // dereference pindex to get width
//成员指针函数表
class Screen {
public:
Screen& home(); // cursor movement functions
Screen& forward();
Screen& back();
Screen& up();
Screen& down();
typedef Screen& (Screen::*Action)();
static Action Menu[]; // function table
enum Directions { HOME, FORWARD, BACK, UP, DOWN };
Screen& move(Directions);
};
Screen::Action Screen::Menu[] = { &Screen::home,
&Screen::forward,
&Screen::back,
&Screen::up,
&Screen::down,
};
Screen& Screen::move(Directions cm) {
// fetch the element in Menu indexed by cm
// run that member on behalf of this object
(this->*Menu[cm])();
return *this;
}
Screen myScreen;
myScreen.move(Screen::HOME); // invokes myScreen.home
myScreen.move(Screen::DOWN); // invokes myScreen.down
18.4. 嵌套类 Nested Classes:名字不是全局可见的
template
// interface functions to Queue are unchanged
private:
//只有 Queue 或 Queue 的友元可以访问 QueueItem 类型,
//所以可以用struct定义QueueItem使成员为 public 成员
struct QueueItem {
QueueItem(const Type &);
Type item; // value stored in this element
QueueItem *next; // pointer to next element in the Queue
};
QueueItem *head; // pointer to first element in Queue
QueueItem *tail; // pointer to last element in Queue
};//在类外部定义的嵌套类成员,构造函数template
因为 Queue 类是一个模板,它的成员也隐含地是模板。具体而言,嵌套类QueueItem 隐含地是一个类模板。
Queue 类的每次实例化用对应于 Type 的适当模板实参产生自己的 QueueItem类。QueueItem 类模板的实例化与外围 Queue 类模板的实例化之间的映射是一对一的。
· 在外围类外部定义嵌套类
template
private:
struct QueueItem; // 嵌套类的前向声明,在实际定义前,该类为不完全类型,不能创建对象,
//只能定义指针或引用,或声明(而非定义)用该类型作为形参或返回类型的函数
QueueItem *head; // pointer to first element in Queue
QueueItem *tail; // pointer to last element in Queue
};
template
struct Queue
QueueItem(const Type &t): item(t), next(0) { }
Type item; // value stored in this element
QueueItem *next; // pointer to next element in the Queue
};
· 嵌套类中的非静态函数具有隐含的 this 指针,指向嵌套类型的对象。同样,外围类中的非静态成员函数也具有 this 指针,它指向外围类型的对象。
嵌套类可以直接引用外围类的静态成员、类型名和枚举成员。
· 实例化外围类模板的时候,不会自动实例化类模板的嵌套类。像任何成员函数一样,只有当在需要完整类类型的情况下使用嵌套类本身的时候,才会实例化嵌套类。
· 处理类成员声明的时候,所用的任意名字必须在使用之前出现。当处理定义的时候,整个嵌套类和外围类均在作用域中。
class Outer {
public:
struct Inner {
// ok: reference to incomplete class
void process(const Outer&);
Inner2 val; // error: Outer::Inner2 not in scope
};
class Inner2 {
public:
// ok: Inner2::val used in definition
Inner2(int i = 0): val(i) { }
// ok: definition of process compiled after enclosing class is complete
void process(const Outer &out) { out.handle(); }
private:
int val;
};
void handle() const; // member of class Outer
};
18.5. 联合:节省空间的类
· 一个 union 对象可以有多个数据成员,但在任何时刻,只有一个成员可以有值。当将一个值赋给 union 对象的一个成员的时候,其他所有都变为未定义的。
每个 union 对象的大小在编译时固定的:它至少与 union 的最大数据成员一样大。
union 可以指定保护标记使成员成为公用的、私有的或受保护的。默认情况下,union 表现得像 struct
union 也可以定义成员函数,包括构造函数和析构函数。但是,union 不能作为基类使用,所以成员函数不能为虚数。
union 不能具有静态数据成员或引用成员,而且,union 不能具有定义了构造函数、析构函数或赋值操作符的类类型的成员
union illegal_members {
Screen s; // error: has constructor
static int is; // error: static member
int &rfi; // error: reference member
Screen *ps; // ok: ordinary built-in pointer type
};
TokenValue first_token = {'a'}; // 显式初始化只能为第一个成员提供初始化式。
TokenValue last_token; // uninitialized TokenValue object
TokenValue *pt = new TokenValue; // pointer to a TokenValue object
last_token.cval = 'z';
pt->ival = 42;
给 union 对象的某个数据成员一个值使得其他数据成员变为未定义的。通过错误的数据成员检索保存在 union 对象中的值,可能会导致程序崩溃或者其他不正确的程序行为。
避免通过错误成员访问 union 值的最佳办法是,定义一个单独的对象跟踪 union中存储了什么值。这个附加对象称为 union 的判别式discriminant。
· 匿名联合: 不能有私有成员或受保护成员,也不能定义成员函数
class Token {
public:
enum TokenKind {INT, CHAR, DBL};
TokenKind tok; //判别式,跟踪 union
union { // anonymous union
char cval;
int ival;
double dval;
};
};
Token token;
switch (token.tok) {
case Token::INT:
token.ival = 42; break; //匿名 union 的成员的名字出现在外围作用域中
case Token::CHAR:
token.cval = 'a'; break;
case Token::DBL:
token.dval = 3.14; break;
}
18.6. 局部类(Local Classes): 在函数体内部定义的类
· 局部类的所有成员(包括函数)必须完全定义在类定义体内部,因此,局部类远不如嵌套类有用。
不允许局部类声明 static 数据成员,没有办法定义它们。
· 局部类只能访问在外围作用域中定义的类型名、static 变量和枚举成员,不能使用定义该类的函数中的变量
int a, val;
void foo(int val)
{
static int si;
enum Loc { a = 1024, b };
// Bar is local to foo
class Bar {
public:
Loc locVal; // ok: uses local type name
int barVal;
void fooBar(Loc l = a) // ok: default argument is Loc::a
{
barVal = val; // error: val is local to foo
barVal = ::val; // ok: uses global object
barVal = si; // ok: uses static local object
locVal = b; // ok: uses enumerator
}
};
// ...
}
可以将一个类嵌套在局部类内部。这种情况下,嵌套类定义可以出现在局部类定义体之外,但是,嵌套类必须在定义局部类的同一作用域中定义。照常,嵌套类的名字必须用外围类的名字进行限定,并且嵌套类的声明必须出现在局部类的定义中
void foo()
{
class Bar {
public:
// ...
class Nested; // declares class Nested
};
// definition of Nested
class Bar::Nested {
// ...
};
}
//嵌套在局部类中的类本身是一个带有所有附加限制的局部类。
//嵌套类的所有成员必须在嵌套类本身定义体内部定义
18.7. 固有的不可移植的特征 (Inherently Nonportable Features)
算术类型的大小随机器不同而变化,位域和 volatile 限定符,链接指示(它使得可以链接到用其他语言编写的程序)
· 18.7.1. 位域 (Bit-fields)
保存特定的位数。当程序需要将二进制数据传递给另一程序或硬件设备的时候,通常使用位域。
位域在内存中的布局是机器相关的。
位域必须是整型数据类型,可以是 signed 或 unsigned。通过在成员名后面接一个冒号以及指定位数的常量表达式,指出成员是一个位域
地址操作符(&)不能应用于位域,所以不可能有引用类位域的指针,位域也不能是类的静态成员.
typedef unsigned int Bit;
class File {
Bit mode: 2;
Bit modified: 1;
Bit prot_owner: 3;
Bit prot_group: 3;
Bit prot_world: 3;
public: //通常使用内置按位操作符操纵超过一位的位域
enum { READ = 01, WRITE = 02 }; // File modes
inline int isRead() { return mode & READ; }
inline int isWrite() { return mode & WRITE; }
void setRead() { mode |= READ;} // set the READ bit
};
(如果可能)将类定义体中按相邻次序定义的位域压缩在同一整数的相邻位,从而提供存储压缩。例如,在前面的声明中,5 个位域将存储在一个首先与位域 mode关联的 unsigned int 中。位是否压缩到整数以及如何压缩与机器有关.
通常最好将位域设为 unsigned 类型。存储在 signed 类型中的位域的行为由实现定义。
18.7.2. volatile 限定符
volatile 的确切含义与机器相关,只能通过阅读编译器文档来理解。使用volatile 的程序在移到新的机器或编译器时通常必须改变。
当可以用编译器的控制或检测之外的方式改变对象值的时候,应该将对象声明为volatile。关键字 volatile 是给编译器的指示,指出对这样的对象不应该执行优化。
类也可以将成员函数定义为 volatile,volatile 对象只能调用 volatile 成员函数。
可以声明 volatile 指针、指向 volatile 对象的指针,以及指向 volatile 对象的 volatile 指针:
像用 const 一样,只能将 volatile 对象的地址赋给指向 volatile 的指针,或者将指向 volatile 类型的指针复制给指向 volatile 的指针。只有当引用为volatile 时,我们才可以使用 volatile 对象对引用进行初始化。
合成的复制控制不适用于 volatile 对象,因为不能将 volatile 对象传递给普通引用或 const 引用。
如果类希望允许复制 volatile 对象,或者,类希望允许从 volatile 操作数或对volatile 操作数进行赋值,它必须定义自己的复制构造函数和/或赋值操作符版本
class Foo {
public:
Foo(const volatile Foo&); // copy from a volatile object
// assign from a volatile object to a non volatile objet
Foo& operator=(volatile const Foo&);
// assign from a volatile object to a volatile object
Foo& operator=(volatile const Foo&) volatile;
// remainder of class Foo
};
//通过将复制控制成员的形参定义为 const volatile 引用,我们可以从任何各类的 Foo 对象进行复制或赋值:普通 Foo 对象、const Foo 对象、volatile Foo对象或 const volatile Foo 对象。
18.7.3. 链接指示 extern "C" 指出任意非 C++ 函数所用的语言。
· 链接指示有两种形式:单个的或复合的。链接指示不能出现在类定义或函数定义的内部,它必须出现在函数的第一次声明上。
// illustrative linkage directives that might appear in the C++ header
// single statement linkage directive
extern "C" size_t strlen(const char *);
// compound statement linkage directive
extern "C" {
int strcmp(const char*, const char*);
char *strcat(char*, const char*);
}
// compound statement linkage directive
extern "C" {
#include
}
有时需要在 C 和 C++ 中编译同一源文件。当编译 C++ 时,自动定义预处理器名字 __cplusplus(两个下划线),所以,可以根据是否正在编译 C++ 有条件地包含代码。
#ifdef __cplusplus
// ok: we're compiling C++
extern "C"
#endif
int strcmp(const char*, const char*);
C 语言不支持函数重载,在一组重载函数中只能为一个 C 函数指定链接指示
// error: two extern "C" functions in set of overloaded functions
extern "C" void print(const char*);
extern "C" void print(int);
· extern "C" 函数和指针
C 函数的指针与 C++ 函数的指针具有不同的类型,不能将 C 函数的指针初始化或赋值为 C++ 函数的指针(反之亦然)。
void (*pf1)(int); // points to a C++ function
extern "C" void (*pf2)(int); // points to a C function
pf1 = pf2; // error: pf1 and pf2 have different types
因为链接指示应用于一个声明中的所有函数,所以必须使用类型别名,以便将 C 函数的指针传递给 C++ 函数
// FC is a pointer to C function
extern "C" typedef void FC(int);
// f2 is a C++ function with a parameter that is a pointer to a C function
void f2(FC *);
A.1
abort
accumulate
allocator
auto_ptr
back_inserter
bad_alloc
bad_cast
bind2nd
boolalpha
less_equal
logic_error
lower_bound
make_pair
dec
ends
negate
noboolalpha
noshowbase
noshowpoint
noskipws
not1
nounitbuf
nouppercase
nth_element
oct
ofstream
getline
hex
ifstream
inner_product
inserter
internal
priority_queue
ptrdiff_t
range_error
replace
replace_copy
stringstream
strlen
strncpy
terminate
set_difference
set_intersection
set_union
setfill
setprecision
setw
showbase
showpoint
size_t
skipws
A.2. 算法简介
A.2.1. 查找对象的算法
find(beg, end, val)
count(beg, end, val)
find_if(beg, end, unaryPred)
count_if(beg, end, unaryPred)
查找许多值中的一个的算法
find_first_of(beg1, end1, beg2, end2)
find_first_of(beg1, end1, beg2, end2, binaryPred)
find_end(beg1, end1, beg2, end2)
find_end(beg1, end1, beg2, end2, binaryPred)
查找子序列的算法
adjacent_find(beg, end)
adjacent_find(beg, end, binaryPred)
search(beg1, end1, beg2, end2)
search(beg1, end1, beg2, end2, binaryPred)
search_n(beg, end, count, val)
search_n(beg, end, count, val, binaryPred)
A.2.2. 其他只读算法
for_each(beg, end, f)
mismatch(beg1, end1, beg2)
mismatch(beg1, end1, beg2, binaryPred)
equal(beg1, end1, beg2)
equal(beg1, end1, beg2, binaryPred)
A.2.3. 二分查找算法
lower_bound(beg, end, val)
lower_bound(beg, end, val, comp)
upper_bound(beg, end, val)
upper_bound(beg, end, val, comp)
equal_range(beg, end, val)
equal_range(beg, end, val, comp)
binary_search(beg, end, val)
binary_search(beg, end, val, comp)
A.2.4. 写容器元素的算法
只写元素不读元素的算法
fill_n(dest, cnt, val)
generate_n(dest, cnt, Gen)
使用输入迭代器写元素的算法
copy(beg, end, dest)
transform(beg, end, dest, unaryOp)
transform(beg, end, beg2, dest, binaryOp)
replace_copy(beg, end, dest, old_val, new_val)
replace_copy_if(beg, end, dest, unaryPred, new_val)
merge(beg1, end1, beg2, end2, dest)
merge(beg1, end1, beg2, end2, dest, comp)
使用前向迭代器写元素的算法
swap(elem1, elem2)
iter_swap(iter1, iter2)
swap_ranges(beg1, end1, beg2)
fill(beg, end, val)
generate(beg, end, Gen)
replace(beg, end, old_val, new_val)
replace_if(beg, end, unaryPred, new_val)
使用双向迭代器写元素的算法
copy_backward(beg, end, dest)
inplace_merge(beg, mid, end)
inplace_merge(beg, mid, end, comp)
A.2.5. 划分与排序算法
划分算法:要求双向迭代器
stable_partition(beg, end, unaryPred)
partition(beg, end, unaryPred)
排序算法
sort(beg, end)
stable_sort(beg, end)
sort(beg, end, comp)
stable_sort(beg, end, comp)
partial_sort(beg, mid, end)
partial_sort(beg, mid, end, comp)
partial_sort_copy(beg, end, destBeg, destEnd)
partial_sort_copy(beg, end, destBeg, destEnd, comp)
nth_element(beg, nth, end)
nth_element(beg, nth, end, comp)
A.2.6. 通用重新排序操作
使用前向迭代器的重新排序算法
remove(beg, end, val)
remove_if(beg, end, unaryPred)
unique(beg, end)unique(beg, end, binaryPred)
rotate(beg, mid, end)
使用双向迭代器的重新排序算法
reverse(beg, end)
reverse_copy(beg, end, dest)
写至输出迭代器的重新排序算法
remove_copy(beg, end, dest, val)
remove_copy_if(beg, end, dest, unaryPred)
unique_copy(beg, end, dest)
unique_copy(beg, end, dest, binaryPred)
rotate_copy(beg, mid, end, dest)
random_shuffle(beg, end)
random_shuffle(beg, end, rand)
A.2.7. 排列算法
要求双向迭代器的排列算法
next_permutation(beg, end)
next_permutation(beg, end, comp)
prev_permutation(beg, end)
prev_permutation(beg, end, comp)
A.2.8. 有序序列的集合算法
要求输入迭代器集合算法
includes(beg, end, beg2, end2)
includes(beg, end, beg2, end2, comp)
set_union(beg, end, beg2, end2, dest)
set_union(beg, end, beg2, end2, dest, comp)
set_union(beg, end, beg2, end2, dest)
set_union(beg, end, beg2, end2, dest, comp)
set_difference(beg, end, beg2, end2, dest)
set_difference(beg, end, beg2, end2, dest, comp)
set_symmetric_difference(beg, end, beg2, end2, dest)
set_symmetric_difference(beg, end, beg2, end2, dest, comp)
A.2.9. 最大值和最小值
min(val1, val2)
min(val1, val2, comp)
max(val1, val2)
max(val1, val2, comp)
min_element(beg, end)min_element(beg, end, comp)max_element(beg, end)
max_element(beg, end, comp)
lexicographical_compare(beg1, end1, beg2, end2)
lexicographical_compare(beg1, end1, beg2, end2, comp)
A.2.10. 算术算法
accumulate(beg, end, init)accumulate(beg, end, init, BinaryOp)
inner_product(beg1, end1, beg2, init)
inner_product(beg1, end1, beg2, init, BinOp1, BinOp2)
partial_sum(beg, end, dest) partial_sum(beg, end, dest, BinaryOp)
adjacent_difference(beg, end, dest)
adjacent_difference(beg, end, dest, BinaryOp)
A.3. 再谈 IO 库
A.3.1. 格式状态
boolalpha 将真和假显示为字符串
x noboolalpha 将真和假显示为 1, 0
showbase 产生指出数的基数的前缀
x noshowbase 不产生记数基数前缀
showpoint 总是显示小数点
x noshowpoint 有小数部分才显示小数点
showpos 显示非负数中的 +
x noshowpos 不显示非负数中的 +
uppercase 在十六进制中打印 0X,科学记数法中打印 E
x nouppercase 在十六进制中打印 0x,科学记数法中打印 e
x dec 用十进制显示
hex 用十六进制显示
oct 用八进制显示
left 在值的右边增加填充字符
right 在值的左边增加填充字符
internal 在符号和值之间增加填充字符
fixed 用小数形式显示浮点数
scientific 用科学记数法显示浮点数
flush 刷新 ostream 缓冲区
ends 插入空字符,然后刷新 ostream 缓冲区
endl 插入换行符,然后刷新 ostream 缓冲区
unitbuf 在每个输出操作之后刷新缓冲区
x nounitbuf 恢复常规缓冲区刷新
x skipws 为输入操作符跳过空白
noskipws 不为输入操作符跳过空白
ws “吃掉”空白
注:带 x 的是默认流状态。
iomanip 中定义的操纵符
setfill(ch) 用 ch 填充空白
setprecision(n) 将浮点精度置为 n
setw(w) 读写 w 个字符的值
setbase(b) 按基数 b 输出整数
· 用 flags 操纵恢复格式状态
不带实参的 flags() 返回流的当前格式状态。返回值是名为 fmtflags 的标准库定义类型。
flags(arg) 接受一个实参并将流格式置为实参所指定的格式
void display(ostream& os)
{
// remember the current format state
ostream::fmtflags curr_fmt = os.flags();
// do output that uses manipulators that change the format state of os
os.flags(curr_fmt); // restore the original format state of os
}
A.3.3. 控制输出格式
控制布尔值和格式:boolalpha 取消:noboolalpha
指定整型值的基数 hex、oct 和 dec
指出输出的基数:需要打印八进制或十六进制值,可能应该也使用 showbase 操纵符 重置noshowbase
控制浮点值的格式:指定显示精度: 成员函数precision一个版本接受一个 int值并将精度设置为那个新值,它返回先前的精度值;另一个版本不接受实参并返回当前精度值。
setprecision 操纵符接受一个实参,用来设置精度。
控制记数法:scientific 操纵符将流变为使用科学记数法
fixed 操纵符将流为使用固定位数小数表
uppercase 操纵符控制科学记数法中的 e和16进制x的大小写
要将流恢复为浮点值的默认处理,必须把floatfield 的标准库定义值传给 unsetf 成员函数来取消 scientific 或fixed 所做的改变
// reset to default handling for notation
cout.unsetf(ostream::floatfield);
显示小数点:默认情况下,当浮点值的小数部分为 0 的时候,不显示小数点。showpoint 操纵符强制显示小数点。noshowpoint 操纵符恢复默认行为
填充输出: setw,指定下一个数值或字符串的最小间隔。
left,左对齐输出。 right,右对齐输出。输出默认为右对齐。
internal,控制负值的符号位置。internal 左对齐符号且右对齐值,用空格填充介于其间的空间
setfill,使我们能够指定填充输出时使用的另一个字符。默认情况下,值是空格。
A.3.4. 控制输入格式化
noskipws 操纵符导致输入操作符读(而不是跳过)空白(空格、制表符、换行符、进纸和回车)。要返回默认行为,应用 skipws 操纵符
A.3.6. 单字节操作
几个未格式化的操作一次一个字节地处理流,它们不忽略空白地读。例如,可以使用未格式化 IO 操作 get 和 put 一次读一个字符
char ch;
while (cin.get(ch)) //与noskipws相同的输入
cout.put(ch);
单字节低级 IO 操作:
is.get(ch) 将 istream is 的下一个字节放入 ch,返回 is
os.put(ch) 将字符 ch 放入 ostream,返回 os
is.get() 返回 is 的下一字节作为一个 int 值
is.putback(ch) 将字符 ch 放回 is,返回 is
is.unget() 将 is 退回一个字节,返回 is. 把已经从流读取出来的那个字符放回去.
is.peek() 将下一字节作为 int 值返回但不移出它. 返回输入流上下一字符的副本但不改变流
这些函数返回 int 值的原因是为了允许它们返回一个文件结束标记。允许给定字符集使用 char 范围的每一个值来表示实际字符,因此,该范围中没有额外值用来表示文件结束符。
相反,这些函数将字符转换为 unsigned char,然后将那个值提升为 int,因此,即使字符集有映射到负值的字符,从这些操作返回的值也将是一个正值. 通过将文件结束符作为负值返回,标准库保证文件结束符区别于任意合法字符值。为了不要求我们知道返回的实际值,头文件 iostream 定义了名为 EOF 的 const,可以使用它来测试 get 的返回值是否为文件结束符。实质上我们使用 int 对象来保存这些函数的返回值
A.3.7. 多字节操作:
is.get(sink, size, delim) 从 is 中读 size 个字节并将它们存储到 sink 所指向的字符数组中。读操作直到遇到 delim 字符,或已经读入了 size 个字节,或遇到文件结束符才结束。如果出现了 delim,就将它留在输入流上,不读入到sink 中。
is.getline(sink, size, delim) 与三个实参的 get 行为类似,但读并丢弃delim
is.read(sink, size)读 size 个字节到数组 sink。返回 is
is.gcount() 返回最后一个未格式化读操作从流 is 中读到的字节数 . 如果在调用 gcount 之前调用 peek、unget 或 putback,则返回值将是 0!
os.write(source, size) 将 size 个字从数组 source 写至 os。返回 os
is.ignore(size, delim) 读并忽略至多 size 个字符,直到遇到 delim,但不包括 delim。默认情况下,size 是 1 而 delim 是文件结束符
将 get 或其他返回 int 值的函数的返回值赋给 char 对象而不是 int 对象,是常见的错误,但编译器不检测这样的错误,相反,发生什么取决于机器和输入数据。例如,在将 char 实现为 unsigned char 的机器上,这是一个死循环:
char ch; // Using a char here invites disaster!
// return from cin.get is converted from int to char and then compared to an int
while ((ch = cin.get()) != EOF)
cout.put(ch);
A.3.8. 流的随机访问
为了支持随机访问,IO类型维持一个标记,该标记决定下一个读或写发生在哪里。IO 类型还提供两个函数:一个通过 seek 指定位置重新安置该标记,另一个 tell 我们标记的当前位置。标准库实际上定义了两对 seek 和 tell 函数, 一对由输入流使用,另一对由输出流使用。输入和输出版本由后缀 g 和 p 区分,g 版本指出正在“获取”(读)数据,p 函数指出正在“放置”(写)数据。
seekg 重新定位输入流中的标记
tellg 返回输入流中标记的当前位置
seekp 重新定位输出流中的标记
tellp 返回输出流中标记的当前位置
普通 iostream 对象一般不允许随机访问,该认为本节其余部分只能应用于fstream 和 sstream 类型
seek 函数有两个版本:一个移动到文件中的一个“绝对”地址,另一个移动到给定位置的字节偏移量处:
// set the indicated marker a fixed position within a file or string
seekg(new_position); // set read marker
seekp(new_position); // set write marker
// offset some distance from the indicated position
seekg(offset, dir); // set read marker
seekp(offset, dir); // set write marker
seek 实参的偏移量: inOut.seekg(0, fstream::beg); // reposition to start of the file
beg 流的开头
cur 流的当前位置
end 流的末尾
名为 pos_type 和 off_type 的类型分别表示文件位置和从该位置的偏移量。off_type 类型的值可以为正也可以为负,在文件中可以进行前向或后向 seek
// remember current write position in mark
ostringstream writeStr; // output stringstream
ostringstream::pos_type mark = writeStr.tellp();
// ...
if (cancelEntry)
// return to marked position
writeStr.seekp(mark);