C程序元素包括变量、类型和函数。为了叙述方便,这里大多时候将函数与变量统称为变量。在一些具体情况下,再明确指出是变量还是函数。
C程序命名变量有三种方式:声明(declaration),定义(definition)和引用(reference)。
声明变量是告知编译器变量的名称和类型。例如,声明函数是把函数名、函数参数的个数和参数类型等信息通知编译系统,以便在调用函数时,编译系统能够正确识别函数并检查调用是否合适。只要保持一致,可以多次声明变量。声明变量不是实现,仅表明变量存在,其他变量可以使用它。
引用变量包括调用函数、赋值及取地址等。通常情况下,C编译器要求声明变量之后才能引用它。使用未声明的变量是不良的C编程习惯。
定义变量是最后一种变量命名方式。声明变量表明变量是存在的,引用变量是使用变量,而定义变量实际上是创建变量。具体地说,定义函数是实现函数,定义变量是为变量分配存储区域。变量可以被声明多次,引用多次,但只能定义一次。
定义变量将变量名字引入到了程序的命名范围。C程序有多个命名范围,可以将其看成一个范围树,在最顶层的根是全局范围。任何代码都可以引用全局范围内的变量。全局变量和非静态(non-static)函数都在全局范围内。大括号{}定义了子范围,该范围内的代码只能引用树中该范围以上范围内命名的变量。另外,还有一个变量范围级别是文件范围,由static关键字来声明,也就是说,带有关键字static标识的变量只能被同文件内的代码引用,不能在全局范围内被引用。
如果一个C程序由多个C文件构成,为了引用别的文件中的变量,需要使用声明变量的方式,将所要使用的变量名字引到全局范围。在下面的例3.1中,文件f2.c定义了函数g(),在文件f1.c中要使用这个函数,需要使用语句extern void g()先声明它。
例3.1:
f1.c包含的C代码如下:(www.xing528.com)
f2.c包含的代码如下:
在全局范围内组织变量要采用诸如头文件和命名约定等技巧。头文件也是一种C源文件。它将变量声明组织在一起,这些声明与相应实现文件或目标文件的定义相匹配。使用头文件可以避免大量重复输入可能带来的差错。命名约定可以在一定程度上避免使用不同的符号为相同的变量命名。例如,类型通常有_t的前缀,保证类型和函数不会有相同的名字。有些库在其变量前使用相同的前缀,如gkt_用在GKT+图形工具的库中。这些前缀提醒用户避免与其他库中的符号名字冲突。但是这样的做法使得编程显得繁冗。
如果某个目标文件引用一个变量,在连接阶段,就会被连接到定义这个变量的目标文件(可能是库文件或标准的目标文件)。以函数为例,由于声明函数仅对应一个函数定义,这表明代码引用一个函数,实际是引用了它的实现。如果两个源文件引用相同的函数名字,则实际上引用的是同一个函数,这就在两块代码间引入了潜在的依赖性。因为如果想改变一个文件使用的函数实现,那么也会改变另一个源文件代码使用的函数实现。这种情况显然不是我们想要看到的。函数指针是避免这种间接绑定的一个常用办法。在代码中不引用特定的函数,而是引用一个存放函数指针的变量,这个指针可以指向任意一个函数。这就使得代码可以在运行时再解析绑定,也就是说,通过选择在变量中存放什么函数地址值(指针)确定绑定。通过使用函数指针,C程序可以调用那些在编译时还没有或不能命名的代码块。这对于具有回调(callback)的系统是非常重要的。
例如,GUI工具箱需要调用函数对用户事件进行反应,函数是应用代码的一部分,但是在GUI工具箱预编译时需要调用它。具体地说,应用创建一个按钮,给这个按钮一个函数指针,当按钮被单击时,调用函数指针所指的函数。C中没有其他方式可以高效地做到这一点,因为工具箱不能命名这个函数。所以必须使用函数指针,而该指针必须在运行时分配。
UNIX或Linux内核使用的虚拟文件系统也是一个典型的例子。操作系统具有访问任意数目的文件系统的需求,将所有文件系统静态编译到内核是很麻烦的。如果为了支持一个新的文件系统,如USB存储设备,就需要重新编译操作系统,这很烦琐,是不应该的。为了避免这一点,操作系统使用虚拟文件系统(Virtual file system,VFS)接口与文件系统进行交互。这是一套基本的操作,比如read()和write()等,存放在一个数据结构中。每个文件系统实现都使用自己的函数来实现这个数据结构,然后把这个数据结构传给内核。比如,当用户程序要读CD时,OS读取与CD文件相关的VFS结构,调用read()函数指针,从而使得内核与特定的文件系统保持独立。这个基本方法与GUI工具箱相同。总之,正是由于C的命名方法,操作系统必须使用运行时分配的函数指针来达到灵活的程序连接与扩展。
C++除了具有更为丰富的命名范围和继承性之外,其他与C相似。C++使用双冒号::表明命名范围的层次性。虽然类可以在其域和方法上使用不同的访问描述符,但这与C中关键字static声明的变量不同,它不是在命名范围的级别上操作。static表明变量和函数不会出现在全局范围,因此也不会在全局范围内被命名,其他文件想要访问这样的变量,系统会报“no such variable exists”的错误。相对的,C++类中使用private指定变量为私有的,意味着如果其他代码引用私有变量,就会产生一个“access violation”的编译错误。
C++的继承性提供了比C更容易扩展功能的方式,程序使用类而不是函数指针来表示可扩展抽象。实践中,每个对象都有一个指向自己的函数表的指针。该函数表是只读的,不需要程序员维护它。基于C++的系统使用对象而非函数指针来扩展功能。与C一样,在运行时建立变量名字之间的绑定关系。
C++中,变量名字的绑定使得工厂函数(Factory)成为软件工程实践中的通用对象。工程将对象与其初始化分开。比如,Tetris游戏可能需要创建游戏片段。一种简单的方式是让游戏引擎直接分配各个片段。将游戏引擎绑定到一个具体类比如JPiece,也 同时绑定了JPiece构造函数的参数列表。这种绑定是不必要的,而且,如果更改类的名字,比如改成JTetrisPiece,会产生问题。如果不将游戏引擎绑定到一组特定类的集合,而是定义一个抽象的类Piece,仅仅要求一个工程来为它创建类,这样,游戏引擎与Piece的名字及实现就相互独立了。也就是说,改变一组Piece仅仅要求改变工程,因为仅有一个分配点,构造函数的变化都可以局部到工程中来。工程的设计模式使得命名具有一定程度的间接性,从而打断了那些不想要的特定类之间的依赖关系。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。