出处:http://blog.vckbase.com/arong/archive/2010/03/05/294.html?Pending=true#Post
首先声明,本文例子只是为了帮助说明函数原型和全局变量。
一个例子引入问题
考虑初学者经常见到的一个简单的问题:从控制台输入10个数字,把他们排序后再输出去。为了看代码轻松一点,决定按照功能把不同代码放到不同文件中去,整个工程由3个cpp构成,分别是:
- main.cpp 主程序
- sort.cpp 排序程序
- inout.cpp 输入输出程序
为了方便设计,决定把输入输出的数放在全局变量数组中,有了这些前提后,可以得出3个cpp文件内容分别是:
inout.cpp
#include using namespace std; int number[10]; void input() {int i; for(i = 0 ; i < 10 ; i ++) {} void output() {cin >> number[i];}int i; for(i = 0 ; i < 10 ; i++) {}cout << number[i] << endl;}
sort.cpp
void swap(int& a,int &b) {a = a+b; b = a - b; a = a- b;} void sort() {int i,j; for(i = 0 ; i < 9 ; i ++) {}for(j = i ; j < 9 - i; j ++) {}if(number[j] > number[j+1]) {}swap(number[j],number[j+1]);}
main.cpp
int main(int argc, char* argv[]) {input(); sort(); output(); return 0;}
函数原型和全局变量声明
对于前面这个C++语言程序来说,编译过程是这样的,编译器首先依次编译main.cpp、sort.cpp和inout.cpp,得到三个obj文件,然后再调用link程序把这三个obj文件链接成为一个可执行程序。
问题就在这,C++/C语言是一个强类型语言,语言在编译时必须了解它涉及到的每个元素的定义。例如在main函数中,调用了sort、 input和output函数,但是编译器在编译这个cpp文件时,它不知道要到哪儿找这些函数的定义,这样它就无法编译这个cpp文件了。如果把所有的代码都放在一个cpp文件中,对于这么简单的问题当然可以,但是对于复杂的问题,这个cpp文件就过于庞大了。为了简化cpp文件, C语言允许程序设计者通过函数原型(prototype)在引用函数的地方声明函数原型,函数原型的格式和函数头(也就是{}前面的东西)必须完全一致。例如,上面三个函数的函数原型分别是:
void input(); void sort(); void output();
更一般的情况,函数定义可能是:
DECORATION1 RETURNTYPE DECORATION2 FunctionName(param list) DECORATION3
其中DECORATION1,DECORATION2和DECORATION3是函数的修饰,说明函数的调用约定、是否是dll中的导出函数、属于哪个类等信息。 RETURNTYPE是函数的返回类型,param list是函数的形参表,FunctionName是函数的名字。那么函数原型必须忠实于函数的定义,一般情况下,都是在函数定义处直接把函数头部分拷贝并复制过去,成为函数原型。
一个特例是定义在扩展名为C的文件中的函数不能这样复制。由于C++有函数重载的要求,为了区分重载后的函数,编译器会在函数的名字前后加上一些特殊的字符来表示函数。但是对于定义在.C文件中的函数,由于没有重载机制,因此不存在这种修改。如果对于C语言的函数也如前面这样修改,则可能会出现链接不成功的情况。为了能正确链接,对于C语言的函数,必须如下声明函数原型:
extern "C" { DECORATION1 RETURNTYPE DECORATION2 FunctionName(param list) DECORATION3 };
如果函数被引用的很少,那么这些定义就足够了,但是很多函数都是被多个文件引用的,这样就比较麻烦了。为了简化,函数的原型一般都放在头文件中,再在使用这个函数的cpp文件中#include这个头文件。由于#include一个头文件在编译时就相当于把这个头文件插入到对应的源文件,因此这样编译也没有问题的。因此前面这个sort.cpp和inout.cpp可以分别建立头文件如下:
sort.h
void sort();inout.h
void input(); void output();
对于.C文件中定义的函数,如果在头文件中声明函数原型,就有一个问题:这个头文件不知道是在C语言中被包含还是在C++语言中被包含,好在C++编译器会定义一个宏__cplusplus,因此对于这种函数必须如下处理:
#ifdef __cplusplus extern "C" { #endif 这儿增加函数原型声明 #ifdef __cplusplus }; #endif
对于全局变量存在和函数一样的问题,为了在其他CPP文件中能够访问这些变量,必须在h文件中加上extern声明,格式如下:
extern varibletype var;
其中varibletype var必须和变量定义完全一致,对于本文中的number数组,这种声明就变成:
extern int number[10];
有些初学者为了能在多个CPP文件中使用函数和全局变量,喜欢在h文件中定义他们,这样将导致这个头文件每被包含依次,函数或变量就被重新定义一次,最终链接时会导致重定义错误。
Guard Macro
当在头文件中声明类型和常量时,如果头文件在一个cpp中被多次直接或者间接包含,将导致这些常量或者类型出现重定义,为此需要使用一种被称为Guard macro的技术来保证不出错。在一个头文件开头加上
#ifndef _MACRO_1_ #define _MACRO_1_
在文件末尾增加
#endif
当编译器编译时,它首先检查是否定义了宏 _MACRO_1_,如果定义则不处理,否则将定义这个宏。这样当第二次包含这个文件时,这个宏必然已经被定义,就不会出现重复定义了。
注意,这里 _MACRO_1_只是guard macro的一个例子,在一个工程中,每个头文件都应该使用一个guard macro,并且这个宏对于不同文件应该是彼此不同的。我习惯定义的宏都是文件名的前面后面各加两个下划线构成guard macro,如sort.h的macro一般是
__SORT_H__
当然,到底怎么定义宏,则依设计者的习惯和高兴了。