以下内容全部是个人总结,如果有错误请指正!
在初学C++的时候,我总是彷徨于不恰当使用头文件、声明、定义、源文件而导致的各种Link错误。今天我想通过一些简单的测试,得到一些概括的,一般性的结论。(因为我没有学习过C++的编译器,所以我所以这些结论仅仅是一些根据一些现象的猜想)
实验环境:集成开发环境(Visual Studio 2017),并没有直接使用过g++的shell命令。
1. 在Visual Studio 2017 环境下,项目中的文件会被赋予不同的类型每个类型有各自的作用。
1.1 头文件
头文件不会参与Link过程,所以头文件中如果存在语法错误,是不会被发现的。
e.g:
在头文件文件夹下新建文件:输入“wrong code”。运行 源.cpp,发现正常运行。
1.2 源文件
源文件既可以被 #include 导入(因为头文件源文件类型的区分是VS的限定,但对于一个编译器来说任何文件都是没有区别的,只有输入的参数不同),但导入之后同样会编译自身之后Link。
e.g:
在源文件文件夹下新建文件a.h: 定义函数 a, 在源.cpp 中include该文件,并使用a函数。
系统将出现符号重定义的错误:因为 VS认为a.h是源文件故编译了他。而源.cpp中include的部分也被编译,故出现了两个相同的符号。
2. C++ 的 #include 对文件的名字(后缀)不敏感
2.1 可以使用无 .h 后缀的文件作为 #include 的目标
这个是一个不需要实验的结论,因为常用的#include <iostream>便是一个很好的例子。
2.2 如果把一个正常的 .h 文件改成 .cpp 后缀,效果是一样的
正常情况:
// 源.cpp
#include "A" #include <iostream> int main() { A a; //int main2(); std::cout << "在main里:" << a.value << std::endl; //std::cout << "在main里:" << a.value << " " << a.functionA() << std::endl; //std::cout << "在main2里:"; //main2(); getwchar(); return 1; }
// A
#pragma once class A { public: int value = 100; //A(); //~A(); int functionA(); private: };
正常输出
当 #include 的对象改为 .cpp 后缀
// 源.cpp
#include "A.cpp" #include <iostream> int main() { A a; //int main2(); std::cout << "在main里:" << a.value << std::endl; //std::cout << "在main里:" << a.value << " " << a.functionA() << std::endl; //std::cout << "在main2里:"; //main2(); getwchar(); return 1; }
// A.cpp
#pragma once class A { public: int value = 100; //A(); //~A(); int functionA(); private: };
正常输出
3. #include 是的本质是将 #include文件的全体代码替换到当前位置
3.1 (猜想)因为 #include 带 “#” 所以是预处理过程,其过程将先与其他语法检查步骤
使用 #include 语句
// 源.cpp
#include "testInclude.h" #include <iostream> int main() { std::cout << functionToTestInclude(); getwchar(); return 1; }
// testInclude.h
#pragma once int functionToTestInclude() { return 1; }
正常输出
直接将 #include 对象替换掉 #include 语句
// 源.cpp
int functionToTestInclude() { return 1; } #include <iostream> int main() { std::cout << functionToTestInclude(); getwchar(); return 1; }
正常输出
结论:
1. 替换后效果一样。
2. 没有检测出“未定义函数”,说明语法检测过程是在 #include 之后。
3. 所以现在 “#include 的文件” 也没有必要讨论了。我们所讨论的项目里剩下的,#include 别的文件的文件,并默认在 #include 展开之后,这些文件就是Visual Studio 2017里标记类型为源文件的需要被编译的文件。
4. 单个文件内支持多次全局声明
4.1 多个文件支持重复全局声明
// 源.cpp
// 声明函数 int functionToTestInclude(); int functionToTestInclude(); int functionToTestInclude(); // 声明变量 extern int intV; extern int intV; // 声明函数指针 int *intFP(); int *intFP(); class classExample; class classExample; // 正常运行 int main() { return 1; }
// 源1.cpp
//声明函数 int functionToTestInclude(); int functionToTestInclude(); int functionToTestInclude(); //声明变量 extern int intV; extern int intV; // 声明函数指针 int *intFP(); int *intFP(); class classExample; class classExample;
5. 单个文件中不允许任何全局数据重复定义,只有类允许在多个文件中重复定义(包括类内方法定义,但不包括类外部实现的方法)
5.1 使用函数时,可以先声明,编译器会自动查找到函数的定义(在后文或其他文件内)。
5.2 定义对象时,必须在之前上文中有类的定义。
#include "iostream" class testClass; class testClass { }; int function(); int main() { testClass as; int ret = function(); getchar(); return 1; }
// 函数在后文定义不会出现问题 int function() { return 1; }
// 将testClass的定义 替换到此处,将导致程序非法,出现 “testClass”未定义的错误。
5.3 定义类时,如果需要某个其它类构造器内部的属性或方法,可以不用定义,声明即可,但必须在同一文件内。
#include "iostream" class testClass; class refClass; class testClass { public : refClass ref_class; }; int main() { testClass as; getchar(); return 1; } int function() { return 1; }
// 先声明,在后文定义不会出现问题 (若将此定义移至其他文件,将会出现“refClass未定义”的错误) class refClass { };
5.4 多个文件重复定义类,构造对象时所选用的类定义是不确定的。
5.4.1 因为一般情况类写在头文件内,所以每个文件 include 展开后类的定义是相同的,故不会出现问题。