c++ primer 摘录

7.8.4. 实参类型转换
为了确定最佳匹配,编译器将实参类型到相应形参类型转换划分等级。转换等级
以降序排列如下:
1. 精确匹配。实参与形参类型相同。
2. 通过类型提升实现的匹配(第 5.12.2 节)。
3. 通过标准转换实现的匹配(第 5.12.3 节)。
4. 通过类类型转换实现的匹配(第 14.9 节将介绍这类转换)。
内置类型的提升和转换可能会使函数匹配产生意想不到的结
果。但幸运的是,设计良好的系统很少会包含与下面例子类似
的形参类型如此接近的函数。
通过这些例子,学习并加深了解特殊的函数匹配和内置类型之间的一般关系。

 

7.9. 指向函数的指针

 

用 typedef 简化函数指针的定义
函数指针类型相当地冗长。使用 typedef 为指针类型定义同义词,可将函
数指针的使用大大简化:(第 2.6 节):
typedef bool (*cmpFcn)(const string &, const string &);
该定义表示 cmpFcn 是一种指向函数的指针类型的名字。该指针类型为“指
向返回 bool 类型并带有两个 const string 引用形参的函数的指针”。在要使
用这种函数指针类型时,只需直接使用 cmpFcn 即可,不必每次都把整个类型声
明全部写出来。

 

通过指针调用函数
指向函数的指针可用于调用它所指向的函数。可以不需要使用解引用操作
符,直接通过指针调用函数:
cmpFcn pf = lengthCompare;
lengthCompare("hi", "bye"); // direct call
pf("hi", "bye"); // equivalent call: pf1 implicitly
dereferenced
(*pf)("hi", "bye"); // equivalent call: pf1 explicitly
dereferenced
如果指向函数的指针没有初始化,或者具有 0 值,则该指针不
能在函数调用中使用。只有当指针已经初始化,或被赋值为指
向某个函数,方能安全地用来调用函数。
函数

 

重载和const 形参
仅当形参是引用或指针时,形参是否为 const 才有影响。

 

12.4.1. 构造函数初始化式

 

从概念上讲,可以认为构造函数分两个阶段执行:(1)初始化阶段;(2)
普通的计算阶段。计算阶段由构造函数函数体中的所有语句组成。

 

有些成员必须在构造函数初始化列表中进行初始化。对于这样
的成员,在构造函数函数体中对它们赋值不起作用。没有默认
构造函数的类类型的成员,以及 const 或引用类型的成员,不
管是哪种类型,都必须在构造函数初始化列表中进行初始化。

 

12.4.4. 隐式类类型转换

 

可以用单个实参来调用的构造函数定义了从形参类型到该类类
型的一个隐式转换。

 

抑制由构造函数定义的隐式转换

 

可以通过将构造函数声明为 explicit,来防止在需要隐式转换的上下文中
使用构造函数

 

复制构造函数、赋值操作符和析构函数总称为复制控制。编译器自动实现这
些操作,但类也可以定义自己的版本。

 

13.1.3. 禁止复制

为了防止复制,类必须显式声明其复制构造函数为 private。

 

如果复制构造函数是私有的,将不允许用户代码复制该类类型的对象,编译
器将拒绝任何进行复制的尝试。


然而,类的友元和成员仍可以进行复制。如果想要连友元和成员中的复制也
禁止,就可以声明一个(private)复制构造函数但不对其定义。


声明而不定义成员函数是合法的,但是,使用未定义成员的任何尝试将导致
链接失败。通过声明(但不定义)private 复制构造函数,可以禁止任何复制类
类型对象的尝试:用户代码中复制尝试将在编译时标记为错误,而成员函数和友
元中的复制尝试将在链接时导致错误。

 

何时编写显式析构函数

 

如果类需要析构函数,则它也需要赋值操作符和复制构造函
数,这是一个有用的经验法则。这个规则常称为三法则,指
的是如果需要析构函数,则需要所有这三个复制控制成员。

 

析构函数与复制构造函数或赋值操作符之间的一个重要区别是,即使我们编
写了自己的析构函数,合成析构函数仍然运行。(运行时,先运行自定义的析构函数,在运行合成的析构函数)

 

重载操作符必须具有至少一个类类型或枚举类型(第 2.7 节)
的操作数。这条规则强制重载操作符不能重新定义用于内置类
型对象的操作符的含义。

 

不再具备短路求值特性


重载操作符并不保证操作数的求值顺序,尤其是,不会保证内置逻辑 AND、
逻辑 OR(第 5.2 节)和逗号操作符(第 5.9 节)的操作数求值。在 && 和 ||
的重载版本中,两个操作数都要进行求值,而且对操作数的求值顺序不做规定。
因此,重载 &&、|| 或逗号操作符不是一种好的做法。

 

 选择成员或非成员实现


为类设计重载操作符的时候,必须选择是将操作符设置为类成员还是普通非
成员函数。在某些情况下,程序员没有选择,操作符必须是成员;在另一些情况
下,有些经验原则可指导我们做出决定。下面是一些指导原则,有助于决定将操
作符设置为类成员还是普通非成员函数:
• 赋值(=)、下标([])、调用(())和成员访问箭头(->)等操作符必
须定义为成员,将这些操作符定义为非成员函数将在编译时标记为错误。
• 像赋值一样,复合赋值操作符通常应定义为类的成员,与赋值不同的是,
不一定非得这样做,如果定义非成员复合赋值操作符,不会出现编译错误。
• 改变对象状态或与给定类型紧密联系的其他一些操作符,如自增、自减和
解引用,通常就定义为类成员。
• 对称的操作符,如算术操作符、相等操作符、关系操作符和位操作符,最
好定义为普通非成员函数。

 

IO 对象不可复制或赋值

14.8. 调用操作符和函数对象

 

函数调用操作符必须声明为成员函数。一个类可以定义函数调
用操作符的多个版本,由形参的数目或类型加以区别。

定义了调用操作符的类,其对象常称为函数对象,即它们是行为类似函数的对象。

 

14.9.2. 转换操作符

 

转换操作符是一种特殊的类成员函数。它定义将类类型值转变为其他类型值
的转换。转换操作符在类定义体内声明,在保留字 operator 之后跟着转换的目
标类型

转换函数采用如下通用形式:
operator type();

转换函数必须是成员函数,不能指定返回类型,并且形参表必
须为空。

 

基类类型引用和指针的关键点在于静态类型(在编译时可知的
引用类型或指针类型)和动态类型(指针或引用所绑定的对象
的类型这是仅在运行时可知的)可能不同。

 

接口继承实现继承
public 派生类继承基类的接口,它具有与基类相同的接口。设计良好的类
层次中,public 派生类的对象可以用在任何需要基类对象的地方。
使用 private 或 protected 派生的类不继承基类的接口,相反,这些派生
通常被称为实现继承。派生类在实现中使用被继承但继承基类的部分并未成为其
接口的一部分。

 

15.2.6. 友元关系与继承

友元关系不能继承。基类的友元对派生类的成员没有特殊访问
权限。如果基类被授予友元关系,则只有基类具有特殊访问权
限,该基类的派生类不能访问授予友元关系的类。

 

15.2.7. 继承与静态成员
如果基类定义 static 成员(第 12.6 节),则整个继承层次中只有一个这
样的成员。无论从基类派生出多少个派生类,每个 static 成员只有一个实例。
static 成员遵循常规访问控制:如果成员在基类中为 private,则派生类不能
访问它。假定可以访问成员,则既可以通过基类访问 static 成员,也可以通过
派生类访问 static 成员。一般而言,既可以使用作用域操作符也可以使用点或
箭头成员访问操作符。

 

用派生类对象对基类对象进行初始化或赋值

用 Bulk_item 类型的对象调用 Item_base 类的复制构造函数或赋值操作符
时,将发生下列步骤:
1)将 Bulk_item 对象转换为 Item_base 引用,这仅仅意味着将一个
Item_base 引用绑定到 Bulk_item 对象。
2) 将该引用作为实参传给复制构造函数或赋值操作符。
3)那些操作符使用 Bulk_item 的 Item_base 部分分别对调用构造函数或
赋值的 Item_base 对象的成员进行初始化或赋值。
4)一旦操作符执行完毕,对象即为 Item_base。它包含 Bulk_item 的
Item_base 部分的副本,但实参的 Bulk_item 部分被忽略。


在这种情况下,我们说 bulk 的 Bulk_item 部分在对 item 进行初始化或赋
值时被“切掉”了。Item_base 对象只包含基类中定义的成员,不包含由任意派
生类型定义的成员,Item_base 对象中没有派生类成员的存储空间。

 

15.6. 纯虚函数

 

含有(或继承)一个或多个纯虚函数的类是抽象基类。除了作
为抽象基类的派生类的对象的组成部分,不能创建抽象类型的
对象。

 

17.1. 异常处理

 

通过异常我们能够将问题的检测和问题的解决分离,这样程序
的问题检测部分可以不必了解如何处理问题。

 

17.1.3. 捕获异常

 

查找匹配的处理代码

 

异常与 catch 异常说明符匹配的规则比匹配实参和形参类型的规则更严格,
大多数转换都不允许——除下面几种可能的区别之外,异常的类型与 catch 说
明符的类型必须完全匹配:
• 允许从非 const 到 const 的转换。也就是说,非 const 对象的 throw
可以与指定接受 const 引用的 catch 匹配。
• 允许从派生类型型到基类类型的转换。
• 将数组转换为指向数组类型的指针,将函数转换为指向函数类型的适当指
针。

 

用类管理资源分配


对析构函数的运行导致一个重要的编程技术的出现,它使程序更为异常安全
的。异常安全的意味着,即使发生异常,程序也能正确操作。在这种情况下,“安
全”来自于保证“如果发生异常,被分配的任何资源都适当地释放”。
通过定义一个类来封闭资源的分配和释放,可以保证正确释放资源。这一技
术常称为“资源分配即初始化”,简称 RAII。

 

auto_ptr 对象的复制和赋值是破坏性操作


auto_ptr 和内置指针对待复制和赋值有非常关键的重要区别。
当复制 auto_ptr 对象或者将它的值赋给其他 auto_ptr 对象
的时候,将基础对象的所有权从原来的 auto_ptr 对象转给副
本,原来的 auto_ptr 对象重置为未绑定状态。

 

与其他复制或赋值操作不同,auto_ptr 的复制和赋值改变右操作数,因此,
赋值的左右操作数必须都是可修改的左值。

 

因为复制和赋值是破坏性操作,所以auto_ptrs 不能将
auto_ptr 对象存储在标准容器中。标准库的容器类要求在复制
或赋值之后两个对象相等,auto_ptr 不满足这一要求,如果将
ap2 赋给 ap1,则在赋值之后 ap1 != ap2,复制也类似。

 

定义异常说明

异常说明跟在函数形参表之后。一个异常说明在关键字 throw 之后跟着一
个(可能为空的)由圆括号括住的异常类型列表:
void recoup(int) throw(runtime_error);

 

空说明列表指出函数不抛出任何异常:
void no_problem() throw();

 

如果一个函数声明没有指定异常说明,则该函数可以抛出任意
类型的异常。

 

每个命名空间是一个作用域


除了在函数或其他作用域内部,头文件不应该包含
using 指示或 using 声明。在其顶级作用域包含
using 指示或 using 声明的头文件,具有将该名字注
入包含该头文件的文件中的效果。头文件应该只定义作
为其接口的一部分的名字,不要定义在其实现中使用的
名字。

 

警告:避免 Using 指示


using 指示注入来自一个命名空间的所有名字,它的使用是靠不住的:
只用一个语句,命名空间的所有成员名就突然可见了。虽然这个方法看
似简单,但也有它自身的问题。如果应用程序使用许多库,并且用 using
指示使得这些库中的名字可见,那么,全局命名空间污染问题就重新出
现。
而且,当引入库的新版本的时候,正在工作的程序可能会编译失败。如
果新版本引入一个与应用程序正在使用的名字冲突的名字,就会引发这
个问题。
另一个问题是,由 using 指示引起的二义性错误只能在使用处检测,这
个后来的检测意味着,可能在特定库引入很久之后才引发冲突,如果程
序开始使用该库的新部分,就可能引发先前未检测到的冲突。
相对于依赖于 using 指示,对程序中使用的每个命名空间名字使用
using 声明更好,这样做减少注入到命名空间中的名字数目,由 using
声明引起的二义性错误在声明点而不是使用点检测,因此更容易发现和
修正。

 

接受类类型形参(或类类型指针及引用形参)的函数(包括
重载操作符),以及与类本身定义在同一命名空间中的函数
(包括重载操作符),在用类类型对象(或类类型的引用及
指针)作为实参的时候是可见的。

 

18.1.1. C++ 中的内存分配

 

对未构造的内存中的对象进行赋值而不是初始化,其行为是未
定义的。对许多类而言,这样做引起运行时崩溃。赋值涉及删
除现存对象,如果没有现存对象,赋值操作符中的动作就会有
灾难性效果。

 

18.1.3. operator new 函数和 operator delete 函数


前几节使用 vector 类说明了怎样使用 allocator 类来管理用于类的内部
数据存储的内存池,下面三节将介绍怎样用更基本的标准库机制实现相同的策
略。
首先,需要对 new 和 delete 表达式怎样工作有更多的理解。当使用 new
表达式
// new expression
string * sp = new string("initialized");
的时候,实际上发生三个步骤。首先,该表达式调用名为 operator new 的标准
库函数,分配足够大的原始的未类型化的内存,以保存指定类型的一个对象;接
下来,运行该类型的一个构造函数,用指定初始化式构造对象;最后,返回指向
新分配并构造的对象的指针。
当使用 delete 表达式
delete sp;
删除动态分配对象的时候,发生两个步骤。首先,对 sp 指向的对象运行适
当的析构函数;然后,通过调用名为 operator delete 的标准库函数释放该对
象所用内存。

 

术语对比:new 表达式与 operator new 函数


标准库函数 operator new 和 operator delete 的命名容易让人误解。

与其他 operator 函数(如 operator=)不同,这些函数没有重载 new 或
delete 表达式,实际上,我们不能重定义 new 和 delete 表达式的行
为。
通过调用 operator new 函数执行 new 表达式获得内存,并接着在该内
存中构造一个对象,通过撤销一个对象执行 delete 表达式,并接着调
用 operator delete 函数,以释放该对象使用的内存。
因为 new(或 delete)表达式与标准库函数同名,所以
二者容易混淆。

 

一般而言,使用 allocator 比直接使用operator new
和 operator delete 函数更为类型安全。

 

operator new 和 operator delete 操作符返回void*

 

成员 new 和 delete 函数

 

 

这些函数隐式地为静态函数(第 12.6.1 节),不必显式地将它们声明为
static,虽然这样做是合法的。成员 new 和 delete 函数必须是静态的,因为
它们要么在构造对象之前使用(operator new),要么在撤销对象之后使用
(operator delete),因此,这些函数没有成员数据可操纵。像任意其他静态
成员函数一样,new 和 delete 只能直接访问所属类的静态成员。

如果类定义了这两个成员中的一个,它也应该定义另一个。

 

18.2. 运行时类型识别

通过下面两个操作符提供 RTTI:
1. typeid 操作符,返回指针或引用所指对象的实际类型。
2. dynamic_cast 操作符,将基类类型的指针或引用安全地转换为派生类型
的指针或引用。


这些操作符只为带有一个或多个虚函数的类返回动态类型信
息,对于其他类型,返回静态(即编译时)类型的信息。


对于带虚函数的类,在运行时执行 RTTI 操作符,但对于其他类型,在编译
时计算 RTTI 操作符。

 

如果指针 p 的值是 0,那么,如果 p 的类型是带虚函数的类型,则
typeid(*p) 抛出一个 bad_typeid 异常;如果 p 的类型没有定义任何虚函数,
则结果与 p 的值是不相关的。正像计算表达式 sizeof(第 5.8 节)一样,编
译器不计算 *p,它使用 p 的静态类型,这并不要求 p 本身是有效指针。

 

18.2.4. type_info 类

 

默认构造函数和复制构造函数以及赋值操作符都定义为 private,所以不能
定义或复制 type_info 类型的对象。程序中创建 type_info 对象的唯一方法是
使用 typeid 操作符。

 

18.5. 联合:节省空间的类

没有静态数据成员、引用成员或类数据成员

某些(但不是全部)类特征同样适用于 union。例如,像任何类一样,union
可以指定保护标记使成员成为公用的、私有的或受保护的。默认情况下,union 表
现得像 struct:除非另外指定,否则 union 的成员都为 public 成员。


union 也可以定义成员函数,包括构造函数和析构函数。但是,union 不能
作为基类使用,所以成员函数不能为虚数。
union 不能具有静态数据成员或引用成员,而且,union 不能具有定义了构
造函数、析构函数或赋值操作符的类类型的成员.

 

18.6. 局部类

局部类的所有成员(包括函数)必须完全定义在类定义体内部,
因此,局部类远不如嵌套类有用。

类似地,不允许局部类声明 static 数据成员,没有办法定义它们。

 

局部类不能使用函数作用域中的变量


局部类可以访问的外围作用域中的名字是有限的。局部类只能访问在外围作
用域中定义的类型名、static 变量(第 7.5.2 节)和枚举成员,不能使用定义
该类的函数中的变量:

 

18.7.1. 位域


可以声明一种特殊的类数据成员,称为位域,来保存特定的位数。当程序需
要将二进制数据传递给另一程序或硬件设备的时候,通常使用位域。
位域在内存中的布局是机器相关的。

 

地址操作符(&)不能应用于位域,所以不可能有引用类位域的指针,位域
也不能是类的静态成员。

 

18.7.2. volatile 限定符


volatile 的确切含义与机器相关,只能通过阅读编译器文档来
理解。使用 volatile 的程序在移到新的机器或编译器时通常
必须改变。

 

直接处理硬件的程序常具有这样的数据成员,它们的值由程序本身直接控制
之外的过程所控制。例如,程序可以包含由系统时钟更新的变量。当可以用编译
器的控制或检测之外的方式改变对象值的时候,应该将对象声明为 volatile。
关键字 volatile 是给编译器的指示,指出对这样的对象不应该执行优化。

18.7.3. 链接指示 extern "C"

C++ 程序有时需要调用用其他程序设计语言编写的函数,最常见的一语言是
C 语言。像任何名字一样,必须声明用其他语言编写的函数的名字,该声明必须
指定返回类型和形参表。编译器按处理普通 C++ 函数一样的方式检查对外部语
言函数的调用,但是,编译器一般必须产生不同的代码来调用用其他语言编写的
函数。C++ 使用链接指示指出任意非 C++ 函数所用的语言。

 

用途

1.声明非 C++ 函数(声明)

2.导出 C++ 函数到其他语言(定义)

通过对函数定义使用链接指示,使得用其他语言编写的程序可以使用 C++ 函数.

用链接指示定义的函数的每个声明都必须使用相同的链接指示。

 

reinterpret_cast适用于静态类型的指针和引用之间的转换,dynamic_cast适用于动态类型(带虚函数)的指针和引用之间转换,具体来说就是,将基类类型的指针或引用安全地转换为派生类型的指针或引用。

这两种转换只是告诉编译器以什么方式解释指针或引用所指向的那块内存,并没有引起对象内存的操作,而static_cast会进行实际的转换工作,将原对象的内存的内容复制给转换后所产生的对象的内存,如果转换后的对象比原对象小,则发生slice down。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值