目录
一、#include头文件的作用
二、头文件的内容限制
1.重定义问题
2.static修饰符和const修饰符
3.头文件内容限制
三、#pragma once
四、推荐观看外国大佬the cherno视频
头文件是我们在编写c++程序时不可避免要打交道的东西,但是有很多人却不能搞明白头文件在程序中的作用和它的内容限制,本篇博客将探讨上诉问题。
一、#include头文件的作用
#include 头文件的作用其实非常简单,就是把头文件文件中的内容在编译时复制到包含它的cpp文件中。
可以用一个例子看到
首先打开visual studio,编写一个头文件test.h,其内容为}:
然后编写一个cpp文件ctest.cpp,包含test.h
- ctest.cpp
- int test(int a, int b){
- int res = a * b;
- return res
- #include"test.h"
复制代码
你可能会觉得上面代码中头文件的内容以及cpp文件的对头文件的包含位置会反常识,但是如果用visual studio对该cpp文件进行编译你会发现并没有出错,接下来进行一些操作我们可以看到编译生成的文件是什么:
打开项目的属性页,找到c/c++ -> 预处理器,将其中的预处理到文件选项改为 是
修改完设置后对ctest.cpp文件进行编译,编译完成后打开项目所在的文件夹,打开x64 -> debug文件夹,会发现一个ctest.i文件(该文件由预处理器生成),用文本编辑器打开查看里面的内容如下:
我们可以看到预处理器处理test.h文件,就是将其中的大括号复制到ctest.cpp文件中,这就是上面看似很奇怪的代码在能够成功编译的原因。你还可以试着用其他预处理语句,编译后查看生成的.i文件内容是什么,这里不再展开。
通过上述例子我们可以直观地看到#include头文件的作用其实就是简单的复制头文件内容到cpp文件中。
二、头文件的内容限制
通过前面的内容不难看出,对于头文件中的内容其实本来是没有限制的。但是我们又总是听到诸如“头文件中不能放入定义只能放声明”这种说法,那么这种说法是什么意思,又为什么会产生这种说法呢?下面我们就来探讨一下怎么判断什么可以放在头文件中,什么不可以放在头文件中。
1.重定义问题
如果编写以下代码:
- main.cpp
- #include<iostream>
- int a = 2;
- int a = 4;
- int main()
- {
- printf("%d\n", a);
- return 0;
- }
复制代码
总所周知,该代码进行编译会引发错误,错误原因是变量a重定义。
那如果我们将这对a的两次定义分别放入两个cpp文件中,还会出错吗?我们对两个cpp文件分别编译发现不会出错,但是我们在用调试器调试时发现还是会出现错误,因为与上面不同的是,这次是错误不是编译错误而是链接错误。关于什么是编译什么是链接,这里就不再展开,请自行查阅其他资料。
对相同变量名的多次定义会出现重定义,那么对相同函数名的多次定义也会引发重定义问题,不论是在同一cpp文件中多次定义还是在不同的cpp文件中多次定义。
出现上述问题的原因主要与作用域有关,函数定义以及在函数体外变量的定义都会使得定义的内容被所有cpp文件可见,因此即使是在不同cpp文件中定义相同的函数都会在链接时出现中重定义错误。
2.static修饰符和const修饰符
同一函数不能被多次定义,即使是在多次定义分布于同一项目的不同cpp文件中。
但是如果函数定义前加上static修饰符又会出现不同情况。下面看一个例子:
编写一个main.cpp文件,其中用static修饰符定义一个test()函数:
- main.cpp
- #include<iostream>
- static void test() {
- printf("%s\n", "function in main.cpp");
- }
- int main()
- {
- test();
- return 0;
- }
复制代码
编写一个test.cpp,其中也用static修饰符定义一个test()函数:
- test.cpp
- #include<iostream>
- void test() {
- printf("%s\n", "function in test.cpp");
- }
复制代码
运行程序结果:
代码没有报错,原因就在于函数的定义被static修饰符所修饰。static修饰符有一个特性,就是使得定义的内容只在定义的cpp文件中可见,其他cpp文件不可见。所以上诉代码看似是在main.cpp和test.cpp文件中都定义了相同的函数test(),实际上两个cpp文件都不能“看到”对方文件中定义的test()函数,所以在运行代码时不会出现重定义错误。
const修饰符修有与static修饰符类似的特性,因此const修饰符定义的常量也只能在定义的cpp文件中可见。
3.头文件内容限制
因为头文件的作用只是简单的复制内容到cpp文件,如果我们在头文件中定义了函数test(),当这个头文件被多个cpp文件包含时,相当于每个cpp文件都定义了一个相同的函数test(),显然这会引发重定义错误。所以,当一个头文件被多个cpp文件包含时,就不能在头文件中定义函数和变量(该定义特指没有用static和const修饰符的定义)
而在一个cpp文件中进行同一个函数的多次声明不会引发错误,如下图代码所示:
- test.cpp
- void test();
- void test();
- int main()
- {
- return 0;
- }
复制代码
即使在test.cpp中多次对test()函数进行声明,生成程序时不会出错。所以,即使一个头文件被多个cpp文件包含,在头文件中声明函数并不会引发错误。
但是当我们在头文件中用static修饰符定义一个函数,然后在多个cpp文件中引用该头文件,运行代码也不会产生重定义错误。这与上面第2点讲的static修饰符只在本文件可见的特性有关。
我们可以得出结论,“在头文件中只能声明不可定义”这句话是错误的,能否在头文件中定义函数和变量要分情况讨论。但是一般情况下我们在头文件中我们只放入函数的声明而不放入函数的定义,因为在头文件中定义函数很可能会不小心导致重定义问题的出现,所以就有了“在头文件中只能声明不可定义”这种说法。
三、#pragma once
我们总是在头文件开头看到#pragma once或者以下的结构:
- #ifndef _TEST_H
- #define _TEST_H
- ...
- #endif
复制代码
这两个语句的作用是为了防止头文件被多次包含,因为这会在头文件中有定义时导致多次定义错误的出现,所有的头文件都应该包含#pragma once或者#ifndef #define #endif结构。
四、推荐观看外国大佬the cherno视频
本篇博客知识来源于视频中以下章节