单独编译
单独编译的好处:c++鼓励程序员将组件函数放在独立的文件中。单独编译这些文件,然后将他们链接成可执行的程序。如果只修改了一个文件,则可以只重新编译该文件,然后再它与其他文件的编译版本链接,可以使大型程序的管理更加便捷。
以下面这个程序为例:将直角坐标转换成极坐标的程序
// strctfun.cpp -- functions with a structure argument
#include <iostream>
#include <cmath>
// structure declarations
struct polar
{
double distance; // distance from origin
double angle; // direction from origin
};
struct rect
{
double x; // horizontal distance from origin
double y; // vertical distance from origin
};
// prototypes
polar rect_to_polar(rect xypos);
void show_polar(polar dapos);
int main()
{
using namespace std;
rect rplace;
polar pplace;
cout << "Enter the x and y values: ";
while (cin >> rplace.x >> rplace.y) // slick use of cin
{
pplace = rect_to_polar(rplace);
show_polar(pplace);
cout << "Next two numbers (q to quit): ";
}
cout << "Done.\n";
return 0;
}
// convert rectangular to polar coordinates
polar rect_to_polar(rect xypos)
{
using namespace std;
polar answer;
answer.distance =
sqrt( xypos.x * xypos.x + xypos.y * xypos.y);
answer.angle = atan2(xypos.y, xypos.x);
return answer; // returns a polar structure
}
// show polar coordinates, converting angle to degrees
void show_polar (polar dapos)
{
using namespace std;
const double Rad_to_deg = 57.29577951;
cout << "distance = " << dapos.distance;
cout << ", angle = " << dapos.angle * Rad_to_deg;
cout << " degrees\n";
}
不能简单地以main()之后为界限,将原来的文件分成两个,因为main函数和其他两个函数都使用了同一个结构声明,因此这样分解的话,两个程序都必须包含这个声明。即使将这个结构声明都复制到两个文件中,需要修改结构时,必须同时修改这两个文件,很麻烦,有些大型程序都不止两个文件。
解决方法:使用#include来处理这种情况,将结构声明放在头文件中,然后每一个源代码文件中包含该头文件。这样想要修改结构时,只需在头文件中做一次改动即可。另外,还可以将函数原型放在头文件中。所以,可以将原来的程序分成三部分:
(1)头文件:包含结构声明和使用这些结构的函数的原型(注意不是函数定义)
(2)源代码文件:包含与结构有关的函数的代码
(3)源代码文件:包含调用与结构有关的函数的代码。
注意:不要将函数定义和变量声明放在头文件中,因为如果在头文件中包含一个函数定义,然后在其他两个文件(属于同一个程序)中包含该头文件,则同一个程序将包含同一个函数的两个定义,除函数是内联的,否则会出错。
头文件中应该包含的内容:
(1)函数原型
(2)使用#define或const定义的符号常量
(3)结构声明(将结构声明放在头文件中是可以的,因为它不创建变量,而只是在源代码文件中声明结构变量时,告诉编译器如何创建给结构变量)
(3)类声明
(4)模板声明(模板声明不是被编译的代码,它们指示编译器如何生成与源代码中的函数调用相匹配的函数定义。)
(5)内联函数
头文件的文件名包含在<>中,编译器将会在存储标准头文件的主机系统中查找;如果文件包含在""中,则编译器将首先查找当前的工作目录或者源代码目录。如果没有在那里找到头文件,则将在标准位置查找,因此在包含自己的头文件时,应使用引号""而不是尖括号<>。
注意:只需将源代码文件加入到项目中,而不用加入头文件。这是因为#include指令管理头文件。另外,不要使用#include来包含源文件代码,这样做将导致多重声明。
在同一个文件中只能将同一个头文件包含一次。但是有时候会在不知情的情况下将头文件包含多次。例如,可能使用包含了另一个头文件的头文件。可以使用基于预处理器编译指令#ifndef(if not defined)的。
可以上面的代码分成一下三个部分:
(1)头文件
coordin.h
// coordin.h -- 结构模板和函数原型
// structure templates
#ifndef COORDIN_H_
//以前没有使用预处理编译指令#define定义名称COORDIN_N_时,才处理#ifndef和#endif之间的语句。
#define COORDIN_H_
//一般使用#define来定义符号常量,但只要将#define用于名称,就足以完成该名称的定义,就如此处。
struct polar
{
double distance; // distance from origin
double angle; // direction from origin
};
struct rect
{
double x; // horizontal distance from origin
double y; // vertical distance from origin
};
// 函数原型
polar rect_to_polar(rect xypos);
void show_polar(polar dapos);
#endif
对#ifndef的注解:
#ifndef 是"if not defined"的简写,是预处理功能(宏定义、文件包含、条件编译)当中的条件编译,可以根据是否已经定义了一个变量来进行分支选择,其作用是:
1、防止头文件的重复包含和编译;
2、便于程序的调试和移植;
一般地,假如有一个C源文件(如sourcefile.cpp),它包含两个头文件(如headfile_1.h和headfile_2.h),而头文件headfile_2.h又包含了headfile_1.h,则最终的效果是该源文件包含了两次headfile_1.h。如果你在头文件里定义了结构体或者类类型,那么问题来了,编译时将会报重复定义的错误。加上条件编译"ifndef"则问题可解决:
它的格式就是如下:
#ifndef 标识符;//这里的标识符时任意的,但每个头文件的这个“标识”都应该是唯一的。标识的明明规则一般是头文件名全大写,前面加下划线,并把文件名中的"."也变成下划线,如这里的COORDIN_H_。
当第一次包含coordin.h文件时,由于没有定义COORDIN_H,条件为真,这样就会执行#infdef COORDIN_H和#endif之间的代码;当第二次包含coordin.h时,前面已经定义了COORDIN_H(也就是#define COORDIN_H这个语句的结果),条件为假,#infdef COORDIN_H和#endif之间的代码也就不会再次被包含,这样就避免了重定义。
小结:还是把头文件的内容都放在#ifndef和#endif中吧。不管你的头文件会不会被多个文件引用,你最好都加上这个。一般格式是这样的:
#ifndef <标识>
#define <标识>
…
…
#endif
注意:#ifndef起到的效果是防止一个源文件多次包含同一个头文件,而不是防止两个源文件包含同一个头文件。事实上,防止同一头文件被两个不同的源文件包含这种要求本身就是不合理的,头文件存在的价值就是被不同的源文件包含。
(2)file1.cpp:包含调用与结构有关的函数的代码
// file1.cpp -- example of a three-file program
#include <iostream>
#include "coordin.h" // structure templates, function prototypes
using namespace std;
int main()
{
rect rplace;
polar pplace;
cout << "Enter the x and y values: ";
while (cin >> rplace.x >> rplace.y) // slick use of cin
{
pplace = rect_to_polar(rplace);
show_polar(pplace);
cout << "Next two numbers (q to quit): ";
}
cout << "Bye!\n";
// keep window open in MSVC++
/*
cin.clear();
while (cin.get() != '\n')
continue;
cin.get();
*/
return 0;
}
(2)file2.cpp(包含与结构有关的函数的代码,也就是函数定义)
// file2.cpp -- contains functions called in file1.cpp
#include <iostream>
#include <cmath>
#include "coordin.h" // structure templates, function prototypes
// convert rectangular to polar coordinates
polar rect_to_polar(rect xypos)
{
using namespace std;
polar answer;
answer.distance =
sqrt( xypos.x * xypos.x + xypos.y * xypos.y);
answer.angle = atan2(xypos.y, xypos.x);
return answer; // returns a polar structure
}
// show polar coordinates, converting angle to degrees
void show_polar (polar dapos)
{
using namespace std;
const double Rad_to_deg = 57.29577951;
cout << "distance = " << dapos.distance;
cout << ", angle = " << dapos.angle * Rad_to_deg;
cout << " degrees\n";
}
多个库的链接