面试题答案
一键面试#include <>
和 #include ""
的行为差异
-
#include <>
:- 行为:在系统指定的包含目录中搜索头文件。这些目录通常是编译器安装时设置的标准库头文件所在位置。例如,在Windows下,可能是
C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\include
这样的路径;在Linux下,一般是/usr/include
等系统级别的目录;在macOS下,类似/usr/include
等。 - 不同操作系统和构建系统下的共性:无论在Windows、Linux还是macOS,使用
#include <>
都是优先搜索系统级别的标准库头文件路径,这是由C++语言标准规定的。不同构建系统(如CMake、Makefile)也遵循这一标准,不会改变其搜索系统目录的行为。
- 行为:在系统指定的包含目录中搜索头文件。这些目录通常是编译器安装时设置的标准库头文件所在位置。例如,在Windows下,可能是
-
#include ""
:- 行为:首先在当前源文件所在目录中搜索头文件。如果在当前目录中未找到,再到系统指定的包含目录中搜索(与
#include <>
搜索的目录相同)。 - 不同操作系统和构建系统下的共性:同样,在不同操作系统(Windows、Linux、macOS)和构建系统(CMake、Makefile)下,
#include ""
都是先从当前源文件目录开始搜索,若找不到再搜索系统目录,这也是C++语言标准规定的行为。但不同构建系统可能会影响当前源文件目录的解析方式,例如在复杂的项目结构中,Makefile可能需要通过相对路径设置等方式来准确解析当前目录,而CMake有自己的机制来处理源文件目录的定位。
- 行为:首先在当前源文件所在目录中搜索头文件。如果在当前目录中未找到,再到系统指定的包含目录中搜索(与
编写可移植且高效代码处理头文件包含的方法
- 使用
#include <>
包含标准库头文件:- 对于C++标准库头文件(如
<iostream>
、<vector>
等)和系统相关的标准头文件(如<windows.h>
在Windows下,<unistd.h>
在Linux和macOS下),始终使用#include <>
。这样可以确保无论在哪个操作系统和构建系统下,都能正确找到标准库头文件,避免因在当前目录搜索标准库头文件带来的潜在问题。例如:
#include <iostream> #include <vector> #ifdef _WIN32 #include <windows.h> #else #include <unistd.h> #endif
- 对于C++标准库头文件(如
- 合理组织自定义头文件路径:
- 相对路径:对于自定义头文件,使用相对路径的方式来包含。例如,如果项目结构是
project/src/module1/header1.h
和project/src/module1/source1.cpp
,在source1.cpp
中可以使用#include "header1.h"
。这种方式在不同操作系统和构建系统下都能保持一致的行为,只要项目结构不变。 - 设置包含目录:在构建系统层面,可以通过设置包含目录来简化头文件包含。
- CMake:使用
include_directories
命令来添加自定义头文件的搜索路径。例如,在CMakeLists.txt
中:
这样在源文件中就可以直接使用include_directories(${PROJECT_SOURCE_DIR}/src)
#include "module1/header1.h"
,而不需要关心src
目录的层级。 - Makefile:通过设置
CFLAGS
变量中的-I
选项来添加包含目录。例如:
然后在源文件中同样可以使用相对路径包含头文件,如CFLAGS = -I$(PROJECT_SOURCE_DIR)/src
#include "module1/header1.h"
。
- CMake:使用
- 相对路径:对于自定义头文件,使用相对路径的方式来包含。例如,如果项目结构是
- 条件编译:
- 对于不同操作系统特有的头文件或代码,可以使用条件编译。例如,对于Windows特有的代码,可以使用
#ifdef _WIN32
,对于Linux和macOS等类Unix系统,可以使用#ifdef __unix__
或#ifdef __APPLE__
(针对macOS)。
#ifdef _WIN32 #include <windows.h> // Windows - specific code #elif defined(__unix__) || defined(__APPLE__) #include <unistd.h> // Unix - like code #endif
- 对于不同操作系统特有的头文件或代码,可以使用条件编译。例如,对于Windows特有的代码,可以使用
- 避免不必要的包含:
- 只包含实际需要的头文件,避免过度包含。例如,如果一个函数只需要使用
std::vector
的部分功能,而不需要整个<iostream>
的功能,就不要包含<iostream>
。这样可以减少编译时间,并且降低因头文件依赖带来的潜在问题。例如:
// 只需要std::vector #include <vector> // 不需要iostream // #include <iostream> void func() { std::vector<int> v; // 函数实现,不涉及iostream相关操作 }
- 只包含实际需要的头文件,避免过度包含。例如,如果一个函数只需要使用
- 使用前向声明:
- 在可能的情况下,使用前向声明代替头文件包含。例如,如果一个类
B
只需要使用类A
的指针或引用,而不需要访问A
的成员,那么可以在B
的头文件中进行前向声明class A;
,而不是包含A
的头文件。
这样在编译// classB.h class A; // 前向声明 class B { private: A* aPtr; public: B(A* ptr); }; // classB.cpp #include "classA.h" #include "classB.h" B::B(A* ptr) : aPtr(ptr) {}
classB.h
依赖的文件时,不需要重新编译classA.h
,提高了编译效率。 - 在可能的情况下,使用前向声明代替头文件包含。例如,如果一个类