类成员指针
成员指针(pointer to member)是可以指向类的非静态成员的指针。成员指针指向类的成员,而非类的对象。当初始化一个成员指针时,我们令其指向类的某个成员,但是不指定该成员所属的对象;直到使用成员指针时,才提供成员所属的对象。
1 | class Screen |
数据成员指针
数据成员指针必须包含所属的类,因此,我们必须在*之前添加classname::以表示当前定义的指针可以指向包含classname的成员。我们将取地址运算符作用于Screen类的成员而非内存中的一个该类对象。定义数据成员指针方式:
1 | const string Screen::*pdata = &Screen::contents; |
当我们初始化一个成员指针或为成员指针赋值时,该指针并没有指向任何数据。.*和->*
运算符解引用数据成员指针获得该对象的成员:
1 | Screen myScreen, *pScreen = &myScreen; |
返回数据成员指针的函数
因为数据成员一般情况下是私有的,所以我们通常不能直接获得数据成员的指针。最好定义一个函数,令其返回值是指向该成员的指针:
1 | class Screen { |
成员函数指针
我们同样可以定义指向类的成员函数的指针。最简单的方法是使用autuo类推断类型:
1 | //pmf是一个指针,它可以指向Screen的某个常量成员函数 |
如果成员函数存在重载,我们必须显式声明函数类型以明确指出我们想要使用的是哪个函数。例如,我们可以声明一个指针,令其指向含有两个形参的get:
1 | char (Screen::*pmf2)(Screen::pos, Screen::pos) const; //Screen::*两端的括号必不可少 |
因为函数调用运算符的优先级较高,所有在声明指向成员函数的指针并使用这样的指针进行函数调用时,括号必不可少:(C::*p)(parms)和(obj.*p)(args)
。
使用成员指针的类型别名
使用类型别名或typedef可以让成员指针更容易理解。
1 | //Action是一种可以指向Screen成员函数的指针,它接受两个pos实参,返回一个char |
成员指针函数表
将函数指针存入一个函数表中,如果一个类含有几个相同类型的成员,则成员指针函数表可以帮助我们从这些成员中选择一个。
1 |
|
将成员函数用作可调用对象
成员函数的指针与普通函数指针不同,成员指针不是一个可调用对象,这样的指针不支持函数调用运算符。所以我们不能直接将一个指向成员函数的指针传递给算法。
使用标准库模板function生成一个可调用对象
1 | function<bool (const string&)> fcn = &string::empty; |
通常情况下,执行成员函数的对象将被传给隐式的this形参。
当一个function对象包含有一个指向成员函数的指针时,function类知道它必须使用正确的指向类成员的指针运算符来执行函数调用。也就是说function将如下的函数调用:
1 | //假设it是find_if内部的迭代器,则*it是给定范围内的一个对象 |
转换成如下形式:
1 | if(((*it).*p)()) //假设p是fcn内部的一个指向成员函数的指针 |
使用mem_fn生成一个可调用对象
通过使用标准库功能mem_fn
来让编译器负责推断成员的类型,它也可以从成员指针生成一个可调用对象。mem_fn也定义在functional头文件中。和function不同的是,mem_fn可以根据成员指针的类型推断可调用对象的类型,而无须用户显式地指定:
1 | find_if(svec.begin(), svec.end(), mem_fn(&string::empty)); |
mem_fn生成的可调用对象可以通过对象调用,也可以通过指针调用:
1 | auto f = mem_fn(&string.empty); //f接受一个string或一个string* |
使用bind生成一个可调用对象
1 | //选择范围中的每个string,并将其bind到empty的第一个隐式实参上 |
union:一种节省空间的类
联合(union)是一种特殊的类。一个union可以有多个数据成员,但是在任意时刻只有一个数据成员可以有值。当我们给union的某个成员赋值之后,该union的其他成员就变成未定义的状态了。
分配一个union对象的存储空间至少要能容纳它的最大的数据成员。
union提供了一种有效的途径使得我们可以方便地表示一组类型不同的互斥值。
使用类管理union成员
对于union来说,要想构造或销毁类类型的成员必须执行非常复杂的操作,因此我们通常把含有类类型成员的union嵌套在另一个类中。这个类管理并控制union的类型成员有关的状态转换。
为了追踪union中到底存储了什么类型的值,我们通常会定义一个独立的对象,该对象称为union的判别式。
带有string类型的union管理类Token实现:
1 | //token.h |
固有的不可移植特性
所谓的不可移植的特性是指因机器而异的特性,当我们将含有不可移植特性的程序从一台机器转移到另一台机器时,通常需要重新编写该程序。算术类型的大小在不同机器上不一样
位域
类可以将其数据成员定义成位域,在一个位域中含有一定数量的二进制位。位域在内存中的布局是与机器相关的。
通常情况下最好将位域设为无符号类型,存储在带符号类型中的位域的行为将因具体实现而定。
如果一个类定义了位域成员,则它通常也会定义一组内联的成员函数以检验或设置位域的值。
链接指示:extern “C”
C++程序有时需要调用C语言编写的函数。C++使用链接指示(linkage directive)
指出任意非C++函数所用的语言。
声明一个非C++的函数
链接指示可以有两种形式:单个的或者复合的。链接指示不能出现在类定义或函数定义的内部,另外,链接指示必须在函数的每个声明中都出现。
cstring头文件中的某些C函数声明:
1 | //可能出现在C++文件<cstring>中的链接指示 |
当使用链接指示时,它不仅对函数有效,而且对返回类型和形参类型的函数指针也有效:
1 | //f1是一个C函数,它的形参是一个指向C函数的指针 |
链接指示不仅对f1有效,对函数指针也有效。当调用f1时,必须传给它一个C函数的名字或者指向C函数的指针。
导出C++函数到其他语言
通过使用链接指示对函数进行定义,我们可以令一个C++函数在其他语言编写的程序中可用:
1 | //calc函数可以被C程序调用 |
有时需要在C和C++中编译同一个源文件,为了实现这一目的,在编译C++版本的程序时预处理定义__cplusplus。利用该变量,我们可以在编译C++程序时有条件包含进来一些代码:
1 |
|