1.书上在说完 以防重复定义所以头文件只声明 后,就讲了条件预处理命令。两者是 并列关系 即 是两种处理 多次定义 的方法?
两者相辅相成吧。还是用例子可能说得清楚些:
程序代码:
/* a.h */
#ifndef A_H
#define A_H
#include <iostream>
int a = 1;
#endif
程序代码:
/* b.h */
#ifndef B_H
#define B_H
#include <iostream>
int a = 1;
#endif
程序代码:
#include "a.h"
#include "b.h"
using namespace std;
int main()
{
cout << a << endl;
return 0;
}
虽然,两个头文件都使用了条件编译,但还是不能避免重定义。
不过它有另外的一些好处。比如如果 iostream 这个头文件也使用了这个技术的话(确实使了),本来可能会展开两次的的头文件,就只展开了一次。这意味着编译器可以少分析数千行的代码(这还得假设重复包含不会出现其它问题)。
另一方面,如果 a.h 和 b.h 里都用的是外部声明。而其实 a 这个变量是定义在比如 a.c 这个文件里的话。那么包多少遍 a.h 和 b.h 都没问题。当然了,如果你又写了一个 b.c 也定义了 a 可能还是不行。
2.“contents 其实只是类 screen 定义的一部分。”是说类定义里的对变量的操作都不是定义?是声明?那怎么还会“因为如果一个变量既在 a.h 中有所定义,又在 b.h 中有所定义。那么最终会因为重复定义而产生错误。所以一般只能用 exterm 声明变量,而在对应的 cpp 中定义。”? 没理解,所以这两句意思感觉矛盾。。。 “变量应该定义在 cpp 里。”到底怎么定义呢?
假如写 class A {}; 那么,无论你在那个括号里写什么东西,都是类 A 的定义,类的定义就是比较长而已。虽然有些语句看上去像是在定义变量,但其实都不是在定义变量。而是在定义一个类的成员,只是类定义语句的一部分。类的定义只能放在 .h 里,原因昨天我有解释,这不重复了。又果有什么不明白的地方可以再问。
而后半段说的其实是上面那个问题。当时可能语序搞的不是很清楚,引起了一些歧义。正确的写法就是头文件里写 extern int a; cpp 里写 int a = 1; 这样。类的定义不受这个限制。
3“.vs2010的项目有个include属性。。。这些是路径?
4.“你可以进入命令行窗口后打set回车。。。怎么没呢?
见 L版 的解释。
5但“一些指定的地方”是?我是可以随便在一个工程里建一个定义函数的cpp吗?只要有,他就能找到是吗?
“一些地方”指的就是像 include 一样,你设置的地方。你不告诉它哪找,它自己是不会自动搜索整个硬盘的。如果你指定的地方它找不到,它就认为是找不到了。
如果你没指定过,其实就是用的默认的,一般就是库文件,和当前工程。如果你指定它到另一个地方去找(这种做法有点像指定了另一个地方也是库文件一样),vc 应该是可以正确链接并生成可执行文件的。只是一般习惯上可能还是把相关的东西拉到当前工程里。我没怎么用过 vc,所以不是很清楚 vc 的做法。
感觉函数定义什么的就可以直接写在头文件里也没事了吧,那为何还要建源文件?
最重要的,一个头文件可能不止插到一个源代码里。和重定义变量一样,比如 a.h 里定义了一个函数 func(),而 main.cpp 和 src.cpp 都包含了 a.h,那么 func 这个函数就会被定义两次。编译也许可以过,但会引发链接错误。
不过这么做还涉及别的考虑。人们普遍意识到,代码版本升级等行为,一般都会对函数的实现做出改动。但函数的原型却很少改动。这即是说,函数的原型比函数的实现更稳定。
把稳定的东西插入到另一段代码里或者给其他人看是合适的。而把经常可能会改动的东西给别人看就不太好。因为人们一但知道某个函数是如何实现的,可能潜意识就会或多或少地利用这个函数的实现机里写自己的代码。但之后实现变了,也不会通知这个人,这个人也可能之后没再看过升级后的实现了。这就可能使得他写的代码兼容性下降。
把定义写在源文件里还有其它效率上的原因,比如在编译时,编译器会分析代码的逻辑。比如我有 100 个源文件,我只改动了其中的三个后,要求重新构建。这时如果某个代码本身没有发生过任何变化,而且它包含的所有头文件也没有发生过变化。那么这些代码本质上就没有发生任何变化,因此没有重新编译的必要。只用重新编译那三个改动了的代码,再把这些重新编译后的东西链接回原来的代码里就行了。如果你动不动就要改头文件,那么所有包含这些头文件的代码,虽然代码本身没有变化,但由于原来在头文件里声明的东西有可能会发生变化,编译器无法确定这些变化会有哪些影响,只好把它们全都重新编译一遍。
如Folder类定义前写一个Message的声明,不就解决了循环包含吗?
这种技术叫做 forward declaration,也许可以译做提前声明。是不完整声明的一种。这种声明是有限制的。如果一个类在声明的过程中,需要知道另一个类的存在,而不需要定义这个类的对象,也不需要知道这个类的任何细节。就可以用这个语法项。
具体讲,提前声明的类型只能用于声明其它的内容,但不能用于定义任何和它自己有关的东西。比如可以用这个方法声明友元类:
程序代码:
class A; // forward declaration.
extern A a; // OK; 声明一个外部对象。只是声明没有问题,没有定义。
class B { friend class A; }; // OK; 声明一个友元类,只要知道 A 是个类就行了。
class C { A *pa; }; // OK; 声明一个指向 A 类的指针。这个有点难理解,因为毕竟这里定义了一个对象,但是只用到了指向 A 的指针,没有用到和 A 有关的任何细节。
class D { A a; }; // error; 定义与 A 有关的对象。不行。
int main()
{
return 0;
}
因此这种语法无法实现循环包含的情况。没有任何方法能实现循环包含。因为先定义的那个类,无法看见另一个类的定义,从而无法定义和那个类有关的任何对象。
还有为什么返回值类型Folder&前没有Folder::
返回值前不需要加 Folder::,因为编译器在这之前看见过 Folder 的定义了,知道 Folder,或者
Folder& 是什么。那个 Folder::operator= 是用来限定这个函数名的。L版 回答的很清楚。
其实可以把 Folder::operator= 看成是这个函数的全名。在类内部使用函数时,默认就是用本类内部的函数,可以省去。在外面的时候就必须得加上了。注意成员函数定义在类的外面,因此要加 :: 限定。如果是在外面调用,那要用 f.operator= 类似的语法,因为编译器知道 f 是 Folder 的一个对象,才知道你要调用的是这个类的 operator= 而不是其它的。
[
本帖最后由 pangding 于 2012-8-23 23:20 编辑 ]