C++ 头文件说明
如果一个程序足够大,代码功能很多,可以想象,不可能把代码写在一个cpp文件里。我们需要模块化,这样的好处很多,方便分工合作,可读性提高,调用也方便。
这个要怎么做呢?
很简单直接当前cpp文件目录下再新建一个test.cpp,如下:
里面就一句代码:
int a=50;
就行;
然后ConsoleAppliciation1.cpp主程序代码:
#include <iostream>
#include <string>
using namespace std;
extern int a;
int main()
{
cout << a;
}
然后编译运行(编译器会自动编译所有文件):
很明显,认到了外部变量a;
关键是这个语句 extern int a;声明a是个外部变量。起了作用,如果没有这个声明,则程序无法正常执行。
这个是针对多文件的吗?
不只是,这个是声明int a 是个外部变量。
只要在main函数里,访问变量a前,按上下顺序没有看到变量的a的定义,都需要用这个extern声明一下,否则编译器不认识。
比如,如下语句:
#include <iostream>
#include <string>
using namespace std;
extern int a;
int main()
{
cout << b;
}
int b = 60;
可以看到 编译直接报错,如下:
不能识别变量“b",所以我们也要在最前面加上extern int b;即使在同一个文件中。
这样再次编译就正常了。
那么函数也是同样的道理,比如test.cpp 如下代码:
#include <iostream>
void test()
{
std::cout << 100;
}
主文件:
#include <iostream>
#include <string>
using namespace std;
void test();
int main()
{
test();
}
可以看到在最前面有void test();声明,那么这里为什么没有extern关键字?其实这里你加上也是可以的,因为跟变量不同,函数声明可以省略extern,默认是extern;
那么同理,你的函数定义是main函数后面, 即使在同一个文件中,你也是要声明一下函数的。
那么现在有一个问题,既然访问其它文件的变量需要extern声明一下,如果不加声明,是否可以在两个cpp文件里定义了一个同名的变量。
比如test.cpp
int a;
主文件cpp:
#include <iostream>
using namespace std;
int a;
int main() {
}
答案是不行,会报错,如下:
如果你很疑惑,那么看下例代码,同样是未声明extern;你就能理解了,跟这个差不多的道理:
#include <iostream>
using namespace std;
int a;
int main() {
}
int a;
结果都是不能重定义,这一点要明白。
最终的结果是两个cpp,不能定义一样的变量。但这并不是因为全局变量的作用域于所有cpp文件,全局变量作用域只是针对当前cpp文件。
不能定义是因为链接obj时是不能有相同的变量名。所以取消extern声明,也是不能定义相同的变量。
这种现象如果很难理解,你可以在单文件里也找到类似现象。
#include <iostream>
using namespace std;
extern int a;
int main() {
}
int a;
下面的那个int a的作用域是从定义处开始,到文件结束,不是对于整个CPP文件的,但你不能说,根据作用域,就可以在最上面再定义一个int a了(取消extern声明),这很显然会引起冲突的。
只不过一个是编译时报错,不能重定义,而两个cpp里不能重定义,是编译通过了(因为编译时都是一次次单独编译CPP文件的),变成中间文件obj 链接的时候会报错,不能有相同的外部链接变量。
那么,可以想象,如果分工合作,在多个cpp文件中,定义全局变量,总会不小心定义了相同的全局变量。
要如何解决:
1.可以使用static修饰变量,这样的全局变量,就只针对单cpp文件,不能被其它cpp文件访问,也不会引起冲突,如:test.h
static int a;
这样就没问题了。
2.可以使用命名空间的方法,这里就不示例代码了,只是提供一种思路。
3.那么函数,跟变量是同样的道理,不能在多个cpp文件里重复定义,只能定义一次,然后extern声明引用。如果实在要定义,加上static关键字。声明不被外部链接。只在当前cpp文件里有效。
明白了上面这些,我们可能需要使用大量的声明,变量和函数(以后还会有类)
如果要有多个cpp文件要使用这些,那么每次都打这么一串代码,实在麻烦。而且阅读体验也较差。
所以我们可以把这些声明做成一个头文件。
比如现在有test.cpp文件,如下代码:
#include<iostream>
using namespace std;
int a = 5;
void test()
{
cout << "\ntest函数\n";
}
然后是test.h头文件:
void test();
extern int a;
主文件ConsoleApplication1.cpp 包含头文件,然后调用:
#include <iostream>
#include"test.h"
using namespace std;
int main() {
cout << "a值:" << a;
test();
}
运行结果:
这里的#include"test.h",实际是预处理命令,即:将test.h里的代码插入当前位置(非编译).
所以实际是这样:
#include <iostream>
void test();
extern int a;
using namespace std;
int main() {
cout << "a值:" << a;
test();
}
本质跟我们之前手动声明是一样的。
那么这里又有几个问题,既然#include只是简单的插入代码,
或者为什么test.h和test.cpp声明和定义要分开写呢?我全写在test.h里。
首先回答test.h里是可以定义变量,或者具体函数代码,但十分不建议这样写,如果你定义了变量a,前面说过了,多个文件#include test.h,链接时会报重复定义。这样极容易造成混乱。
所以标准的做法是分开写,如果一个cpp里的变量和函数要被其它文件使用,声明部分要分开写成一个头文件。以表示共用。
#ifndef
讲到头文件,这里不得不提一下ifndef 宏定义相关。
比如,将上面test.h代码改成这样:
#ifndef TEST_H
#define TEST_H
void test();
extern int a;
#endif
上面的语句意思是,如果没有定义宏TEST_H,则执行#endif之前的语句,就是定义宏TEST_H,然后声明函数test和变量a.
这样做的好处是防止代码重复包含,比如一个cpp里多次引用了头文件,它只会插入一次代码,如果识别到了宏TEST_H.
如类似这样:
#include <iostream>
using namespace std;
#include"test.h"
#include"test.h"
int main() {
test();
}
注意这里的宏定义只是对当前cpp文件进行判断,因为本质上宏定义也是预处理,只是单纯替换文本。
所以
#ifndef TEST_H
#define TEST_H
#endif
这些语句,就像你在一个文件里#define max 100
然后你在另一个文件里使用max,是不合法的,只作用于当前文件,所以如果在另一个文件用#ifndef 判断max,肯定也是未定义的。
引申:那么如果你在test.h里面定义了一个变量a,而不是声明,就不要指望这个#ifndef 帮你解决前面多个文件包含test.h冲突的问题,而且这样即使解决了(比如编译器改了逻辑,宏列表对所有cpp有效),也只是假象,因为它会把代码都清掉了(除了第一次文件),其它文件根本就访问不到这个变量a,自然也不会有冲突了。
我们要做的就是按规矩写代码。这里就告诉了我们为什么要这样做。这样就能避免很多问题。