From 78ea77d8517f52303c2af4dffbddb9008b8954e9 Mon Sep 17 00:00:00 2001 From: Chenzs108 Date: Wed, 6 Apr 2022 23:41:19 +0100 Subject: [PATCH] =?UTF-8?q?Style:=20=E5=9C=A8=E5=88=97=E8=A1=A8=E9=A1=B9?= =?UTF-8?q?=E4=B9=8B=E9=97=B4=E6=B7=BB=E5=8A=A0=E7=A9=BA=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Chapter-10 Generic Algorithms/README.md | 18 +++++-- Chapter-11 Associative Containers/README.md | 4 ++ Chapter-12 Dynamic Memory/README.md | 6 +++ Chapter-13 Copy Control/README.md | 8 +++ .../README.md | 29 +++++++---- .../README.md | 14 +++-- .../README.md | 33 +++++++++--- .../README.md | 30 +++++++++++ Chapter-18 Tools for Large Programs/README.md | 17 ++++++ .../README.md | 10 ++++ Chapter-2 Variables and Basic Types/README.md | 10 ++++ .../README.md | 2 + Chapter-4 Expressions/README.md | 14 +++++ Chapter-5 Statements/README.md | 6 +++ Chapter-6 Functions/README.md | 5 ++ Chapter-7 Classes/README.md | 52 +++++++++++++------ Chapter-8 The IO Library/README.md | 17 ++++++ Chapter-9 Sequential Containers/README.md | 18 +++++++ 18 files changed, 253 insertions(+), 40 deletions(-) diff --git a/Chapter-10 Generic Algorithms/README.md b/Chapter-10 Generic Algorithms/README.md index eda7941..7ee2d3d 100644 --- a/Chapter-10 Generic Algorithms/README.md +++ b/Chapter-10 Generic Algorithms/README.md @@ -283,8 +283,11 @@ for_each(words.begin(), words.end(), bind(print, ref(os), _1, ' ')); 除了为每种容器定义的迭代器之外,标准库还在头文件`iterator`中定义了另外几种迭代器。 - 插入迭代器(insert iterator):该类型迭代器被绑定到容器对象上,可用来向容器中插入元素。 + - 流迭代器(stream iterator):该类型迭代器被绑定到输入或输出流上,可用来遍历所关联的IO流。 + - 反向迭代器(reverse iterator):该类型迭代器向后而不是向前移动。除了`forward_list`之外的标准库容器都有反向迭代器。 + - 移动迭代器(move iterator):该类型迭代器用来移动容器元素。 ### 插入迭代器(Insert Iterators) @@ -298,7 +301,9 @@ for_each(words.begin(), words.end(), bind(print, ref(os), _1, ' ')); 插入器有三种类型,区别在于元素插入的位置: - `back_inserter`:创建一个调用`push_back`操作的迭代器。 + - `front_inserter`:创建一个调用`push_front`操作的迭代器。 + - `inserter`:创建一个调用`insert`操作的迭代器。此函数接受第二个参数,该参数必须是一个指向给定容器的迭代器,元素会被插入到该参数指向的元素之前。 ```c++ @@ -407,14 +412,18 @@ C++标准指定了泛型和数值算法的每个迭代器参数的最小类别 - 输入迭代器(input iterator):可以读取序列中的元素,只能用于单遍扫描算法。必须支持以下操作: -- - 用于比较两个迭代器相等性的相等`==`和不等运算符`!=`。 + - 用于比较两个迭代器相等性的相等`==`和不等运算符`!=`。 + - 用于推进迭代器位置的前置和后置递增运算符`++`。 + - 用于读取元素的解引用运算符`*`;解引用只能出现在赋值运算符右侧。 + - 用于读取元素的箭头运算符`->`。 - 输出迭代器(output iterator):可以读写序列中的元素,只能用于单遍扫描算法,通常指向目的位置。必须支持以下操作: -- - 用于推进迭代器位置的前置和后置递增运算符`++`。 + - 用于推进迭代器位置的前置和后置递增运算符`++`。 + - 用于读取元素的解引用运算符`*`;解引用只能出现在赋值运算符左侧(向已经解引用的输出迭代器赋值,等价于将值写入其指向的元素)。 - 前向迭代器(forward iterator):可以读写序列中的元素。只能在序列中沿一个方向移动。支持所有输入和输出迭代器的操作,而且可以多次读写同一个元素。因此可以使用前向迭代器对序列进行多遍扫描。 @@ -423,9 +432,12 @@ C++标准指定了泛型和数值算法的每个迭代器参数的最小类别 - 随机访问迭代器(random-access iterator):可以在常量时间内访问序列中的任何元素。除了支持所有双向迭代器的操作之外,还必须支持以下操作: -- - 用于比较两个迭代器相对位置的关系运算符`<`、`<=`、`>`、`>=`。 + - 用于比较两个迭代器相对位置的关系运算符`<`、`<=`、`>`、`>=`。 + - 迭代器和一个整数值的加减法运算`+`、`+=`、`-`、`-=`,计算结果是迭代器在序列中前进或后退给定整数个元素后的位置。 + - 用于两个迭代器上的减法运算符`-`,计算得到两个迭代器的距离。 + - 下标运算符`[]`。 ### 算法形参模式(Algorithm Parameter Patterns) diff --git a/Chapter-11 Associative Containers/README.md b/Chapter-11 Associative Containers/README.md index dc0f099..5f3b77e 100644 --- a/Chapter-11 Associative Containers/README.md +++ b/Chapter-11 Associative Containers/README.md @@ -3,12 +3,15 @@ 关联容器支持高效的关键字查找和访问操作。2个主要的关联容器(associative-container)类型是`map`和`set`。 - `map`中的元素是一些键值对(key-value):关键字起索引作用,值表示与索引相关联的数据。 + - `set`中每个元素只包含一个关键字,支持高效的关键字查询操作:检查一个给定关键字是否在`set`中。 标准库提供了8个关联容器,它们之间的不同体现在三个方面: - 是`map`还是`set`类型。 + - 是否允许保存重复的关键字。 + - 是否按顺序保存元素。 允许重复保存关键字的容器名字都包含单词`multi`;无序保存元素的容器名字都以单词`unordered`开头。 @@ -175,6 +178,7 @@ word_count.insert(map::value_type(word, 1)); `insert`或`emplace`的返回值依赖于容器类型和参数: - 对于不包含重复关键字的容器,添加单一元素的`insert`和`emplace`版本返回一个`pair`,表示操作是否成功。`pair`的`first`成员是一个迭代器,指向具有给定关键字的元素;`second`成员是一个`bool`值。如果关键字已在容器中,则`insert`直接返回,`bool`值为`false`。如果关键字不存在,元素会被添加至容器中,`bool`值为`true`。 + - 对于允许包含重复关键字的容器,添加单一元素的`insert`和`emplace`版本返回指向新元素的迭代器。 ### 删除元素(Erasing Elements) diff --git a/Chapter-12 Dynamic Memory/README.md b/Chapter-12 Dynamic Memory/README.md index 03dd7a2..ad85d4f 100644 --- a/Chapter-12 Dynamic Memory/README.md +++ b/Chapter-12 Dynamic Memory/README.md @@ -61,7 +61,9 @@ r = q; // assign to r, making it point to a different address 程序使用动态内存通常出于以下三种原因之一: - 不确定需要使用多少对象。 + - 不确定所需对象的准确类型。 + - 需要在多个对象间共享数据。 ### 直接管理内存(Managing Memory Directly) @@ -209,9 +211,13 @@ void f(destination &d /* other parameters */) 智能指针规范: - 不使用相同的内置指针值初始化或`reset`多个智能指针。 + - 不释放`get`返回的指针。 + - 不使用`get`初始化或`reset`另一个智能指针。 + - 使用`get`返回的指针时,如果最后一个对应的智能指针被销毁,指针就无效了。 + - 使用`shared_ptr`管理并非`new`分配的资源时,应该传递删除函数。 ### unique_ptr(unique_ptr) diff --git a/Chapter-13 Copy Control/README.md b/Chapter-13 Copy Control/README.md index 35ecdb9..9d546a8 100644 --- a/Chapter-13 Copy Control/README.md +++ b/Chapter-13 Copy Control/README.md @@ -3,9 +3,13 @@ 一个类通过定义五种特殊的成员函数来控制对象的拷贝、移动、赋值和销毁操作。 - 拷贝构造函数(copy constructor) + - 拷贝赋值运算符(copy-assignment operator) + - 移动构造函数(move constructor) + - 移动赋值运算符(move-assignment operator) + - 析构函数(destructor) 这些操作统称为拷贝控制操作(copy control)。 @@ -68,8 +72,11 @@ string nines = string(100, '9'); // copy initialization 发生拷贝初始化的情况: - 用`=`定义变量。 + - 将对象作为实参传递给非引用类型的形参。 + - 从返回类型为非引用类型的函数返回对象。 + - 用花括号列表初始化数组中的元素或聚合类中的成员。 当传递一个实参或者从函数返回一个值时,不能隐式使用`explicit`构造函数。 @@ -187,6 +194,7 @@ struct NoCopy `=delete`和`=default`有两点不同: - `=delete`可以对任何函数使用;`=default`只能对具有合成版本的函数使用。 + - `=delete`必须出现在函数第一次声明的地方;`=default`既能出现在类内,也能出现在类外。 析构函数不能是删除的函数。对于析构函数被删除的类型,不能定义该类型的变量或者释放指向该类型动态分配对象的指针。 diff --git a/Chapter-14 Overloaded Operations and Conversions/README.md b/Chapter-14 Overloaded Operations and Conversions/README.md index cc05c1b..75208b3 100644 --- a/Chapter-14 Overloaded Operations and Conversions/README.md +++ b/Chapter-14 Overloaded Operations and Conversions/README.md @@ -43,8 +43,11 @@ string u = "hi" + s; // would be an error if + were a member of string 如何选择将运算符定义为成员函数还是普通函数: - 赋值`=`、下标`[]`、调用`()`和成员访问箭头`->`运算符必须是成员函数。 + - 复合赋值运算符一般是成员函数,但并非必须。 + - 改变对象状态或者与给定类型密切相关的运算符,如递增、递减、解引用运算符,通常是成员函数。 + - 具有对称性的运算符可能转换任意一端的运算对象,如算术、相等性、关系和位运算符,通常是普通函数。 ## 输入和输出运算符(Input and Output Operators) @@ -88,7 +91,9 @@ istream &operator>>(istream &is, Sales_data &item) 以下情况可能导致读取操作失败: - 读取了错误类型的数据。 + - 读取操作到达文件末尾。 + - 遇到输入流的其他错误。 当读取操作发生错误时,输入操作符应该负责从错误状态中恢复。 @@ -132,7 +137,7 @@ Sales_data operator+(const Sales_data &lhs, const Sales_data &rhs) lhs.units_sold == rhs.units_sold && lhs.revenue == rhs.revenue; } - + bool operator!=(const Sales_data &lhs, const Sales_data &rhs) { return !(lhs == rhs); @@ -146,7 +151,9 @@ Sales_data operator+(const Sales_data &lhs, const Sales_data &rhs) 关系运算符设计准则: - 定义顺序关系,令其与关联容器中对关键字的要求保持一致。 + - 如果类定义了`operator==`,则关系运算符的定义应该与`operator==`保持一致。特别是,如果两个对象是不相等的,那么其中一个对象应该小于另一个对象。 + - 只有存在唯一一种逻辑可靠的小于关系时,才应该考虑为类定义`operator<`。 ## 赋值运算符(Assignment Operators) @@ -267,6 +274,7 @@ public: 对于形如`point->mem`的表达式来说,`point`必须是指向类对象的指针或者是一个重载了`operator->`的类的对象。`point`类型不同,`point->mem`的含义也不同。 - 如果`point`是指针,则调用内置箭头运算符,表达式等价于`(*point).mem`。 + - 如果`point`是重载了`operator->`的类的对象,则使用`point.operator->()`的结果来获取`mem`,表达式等价于`(point.operator->())->mem`。其中,如果该结果是一个指针,则执行内置操作,否则重复调用当前操作。 ## 函数调用运算符(Function-Call Operator) @@ -388,7 +396,7 @@ struct div function f1 = add; // function pointer function f2 = div(); // object of a function-object class function f3 = [](int i, int j) { return i * j; }; // lambda - + cout << f1(4,2) << endl; // prints 6 cout << f2(4,2) << endl; // prints 2 cout << f3(4,2) << endl; // prints 8 @@ -420,9 +428,9 @@ public: { if (i < 0 || i > 255) throw std::out_of_range("Bad SmallInt value"); - } + } operator int() const { return val; } - + private: std::size_t val; }; @@ -458,8 +466,11 @@ static_cast(si) + 3; // ok: explicitly request the conversion 如果表达式被用作条件,则编译器会隐式地执行显式类型转换。 - `if`、`while`、`do-while`语句的条件部分。 + - `for`语句头的条件表达式。 + - 条件运算符`? :`的条件表达式。 + - 逻辑非运算符`!`、逻辑或运算符`||`、逻辑与运算符`&&`的运算对象。 类类型向`bool`的类型转换通常用在条件部分,因此`operator bool`一般被定义为显式的。 @@ -479,13 +490,13 @@ static_cast(si) + 3; // ok: explicitly request the conversion A(const B&); // converts a B to an A // other members }; - + struct B { operator A() const; // also converts a B to an A // other members }; - + A f(const A&); B b; A a = f(b); // error ambiguous: f(B::operator A()) @@ -503,7 +514,7 @@ static_cast(si) + 3; // ok: explicitly request the conversion operator double() const; // conversions to arithmetic types // other members }; - + void f2(long double); A a; f2(a); // error ambiguous: f(A::operator int()) @@ -554,11 +565,11 @@ manip2(10); // manip2(C(10) or manip2(E(double(10))) class SmallInt { friend SmallInt operator+(const SmallInt&, const SmallInt&); - + public: SmallInt(int = 0); // conversion from int operator int() const { return val; } // conversion to int - + private: std::size_t val; }; diff --git a/Chapter-15 Object-Oriented Programming/README.md b/Chapter-15 Object-Oriented Programming/README.md index 75ca8e2..f697122 100644 --- a/Chapter-15 Object-Oriented Programming/README.md +++ b/Chapter-15 Object-Oriented Programming/README.md @@ -64,7 +64,7 @@ Quote &r = bulk; // r bound to the Quote part of bulk 每个类控制它自己的成员初始化过程,派生类必须使用基类的构造函数来初始化它的基类部分。派生类的构造函数通过构造函数初始化列表来将实参传递给基类构造函数。 ```c++ -Bulk_quote(const std::string& book, double p, +Bulk_quote(const std::string& book, double p, std::size_t qty, double disc) : Quote(book, p), min_qty(qty), discount(disc) { } ``` @@ -150,7 +150,7 @@ struct B void f3(); }; -struct D1 : B +struct D1 : B { void f1(int) const override; // ok: f1 matches f1 in the base void f2(int) override; // error: B has no f2(int) function @@ -237,13 +237,17 @@ void clobber(Base &b) { b.prot_mem = 0; } 派生访问说明符的作用是控制派生类(包括派生类的派生类)用户对于基类成员的访问权限。 - 如果使用公有继承,则基类的公有成员和受保护成员在派生类中属性不发生改变。 + - 如果使用受保护继承,则基类的公有成员和受保护成员在派生类中变为受保护成员。 + - 如果使用私有继承,则基类的公有成员和受保护成员在派生类中变为私有成员。 派生类到基类转换的可访问性(假定`D`继承自`B`): - 只有当`D`公有地继承`B`时,用户代码才能使用派生类到基类的转换。 + - 不论`D`以什么方式继承`B`,`D`的成员函数和友元都能使用派生类到基类的转换。 + - 如果`D`继承`B`的方式是公有的或者受保护的,则`D`的派生类的成员函数和友元可以使用`D`到`B`的类型转换;反之,如果`D`继承`B`的方式是私有的,则不能使用。 对于代码中的某个给定节点来说,如果基类的公有成员是可访问的,则派生类到基类的类型转换也是可访问的。 @@ -313,7 +317,7 @@ protected: struct Derived : Base { - int get_mem() { return mem; } // returns Derived::mem + int get_mem() { return mem; } // returns Derived::mem protected: int mem; // hides mem in the base }; @@ -342,7 +346,7 @@ class Base { private: int x; - + public: virtual void mf1() = 0; virtual void mf1(int); @@ -403,7 +407,9 @@ delete itemP; // destructor for Bulk_quote called 派生类中删除的拷贝控制与基类的关系: - 如果基类中的默认构造函数、拷贝构造函数、拷贝赋值运算符或析构函数是被删除的或者不可访问的函数,则派生类中对应的成员也会是被删除的。因为编译器不能使用基类成员来执行派生类对象中基类部分的构造、赋值或销毁操作。 + - 如果基类的析构函数是被删除的或者不可访问的,则派生类中合成的默认和拷贝构造函数也会是被删除的。因为编译器无法销毁派生类对象中的基类部分。 + - 编译器不会合成一个被删除的移动操作。当我们使用`=default`请求一个移动操作时,如果基类中对应的操作是被删除的或者不可访问的,则派生类中的操作也会是被删除的。因为派生类对象中的基类部分不能移动。同样,如果基类的析构函数是被删除的或者不可访问的,则派生类的移动构造函数也会是被删除的。 在实际编程中,如果基类没有默认、拷贝或移动构造函数,则一般情况下派生类也不会定义相应的操作。 diff --git a/Chapter-16 Templates and Generic Programming/README.md b/Chapter-16 Templates and Generic Programming/README.md index 568b800..3f1156a 100644 --- a/Chapter-16 Templates and Generic Programming/README.md +++ b/Chapter-16 Templates and Generic Programming/README.md @@ -168,10 +168,10 @@ BlobPtr& BlobPtr::operator++() // forward declarations needed for friend declarations in Blob template class BlobPtr; template class Blob; // needed for parameters in operator== - + template bool operator==(const Blob&, const Blob&); - + template class Blob { @@ -189,7 +189,7 @@ BlobPtr& BlobPtr::operator++() ```c++ // forward declaration necessary to befriend a specific instantiation of a template template class Pal; - + class C { // C is an ordinary, nontemplate class friend class Pal; // Pal instantiated with class C is a friend to C @@ -197,7 +197,7 @@ BlobPtr& BlobPtr::operator++() // no forward declaration required when we befriend all instantiations template friend class Pal2; }; - + template class C2 { // C2 is itself a class template @@ -236,7 +236,7 @@ class Foo { public: static std::size_t count() { return ctr; } - + private: static std::size_t ctr; }; @@ -332,11 +332,11 @@ public: // as with any function template, the type of T is deduced by the compiler template void operator()(T *p) const - { + { os << "deleting unique_ptr" << std::endl; delete p; } - + private: std::ostream &os; }; @@ -411,7 +411,9 @@ int i = compare(a1[0], a2[0]); // instantiation will appear elsewhere 有3种类型转换可以在调用中应用于函数模板: - 顶层`const`会被忽略。 + - 可以将一个非`const`对象的引用或指针传递给一个`const`引用或指针形参。 + - 如果函数形参不是引用类型,则可以对数组或函数类型的实参应用正常的指针转换。数组实参可以转换为指向其首元素的指针。函数实参可以转换为该函数类型的指针。 其他的类型转换,如算术转换、派生类向基类的转换以及用户定义的转换,都不能应用于函数模板。 @@ -595,6 +597,7 @@ void f3(int&); // when T is int&, function parameter collapses to in 模板参数绑定的两个例外规则导致了两个结果: - 如果一个函数参数是指向模板类型参数的右值引用,则可以传递给它任意类型的实参。 + - 如果将一个左值传递给这样的参数,则函数参数被实例化为一个普通的左值引用。 当代码中涉及的类型可能是普通(非引用)类型,也可能是引用类型时,编写正确的代码就变得异常困难。 @@ -639,17 +642,25 @@ s2 = std::move(s1); // ok: but after the assigment s1 has indeterminate valu - 在`std::move(string("bye!"))`中传递的是右值。 - 推断出的`T`类型为`string`。 + - `remove_reference`用`string`进行实例化。 + - `remove_reference`的`type`成员是`string`。 + - `move`的返回类型是`string&&`。 + - `move`的函数参数`t`的类型为`string&&`。 - 在`std::move(s1)`中传递的是左值。 - 推断出的`T`类型为`string&`。 + - `remove_reference`用`string&`进行实例化。 + - `remove_reference`的`type`成员是`string`。 + - `move`的返回类型是`string&&`。 + - `move`的函数参数`t`的类型为`string& &&`,会折叠成`string&`。 可以使用`static_cast`显式地将一个左值转换为一个右值引用。 @@ -718,6 +729,7 @@ intermediary(Type &&arg) ``` - 如果实参是一个右值,则`Type`是一个普通(非引用)类型,`forward`返回类型`Type&&`。 + - 如果实参是一个左值,则通过引用折叠,`Type`也是一个左值引用类型,`forward`返回类型`Type&& &`,对返回类型进行引用折叠,得到`Type&`。 使用`forward`编写完善的转发函数。 @@ -739,11 +751,17 @@ void flip(F f, T1 &&t1, T2 &&t2) 如果重载涉及函数模板,则函数匹配规则会受到一些影响: - 对于一个调用,其候选函数包括所有模板实参推断成功的函数模板实例。 + - 候选的函数模板都是可行的,因为模板实参推断会排除任何不可行的模板。 + - 和往常一样,可行函数(模板与非模板)按照类型转换(如果需要的话)来排序。但是可以用于函数模板调用的类型转换非常有限。 + - 和往常一样,如果恰有一个函数提供比其他任何函数都更好的匹配,则选择此函数。但是如果多个函数都提供相同级别的匹配,则: + - 如果同级别的函数中只有一个是非模板函数,则选择此函数。 + - 如果同级别的函数中没有非模板函数,而有多个函数模板,且其中一个模板比其他模板更特例化,则选择此模板。 + - 否则该调用有歧义。 通常,如果使用了一个没有声明的函数,代码将无法编译。但对于重载函数模板的函数而言,如果编译器可以从模板实例化出与调用匹配的版本,则缺少的声明就不再重要了。 @@ -769,6 +787,7 @@ string debug_rep(char *p) 可变参数模板指可以接受可变数量参数的模板函数或模板类。可变数量的参数被称为参数包(parameter pack),分为两种: - 模板参数包(template parameter pack),表示零个或多个模板参数。 + - 函数参数包(function parameter pack),表示零个或多个函数参数。 用一个省略号`…`来指出模板参数或函数参数表示一个包。在一个模板参数列表中,`class…`或`typename…`指出接下来的参数表示零个或多个类型的列表;一个类型名后面跟一个省略号表示零个或多个给定类型的非类型参数列表。在函数参数列表中,如果一个参数的类型是模板参数包,则此参数也是函数参数包。 diff --git a/Chapter-17 Specialized Library Facilities/README.md b/Chapter-17 Specialized Library Facilities/README.md index ee7251c..97eba9a 100644 --- a/Chapter-17 Specialized Library Facilities/README.md +++ b/Chapter-17 Specialized Library Facilities/README.md @@ -34,6 +34,7 @@ get<2>(item) *= 0.8; // apply 20% discount 可以使用`tuple_size`和`tuple_element`这两个辅助类模板查询`tuple`成员的数量和类型。 - `tuple_size`通过一个`tuple`类型来初始化,它有一个名为`value`的静态公有数据成员,类型为`size_t`,表示给定`tuple`中成员的数量。 + - `tuple_element`通过一个索引值(整型常量)和一个`tuple`类型来初始化,它有一个名为`type`的公有数据成员,表示给定`tuple`中指定成员的类型。 使用`decltype`可以确定一个对象的类型。 @@ -104,8 +105,11 @@ cout << "ulong = " << ulong << endl; `bitset`的输入运算符从输入流读取字符,保存到临时的`string`对象中。遇到下列情况时停止读取: - 读取的字符数达到对应`bitset`的大小。 + - 遇到不是1和0的字符。 + - 遇到文件结尾。 + - 输入出现错误。 读取结束后用临时`string`对象初始化`bitset`。如果读取的字符数小于`bitset`的大小,则`bitset`的高位被置为0。 @@ -153,6 +157,7 @@ if (regex_search(test_str, results, r)) // if there is a match RE库为不同的输入序列都定义了对应的类型。使用时RE库类型必须与输入类型匹配。 - `regex`类保存`char`类型的正则表达式;`wregex`保存`wchar_t`类型的正则表达式。 + - `smatch`表示`string`类型的输入序列;`cmatch`表示字符数组类型的输入序列;`wsmatch`表示`wstring`类型的输入序列;`wcmatch`表示宽字符数组类型的输入序列。 ![17-9](Images/17-9.png) @@ -220,11 +225,17 @@ regex r("([[:alnum:]]+)\\.(cpp|cxx|cc)$", regex::icase); ECMAScript正则表达式语言的一些特性: - 模式`[[:alnum:]]`匹配任意字母。 + - 符号`+`表示匹配一个或多个字符。 + - 符号`*`表示匹配零个或多个字符。 + - `\{d}`表示单个数字,`\{d}{n}`表示一个n个数字的序列。 + - 在方括号中的字符集合表示匹配这些字符中的任意一个。 + - 后接`?`的组件是可选的。 + - 类似C++,ECMAScript使用反斜线进行转义。由于模式包含括号,而括号是ECMAScript中的特殊字符,因此需要用`\(`和`\)`来表示括号是模式的一部分。 因为反斜线`\`是C++中的特殊字符,所以在模式中使用`\`时,需要一个额外的反斜线进行转义。 @@ -323,6 +334,7 @@ vector good_randVec() 为引擎设置种子有两种方式: - 在创建对象时提供种子。 + - 调用引擎的`seed`成员设置种子。 ```c++ @@ -421,7 +433,9 @@ decimal: 20 1024 默认情况下,在输出数值时,没有可见的标识指出当前使用的进制模式。如果需要输出八进制或十六进制值,应该使用`showbase`操纵符。对流应用`showbase`后,在输出结果中会显示进制,显示模式和指定整型常量进制的规范相同。 - 前导`0x`表示十六进制。 + - 前导`0`表示八进制。 + - 无前导字符表示十进制。 还原格式时使用`noshowbase`操纵符。 @@ -461,7 +475,9 @@ printed in hexadecimal: 0X14 0X400 浮点数的输出格式涉及三个方面: - 输出精度(即输出多少个数字)。 + - 十六进制、定点十进制或者科学记数法形式输出。 + - 没有小数部分的浮点值是否输出小数点。 默认情况下,浮点值按六位数字精度输出;如果浮点值没有小数部分,则不输出小数点;根据浮点数的值选择输出为定点十进制或科学计数法形式:非常大或非常小的值输出为科学记数法形式,其他值输出为定点十进制形式。 @@ -471,6 +487,7 @@ printed in hexadecimal: 0X14 0X400 调用IO对象的`precision`成员或者使用`setprecision`操纵符可以改变精度。 - `precision`成员是重载的。一个版本接受一个`int`值,将精度设置为此值,并返回旧精度值。另一个版本不接受参数,直接返回当前精度值。 + - `setprecision`操纵符接受一个参数来设置精度。 `setprecision`操纵符和其他接受参数的操纵符都定义在头文件`iomanip`中。 @@ -504,8 +521,11 @@ Precision: 3, Value: 1.41 操纵符可以强制流使用科学记数法、定点十进制或十六进制形式输出浮点值。 - `scientific`使用科学记数法表示浮点值。 + - `fixed`使用定点十进制表示浮点值。 + - `hexfloat`(新标准库)使用十六进制表示浮点值。 + - `defaultfloat`(新标准库)将流恢复到默认状态。 除非程序需要控制浮点数的表示方式,否则最好由标准库来选择计数法。 @@ -541,9 +561,13 @@ cout << showpoint << 10.0 // prints 10.0000 按列输出时,通常需要非常精细地控制数据格式。 - `setw`指定下一个数字或字符串值的最小空间。 + - `left`表示左对齐输出。 + - `right`表示右对齐输出(默认格式)。 + - `internal`控制负数的符号位置,它左对齐符号,右对齐值,中间空间用空格填充。 + - `setfill`指定一个字符代替默认的空格进行补白。 `setw`类似`endl`,不改变输出流的内部状态,只影响下一次输出的大小。 @@ -621,7 +645,9 @@ while (cin.get(ch)) 有时读取完一个字符后才发现目前无法处理该字符,希望将其放回流中。标准库提供了三种方法退回字符。 - `peek`返回输入流中下一个字符的副本,但不会将其从流中删除。 + - `unget`使输入流向后移动,令最后读取的值回到流中。即使不知道最后从流中读取了什么值,也可以调用`unget`。 + - `putback`是特殊版本的`unget`,它退回从流中读取的最后一个值,但它接受一个参数,该参数必须与最后读取的值相同。 一般情况下,在读取下一个值之前,标准库保证程序可以退回最多一个值。 @@ -653,7 +679,9 @@ while ((ch = cin.get()) != EOF) `get`和`getline`函数接受相同的参数,它们的行为类似但不相同。两个函数都一直读取数据,直到遇到下列情况之一: - 已经读取了`size - 1`个字符。 + - 遇到了文件尾(`EOF`)。 + - 遇到了分隔符。 两个函数的区别在于处理分隔符的方式:`get`将分隔符留在输入流中作为下一个字符,而`getline`读取并丢弃分隔符。两个函数都不会将分隔符保存在结果数组中。 @@ -689,7 +717,9 @@ cin.ignore(numeric_limits::max(), '\n'); 从逻辑上考虑,`seek`和`tell`函数的使用范围如下: - 可以对`istream`、`ifstream`、`istringstream`类型使用`g`版本。 + - 可以对`ostream`、`ofstream`、`ostringstream`类型使用`p`版本。 + - 可以对`iostream`、`fstream`、`stringstream`类型使用`g`和`p`版本。 一个流中只有一个标记——不存在独立的读标记和写标记。`fstream`和`stringstream`类型可以读写同一个流。在这些类型中,有单一的缓冲区用于保存读写的数据,同时标记也只有一个,表示缓冲区中的当前位置。标准库将两个版本的`seek`和`tell`函数都映射到这个标记。 diff --git a/Chapter-18 Tools for Large Programs/README.md b/Chapter-18 Tools for Large Programs/README.md index a1ea60a..abf907d 100644 --- a/Chapter-18 Tools for Large Programs/README.md +++ b/Chapter-18 Tools for Large Programs/README.md @@ -11,11 +11,13 @@ 执行一个`throw`语句时,跟在`throw`后面的语句将不再执行。程序的控制权从`throw`转移到与之匹配的`catch`语句中。该`catch`可能是同一个函数中的局部`catch`,也可能位于直接或间接调用了发生异常的函数的另一个函数中。控制权的转移意味着两个问题: - 沿着调用链的函数可能会提前退出。 + - 一旦程序开始执行异常处理代码,则沿着调用链创建的对象会被销毁。 抛出异常后,程序暂停执行当前函数并立即寻找对应`catch`语句的过程叫做栈展开(stack unwinding)。栈展开沿着嵌套函数的调用链不断查找,直到找到了与异常匹配的`catch`语句为止。如果没有对应的`catch`语句,则退出主函数后查找过程结束。 - 如果找到了匹配的`catch`语句,则程序进入该子句并执行其中的代码。`catch`语句执行结束后,程序会转移到与`try`块关联的最后一个`catch`语句之后的位置继续执行。 + - 如果没有找到匹配的`catch`语句,程序会调用标准库的`terminate`函数,终止运行。 在栈展开过程中,位于调用链上的语句块可能会提前退出,其中的局部对象也会被销毁。如果异常发生在构造函数或者数组及容器的元素初始化过程中,则当前的对象可能只构造了一部分,此时必须确保已构造的成员能被正确销毁。 @@ -37,9 +39,13 @@ 进入`catch`语句后,使用异常对象初始化异常声明中的参数。`catch`参数的特性和函数参数类似。 - 如果`catch`的参数类型是非引用类型,则该参数是异常对象的一个副本,改变参数不会影响异常对象本身。 + - 如果`catch`的参数类型是引用类型,则该参数是异常对象的一个别名,改变参数就是改变异常对象本身。 + - 在继承体系中,如果`catch`的参数类型是基类类型,则可以使用其派生类类型的异常对象对其初始化。 + - `catch`的参数是基类非引用类型时,异常对象会被切除一部分。 + - `catch`的参数是基类引用类型时,以常规方式绑定到异常对象。 异常声明的静态类型决定了`catch`语句所能执行的操作。如果`catch`的参数是基类类型,则无法使用派生类特有的成员。 @@ -51,7 +57,9 @@ 异常和异常声明的匹配规则比函数参数严格,绝大多数类型转换都不能使用。 - 允许从非常量到常量的类型转换。 + - 允许从派生类到基类的类型转换。 + - 数组被转换成指向数组元素类型的指针,函数被转换成指向该函数类型的指针。 除此之外,包括标准算术类型转换和类类型转换在内的其他所有转换规则都不能在`catch`匹配过程中使用。 @@ -132,9 +140,13 @@ void alloc(int); // might throw `noexcept`说明的出现位置: - 关键字`noexcept`位于函数的参数列表之后,尾置返回类型之前。 + - 对于一个函数来说,`noexcept`说明必须同时出现在该函数的所有声明和定义语句中。 + - 函数指针的声明和定义也可以指定`noexcept`。 + - 在`typedef`或类型别名中不能使用`noexcept`。 + - 在成员函数中,关键字`noexcept`位于`const`或引用限定符之后,`final`、`override`或虚函数的`=0`之前。 编译器并不会在编译时检查`noexcept`说明。如果一个函数在指定了`noexcept`的同时又含有`throw`语句或其他可能抛出异常的操作,仍然会通过编译(个别编译器可能会提出警告)。 @@ -150,6 +162,7 @@ void f() noexcept // promises not to throw any exception 一旦`noexcept`函数抛出异常,程序会调用`terminate`函数终止运行(该过程是否执行栈展开未作规定)。因此`noexcept`可以用于两种情况: - 确认函数不会抛出异常。 + - 不知道该如何处理函数抛出的异常。 指明某个函数不会抛出异常可以让调用者不必再考虑异常处理操作。 @@ -273,6 +286,7 @@ namespace nsp 利用命名空间不连续的特性可以将几个独立的接口和实现文件组成一个命名空间。此时,命名空间的组织方式类似管理自定义类和函数的方式。 - 命名空间的一部分成员用于定义类,以及声明作为类接口的函数和对象。这些成员应该放置在头文件中。 + - 命名空间成员的定义部分放置在另外的源文件中。源文件需要包含对应的头文件。 程序中的某些实体只能定义一次,如非内联函数、静态数据成员等,命名空间中定义的名字也需要满足该要求。 @@ -478,6 +492,7 @@ void manip() 相比于使用`using`指示,在程序中对命名空间中的每个成员分别使用`using`声明效果更好。 - 如果程序使用了多个不同的库,而这些库中的名字通过`using`指示变得可见,则全局命名空间污染问题将重新出现。 + - `using`指示引发的二义性错误只有在使用了冲突名字的地方才会被发现。而`using`声明引发的二义性错误在声明处就能发现。 建议在命名空间本身的实现文件中使用`using`指示。 @@ -701,7 +716,9 @@ class Panda : public Bear, public Raccoon, public Endangered { /* ... */ }; 因为在每个共享的虚基类中只有唯一一个共享的子对象,所以该基类的成员可以被直接访问,而且不会产生二义性。此外,如果虚基类的成员只被一条派生路径覆盖,则也可以直接访问该成员。但如果成员被多个基类覆盖,则一般情况下派生类必须为该成员定义新的版本。例如,假设类`B`定义了一个名为`X`的成员,`D1`和`D2`都从`B`虚继承得到,`D`继承了`D1`和`D2`。则在`D`的作用域中,`X`通过`D`的两个基类都是可见的。如果通过`D`的对象使用`X`,则有三种可能性: - 如果`D1`和`D2`中都没有`X`的定义,则`X`会被解析为`B`的成员,此时不存在二义性。 + - 如果`D1`和`D2`中的某一个定义了`X`,派生类的`X`会比共享虚基类`B`的`X`优先级更高,此时同样没有二义性。 + - 如果`D1`和`D2`都定义了`X`,则直接访问`X`会产生二义性问题。 ### 构造函数与虚继承(Constructors and Virtual Inheritance) diff --git a/Chapter-19 Specialized Tools and Techniques/README.md b/Chapter-19 Specialized Tools and Techniques/README.md index 1672068..c75ccbf 100644 --- a/Chapter-19 Specialized Tools and Techniques/README.md +++ b/Chapter-19 Specialized Tools and Techniques/README.md @@ -7,12 +7,15 @@ 使用`new`表达式时,实际执行了三步操作: - `new`表达式调用名为`operator new`(或`operator new[]`)的标准库函数。该函数分配一块足够大、原始、未命名的内存空间以便存储特定类型的对象(或对象数组)。 + - 编译器调用对应的构造函数构造这些对象并初始化。 + - 对象被分配了空间并构造完成,返回指向该对象的指针。 使用`delete`表达式时,实际执行了两步操作: - 对指针所指向的对象(或对象数组)执行对应的析构函数。 + - 编译器调用名为`operator delete`(或`operator delete[]`)的标准库函数释放内存空间。 如果程序希望控制内存分配的过程,则需要定义自己的`operator new`和`operator delete`函数。编译器会用自定义版本替换标准库版本。 @@ -95,6 +98,7 @@ new (place_address) type [size] { braced initializer list } 运行时类型识别(RTTI)的功能由两个运算符实现: - `typeid`运算符,用于返回表达式的类型。 + - `dynamic_cast`运算符,用于将基类的指针或引用安全地转换为派生类的指针或引用。 RTTI运算符适用于以下情况:想通过基类对象的指针或引用执行某个派生类操作,并且该操作不是虚函数。 @@ -112,7 +116,9 @@ dynamic_cast(e) 其中`type`是一个类类型,并且通常情况下该类型应该含有虚函数。在第一种形式中,`e`必须是一个有效指针;在第二种形式中,`e`必须是一个左值;在第三种形式中,`e`不能是左值。在所有形式中,`e`的类型必须符合以下条件之一: - `e`是`type`的公有派生类。 + - `e`是`type`的公有基类。 + - `e`和`type`类型相同。 如果条件符合,则类型转换成功,否则转换失败。转换失败可能有两种结果: @@ -321,8 +327,11 @@ enum class intTypes 可以在任何需要常量表达式的地方使用枚举成员。如: - 定义枚举类型的`constexpr`变量。 + - 将枚举类型对象作为`switch`语句的表达式,而将枚举值作为`case`标签。 + - 将枚举类型作为非类型模板形参使用。 + - 在类的定义中初始化枚举类型的静态数据成员。 初始化枚举对象或者给枚举对象赋值时,必须使用该类型的一个枚举成员或者该类型的另一个对象。即使某个整型值恰好与枚举成员的值相等,也不能用其初始化枚举对象。 @@ -559,6 +568,7 @@ ival = 42; // that object now holds the value 42 C++的早期版本规定,在联合中不能含有定义了构造函数或拷贝控制成员的类类型成员。C++11取消了该限制。但是如果联合的成员类型定义了自己的构造函数或拷贝控制成员,该联合的用法会比只含有内置类型成员的联合复杂得多。 - 当联合只包含内置类型的成员时,可以使用普通的赋值语句改变联合的值。但是如果想将联合的值改为类类型成员对应的值,或者将类类型成员的值改为一个其他值,则必须构造或析构该类类型的成员。 + - 当联合只包含内置类型的成员时,编译器会按照成员顺序依次合成默认构造函数或拷贝控制成员。但是如果联合含有类类型成员,并且该类型自定义了默认构造函数或拷贝控制成员,则编译器会为该联合合成对应的版本并将其声明为删除的。 对于联合来说,构造或销毁类类型成员的操作非常复杂。通常情况下,可以把含有类类型成员的联合内嵌在另一个类中,这个类可以管理并控制与联合的类类型成员相关的状态转换。 diff --git a/Chapter-2 Variables and Basic Types/README.md b/Chapter-2 Variables and Basic Types/README.md index 7bed6dd..8c0bc5c 100644 --- a/Chapter-2 Variables and Basic Types/README.md +++ b/Chapter-2 Variables and Basic Types/README.md @@ -21,6 +21,7 @@ 字符型分为`char`、`signed char`和`unsigned char`三种,但是表现形式只有带符号和无符号两种。类型`char`和`signed char`并不一样, `char`的具体形式由编译器(compiler)决定。 如何选择算数类型: + - 当明确知晓数值不可能为负时,应该使用无符号类型。 - 使用`int`执行整数运算,如果数值超过了`int`的表示范围,应该使用`long long`类型。 @@ -34,10 +35,15 @@ 进行类型转换时,类型所能表示的值的范围决定了转换的过程。 - 把非布尔类型的算术值赋给布尔类型时,初始值为0则结果为`false`,否则结果为`true`。 + - 把布尔值赋给非布尔类型时,初始值为`false`则结果为0,初始值为`true`则结果为1。 + - 把浮点数赋给整数类型时,进行近似处理,结果值仅保留浮点数中的整数部分。 + - 把整数值赋给浮点类型时,小数部分记为0。如果该整数所占的空间超过了浮点类型的容量,精度可能有损失。 + - 赋给无符号类型一个超出它表示范围的值时,结果是初始值对无符号类型表示数值总数(8比特大小的`unsigned char`能表示的数值总数是256)取模后的余数。 + - 赋给带符号类型一个超出它表示范围的值时,结果是未定义的(undefined)。 避免无法预知和依赖于实现环境的行为。 @@ -222,6 +228,7 @@ int &refVal2; // error: a reference must be initialized 与引用类似,指针也实现了对其他对象的间接访问。 - 指针本身就是一个对象,允许对指针赋值和拷贝,而且在生命周期内它可以先后指向不同的对象。 + - 指针无须在定义时赋初值。和其他内置类型一样,在块作用域内定义的指针如果没有被初始化,也将拥有一个不确定的值。 通过将声明符写成`*d`的形式来定义指针类型,其中`d`是变量名称。如果在一条语句中定义了多个指针变量,则每个量前都必须有符号`*`。 @@ -245,8 +252,11 @@ int *p = &ival; // p holds the address of ival; p is a pointer to ival 指针的值(即地址)应属于下列状态之一: - 指向一个对象。 + - 指向紧邻对象所占空间的下一个位置。 + - 空指针,即指针没有指向任何对象。 + - 无效指针,即上述情况之外的其他值。 试图拷贝或以其他方式访问无效指针的值都会引发错误。 diff --git a/Chapter-3 Strings, Vectors, and Arrays/README.md b/Chapter-3 Strings, Vectors, and Arrays/README.md index 73bf06d..1d3d609 100644 --- a/Chapter-3 Strings, Vectors, and Arrays/README.md +++ b/Chapter-3 Strings, Vectors, and Arrays/README.md @@ -290,7 +290,9 @@ C风格字符串函数不负责验证其参数的正确性,传入此类函数 任何出现字符串字面值的地方都可以用以空字符结束的字符数组来代替: - 允许使用以空字符结束的字符数组来初始化`string`对象或为`string`对象赋值。 + - 在`string`对象的加法运算中,允许使用以空字符结束的字符数组作为其中一个运算对象(不能两个运算对象都是)。 + - 在`string`对象的复合赋值运算中,允许使用以空字符结束的字符数组作为右侧运算对象。 不能用`string`对象直接初始化指向字符的指针。为了实现该功能,`string`提供了一个名为`c_str`的成员函数,返回`const char*`类型的指针,指向一个以空字符结束的字符数组,数组的数据和`string`对象一样。 diff --git a/Chapter-4 Expressions/README.md b/Chapter-4 Expressions/README.md index 54a79ac..cc18859 100644 --- a/Chapter-4 Expressions/README.md +++ b/Chapter-4 Expressions/README.md @@ -15,8 +15,11 @@ C++定义了运算符作用于内置类型和复合类型的运算对象时所 C++的表达式分为右值(rvalue)和左值(lvalue)。当一个对象被用作右值的时候,用的是对象的值(内容);当对象被用作左值时,用的是对象的地址。需要右值的地方可以用左值代替,反之则不行。 - 赋值运算符需要一个非常量左值作为其左侧运算对象,返回结果也是一个左值。 + - 取地址符作用于左值运算对象,返回指向该运算对象的指针,该指针是一个右值。 + - 内置解引用运算符、下标运算符、迭代器解引用运算符、`string`和`vector`的下标运算符都返回左值。 + - 内置类型和迭代器的递增递减运算符作用于左值运算对象。前置版本返回左值,后置版本返回右值。 如果`decltype`作用于一个求值结果是左值的表达式,会得到引用类型。 @@ -39,6 +42,7 @@ cout << i << " " << ++i << endl; // undefined 处理复合表达式时建议遵循以下两点: - 不确定求值顺序时,使用括号来强制让表达式的组合关系符合程序逻辑的要求。 + - 如果表达式改变了某个运算对象的值,则在表达式的其他位置不要再使用这个运算对象。 当改变运算对象的子表达式本身就是另一个子表达式的运算对象时,第二条规则无效。如`*++iter`,递增运算符改变了`iter`的值,而改变后的`iter`又是解引用运算符的运算对象。类似情况下,求值的顺序不会成为问题。 @@ -60,6 +64,7 @@ cout << i << " " << ++i << endl; // undefined 逻辑与(logical AND)运算符`&&`和逻辑或(logical OR)运算符`||`都是先计算左侧运算对象的值再计算右侧运算对象的值,当且仅当左侧运算对象无法确定表达式的结果时才会去计算右侧运算对象的值。这种策略称为短路求值(short-circuit evaluation)。 - 对于逻辑与运算符来说,当且仅当左侧运算对象为真时才对右侧运算对象求值。 + - 对于逻辑或运算符来说,当且仅当左侧运算对象为假时才对右侧运算对象求值。 进行比较运算时,除非比较的对象是布尔类型,否则不要使用布尔字面值`true`和`false`作为运算对象。 @@ -95,6 +100,7 @@ ival = jval = 0; // ok: each assigned 0 递增和递减运算符分为前置版本和后置版本: - 前置版本首先将运算对象加1(或减1),然后将改变后的对象作为求值结果。 + - 后置版本也会将运算对象加1(或减1),但求值结果是运算对象改变前的值的副本。 ```c++ @@ -157,10 +163,15 @@ cond ? expr1 : expr2; `sizeof`运算符的结果部分依赖于其作用的类型: - 对`char`或者类型为`char`的表达式执行`sizeof`运算,返回值为1。 + - 对引用类型执行`sizeof`运算得到被引用对象所占空间的大小。 + - 对指针执行`sizeof`运算得到指针本身所占空间的大小。 + - 对解引用指针执行`sizeof`运算得到指针指向的对象所占空间的大小,指针不需要有效。 + - 对数组执行`sizeof`运算得到整个数组所占空间的大小。 + - 对`string`或`vector`对象执行`sizeof`运算只返回该类型固定部分的大小,不会计算对象中元素所占空间的大小。 ## 逗号运算符(Comma Operator) @@ -207,8 +218,11 @@ cast-name(expression); 其中`type`是转换的目标类型,`expression`是要转换的值。如果`type`是引用类型,则转换结果是左值。`cast-name`是`static_cast`、`dynamic_cast`、`const_cast`和`reinterpret_cast`中的一种,用来指定转换的方式。 - `dynamic_cast`支持运行时类型识别。 + - 任何具有明确定义的类型转换,只要不包含底层`const`,都能使用`static_cast`。 + - `const_cast`只能改变运算对象的底层`const`,不能改变表达式的类型。同时也只有`const_cast`能改变表达式的常量属性。`const_cast`常常用于函数重载。 + - `reinterpret_cast`通常为运算对象的位模式提供底层上的重新解释。 早期版本的C++语言中,显式类型转换包含两种形式: diff --git a/Chapter-5 Statements/README.md b/Chapter-5 Statements/README.md index b1be1e4..edd810b 100644 --- a/Chapter-5 Statements/README.md +++ b/Chapter-5 Statements/README.md @@ -59,6 +59,7 @@ else 其中`condition`是判断条件,可以是一个表达式或者初始化了的变量声明。`condition`必须用圆括号括起来。 - 如果`condition`为真,则执行`statement`。执行完成后,程序继续执行`if`语句后面的其他语句。 + - 如果`condition`为假,则跳过`statement`。对于简单`if`语句来说,程序直接执行`if`语句后面的其他语句;对于`if-else`语句来说,程序先执行`statement2`,再执行`if`语句后面的其他语句。 `if`语句可以嵌套,其中`else`与离它最近的尚未匹配的`if`相匹配。 @@ -186,7 +187,9 @@ while (cin >> buf && !buf.empty()) `continue`语句中断当前迭代后,具体操作视迭代语句类型而定: - 对于`while`和`do-while`语句来说,继续判断条件的值。 + - 对于传统的`for`语句来说,继续执行`for`语句头中的第三部分,之后判断条件的值。 + - 对于范围`for`语句来说,是用序列中的下一个元素初始化循环变量。 ### goto语句(The goto Statement) @@ -216,7 +219,9 @@ goto label; 异常处理机制包括`throw`表达式(throw expression)、`try`语句块(try block)和异常类(exception class)。 - 异常检测部分使用`throw`表达式表示它遇到了无法处理的问题(`throw`引发了异常)。 + - 异常处理部分使用`try`语句块处理异常。`try`语句块以关键字`try`开始,并以一个或多个`catch`子句(catch clause)结束。`try`语句块中代码抛出的异常通常会被某个`catch`子句处理,`catch`子句也被称作异常处理代码(exception handler)。 + - 异常类用于在`throw`表达式和相关的`catch`子句之间传递异常的具体信息。 ### throw表达式(A throw Expression) @@ -257,6 +262,7 @@ catch (exception-declaration) ![5-2](Images/5-2.png) - 头文件`new`定义了`bad_alloc`异常类。 + - 头文件`type_info`定义了`bad_cast`异常类。 标准库异常类的继承体系: diff --git a/Chapter-6 Functions/README.md b/Chapter-6 Functions/README.md index 1718f76..ba15dff 100644 --- a/Chapter-6 Functions/README.md +++ b/Chapter-6 Functions/README.md @@ -29,11 +29,13 @@ int main() 函数调用完成两项工作: - 用实参初始化对应的形参。 + - 将控制权从主调函数转移给被调函数。此时,主调函数(calling function)的执行被暂时中断,被调函数(called function)开始执行。 `return`语句结束函数的执行过程,完成两项工作: - 返回`return`语句中的值(可能没有值)。 + - 将控制权从被调函数转移回主调函数,函数的返回值用于初始化调用表达式的结果。 实参是形参的初始值,两者的顺序和类型必须一一对应。 @@ -83,6 +85,7 @@ int f4(int v1, int v2) { /* ... */ } // ok 形参的类型决定了形参和实参交互的方式: - 当形参是引用类型时,它对应的实参被引用传递(passed by reference),函数被传引用调用(called by reference)。引用形参是它对应实参的别名。 + - 当形参不是引用类型时,形参和实参是两个相互独立的对象,实参的值会被拷贝给形参(值传递,passed by value),函数被传值调用(called by value)。 ### 传值参数(Passing Arguments by Value) @@ -406,7 +409,9 @@ string &shorterString(string &s1, string &s2) 调用重载函数时有三种可能的结果: - 编译器找到一个与实参最佳匹配(best match)的函数,并生成调用该函数的代码。 + - 编译器找不到任何一个函数与实参匹配,发出无匹配(no match)的错误信息。 + - 有一个以上的函数与实参匹配,但每一个都不是明显的最佳选择,此时编译器发出二义性调用(ambiguous call)的错误信息。 ### 重载与作用域(Overloading and Scope) diff --git a/Chapter-7 Classes/README.md b/Chapter-7 Classes/README.md index 5f75b7b..a6d4203 100644 --- a/Chapter-7 Classes/README.md +++ b/Chapter-7 Classes/README.md @@ -19,7 +19,7 @@ struct Sales_data std::string isbn() const { return bookNo; } Sales_data& combine(const Sales_data&); double avg_price() const; - + // data members std::string bookNo; unsigned units_sold = 0; @@ -27,7 +27,7 @@ struct Sales_data }; ``` -成员函数通过一个名为`this`的隐式额外参数来访问调用它的对象。`this`参数是一个常量指针,被初始化为调用该函数的对象地址。在函数体内可以显式使用`this`指针。 +成员函数通过一个名为`this`的隐式额外参数来访问调用它的对象。`this`参数是一个常量指针,被初始化为调用该函数的对象地址。在函数体内可以显式使用`this`指针。 ```c++ total.isbn() @@ -47,7 +47,7 @@ C++允许在成员函数的参数列表后面添加关键字`const`,表示`thi // this code is illegal: we may not explicitly define the this pointer ourselves // note that this is a pointer to const because isbn is a const member std::string Sales_data::isbn(const Sales_data *const this) -{ +{ return this->isbn; } ``` @@ -59,7 +59,7 @@ std::string Sales_data::isbn(const Sales_data *const this) 在类的外部定义成员函数时,成员函数的定义必须与它的声明相匹配。如果成员函数被声明为常量成员函数,那么它的定义也必须在参数列表后面指定`const`属性。同时,类外部定义的成员名字必须包含它所属的类名。 ```c++ -double Sales_data::avg_price() const +double Sales_data::avg_price() const { if (units_sold) return revenue / units_sold; @@ -112,7 +112,7 @@ ostream &print(ostream &os, const Sales_data &item) 构造函数的名字和类名相同,没有返回类型,且不能被声明为`const`函数。构造函数在`const`对象的构造过程中可以向其写值。 ```c++ -struct Sales_data +struct Sales_data { // constructors added Sales_data() = default; @@ -129,12 +129,15 @@ struct Sales_data 如果类没有显式地定义构造函数,则编译器会为类隐式地定义一个默认构造函数,该构造函数也被称为合成的默认构造函数(synthesized default constructor)。对于大多数类来说,合成的默认构造函数初始化数据成员的规则如下: - 如果存在类内初始值,则用它来初始化成员。 + - 否则默认初始化该成员。 某些类不能依赖于合成的默认构造函数。 - 只有当类没有声明任何构造函数时,编译器才会自动生成默认构造函数。一旦类定义了其他构造函数,那么除非再显式地定义一个默认的构造函数,否则类将没有默认构造函数。 + - 如果类包含内置类型或者复合类型的成员,则只有当这些成员全部存在类内初始值时,这个类才适合使用合成的默认构造函数。否则用户在创建类的对象时就可能得到未定义的值。 + - 编译器不能为某些类合成默认构造函数。例如类中包含一个其他类类型的成员,且该类型没有默认构造函数,那么编译器将无法初始化该成员。 在C++11中,如果类需要默认的函数行为,可以通过在参数列表后面添加`=default`来要求编译器生成构造函数。其中`=default`既可以和函数声明一起出现在类的内部,也可以作为定义出现在类的外部。和其他函数一样,如果`=default`在类的内部,则默认构造函数是内联的。 @@ -157,7 +160,7 @@ Sales_data(const std::string &s): bookNo(s), units_sold(0), revenue(0) { } ``` -构造函数不应该轻易覆盖掉类内初始值,除非新值与原值不同。如果编译器不支持类内初始值,则所有构造函数都应该显式初始化每个内置类型的成员。 +构造函数不应该轻易覆盖掉类内初始值,除非新值与原值不同。如果编译器不支持类内初始值,则所有构造函数都应该显式初始化每个内置类型的成员。 ### 拷贝、赋值和析构(Copy、Assignment,and Destruction) @@ -168,10 +171,11 @@ Sales_data(const std::string &s): 使用访问说明符(access specifier)可以加强类的封装性: - 定义在`public`说明符之后的成员在整个程序内都可以被访问。`public`成员定义类的接口。 + - 定义在`private`说明符之后的成员可以被类的成员函数访问,但是不能被使用该类的代码访问。`private`部分封装了类的实现细节。 ```c++ -class Sales_data +class Sales_data { public: // access specifier added Sales_data() = default; @@ -181,9 +185,9 @@ public: // access specifier added Sales_data(std::istream&); std::string isbn() const { return bookNo; } Sales_data &combine(const Sales_data&); - + private: // access specifier added - double avg_price() const { return units_sold ? revenue/units_sold : 0; } + double avg_price() const { return units_sold ? revenue/units_sold : 0; } std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; @@ -199,13 +203,13 @@ private: // access specifier added 类可以允许其他类或函数访问它的非公有成员,方法是使用关键字`friend`将其他类或函数声明为它的友元。 ```C++ -class Sales_data +class Sales_data { // friend declarations for nonmember Sales_data operations added friend Sales_data add(const Sales_data&, const Sales_data&); friend std::istream &read(std::istream&, Sales_data&); friend std::ostream &print(std::ostream&, const Sales_data&); - + // other members and access specifiers as before public: Sales_data() = default; @@ -215,7 +219,7 @@ public: Sales_data(std::istream&); std::string isbn() const { return bookNo; } Sales_data &combine(const Sales_data&); - + private: std::string bookNo; unsigned units_sold = 0; @@ -235,6 +239,7 @@ std::ostream &print(std::ostream&, const Sales_data&); 封装的好处: - 确保用户代码不会无意间破坏封装对象的状态。 + - 被封装的类的具体实现细节可以随时改变,而无须调整用户级别的代码。 友元声明仅仅指定了访问权限,而并非一个通常意义上的函数声明。如果希望类的用户能调用某个友元函数,就必须在友元声明之外再专门对函数进行一次声明(部分编译器没有该限制)。 @@ -248,7 +253,7 @@ std::ostream &print(std::ostream&, const Sales_data&); 由类定义的类型名字和其他成员一样存在访问限制,可以是`public`或`private`中的一种。 ```c++ -class Screen +class Screen { public: // alternative way to declare a type member using a type alias @@ -268,7 +273,7 @@ public: 使用关键字`mutable`可以声明可变数据成员(mutable data member)。可变数据成员永远不会是`const`的,即使它在`const`对象内。因此`const`成员函数可以修改可变成员的值。 ```c++ -class Screen +class Screen { public: void some_member() const; @@ -293,7 +298,7 @@ void Screen::some_member() const 通过区分成员函数是否为`const`的,可以对其进行重载。在常量对象上只能调用`const`版本的函数;在非常量对象上,尽管两个版本都能调用,但会选择非常量版本。 ```c++ -class Screen +class Screen { public: // display overloaded on whether the object is const or not @@ -301,7 +306,7 @@ public: { do_display(os); return *this; } const Screen &display(std::ostream &os) const { do_display(os); return *this; } - + private: // function to do the work of displaying a Screen void do_display(std::ostream &os) const @@ -343,7 +348,7 @@ class Link_screen 除了普通函数,类还可以把其他类或其他类的成员函数声明为友元。友元类的成员函数可以访问此类包括非公有成员在内的所有成员。 ```c++ -class Screen +class Screen { // Window_mgr members can access the private parts of class Screen friend class Window_mgr; @@ -429,7 +434,9 @@ private: 成员函数中名字的解析顺序: - 在成员函数内查找该名字的声明,只有在函数使用之前出现的声明才会被考虑。 + - 如果在成员函数内没有找到,则会在类内继续查找,这时会考虑类的所有成员。 + - 如果类内也没有找到,会在成员函数定义之前的作用域查找。 ```c++ @@ -520,13 +527,17 @@ public: 默认初始化的发生情况: - 在块作用域内不使用初始值定义非静态变量或数组。 + - 类本身含有类类型的成员且使用合成默认构造函数。 + - 类类型的成员没有在构造函数初始值列表中显式初始化。 值初始化的发生情况: - 数组初始化时提供的初始值数量少于数组大小。 + - 不使用初始值定义局部静态变量。 + - 通过`T()`形式(`T`为类型)的表达式显式地请求值初始化。 类必须包含一个默认构造函数。 @@ -603,9 +614,13 @@ item.combine(static_cast(cin)); 聚合类满足如下条件: - 所有成员都是`public`的。 + - 没有定义任何构造函数。 + - 没有类内初始值。 + - 没有基类。 + - 没有虚函数。 ```c++ @@ -628,8 +643,11 @@ Data val1 = { 0, "Anna" }; 数据成员都是字面值类型的聚合类是字面值常量类。或者一个类不是聚合类,但符合下列条件,则也是字面值常量类: - 数据成员都是字面值类型。 + - 类至少含有一个`constexpr`构造函数。 + - 如果数据成员含有类内初始值,则内置类型成员的初始值必须是常量表达式。如果成员属于类类型,则初始值必须使用成员自己的`constexpr`构造函数。 + - 类必须使用析构函数的默认定义。 `constexpr`构造函数用于生成`constexpr`对象以及`constexpr`函数的参数或返回类型。 diff --git a/Chapter-8 The IO Library/README.md b/Chapter-8 The IO Library/README.md index 9905a61..bf7c015 100644 --- a/Chapter-8 The IO Library/README.md +++ b/Chapter-8 The IO Library/README.md @@ -3,12 +3,19 @@ 部分IO库设施: - `istream`:输入流类型,提供输入操作。 + - `ostream`:输出流类型,提供输出操作。 + - `cin`:`istream`对象,从标准输入读取数据。 + - `cout`:`ostream`对象,向标准输出写入数据。 + - `cerr`:`ostream`对象,向标准错误写入数据。 + - `>>`运算符:从`istream`对象读取输入数据。 + - `<<`运算符:向`ostream`对象写入输出数据。 + - `getline`函数:从`istream`对象读取一行数据,写入`string`对象。 ## IO类(The IO Classes) @@ -64,9 +71,13 @@ cin.setstate(old_state); // now reset cin to its old state 每个输出流都管理一个缓冲区,用于保存程序读写的数据。导致缓冲刷新(即数据真正写入输出设备或文件)的原因有很多: - 程序正常结束。 + - 缓冲区已满。 + - 使用操纵符(如`endl`)显式刷新缓冲区。 + - 在每个输出操作之后,可以用`unitbuf`操纵符设置流的内部状态,从而清空缓冲区。默认情况下,对`cerr`是设置`unitbuf`的,因此写到`cerr`的内容都是立即刷新的。 + - 一个输出流可以被关联到另一个流。这种情况下,当读写被关联的流时,关联到的流的缓冲区会被刷新。默认情况下,`cin`和`cerr`都关联到`cout`,因此,读`cin`或写`cerr`都会刷新`cout`的缓冲区。 `flush`操纵符刷新缓冲区,但不输出任何额外字符。`ends`向缓冲区插入一个空字符,然后刷新缓冲区。 @@ -142,11 +153,17 @@ ofstream out; // output file stream that is not associated with any file ![8-4](Images/8-4.png) - 只能对`ofstream`或`fstream`对象设定`out`模式。 + - 只能对`ifstream`或`fstream`对象设定`in`模式。 + - 只有当`out`被设定时才能设定`trunc`模式。 + - 只要`trunc`没被设定,就能设定`app`模式。在`app`模式下,即使没有设定`out`模式,文件也是以输出方式打开。 + - 默认情况下,即使没有设定`trunc`,以`out`模式打开的文件也会被截断。如果想保留以`out`模式打开的文件内容,就必须同时设定`app`模式,这会将数据追加写到文件末尾;或者同时设定`in`模式,即同时进行读写操作。 + - `ate`和`binary`模式可用于任何类型的文件流对象,并可以和其他任何模式组合使用。 + - 与`ifstream`对象关联的文件默认以`in`模式打开,与`ofstream`对象关联的文件默认以`out`模式打开,与`fstream`对象关联的文件默认以`in`和`out`模式打开。 默认情况下,打开`ofstream`对象时,文件内容会被丢弃,阻止文件清空的方法是同时指定`app`或`in`模式。 diff --git a/Chapter-9 Sequential Containers/README.md b/Chapter-9 Sequential Containers/README.md index b29770c..5bfa8e7 100644 --- a/Chapter-9 Sequential Containers/README.md +++ b/Chapter-9 Sequential Containers/README.md @@ -20,12 +20,19 @@ 容器选择原则: - 除非有合适的理由选择其他容器,否则应该使用`vector`。 + - 如果程序有很多小的元素,且空间的额外开销很重要,则不要使用`list`或`forward_list`。 + - 如果程序要求随机访问容器元素,则应该使用`vector`或`deque`。 + - 如果程序需要在容器头尾位置插入/删除元素,但不会在中间位置操作,则应该使用`deque`。 + - 如果程序只有在读取输入时才需要在容器中间位置插入元素,之后需要随机访问元素。则: + - 先确定是否真的需要在容器中间位置插入元素。当处理输入数据时,可以先向`vector`追加数据,再调用标准库的`sort`函数重排元素,从而避免在中间位置添加元素。 + - 如果必须在中间位置插入元素,可以在输入阶段使用`list`。输入完成后将`list`中的内容拷贝到`vector`中。 + - 不确定应该使用哪种容器时,可以先只使用`vector`和`list`的公共操作:使用迭代器,不使用下标操作,避免随机访问。这样在必要时选择`vector`或`list`都很方便。 ## 容器库概览(Container Library Overview) @@ -43,7 +50,9 @@ 假定`begin`和`end`构成一个合法的迭代器范围,则: - 如果`begin`等于`end`,则范围为空。 + - 如果`begin`不等于`end`,则范围内至少包含一个元素,且`begin`指向该范围内的第一个元素。 + - 可以递增`begin`若干次,令`begin`等于`end`。 ```c++ @@ -198,7 +207,9 @@ a2 = {0}; // error: cannot assign to an array from a braced list 两个容器的比较实际上是元素的逐对比较,其工作方式与`string`的关系运算符类似: - 如果两个容器大小相同且所有元素对应相等,则这两个容器相等。 + - 如果两个容器大小不同,但较小容器中的每个元素都等于较大容器中的对应元素,则较小容器小于较大容器。 + - 如果两个容器都不是对方的前缀子序列,则两个容器的比较结果取决于第一个不等元素的比较结果。 ```c++ @@ -335,12 +346,19 @@ elem1 = slist.erase(elem1, elem2); // after the call elem1 == elem2 向容器中添加或删除元素可能会使指向容器元素的指针、引用或迭代器失效。失效的指针、引用或迭代器不再表示任何元素,使用它们是一种严重的程序设计错误。 - 向容器中添加元素后: + - 如果容器是`vector`或`string`类型,且存储空间被重新分配,则指向容器的迭代器、指针和引用都会失效。如果存储空间未重新分配,指向插入位置之前元素的迭代器、指针和引用仍然有效,但指向插入位置之后元素的迭代器、指针和引用都会失效。 + - 如果容器是`deque`类型,添加到除首尾之外的任何位置都会使迭代器、指针和引用失效。如果添加到首尾位置,则迭代器会失效,而指针和引用不会失效。 + - 如果容器是`list`或`forward_list`类型,指向容器的迭代器、指针和引用仍然有效。 + - 从容器中删除元素后,指向被删除元素的迭代器、指针和引用失效: + - 如果容器是`list`或`forward_list`类型,指向容器其他位置的迭代器、指针和引用仍然有效。 + - 如果容器是`deque`类型,删除除首尾之外的任何元素都会使迭代器、指针和引用失效。如果删除尾元素,则尾后迭代器失效,其他迭代器、指针和引用不受影响。如果删除首元素,这些也不会受影响。 + - 如果容器是`vector`或`string`类型,指向删除位置之前元素的迭代器、指针和引用仍然有效。但尾后迭代器总会失效。 必须保证在每次改变容器后都正确地重新定位迭代器。