前言的碎碎念
作者在第一次做项目实践的时候尝试分文件去写函数,由于事先没了解过头文件的使用所以把函数全写头文件里面了(然后就出现了编译错误的情况)上网查资料的时候没找到关于自己写的头文件的较详细的说明,所以有了这篇文章。
如果你认为这篇文章帮助到你的话记得点赞收藏评论关注分享。
这篇文章就来讲讲:
1、头文件是什么,为什么我们要用。
2、自己写的头文件里应该有什么,不能有什么,为什么。
3、以及一些头文件使用中的常见问题
正文
准备工作
不少学生可能是直接使用vs作为学习软件,所以对于编译原理不太了解,对于声明与定义的区别也不太了解(主打一个能动就行),所以在文章开始前必须先了解一些东西:
1、什么是定义,什么是声明
2、编译与链接
(其实你看完作者写的定义与声明也差不多可以了)
头文件是什么
首先我们先看头文件是怎么工作的:
//头文件h.h
int a=1
//源文件.cpp
#include<iostream>
#include"h.h"
using namespace std;
int main() {
cout << a;
}
就等效于
#include<iostream>
int a=1;
using namespace std;
int main() {
cout << a;
}
PS:请注意把定义写在头文件里并不规范(很容易报错),这只是为了举例子
其实头文件里的内容并不会直接被编译,在预处理环节(编译前)原封不动地粘贴到源文件里。然后在编译时,直接编译源文件。
其实头文件就是一个巨大的复制粘贴文本。
我合理的怀疑头文件最最最大的作用就是偷懒用。
因为我们懒得把一段代码写很多次,写到不同的源文件里。
所以直接写头文件里这样就可以直接在不同源文件里调用头文件了而不是重复写了。
PS:当然还要其他作用的。
我们为什么要用头文件
前面好像就把这个问题讲完了:
1、方便(不用每个文件都写一样的代码) 2、可读性强 3、避免循环调用
方便这个很好理解了。
对于可读性:
当我有多个源文件没有头文件时,我想调用其中一个函数,我就得一个个源文件里找,但如果我们借助头文件,我们就能更快找到函数名字、所在文件极其功能功能(这些都应该//标注好)。
头文件可以起到一个类似于目录的作用。
避免循环调用:
如果我有三个源文件(源1源2源3)我在源1里要调用源2源3的函数,在源2又要调用源3的函数,这时候没有头文件,我们就得重复的声明,但如果我们把全部声明都放到头文件里,就不用考虑声明的问题,直接调用头文件就好了。
怎么使用头文件
一句话,头文件用来存放声明,不能存放定义。
头文件里存放定义会导致重定义(报错)
头文件里应该有什么
头文件里可以有:
1、导入一些库 2、函数的声明 3、声明一些常量(#define A 1)
4、变量的声明 5、创造类或结构体
举例代码:
//头文件h.h
#include<iostream>//导入库
#define E 2//声明常量
int sum(int, int);//声明函数
int mul(int, int);
//源1
#include"h.h"
using namespace std;
int main() {
int a = 1;
cout << sum(a, E);
cout << mul(a, E);
return 0;
}
//源2
int sum(int a, int b) {
return a + b;
}
int mul(int a, int b) {
return a * b;
}
对于声明变量需要特别说以下:
对于函数int sum(int,int);是声明
但对于变量int a,是对变量a的定义而不是声明(即便没赋值也是定义
变量在头文件中的定义应该写成 extern int a
对于结构体也一样,例子如下:
//头文件h.h
#include<iostream>
#define E 2
using namespace std;
int sum(int, int);
extern int a;//这是声明不是定义
struct dot {
int x=2, y=2;
};
extern dot d;//声明结构体
//源1
#include"h.h"
int main() {
cout << sum(a, E);
cout << sum(d.x, d.y);
return 0;
}
//源2
#include"h.h"
int sum(int a, int b) {
return a + b;
}
int a = 1;//这是定义
dot d;//这是定义
可以看到,在源2中定义,经过头文件声明,源1中也可以直接使用
对于类class更是要强调:
方法一定不能在头文件里定义(和函数一个原理)具体格式,请看vcr:
//头文件h.h
#include<iostream>
#define E 2
using namespace std;
int sum(int, int);
class axis {
public:
int x=2, y=2;
void out();//这是声明
};
extern axis dot;//声明和结构体的声明一样
//源1
#include"h.h"
int main() {
cout<<sum(dot.x, dot.y);
dot.out();
return 0;
}
//源2
#include"h.h"
int sum(int a, int b) {
return a + b;
}
axis dot;//这是定义
//以下是定义
void axis::out() {
cout << dot.x << ' ' << dot.y;
}
可以看见方法的声明得在类里面,但方法的定义不能在头文件里,定义的格式为:
返回值类型+类名字+::+方法名字+(参数)
其他的和类差不多。
头文件里不能有什么,为什么
绝对绝对不能有定义,只能有声明,就这么简单。
所以分清楚类和声明就非常重要,建议去看作者的声明与定义,并理解上方的例子
为什么不能有定义,奥秘就在作者的这篇文章中。
众所周知啊,头文件就是复制粘贴,如果你在头文件中定义了,那就是在每一个使用了这个头文件的源文件都定义了一次,也就是重定义了。
一些常见的错误
以下为一些个人经验,欢迎评论私信补充,错误代码不一定是因为这个原因产生的,请各位一定要结合实践来排错。
1、LNK2001、LNK1120
在头文件中写extern int a是声明不是定义,也就是告诉电脑有这个变量的存在,但并没有给这个变量分配地址,所以在其他源文件中一定要有一个int a也即是一定要有一个定义。
这个错误可能是头文件里有extern int a(任意声明语句,声明结构体和类也一样),然后其他文件有a=1(任意赋值语句或者比较、输出语句),没有int a(定义语句)。
2、LNK1969、LNK2005
在头文件中有extern int a=1;直接赋值了,这个声明语句就被当成了定义语句,报了重定义的错
作者还没有遇到个其他问题,欢迎补充捏
如果你认为这篇文章帮助到你的话记得点赞收藏评论关注分享。