单独编译的简单原理:
C++在内存中储存数据提供了多种选择。
可以选择数据保留在内存中的时间长度(存储持续性)以及程序的哪一部分可以访问数据(作用域和链接)等。可以使用new来动态地分配内存,而定位new运算符提供了这种技术的一种变种。C++名称空间是另一种控制访问权的方式。
通常,大型程序都由多个源代码文件组成,这些文件可能共享一些数据。这样的程序涉及到文件的单独编译。
————***一〇九谈的是单独编译***一一〇谈的是存储***——————
和C语言一样,C++也允许,甚至鼓励程序员将组件函数放在独立的文件中。可以单独编译这些文件,然后将他们链接成可执行的程序。(通常,C++编译器既要编译程序,也要管理链接器)。
如果只是修改一个文件,则可以只重新编译该文件,然后将它与其他文件的编译版本链接。这使得大程序的管理更便捷。
另外,大多数C++环境都提供了其他工具来帮助管理。例如,UNIX和Linux系统都具有make程序,可以跟着程序依赖的文件以及这些文件的最后修改时间。
运行make时,如果它检测到上次编译后修改了源文件,make将记住重新构建程序所需的步骤。大多数集成开发环境(包括Embarcadero C++ Builder、Microsoft Visual C++、Apple Xcode和Freescale CodeWarrior)都在Project菜单中提供了类似的工具。
假如有这样一个程序。他包含一个结构,有main()函数和其他两个函数,这三个函数都使用了这样一个结构。
如果我们要把main函数和其他两个函数分拆到两个文件之中,那个这个结构怎么办?是放在main函数还是放在其他两个函数所在的文件?还有,这两个函数的原型怎么办?
事实上,是把这个结构放在头文件之中(也可以顺便将两个函数的原型放在同一个头文件之中,原因见之后,是我自己推测的),把main()函数放在一个源代码文件之中,把其他两个函数放在另一个源代码文件之中。这两个源代码文件都包含这个头文件。
程序分为三部分:
①头文件:包含结构声明、以及两个函数原型;
②源代码文件a:包含main()函数;
③源代码文件b:包含另外两个函数。
这是一个非常有用的组织程序的策略。
例如,我们编写另外一个程序时,也需要使用这些函数(指另外两个函数),则只需要包含头文件(里面有结构和另外两个函数的原型),并将函数文件(指上面的源代码文件b)添加到项目列表或make列表之中即可。
另外,这种组织程序也与OOP方法一致。
一个文件(头文件)包含了用户定义类型的定义;
另一个文件包含操纵用户定义类型的函数的代码。
这两个文件组成了一个软件包,可以用于各种程序之中。
请不要将函数定义或变量声明放在头文件之中。
这样对于简单的文件可能是可行的,但通常会引发问题。
例如,如果在头文件之中包含一个函数定义,然后在其他两个文件(属于同一个程序)中包含该头文件,则同一个程序中(因为据说是程序是将多个文件编译后链接在一起)将包含同一个函数的两个定义,除非函数是内联的(why内联就可以?),否则这将出错。
头文件中常包含的内容:
①函数原型;
②使用#define或const定义的符号常量;
③结构声明;
④类声明;
⑤模板声明;(因为不生成函数定义,只有在调用的时候才生成)
⑥内联函数。
原因:
②被声明为const的数据和内联函数有特殊的链接属性,(不清楚)
③结构声明:因为不创建变量,只是在源代码文件中声明结构变量时,告诉编译器如何创建该结构变量。
⑤模板声明:类似结构声明,注意,模板并不直接创建函数定义,而是调用的时候创建对应的函数定义。
如果要将多个源代码文件和头文件组合在一起,应如下做:
①首先,可以将上面所说的,头文件中常包含的内容,放在一个或者多个头文件中;
②其他源代码文件需要引用头文件,且不能用方括号“<>”,而应该用双引号“""”。语法为:#include"头文件名"
例如,头文件名为aaa.h,那么其他源代码文件在需要引用这个头文件时,应加入:
#include"aaa.h"
且每个需要引用头文件内容的源代码文件,都需要有这样一行代码。
③头文件中如果要包含其他头文件的内容,例如在结构声明中需要string类,就如同普通源文件那样,添加#include<string>,然后需要记得string之前要加std::
例如:
#include<string>
struct abc
{
std::string c;
};
这样才可以正常使用string类。
④头文件中一般要包含函数原型。
这样的话,凡是调用这个头文件的cpp文件(源代码文件)都可以省略函数原型,否则还要像正常函数调用那样在main函数之前加入函数原型。
⑤在同一个源代码文件,若要包含一个头文件,那么通常只会包含一次。
例如如果需要使用string类,那么在这个源文件内,我们会加入#include<string>
毫无疑问,我们不会将这行代码在同一个cpp文件里输入两遍。
头文件可能被调用多次的问题:
但若我们在一个头文件里引用另外一个头文件,就有可能涉及到将同一个头文件引用多次的问题。例如:
(1)我们在头文件m1.h里面创建了一个结构声明,然后头文件m2.h里放了一个函数原型;头文件m3.h放了另外一个函数原型;
(2)由于m2.h和m3.h都需要使用这个结构,于是在两个函数都引用了头文件m1.h。即,加入了#include"m1.h"
(3)然后我们在源代码文件a.cpp里面,引用了头文件m2.h和m3.h(因为要使用它们提供的原型);即:
#include"nn.h"
#include"nn2.h"
(4)假如我们没在m1.h里面添加防多次引用的代码。那么这个时候,m2.h里面和m3.h里面都包含了一个结构声明(因为他们都包含了m1.h),编译器会提示错误(因为它遇见了两次结构声明)。
防止头文件被调用多次:
因此,我们需要一个解决办法,方法有两种:
(1)在头文件的开始,加入#pragma once,那么这个头文件,在编译时,由编译器保证这个头文件不会被拷贝多次;
(2)在头文件的开始,加入
#ifndef 宏名字
#define 宏名字
..... //一堆声明代码
#endif
例如:
#ifndef aa_bb_
#define aa_bb_
struct abc
{
int a;
};
#endif
第二种方法的原理是,#ifndef(if no define如果没有宏xxx的意思)aa_bb_,在编译器第一次遇见这行命令的时候,编译器发现自己没有遇见过这个宏名字(因为之间没有定义aa_bb_);
于是向下执行,遇见#define aa_bb_ ,这个时候,编译器知道宏定义了aa_bb_(这样的话,下次遇见定义的这个名字,#ifndef aa_bb_将不会执行一直到#endif)之间的代码;
因为是第一次遇见,所以在这里,结构声明被编译器编译了(且记住下次遇见就不会被编译)。
这样的话,第一次遇见,于是编译结构abc,下一次遇见这几行代码(比如说又包含了这个头文件),这里的结构声明,便被编译器自动跳过了。
⑥多个源代码文件,应如正常那样包含头文件,调用名称空间等。
只不过包含头文件的话,就可以理解为将头文件的内容放在了源代码文件之前。即如果头文件里有一个函数原型,源代码文件包含这个头文件,就可以视为源代码文件中的代码,有这个函数原型。
但若一个源代码文件中包含名称空间(假如使用了using namespace std;),那么并不会让另一个源代码文件视为可以节约了这行代码。
多个文件代码如下:
//头文件mm.h #pragma once //由编译器提供保证:同一个文件不会被包含多次。注意这里所说的“同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件。带来的好处是,你不必再费劲想个宏名了,当然也就不会出现宏名碰撞引发的奇怪问题。对应的缺点就是如果某个头文件有多份拷贝,本方法不能保证他们不被重复包含。当然,相比宏名碰撞引发的“找不到声明”的问题,重复包含更容易被发现并修正。 //#ifndef COORDIN_H_ //#define COORDIN_H_ //... ... // 一些声明语句 //#endif // #ifndef的方式依赖于宏名字不能冲突,这不光可以保证同一个文件不会被包含多次,也能保证内容完全相同的两个文件不会被不小心同时包含。当然,缺点就是如果不同头文件的宏名不小心“撞车”,可能就会导致头文件明明存在,编译器却硬说找不到声明的状况 #include<string> struct abc { int a; int b; std::string c; };
//头文件nn.h #pragma once #include"mm.h" void plus1(abc&); //头文件nn2.h #pragma once #include"mm.h" void set(abc&); void show(abc);
//源代码文件:源.cpp #include<iostream> #include"nn.h" #include"nn2.h" using namespace std; int main() { abc a; string c = "abb"; set(a); show(a); plus1(a); show(a); system("pause"); return 0; }
//源代码文件:源1.cpp #include<iostream> #include"nn.h" #include"nn2.h" void set(abc &a) { a.a = 5; a.b = 3; a.c = "abb"; } void show(abc a) { using namespace std; //虽然源.cpp包含了using namespace std,但这个函数使用cout和cin,依然要加入这行代码,或者加入using std::cout和using std::cin cout << a.a << endl; cout << a.b << endl; cout << a.c << endl; } void plus1(abc &a) { a.a++; a.b++; a.c += a.c; }
输出:
5 3 abb 6 4 abbabb 请按任意键继续. . .
头文件mm.h的代码,也可以修改为:
#ifndef aa_bb_ #define aa_bb_ #include<string> struct abc { int a; int b; std::string c; }; #endif
其效果是相同的。