-
为了位置底层编程,C++定义了一些固有的不可移植的特性。所谓不可移植的特性是指因机器而异的特性
- 当我们将含有不可移植的程序从一台机器转移到另一台机器上时,通常需要重新编写该程序。算术类型的大小在不同机器上不一样,这是一个典型实例
- 本文介绍的特性有:
- 位域:从C语言继承而来的特性
- volatile:从C语言继承而来的特性
- extern:C++新增的链接指示
一、位域
- 类可以将其(非静态)数据成员定义为位域,在一个位域中含有一定数量的二进制位。当一个程序需要向其他程序或硬件设备传递二进制数据时,通常会用到位域
- 取地址运算符(&)不能作用域位域,因此任何指针都无法指向类的位域
位域的定义
- 位域的类型必须是整型或枚举类型
- 因为带符号位域的行为是由具体实现确定的,所以通常情况下使用无符号类型保存一个位域
- 位域在表达式的后面用一个冒号跟一个常量表达式,常量表达式用来指示成员所占的二进制位数
- 例如:
typedef unsigned int Bit; class File { Bit mode : 2; //占2位 Bit modified : 1; //占1位 Bit prot_owner : 3; //占3位 Bit prot_group : 3; //占3位 Bit prot_world : 3; //占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) //... }
- 通常使用内置的位运算符操作超过1位的位域:
File &File::open(File::modes m) { mode |= READ; //按默认方式设置READ //其他处理 //如果打开了READ和WRITE if (m&WRITE) return *this; }
- 如果一个类定义了位域成员,则它通常也会定义一组内联的成员函数以检验或设置位域的值:
inline bool File::isRead()const { return mode&READ; } inline void File::setWrite() { mode |= WRITE; }
二、volatile
volatile的使用场景
- 直接处理硬件的程序常常包含这样的元素,它们的值由程序直接控制之外的过程控制
- 例如,程序可能包含一个由系统时钟定时更新的变量。当对象的值可能在程序的控制或检测之外被改变时,应该将该对象声明为volatile。关键字volatile告诉编译器不应该对这样的对象进行优化
volatile变量的定义
- 下面是一些volatile变量的定义:
volatile int display_register; //该int值可能发生改变 volatile Task *curr_task; //curr_task指向一个volatile对象 volatile int iax[max_size]; //iax的每个元素都是volatile volatile Screen bitmapBuf; //bitmapBuf的每个成员都是volatile
- volatile的成员函数只能被volatile的对象调用
volatile与指针
- volatile也可以与指针在一起使用
- 我们可以声明volatile指针、指向volatile对象的指针、以及指向volatile对象的volatile指针
- 例如:
volatile int v; //v是一个volatile int int *volatile vip; //vip是一个指针,它指向int volatile int *ivp; //ivp是一个指针,它指向一个volatile int volatile int *volatile vivp; //vivip是一个volatile指针,它指向一个volatile int //int *ip = &v; 错误,必须使用volatile的指针 ivp = &v; //正确,ivp是一个指向volatile的指针 vivp = &v; //正确,vivip是一个指向volatile的volatile指针
- 引用的规则类似,只有当某个引用时volatile的时,我们才能使用一个volatile对象初始化该引用
合成的拷贝对volatile对象无效
- 一个重要概念:
- 我们不能使用合成的拷贝/移动构造函数及赋值运算符初始化volatile对象或从volatile对象赋值
- 原因:合成的成员接受的形参类型是(非volatile)常量引用,显然我们不能把一个非volatile引用绑定到一个volatile对象上
- 因此,如果我们想拷贝/移动或赋值它的volatile对象,则该类必须自定义拷贝或移动操作。例如,我们可以将形参类型指定为const volatile引用,这样我们就可以进行拷贝或赋值操作了:
class Foo { public: //从一个volatile对象进行拷贝 Foo(const volatile Foo&); //将一个volatile对象赋值给一个非vaoltile对象 Foo &operator=(volatile const Foo&); //将一个volatile对象赋值给一个vaoltile对象(因为只有volatile对象才能调用这个函数) Foo &operator=(volatile const Foo&)volatile; };
- 尽管我们可以为volatile对象定义拷贝或赋值操作,但是一个更深层次的问题是:拷贝volatile对象是否有意义?这个问题与具体的实现有关
三、extern
- C++程序有时需要调用其他语言编写的函数(C语言为主)。像所有其他名字一样,其他语言中的函数名字也必须在C++中进行声明(并且带有返回类型和形参列表)
- 对于其他语言编写的函数来说,编译器检查其调用的方式与处理普通C++函数的方式相同,但是生成的代码有所区别。C++使用链接指示指出任意非C++函数所用的语言
声明一个非C++的函数
- 链接指示:
- 链接指示可以有两种形式:单个的、复合的
- 链接指示不能出现在类定义或函数定义的内部
- 链接指示格式:
- extern关键字+一个字符串字面值+单个/一组函数的声明
- 字符串字面值:指示出编写函数所用的语言,编译器可以支持对C语言的链接指示(extern "C"),编译器还可能支持Ada、FORTRAN等语言
- 演示案例:
//可能出现在C++头文件<cstring>中的链接指示 //单语句的链接指示 extern "C" size_t strlen(const char *);
//可能出现在C++头文件<cstring>中的链接指示 //复合语句的链接指示 extern "C" { int strcmp(const char*, const char*); char *strcat(char*, const char*); }
链接指示与头文件
- 复合声明的形式可以应用于整个头文件。例如,C++的cstring头文件可能有下面形式的代码:
//复合语句链接指示 extern "C" { #include <string.h> //操作C风格字符串的C函数 }
- 链接指示可以嵌套,如果上面的头文件中也包含有链接指示的函数,则该函数的链接不受影响
指向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函数的指针 extern "C" void f1(void(*)(int));
- 当我们调用上面的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 cal(double dparm) { //... }
- 编译器将为calc函数生成适合于指定语言的代码
- 值得注意的是:
- 可被多种语言共享的函数的返回类型或形参类型受到很多限制
- 例如,我们不太可能把一个C++类的对象传给C程序,因为C程序根本无法理解构造函数、析构函数以及其他类特有的操作
重载函数与链接指示
链接指示与重载函数的相互作用依赖于目标语言。如果目标语言支持重载函数,则为该函数实现链接指示的编译器很可能也支持重载这些C++的函数
C语言不支持函数重载,因此也就不难理解为什么一个C链接指示只能用于说明一组重载函数中的某一个了。例如:
//错误,两个extern "C"函数的名字相同 extern "C" void print(const char*); extern "C" void print(int);
- 如果在一组重载函数中有一个是C函数,则其余的必定都是C++函数。例如:
class SmallInt {}; class BigNum {}; //C函数可以在C/C++程序中使用 //C++函数重载了该函数,可以在C++程序中调用 extern "C" double calc(double); extern SmallInt calc(const SmallInt&); extern BigNum calc(const BigNum&);
- C版本的calc函数可以在C或C++程序中调用,而使用了类类型形参的C++函数只能在C++程序中调用。上述性质与声明的顺序无关