(参考原书勘误信息)
中文版是按照出版社提供的特别版第1次印刷翻译的,后来又根据第15次印刷做了所有可能的更正(出版社在最后付印前做了许多工作),大部分更正已经反映在目前的中文译本中,但还是有些很长的程序段无法放入。
在中文翻译本交稿之后我整理了这个文件,在其中汇集了英文特别版第1次印刷之后的主要更正。中文本出版后,一位未具名的网友填入了对应的中文页码,并将它发到清华大学的BBS上,也发给我一份。我又仔细检查了其中的页码和其他信息,做了一些修改(包括将一些英文译为中文),现将它放在这里,供关心这些信息的人们参考。
【最新更新,2002年8月20日】这里链接的页中总结了中文本中发现的若干错误(包括原书错误和原书新更正)。
更正项形式说明:
1) s/xxxx/yyyy/ 表示将原来的 xxxx 替换为 yyyy;
2) (pg xx/yyLnn)表示英文原版第xx页和中文版第yy页第nn行,L-11表示倒数第11行。有些项目用其他方式说明。
3) (pg xx/yyLnn*)代表中文版相应的地方已经被改正过来了。
-------------------------------------------------------------------------------
注:由于中文版在交稿时已经尽可能更正了一些错误,而那些错误就没有包括在这里。因此这个勘误表不能完全包括英文原版影印书(高教出版社影印本中)的所有错误,如欲勘误该书,请见
http://www.research.att.com/~bs/3rd_errata.html
或者参考相关页面中的信息。
(pg 55/48L-11*) s/Entry& e =/const Entry& e =/
(pg 76/69*) 例子的更好版本(原来缺 #include < iostream>):
#include < limits>
#include < iostream>
int main()
{
std::cout << "largest
float == " << std::numeric_limits< float>::max()
<< ", char is signed == " << std::numeric_limits< char>::is_signed
<< '\n';
}
(pg 129/115) 6.2.6.2第一个例子加注释:
void* operator new(size_t);
// 为单个对象分配空间
void operator delete(void* p);
// 如果 (p) 则释放用 operator new() 分配的空间
void* operator new[](size_t);
// 为数组分配的空间
void operator delete[](void* p);
// 如果 (p) 则释放由 operator new[]() 分配的空间
(pg 193/172) 8.3.3.1第一个例子,在每个catch子句里加语句 "++Driver::no_of_errors;"
(pg 202/180) 中文180页第一个程序例子。改进为:
#ifdef __cplusplus // 仅为 C++ 编译器
(9.2.4)
namespace std { //
标准库定义在名字空间 std 里 (8.2.9)
extern "C" {
// stdio 函数具有 C 连接 (9.2.4)
#endif
/* ... */
int printf(const char*, ...);
/* ... */
#ifdef __cplusplus
}
}
// ...
using std::printf; // 使 printf 在全局名字空间里可用
// ...
#endif
(pg 251/224) 10.4.7最后的例子改为:
void g()
{
vector< Table> v(10);
// 不必 delete
vector< Table>* p = new vector< Table>(10);
// 用普通 delete 而不是 delete[]
delete p;
}
(Using a container, such as vector, is simpler than writing a new/delete
pair. Furthermore, vector provides exception safety (Appendix E).)
用一个容器,例如 vector,比自己写一对 new/delete 简单得多。进一步说,
vector 还提供了异常时安全性(附录E)。
(pg280/250) 第一个例子替换为:
class AE { /* ... */ }; // 不是 Y 的友元
namespace N {
class X { /* ... */ };
// Y 的友元
class Y {
friend class X;
friend class Z;
friend class AE;
};
class Z { /* ... */ };
// Y 的友元
}
(pg 288/257L-13*) s/return b;/return f;/
(pg 335/299L12*) s/const char[12]/const char v[12]/
(pg 346/308L-11L-12*) s/}/};/ 在第一个例子里出现两处
(pg 380/338L11*) s/delete p;/delete pe;/
(pg 422/374) 第15.6节改进的例子:
void Employee::operator delete(void* p, size_t s)
{
if (p) { // 仅在 p!=0 时销毁,见第 6.2.6、6.2.6.2
节
// 假定 `p' 指向通过 Employee::operator
new() 分配的 `s' 个字节的存储
// 释放它以便另行使用
}
}
(pg 433/384*) 有关 Input/Output 的头文件表:
<cctype> 字符分类函数 第20.4.2节
(pg 434/385) s/fabs(),// 第二个表格下面,去掉。
(pg 457/406) s/vector tmp/vector<...> tmp/ // 这里的...表示某个类型
最好将这个例子改为:
{
vector<...> tmp = v;
v.swap(tmp);
}
已保证 v 原来占用存储能立即释放。
(pg 485) s/value_type;/value_type,/
(pg 493/437) bitset 的构造函数的参数需要转换到 string:
bitset< 10> b4(string("1010101010"));
// 1010101010
bitset< 10> b5(string("10110111011110",4));
// 0111011110
bitset< 10> b6(string("10110111011110",2,8));
// 0011011101
bitset< 10> b7(string("n0g00d"));
// 抛出 invalid_argument
bitset< 10> b8 = string("n0g00d");
// 错误: 不存在 string 到 bitset 的转换
(pg 496/439页中间的例子中间部分) s/size_t/ptrdiff_t/ 两次
(pg 496/439)
在 c_array 的定义之后加:
“为了与数组的兼容性,我用有符号的 ptrdiff_t (16.1.2节)而不是无符号的
size_t 作为下标的类型。将 size_t 用于 c_array 的下标时可能导致微妙的岐义性。”
"For compatibility with arrays, I use the signed ptrdiff_t (16.1.2)
rather
than the unsigned size_t as the subscript type. Using size_t could
lead to
subtle ambiguities when using [] on a c_array."
(pg 524/464L8*) s/operator()(const Club&)/operator()(const Club&) const/
(pg 535/473L5*)
s/sort(off.begin(),off.end(),Person_lt)/off.sort(off,Person_lt)/
(pg 536/474L16*)
s/class located_in {/class located_in : public unary_function< Club,bool>/
(pg 536/474L25*) s/output_iterator/ostream_iterator/
(pg 538/475最下面) 将第二个实例换为:
对于调用 random_shuffle(b,e,r),将用序列中的元素个数作为参数去调用一个随机数生成器
r(b-e),该生成器必须产生位于范围 [0,b-e) 中的值。 如果 My_rand 是这样的一个生成器,我们就可以用如下方式去洗一叠扑克牌:
void f(deque< Card>& dc, My_rand& r)
{
random_shuffle(dc.begin(),dc.end(),r);
// ...
}
(pg 563/497中间) s/d<curr-c->begin()/d< -(curr-c->begin())/ 两处
(pg 596/526L11*) 在类 basic_string 里加入:
void clear(); // 删除所有字符
(pg 599/529L3L4*) 更正注释:
size_t strspn(const char* p, const char* q);
// number of char in p before a char not in q
// 在 p 中出现不属于 q 的字符之前的字符个数
size_t strcspn(const char* p, const char* q);
// number of char in p before a char in q
// 在 p 中出现属于 q 的字符之前的字符个数
(pg 600-601/529-530*) 扩充描述转换函数的一节(中译本已经更正):
在 < stdlib.h> 和 < cstdlib> 里,标准库为将表示数值的字符串转换为数值提供了
一些很有用的函数。例如:
double atof(const char* p); // 从
p[] 到 double (``alpha to
floating'')
double strtod(const char* p, char** end);
// 从 p[] 到 double (``string to double'')
int atoi(const char* p);
// 从 p[] 到 int,假定数基为 10
long atol(const char* p);
// 从 p[] 到 long,假定数基为 10
long strtol(const char* p, char** end, int b);
// 从 p[] 到 long,假定数基为 b
这些函数将忽略前导的空白,如果输入串并不表示数值则返回0。例如:atoi("seven")
的值为 0。
If end is non-zero in a call strtol(p,end,b), the position of the first
unread character in the input string is made available by assigning
it to
*end.
If b==0, a number is interpreted the way a C++ integer literal is (4.4.1);
for example, a 0x prefix means hexadecimal, 0 means octal, etc.
If is undefined what happens if atof(), atoi(), or atol() converts a
value
that cannot be represented as its respective return type. If the input
string for strtol() represents a number that cannot be represented
as a long
int or if the input string for strtod() represents a number that cannot
be
represented as a double, errno (16.1.2, 22.3) is set to ERANGE and
an
appropriately huge or tiny value is returned.
Except for the error handling, atof(s) is equivalent to strtod(s,0),
atoi(s)
is equivalent to int(strtol(s,0,10)), and atol(s) is equivalent to
strtol(s,0,10).
(pg 619/545)
在某处加入:“如果一个 get() 或者 getline() 函数没有从流中读到至少一个字符,就会调用
setstate(failbit)。这样,随后从这个流读入将失败(或者抛出一个异常(21.3.6节))。
(somewhere add "If a get() or getline() function doesn't read
and remove at least one character from the tream, setstate(failbit) is
called, so that subsequent reads from the stream will fail (or an exception
is thrown (21.3.6))." )
将该页第一个实例修改为(also modify the example to:):
void subtle_error()
{
char buf[256];
while (cin) {
cin.get(buf,256);
// read a line
cout << buf;
// print a line
// Oops: forgot to remove
'\en' from cin - the next get() will fail
}
}
(pg 621/547L15*) s/badbit/failbit/
(pg 623/548L-16*) 将异常处理器换为:
catch(ios_base::failure) {
// 可以:遇到文件结束
break;
}
(pg 632/556L16*) s/noskipws()/unsetf(ios_base::skipws)/
(pg 633/557L9*) s/smanip& m)/const smanip& m)/
(pg 635/558L-10*) s/<< d << endl;/<< 1.41421 << endl;/
(pg 646/568L-7L-10*) s/eptr/egptr/ 两处
(pg 669/587L-1) s/int i/size_t i/
(pg 671/590) 中译本第二个程序实例(英文671页最后那个实例)用到非标准特征。更好的例子是:
v_even *= v_odd;
// 乘起每对元素并存入偶数下标的元素里
v_odd = 0;
// 将d的奇数下标元素置0
(pg 674/592中间两个for语句里) s/int i/size_t i/ 两处
(pg 683/600) 更好的 operator*():
valarray< double> operator*(const Matrix& m, valarray< double>&
v)
{
valarray< double> res(m.dim2());
for (size_t i = 0; i< m.dim2(); i++) {
const Cslice_iter< double>&
ri = m.row(i);
res[i] = inner_product(ri,ri.end(),&v[0],double(0));
}
return res;
}
(pg 684/600) 更好的 operator*():
valarray< double> operator*(valarray& v, const Matrix& m)
{
valarray< double> res(m.dim1());
for (size_t i = 0; i< m.dim1(); i++) {
const Cslice_iter< double>&
ci = m.column(i);
res[i] = inner_product(ci,ci.end(),&v[0],double(0));
}
return res;
}
(pg 788/690*) s/Circle*/Circle/ 练习 2.
(pg 788690*) s/Shape*/Shape/ 练习 2.
附录A
(pg 808/707*) s/extern const volatile clock;/extern const volatile
long clock;/
(pg 809/708) 在 class-head 中将
class-key nested-name-specifier template template-id base-clause(opt
换为:
class-key nested-name-specifier(opt template-id base-clause(opt
(pg 811/710)
s/indentifier(opt) = template-name/indentifier(opt)
= id-expression/
(pg 811/710) 在 "template-argument:" 里: s/template-name/id-expression/
(pg 851/744) 将第一个例子换为:
void Z2::f(Y1* py1, Y2* py2, Y3* py3)
{
X* px = py1; // 可以: X 是 Y1 的一个公用基类
py1->a = 7; // ok
px = this; // 可以: X 是 Y2 的一个保护基类,
且 Z2 由 Y2 派生
a = 7; // ok
px = py2; // 错误: X 是 Y2 的一个保护基类,
且 Z2 由 Y2 派生,
// 但我们既不知道 py2 是一个 Z2, 也不知道如何在非 Z2 对象中使用 Y2::X
py2->a = 7; // 错误: Z2 不知道怎样在非 Z2
对象中使用 Y2::a
px = py3; // 错误: X 是 Y3 的一个私用基类
py3->a = 7; // 错误
}
(pg 858/750L9*) 在 "void k(" 之前加 "template< class T> "
(pg 867/757L-3*) s/template Calls_foo< Shape*>::constraints();/
template void Calls_foo< Shape*>::constraints(Shape*);/
(pg 871/761L11*) s/istream& fout/ostream& fout/ 两处
(pg 876/765L10*) s/print_locale names/print_locale_names/
(pg 880/768L-5*) 修改例子里的注释:
sort(v.begin(),v.end());
// 排序中用 < 比较元素
(pg 890/777L14*) s/cs2+size()/cs2+s2.size()/
(pg 890/777L-5L-3*) s/cs2+cs2.size()/cs2+s2.size()/ 两处
(pg 891/777L-1*) s/cs2+cs2.size()/cs2+s2.size()/
(pg 893/780L19*) s/: numpunct< char>(r) { }/: std::numpunct< char>(r) { }/
(pg 894/780L-10*) 默认格式和精度下的输出:
style A: 12345678 *** 1.23457e+06
style B: 12 345 678 ***
1,23457e+06
(pg 895/781L-18*) s/: num_put/: std::num_put/
(pg 895/781L-13*) s/&s[pos]/s.begin()+pos/
(pg 896/782L16*) s/fill()/this->fill()/
(pg 898/784L6*) s/eos,state/eos,*this,state/
(pg 899/785L7~11*) s/amount/amount=/ 在输出中有5处
(pg 904/789L27*) s/d = dd/m = dd/
(pg 904/789) 一个更好的输入函数:
istream& operator>>(istream& s, Money& m)
{
istream::sentry guard(s);
// 见 21.3.8
if (guard) try {
ios_base::iostate state
= 0; // 好
istreambuf_iterator<
char> eos;
string str;
use_facet< money_get< char> >(s.getloc()).get(s,eos,true,state,str);
if (state==0 || state==ios_base::eofbit)
{ // 只在 get() 成功时设置值
long int i = strtol(str.c_str(),0,0); // 为 strtol(), 见20.4.1
if (errno==ERANGE)
state |= ios_base::failbit;
else
m = i; // 只在到到 long int 的转换成功时设置值
s.setstate(state);
}
}
catch (...) {
handle_ioexception(s);
// 见 D.4.2.2
}
return s;
}
我用 get() 读入到一个 string 里,因为读入到一个 double 而后再转换到
long int 可能导致丢失精度。
(I use the get() that reads into a string because reading into a double
and
then converting to a long int could lead to loss of precision.)
(pg 909/794L1*) 注释:
s/sloppy: no protection against buffer overflow/
sloppy: hope strftime () will never produce more than
20 characters/
(pg 910/794*) 在 D.4.4.4 前加:
同时提供了 time_put 的一个 _byname 版本 (D.4, D.4.1):
template < class Ch,
class Out = ostreambuf_iterator< Ch> >
class std::time_put_byname
: public time_put< Ch,Out> { /* ... */ };
(pg 912/796L16*) s/Month(x.tm_mon)+1/Month(x.tm_mon+1)/
(pg 912/796*) 将D.4.4.4最后一段换为:
同时提供了 time_get 的一个 _byname 版本 (D.4, D.4.1):
template < class Ch,
class In = istreambuf_iterator< Ch> >
class std::time_get_byname
: public time_get< Ch,In> { /* ... */ };
(pg 915/798*) s/const { curr=p;/{ curr=p;/ 页中间两个set的定义
(pg 915/798L-15*) s/Date_format< char>::/Date_format::/
(pg 915/799L5*) s/(f.put( ... .failed)/(f.put( ... .failed())/
(pg 916/799L-4*) s/Month(x.tm_mon)+1/Month(x.tm_mon+1)/
(pg 916/800L4L6*) s/std::time_get< Ch>/std::time_get< Ch,In>/ 两处
(pg 917/800) 更好的 getval():
template< class Ch, class In>
In Date_in< Ch,In>::getval(In b, In e, ios_base& s, ios_base::iostate&
r,
int* v, Vtype* res) const
// read part of Date: number, day_of_week,
or month. Skip whitespace
// and punctuation.
{
const ctype< Ch>& ct = use_facet< ctype<
Ch> >(s.getloc());
// ctype is defined in D.4.5
Ch c;
*res = novalue; // no value found
for (;;) { // skip
whitespace and punctuation
if (b == e) return e;
c = *b;
if (!(ct.is(ctype_base::space,c)
|| ct.is(ctype_base::punct,c)))
break;
++b;
}
if (ct.is(ctype_base::digit,c)) {
// read integer without regard
int i = 0;
// for numpunct
do { // turn digit from arbitrary
character set into decimal value:
static char const digits[] = "0123456789";
i = i*10 + find(digits,digits+10,ct.narrow(c,' '))-digits;
c = *++b;
} while (ct.is(ctype_base::digit,c));
*v = i;
*res = unknown; // an integer,
but we don't know what it represents
return b;
}
if (ct.is(ctype_base::alpha,c)) { // look for name
of month or day of week
basic_string< Ch> str;
while (ct.is(ctype_base::alpha,c))
{ // read characters into string
str += c;
if (++b == e) break;
c = *b;
}
tm t;
basic_stringstream< Ch>
ss(str);
typedef istreambuf_iterator<
Ch> SI; // iterator type for ss' buffer
get_monthname(ss.rdbuf(),SI(),s,r,&t);
// read from in-memory stream
// buffer
if ((r&(ios_base::badbit|ios_base::failbit))==0)
{
*v= t.tm_mon;
*res = month;
r = 0;
return b;
}
r = 0; // clear state
before trying to read a second time
get_weekday(ss.rdbuf(),SI(),s,r,&t);
// read from in-memory stream
if ((r&ios_base::badbit)==0)
{ // buffer
*v = t.tm_wday;
*res = dayofweek;
r = 0;
return b;
}
}
r |= ios_base::failbit;
return b;
}
(pg 919/801L-6*) 将
template< class Ch, class
In = istreambuf_iterator< Ch> >
In Date_in::do_get_date(
替换为
template< class Ch, class
In>
In Date_in< Ch,In>::do_get_date(
注意:中译本修改后最后丢了一个 >。
(pg 919/802L13*) s/order = dateorder();/order = date_order();/
(pg 919/802页中部*) s/tmp->tm_mday = val[1];/tmp->tm_mday = val[2];/
(pg 922/804) 一个更好的 count_spaces():
int count_spaces(const string& s, const locale& loc)
{
const ctype< char>& ct = use_facet< ctype<
char> >(loc);
int i = 0;
for (string::const_iterator p = s.begin(); p !=
s.end(); ++p)
if (ct.is(ctype_base::space,*p))
++i; // whitespace as defined by ct
return i;
}
(pg 922/804*)
s/loc.is(space|punct,c)/ct.is(ctype_base::space|ctype_base::punct,c)/
原书勘误给的是 ct.is,似乎还是应该用 loc.is。中文译本用的是loc.is。
(pg 922-923/805L3L14*) s/std::ctype/std::ctype< Ch>/ 两处
(pg 924/806L7*) s/widen(narrow('x')) == 'x'/widen(narrow('x' ,0)) == 'x'/
(pg 928/809L-6*) 在 while 语句前加 "char ch;"
(pg 931/812*) s/to_str(Season) ... of s/to_str(Season x) ... of x/
(pg 932/812*) 将 to_str() 改为:
const string& Season_io::to_str(Season x) const
{
return m->get(cat,x,"no-such-season");
}
(pg 939/818L-20*) s/rand()/(rand())/
(pg 944/822L-5*) 将
void destroy_elements() { for (T* p = v; p!=space; ++p) p->~T(); }
改为:
void destroy_elements() { for (T* p = v; p!=space; ++p) p->~T(); space=v;
}
(pg 945/824) 在E3.3第二个例子之后加入:
然而,默认的 swap() 实现不适合我们对vector_base 的需要,因为它将赋值并销毁一个
vector_base。 为此我们提供一个专门化:
template void swap(vector_base& a, vector_base& b)
{
swap(a.a,b.a); swap(a.v,b.v); swap(a.space,b.space);
swap(a.last,b.last);
}
(pg 952/829L-1*,830L9)
s/T& vector< T,A >::emergency_exit()/void vector< T,A >::emergency_exit()/
| 本页由裘宗燕建立和维护,保留所有权利。
这里的材料可自由地用于个人学习或普通教学活动。其他方式的使用应事先得到作者书面认可。 |