(一〇九)单独编译(多个源代码文件和头文件)

单独编译的简单原理:

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

其效果是相同的。

时间: 2024-07-29 10:15:08

(一〇九)单独编译(多个源代码文件和头文件)的相关文章

mfc-请问软件著作权的源代码提交中头文件该如何处理?

问题描述 请问软件著作权的源代码提交中头文件该如何处理? RT. 源代码要求源程序前.后各连续的30页. 程序是用MFC编写的. 我看到有人说开头处是app类的cpp文件中的InitInstance. 然后请问头文件的内容需要提交吗? 如果定义很多类的话需要先提交头文件再提交源文件? 解决方案 头文件应该都需要提交吧.也是属于源代码一部分 解决方案二: 如果你的源代码不满60页,那么没什么好说的,全部给吧.但是作为一个要申请著作权的软件,我想一般都会超过60页吧. 现在的源代码都分成很多个文件,

【COCOS2DX通信(HTTP&amp;SOCKET)相关编译到ANDROID细节总结】编译加入CURL关联LIB与头文件 &amp;&amp; 解决PTHREAD的CANCEL函数NDK不支持,找不到SOCKADDR_IN、HTONS等问题;

本站文章均为 李华明Himi 原创,转载务必在明显处注明:  转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/android-game/881.html 本篇介绍在Cocos2dx中加入网络通信相关代码,然后编译到Android时出现的一些细节和需要注意的地方总结.不多废话了,直接进入正题:    1.  首先介绍在Cocos2dx中使用pthread编译时应注意: 由于NDK明确指明不支持 pthread_cancel() 函数,编译的错误提示如下

C++头文件编译问题

一.C++编译模式 通常,在一个C++程序中,只包含两类文件--.cpp文件和.h文件.其中,.cpp文件被称作C++源文件,里面放的都是C++的源代码:而.h文件则被称作C++头文件,里面放的也是C++的源代码. C+ +语言支持"分别编译"(separate compilation).也就是说,一个程序所有的内容,可以分成不同的部分分别放在不同的.cpp文件里..cpp文件里的东西都是相对独立的,在编 译(compile)时不需要与其他文件互通,只需要在编译成目标文件后再与其他的目

C++预编译头文件

许多初学 VC 的朋友也许都为那么一个问题困扰过:     为什么所有的 cpp 都必须 #include "stdafx.h"     也许请教了别的高手之后,他们会告诉你,这是预编译头,必须包含.可是,这到底是为什么呢?预编译头有什么用呢?     这得从头文件的编译原理讲起.其实头文件并不神秘,它的全部作用,就是把自己的所有内容直接"粘贴"到相应的 #include 语句处.如果不相信的话,不妨做个实验,将一个 cpp 中的所有 #include 语句删掉,并

linux gcc 编译时头文件和库文件搜索路径

一.头文件    gcc 在编译时寻找所需要的头文件 :    ※搜寻会从-I开始    ※然后找gcc的环境变量 C_INCLUDE_PATH,CPLUS_INCLUDE_PATH,OBJC_INCLUDE_PATH    ※再找内定目录   /usr/include   /usr/local/include   /usr/lib/gcc-lib/i386-linux/2.95.2/include   /usr/lib/gcc-lib/i386-linux/2.95.2/../../../..

单独编译Android 源代码中的模块实现方法_Android

      第一次下载好Android源代码工程后,我们通常是在Android源代码工程目录下执行make命令,经过漫长的等待之后,就可以得到Android系统镜像system.img了.以后如果我们修改了Android源代码中的某个模块或者在Android源代码工程新增一个自己的模块,是不是还是执行make命令呢?答案是否定的,Google为我们准备了另外的命令来支持编译单独的模块,以及重新打包system.img的命令.在继续学习Android源代码之前,就让我们先来看看这个命令吧.    

byte-java中 单独编译为什么会报错

问题描述 java中 单独编译为什么会报错 byte b=10; b=b+10; System.out.println(b); 解决方案 java是一种强类型语言,运算时,转换的一般原则是位少的类型转换为位数多的类型:10 是int 类型,32位,和byte类型8位相加,结果是int 类型的,可以这样 :b = (byte) (10 + b); 解决方案二: 要么b=(byte)(b+10);要么int b = 10; b + 10,因为后面的10被当作整数,所以表达式的结果也是整数,而没有整数

编译CDH HBase源代码并打补丁

写了一篇博客记录编译CDH HBase源代码并打补丁的过程,如有不正确的,欢迎指出! 下载源代码 从Cloudera github上下载最新分支源代码,例如:当前最新分支为cdh4-0.94.6_4.4.0 $ git clone git@github.com:cloudera/hbase.git -b cdh4-0.94.6_4.4.0 cdh4-0.94.6_4.4.0 说明: -b 指定下载哪个分支 最后一个参数指定下载下来的文件名称 添加snappy压缩支持 编译snappy $ svn

单独编译使用WebRTC的音频处理模块 - android

前言    最近一直在捣腾如何在android和iOS上使用Google的WebRTC--一个无疑大力推动了互联网即时通信以及VoIP发展的开源项目.    虽然WebRTC主要目标是为互联网提供高质量的富媒体即时通信,但其源码为C/C++所写,且其开发版中也包含对android 和 iOS 等移动设备的支持,因此对于如今飞速发展的移动互联网,WebRTC也能推波助澜大显神通.    WebRTC提供一套音频处理引擎VOE(本文不涉及视频处理引擎VIE),但VOE在 android 和 iOS