c++ primer 第十九章特殊工具与技术

19.1 控制内存分配

19.1.1 重载new和delete

调用new表达式执行了三步操作:第一步,调用名为operator new的标准库函数分配一块内存空间。第二步,编译器运行构造函数传入初始值。第三步,对象分配空间并构造完成,返回指向它的指针。

调用delete表达式执行了两步操作:第一步,对参数对应空间的对象执行析构函数。第二步,调用operator delete释放空间。

可以自定义operator new与operator delete函数。既可以是全局函数,也可以是成员函数。优先在分配的类的作用域里查找,之后到全局,再到标准库。使用::new可以直接调用全局作用域的版本。

operator new接口和operator delete共有8个接口,4个可以抛出异常,另外四个承诺不抛出。自定义这8个版本的函数必须在全局作用域或类作用域,定义为类成员函数时会隐式声明为静态类型。

也可以自定义以上8个接口以外的自定义版本,但是不能重载void *operator new(size_t, void*);这个函数。

分清operator new和new表达式的区别。new和delete符号的意思不能变。

malloc与operator new类似,free和operator delete类似。

19.1.2 定位new表达式

除了new和delete函数,也可以直接调用operator new和operator delete函数。

operator new与allocator类类似,但是operator new分配的空间无法使用construct函数构造。

定位new可以用来构造对象。当仅通过一个地址值调用时,定位new使用operator new(size_t, void*)分配内存。这个函数仅仅返回指针实参而不是真正的分配。定位new允许我们在一个特定的、预先分配的内存地址上构造对象。

定位new与construct的区别在于,传递的指针无需是allocator分配的。

显示的析构函数调用与其它成员函数没有区别。显示调用析构函数不会释放该对象所在的空间。

19.2 运行时类型识别

运行时类型识别(RTTI)由两个运算符实现:

  • typeid运算符,返回表达式的类型。
  • dynamic_cast运算符,将基类指针或引用安全转换为派生类的指针或引用。

RTTI运算符一般在无法使用虚函数时候使用。使用时需要清楚转换的类型并检查是否转换成功。

19.2.1 dynamic_cast 运算符

dynamic_cast运算符的参数需要满足三个条件之一:

  1. e的类型是目标type的公有派生类
  2. e的类型是目标type的公有基类(但指向的对象应该也有限制)
  3. e的类型就是type的类型。

返回结果是0的时候表示转换失败,在转换引用失败是抛出bad_cast。

指针类型的dynamic_cast

if(Derived *dp = dynamic_cast<Derived*>(bp)) 
{}
else {}

引用类型的dynamic_cast

void f(const Base& b) {
    try{
        const Derived&d = dynamic_cast<const Derived&>(b);
        } catch(bad_cast) {
        }

19.2.2 typeid 运算符

typeid(e)返回e的类型,e可以是任意表达式或者类型的名字。

使用时忽略顶层const,不会把数组或函数自动转为指针。

对于没有虚函数的类返回静态类型,否则检查运行时类型。

typeid作用于对象,因此检查指针指向的对象类型时需要解引用。

typeid是否需要进行运行时检查决定了表达式是否被求值。

19.2.3 使用RTTI

检查一个派生体系里的对象是否相等,单纯使用虚函数equal无法完成,因为参数必须是基类指针或引用,此时无法调用派生类对象的成员。因此需要配合dynamic_cast使用。而先使用typeid比较类型也可以直接判断不同类型的对象。

19.2.4 type_info类

type_info类的精确定义根据编译器有所不同。

type_info没有默认构造函数,拷贝和移动构造函数是删除的。创建type_info对象唯一方式是使用typeid运算符。typeid的调用返回结果是一个type_info对象。

19.3 枚举类型

枚举类型可以将一组整型常量组织在一起。每个枚举类型也是一个新的数据类型。枚举属于字面值常量类型。

C++中有两种枚举:限定作用域的枚举类型使用enum class加名字和花括号初始值。enum class open_models {input, output, append};
不限定作用域的枚举类型省略掉class,名字可选。

限定作用域的枚举类型的枚举成员作用域与常规相同,枚举类型的作用域外不可访问。而不限定作用域的枚举类型中,枚举成员可以在当前作用域直接访问。

默认情况枚举成员的值从0开始依次加1,但是枚举值也可以指定而且可以不唯一。

可以把枚举类型用到switch语句,非类型模板形参或者类的初始化静态数据成员。

enum可以定义新的类型。非限定作用域的枚举成员可以隐式转换为int,限定作用域的不可以。int不能直接转换为枚举类型。

enum可以定义枚举成员的数据类型,限定作用域的默认为int,不限定作用域的没有默认类型而是足够大。

enum类型可以提前声明。声明里必须指定成员的大小。不限定作用域的必须指定类型,而限定作用域的隐式为int。定义需要与声明匹配。

初始化一个enum对象必须使用另一个enum对象或者枚举成员。不能将整型实参传给enum形参,但可以将不限定作用域的enum成员或对象传给整型形参。一般提升为int。

19.4 类成员指针

成员指针指可以指向类的非静态成员的指针。成员指针指向某个类的成员但不指定所属的对象,直到使用时才提供对象。

const string Screen::*pdata;声明的是指向Screen类中string成员的指针。
赋值为pdata = &Screen::contents;

使用成员指针时使用.*和->*两种指针访问运算符,分别对应对象类型调用与指针类型的调用。

常规的访问控制规则对成员指针同样有效。因此不能在类外部指向类的私有成员。

函数可以返回类成员指针。

19.4.2 成员函数指针

指向成员函数的指针最简单的是使用auto,如auto pmf = &Screen::get_cursor;

指向成员函数的指针同样需要说明返回类型和形参,如果是const形式或引用形式,也需要包含。

char (Screen::*pmf2)(Screen::pos, Screen::pos) const;
pmf2 = &Screen::get;

与普通函数指针不同,成员函数和函数指针之间不存在自动转换。

调用成员函数指针

Screen myScreen, *pScreen = &myScreen;
char c1 = (pScreen->*pmf)();
char c2 = (myScreen.*pmf2)(0,0);

使用成员指针的类型别名或typedef让成员指针更容易理解:
using Action = char (Screen::*)(Screen::pos, Screen::pos) const;
Action表示这个对应类型函数的指针形式。Action get = &Screen::get;

指向成员函数的指针也可以作为函数返回类型或形参,而且可以有默认实参。

成员指针函数可以作为一个指针类型数组,对应不同情况调用不同的函数。

19.4.3 将成员函数用作可调用对象

和普通函数指针不同,成员函数指针不是一个可调用对象。

当我们使用find_if之类的标准算法时,传递成员函数指针无法被调用。

使用function模板库类型可以自动根据参数调整函数指针的调用形式。

function<bool (const string&)> fcn = &string::empty;
find_if(svec.begin(),svec.end(),fcn);

若it为svec的迭代器,那么传入普通函数指针时调用fcn(*it),这对成员函数指针不适用。而使用了function模板库之后,如果传入成员函数指针,会相当于调用((*it).*p)()。其中p是fcn中保存的成员函数指针。

使用men_fn函数也可以让编译器来推断成员的类型。使用men_fn(&string::empty)生成一个可调用对象,接受一个string实参,返回一个bool。

也可以使用bind生成一个可调用对象。auto it = find_if(svec.begin(), svec.end(), bind(&string::empty,_1));

19.5 嵌套类

定义在一个类内部的类叫做嵌套类。

嵌套类是个独立的类,与外层类基本没有关系。嵌套类不包含外部类定义的成员。外层类不包含嵌套类的成员。

嵌套类名在外层类作用域内可见,外层类之外不可见。
嵌套类成员种类与普通类一样。
嵌套类在其外层类中定义了一个类型成员。

在外层类之外可以定义一个嵌套类,但是声明必须在外层类之内。

嵌套类的静态成员定义,在类外使用嵌套的作用域。

嵌套类是外层类内部的一个作用域,因此可以像普通嵌套作用域一样进行名字查找。

嵌套类和外层类的对象是相互独立的。

19.6 union:一种节省空间的类

union是一种特殊的类。可以有多个数据成员,但任意时刻只有一个数据成员有值。union也定义了一种类型。

union成员不能是引用类型。默认成员是公有类型。

union可以定义成员函数,但不能继承和派生,因此不能有虚函数。

union默认是未初始化的,可以使用一个花括号的初始值来初始化union对象。

为union的一个成员赋值会令其它成员变成未定义的状态。因此使用时必须清楚知道其中存储的类型。

匿名union没有名字,只能有公有数据成员,在定义所在的作用域可以直接调用成员名。

含有类类型成员的union需要在赋值时调用类型成员的构造函数与析构函数。

一般对于union来说,包含了类类型的成员的情况构造或销毁的操作很复杂,一般使用一个类来管理。比如书上的Token类例子,例子中使用一个enum枚举类型来追踪union中存储的值类型。

union中类成员无法自动销毁。为union中类成员赋值时使用定位new操作。

19.7 局部类

类可以定义在某个函数的内部,这种叫做局部类。局部类不允许静态成员。

局部类只能访问外层作用域定义的类型名、静态变量以及枚举成员。普通局部变量不能被使用。

局部类的名字查找与普通作用域相似。

19.8 固有的不可移植的特性

C++中有些因机器而异的特性叫做不可移植特性。

19.8.1 位域

类可以将其非静态数据成员定义为位域。位域的类型必须是整型或枚举类型。通常情况使用一个无符号类型保存一个位域。

取地址运算符不能用于位域。

19.8.2 volatile限定符

当对象的值可能在程序的控制或检测之外被改变时,可以声明为volatile。

volatile与const不矛盾。只有volatile的成员函数才能被volatile的对象调用。

合成的拷贝/移动构造函数以及赋值运算符不能为volatile对象初始化和赋值。

19.8.3 链接指示:extern ”C“

C++可以调用其他语言编写的函数,如C语言。其它语言编写的函数也要在C++中声明。使用链接指示来指出非C++语言。

声明一个非C++的函数:

extern ”C" size_t strlen(const char*);
extern "C" {
int strcmp(const char*, const char*);
char* strcat(char*, const char*);
}

链接指示可以直接用花括号包含一个头文件,表示这个头文件所有函数声明都是其它语言。

extern “C” void(*pf)(int);是一个 指向C语言函数的指针。指向C函数的指针与指向C++函数的指针是两种类型的变量。

链接指示对生命中的返回类型或形参类型都有效。

也可以通过使用链接指示对函数进行定义,使一个C++函数可以在其它语言中使用。但是需要注意返回类型和形参类型的限制。

C语言不支持重载,因此链接指示为C语言的不能够说明同名函数。

猜你喜欢

转载自blog.csdn.net/qq_25037903/article/details/83662830