全局变量、函数原型和Guard macro

出处: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 ++) {
cin >> number[i];
}
} void output() {
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__

当然,到底怎么定义宏,则依设计者的习惯和高兴了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值