C++ 定义了一些固有的不可移植的特性。所谓不可移植的特性是指因机器而异的特性,当含有不可移植特性的程序从一台机器转移到另一台机器上,通常需要重新编写该程序。例如,算术类型的大小在不同机器上是不一样的。
位域
类可以将其非静态数据成员定义成位域,在一个位域中含有一定数量的二进制位。当一个程序需要向其他程序或硬件设备传递二进制数据时,通常会遇到位域。
位域在内存中的布局是与机器相关的。
位域的类型必须是整型或枚举类型。因为带符号的位域的行为是由具体实现确定的,所以通常情况下使用无符号类型保存一个位域。
位域的声明形式是在成员名字之后紧跟一个冒号以及一个常量表达式,该表达式用于指定成员所占的二进制位数:
typedef unsigned int Bit;
class File {
Bit mode : 2; //@ mode 占2位
Bit modified : 1; //@ modified 占1位
Bit prot_owner : 3; //@ prot_owner 占3位
Bit prot_group : 3; //@ prot_group 占3位
Bit prot_world : 3; //@ prot_world 占3位
public:
//@ 文件类型以八进制的形式表示
enum modes {READ=01,WRITE=02,EXECUTE=03};
FILE &open(modes);
void close();
void write();
bool isRead() const;
void setWrite();
};
如果可能的话,在类的内部连续定义的位域压缩在同一个整数的相邻位,从而提供存储压缩。例如在之前的声明中,五个位域可能会存储在同一个 unsigned int 中,这些二进制位是否能压缩到一个整数中以及如何压缩是机器相关的。
取地址符不能作用于位域,因此任何指针都无法指向类的位域。
通常情况下最好将位域设为无符号数,存储在带符号数中的位域的行为因具体实现而定
使用位域
访问位域的方式与访问类的其他数据成员的方式非常相似:
void File::write()
{
modified = 1;
//...
}
void File::close()
{
if(modified)
//@ save
}
通常使用内置的位运算符操作超过1位的位域:
File& File::open(FILE::modes m)
{
mode |= READ; //@ 按默认方式设置 READ
//@ 其它处理
if (m & WRITE) //@ 如果打开了 READ 和 WRITE
//@ 安装读/写方式打开文件
return *this;
}
如果一个类定义了位域成员,则它通常也会定义一组内联的成员函数以检验或设置位域的值:
inline bool File::isRead()const { return mode & READ; }
inline bool File::setWrite() { mode |= WRITE; }
volatile 限定符
volatile 的确切含义与机器有关,只能通过阅读编译器文档来理解。要想让使用了 volatile 的程序在移植到新机器或编译器后仍有效,通常需要对该程序进行某些改变。
当对象的值可能在程序的控制或检测之外被改变时,应该将该对象声明为 volatile。关键字 volatile 告诉编译器不应对这样的对象进行优化。
volatile 限定符的用法和 const 很相似,它起到对类型额外修饰的作用:
volatile int display_register; //@ 该 int 值可能发生改变
volatile Task *curr_task; //@ curr_task 指向一个 volatile 对象
volatile int iax[max_size]; //@ iax 中的每个元素都是 volatile
volatile Screen bitmapBuf; //@ bitmapBuf 的每个成员都是 volatile
const 和 volatile 限定符互相没有影响,某种类型可能就是 const 的也是 volatile 的,此时它具有二者的属性。
就像一个类可以定义 const 成员函数一样,它可以将成员函数定义成 volatile 的,只有 volatile 的成员函数才能被 volatile 的对象调用。
volatile 修饰指针时与 const 也类似:
volatile int v; //@ v 是一个 volatile int
int* volatile vip; //@ vip 是一个 volatile 指针,它指向 int
volatile int* ivp; //@ ivp 是一个指针,它指向一个 volatile int
volatile int *volatile vivp; //@ vivp 是一个 volatile 指针,它指向一个 volatile int
int* ip = &v; //@ error,必须使用指向 volatile 的指针
ivp = &v; //@ ok,ivp 是一个指向 volatile 的指针
vivp = &v; //@ ok,vivp 是一个指向 volatile 的 volatile 指针
只能将一个 volatile 对象的地址赋给一个指向 volatile 的指针。
只有当某个引用是 volatile 的时,才能使用一个 volatile 对象初始化该引用。
合成的拷贝对 volatile 对象无效
const 和 volatile 的一个重要区别是不能使用合成的拷贝/移动构造含糊及赋值运算符初始化 volatile 对象或从 volatile 对象赋值。
合成的成员接受的形参类型是常量引用而非 volatile的,显然不能把一个非 volatile 引用绑定到一个 volatile 对象上。
如果一个类希望拷贝、移动或赋值它的 volatile 对象,则该类必须自定义拷贝或移动操作:
class Foo {
public:
Foo(const volatile Foo&); //@ 从一个 volatile 对象进行拷贝
//@ 将一个 volatile 对象赋值给一个非 volatile 对象
Foo& operator=(volatile const Foo&);
//@ 将一个 volatile 对象赋值给一个 volatile 对象
Foo& operator=(volatile const Foo&) volatile;
};
链接指示:extern "C"
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 “Ada”,extern “FORTRAN”。
链接指示与头文件
可以令链接指示后面跟上花括号括起来的若干函数的声明,从而一次性建立多个链接。
多重声明的形式可以应用于整个头文件,例如,C++ 的 cstring 头文件可能形如:
extern "C"
{
#include <string.h> //@ 操作 C 风格字符串的 C 函数
}
当一个 #include 指示被放置在复合链接指示的花括号中时,头文件中的所有普通函数声明都被认为是由链接指示的语言编写的。
链接指示可以嵌套,因此如果头文件包含带自带链接指示的函数,则该函数的链接不受影响。
指向 extern “C” 函数的指针
对于使用链接指示定义的函数来说,它的每个声明都必须使用相同的链接指示。指向其他语言编写的函数的指针必须与函数本身使用相同的链接指示:
//@ pf 指向一个 C 函数,该函数接受一个 int 返回 void
extern "C" void(*pf)(int);
当使用 pf 调用函数时,编译器认定当前调用的是一个 C 函数。
指向 C 函数的指针与指向 C++ 函数的指针是不一样的类型。一个指向 C 函数的指针不能用在执行初始化或赋值操作后指向 C++ 函数,反之亦然。
像其他类型不匹配的问题一样,如果试图在两个链接指示不同的指针之间进行赋值操作,则程序将发生错误:
void(*pf1)(int); //@ 指向一个 C++ 函数
extern "C" void(*pf2)(int); //@ 指向一个 C 函数
pf1 = pf2; //@ 错误,pf1 和 pf2 的类型不同
链接指示对整个声明都有效
当使用链接指示时,它不仅对函数有效,而且对作为返回类型或形参类型的函数指针也有效:
//@ f1 是一个 C 函数,它的形参是一个指向 C 函数的指针
extent "C" void f1(void(*) (int));
f1 是一个不返回任何值的 C 函数,它有一个类型是函数指针的形参。这里的链接指示不仅对 f1 有效,对函数指针同样有效。当我们调用 f1 时,必须传给它一个 C 函数的名字或者指向 C 函数的指针。
如果想给 C++ 传给一个 C 函数的指针,则必须使用类型别名:
//@ FC 是一个指向 C 函数的指针
extern "C" typedef void FC(int);
//@ f2 是一个 C++ 函数,该函数的形参指向一个 C 函数的指针
void f2(FC*);
导出 C++ 函数到其他语言
通过连接指示对函数的定义可以令一个 C++ 函数在其他语言编写的程序中使用:
// calc 函数可以被C程序调用
extern "C" double calc(double dparm) {/*...*/}
编译器将为该函数生成适合于指定语言的代码。
需要注意的是可被多种语言共享的函数的返回类型或形参类型受到诸多限制,例如不能把一个C++ 对象传递给C程序。
有时需要在 C 和 C++ 中编译同一个源文件:
#ifdef __cplusplus
extern "C"
#endif
int strcmp(const char*, const char*);
重载函数与链接指示
C 语言不支持重载函数,因此 C 链接指示只能用于说明一组重载函数中的某一个了:
//@ error, 两个 extern "C" 函数名字相同
extern "C" void print(const char*);
extern "C" void print(int);
如果在一组重载函数中有一个 C 函数,则其余的必定是 C++ 函数:
//@ C 函数可以在 C 或者 C++ 程序中调用
extern "C" double calc(double);
//@ C++ 重载了这个函数,可以在 C++ 中程序中调用
extern SmallInt calc(const SmallInt&);
extern BigNum calc(const BigNum&);