1. C++ compilation model

Usually, a C++ program contains only two types of files — .cpp files and .h files. Among them, .cpp files are called C++ source files, which contain C++ source code; .h files are called C++ header files, which also contain C++ source code.

The C++ language supports "separate compilation". That is, all the contents of a program can be divided into different parts and placed in different .cpp files. The contents in a .cpp file are relatively independent; during compilation, they do not need to communicate with other files. It is only necessary to link them with other object files after compiling into object files. For example, a global function "void a(){}" is defined in file a.cpp, and file b.cpp needs to call this function. Even so, file a.cpp and file b.cpp do not need to know of each other's existence. They can be compiled separately, and after compiling into object files, they are linked, and the whole program can run.

How is this achieved? From the perspective of writing a program, it is very simple. In file b.cpp, before calling the function "void a()", just declare the function "void a();". This is because when the compiler compiles b.cpp, it generates a symbol table. Symbols without definitions, such as "void a()", are stored in this table. During linking, the compiler looks for the definition of this symbol in other object files. Once found, the program can be generated successfully.

Note that two concepts are mentioned here: "definition" and "declaration". Simply put, a "definition" describes a symbol completely: whether it is a variable or a function, what return type it has, what parameters it needs, and so on. A "declaration" only declares the existence of the symbol, that is, it tells the compiler: this symbol is defined in another file; I will use it here, and when you link, go somewhere else to find out what it actually is. When defining, you must completely define a symbol (variable or function) according to C++ syntax, but when declaring, you only need to write out the prototype of the symbol. It should be noted that a symbol can be declared multiple times in an entire program, but it must be defined exactly once. Imagine, if a symbol has two different definitions, which one should the compiler listen to?This mechanism brings many benefits to C++ programmers, and also leads to a method of writing programs. Consider this: if there is a commonly used function "void f() {}", which is called in many .cpp files throughout the program, then we only need to define the function in one file and declare it in the other files. One function is easy to handle; declaring it is just one sentence. But what if there are many functions, such as a large set of mathematical functions, several hundred of them? Can we guarantee that every programmer can accurately remember and write out the forms of all functions?

2. What is a header file?

Obviously, the answer is impossible. But there is a very simple way to help programmers save the trouble of remembering so many function prototypes: we can write all the declaration statements of those hundreds of functions in advance, put them in one file, and when programmers need them, they can copy all of these into their source code.

This method is indeed feasible, but it is still too troublesome and rather clumsy. So header files can play their role. The so-called header file, in fact, has the same content as .cpp files; both are C++ source code. However, header files do not need to be compiled. We put all function declarations into a header file. When a .cpp source file needs them, they can be included into the .cpp file through a macro command "#include", thereby merging their contents into the .cpp file. When the .cpp file is compiled, the included .h files take effect.

For example, suppose there are only two mathematical functions: f1 and f2. Then we put their definitions in math.cpp:

/* math.cpp */ double f1() { //do something here.... return; } double f2(double a) { //do something here... return a * a; } /* end of math.cpp */

And put the declarations of "these" functions in a header file math.h:

/* math.h */ double f1(); double f2(double); /* end of math.h */

In another file, main.cpp, I want to call these two functions, so I only need to include the header file:

/* main.cpp */ #include "math.h" main() { int number1 = f1(); int number2 = f2(number1); } /* end of main.cpp */

In this way, it is a complete program. It should be noted that the .h file does not need to be written after the compiler command, but it must be in a place where the compiler can find it (for example, in the same directory as main.cpp). Both main.cpp and math.cpp can be compiled separately, generating main.o and math.o. Then, after linking these two object files, the program can run.

3. #include

#include is a macro command from the C language. It takes effect before the compiler compiles, that is, during preprocessing. The function of #include is to include the contents of the file written after it into the current file completely and without changing a word. It is worth mentioning that it has no other functions or side effects. Its function is to replace every place where it appears with the contents of the file written after it. Simple text replacement, nothing else. Therefore, the first line in main.cpp (#include "math.h") will be replaced with the contents of math.h before compilation. That is, when the compilation process is about to begin, the content of main.cpp has already changed:

/* ~main.cpp */ double f1(); double f2(double); main() { int number1 = f1(); int number2 = f2(number1); } /* end of ~main.cpp */

Not more, not less, just right. By the same reasoning, if in addition to main.cpp, there are many other .cpp files that also use the f1 and f2 functions, then they all only need to write #include "math.h" before using these two functions.


4. What should be written in a header file?

From the discussion above, we can understand that the role of header files is to be included by other .cpp files. They themselves do not participate in compilation, but in fact, their contents are compiled in multiple .cpp files. Through the rule that "a definition can occur only once," we can easily conclude that header files should only contain declarations of variables and functions, not their definitions. Because the content of a header file is actually introduced into multiple different .cpp files, and they are all compiled. Putting declarations is fine. If definitions are put there, it is equivalent to having a definition for a symbol (variable or function) appearing in multiple files. Even if these definitions are all identical, it is illegal for the compiler.

So, one point to remember is that in .h header files, only declarations of variables or functions should exist, not definitions. That is, you can only write sentences like: extern int a; and void f(); in header files. These are declarations. If you write sentences like int a; or void f() {}, once this header file is included by two or more .cpp files, the compiler will immediately report an error. (Regarding extern, we discussed it earlier; here we will not discuss the difference between definitions and declarations again.)

However, there are three exceptions to this rule:

  • First, definitions of const objects can be written in header files. Because global const objects do not have extern declarations by default, they are valid only in the current file. Writing such objects in a header file, even if it is included in multiple other .cpp files, the object is only valid in the file that includes it and is invisible to other files, so it will not cause multiple definitions. At the same time, because the object in these .cpp files is included from one header file, this ensures that the value of the const object in these .cpp files is the same. It can be said to kill two birds with one stone. Similarly, definitions of static objects can also be placed in header files.
  • Second, definitions of inline functions can be written in header files. Because an inline function needs the compiler to expand it inline at the place where it is encountered according to its definition, and unlike ordinary functions, it cannot be declared first and then linked (inline functions are not linked), the compiler needs to see the complete definition of the inline function at compile time. If an inline function could only be defined once like an ordinary function, this would be difficult. Because in one file it is fine; I can put the definition of the inline function at the very beginning, so that the definition can be seen wherever it is used later. But what if I also use this function in other files? There is almost no good solution. Therefore, C++ stipulates that an inline function can be defined multiple times in a program, as long as the inline function appears only once in a .cpp file, and the definition of the inline function is the same in all .cpp files, it can pass compilation. Obviously, putting inline function definitions into a header file is a very wise practice.
  • Third, class definitions can be written in header files. Because when a program creates an object of a class, the compiler can know how the object of this class should be laid out only when the class definition is fully visible. Therefore, the requirements for class definitions are basically the same as those for inline functions. So putting a class definition into a header file and including that header file in the .cpp files that use the class is a good practice. Here, it is worth mentioning that a class definition contains data members and function members. Data members are defined (space is allocated) only when a specific object is created, but function members need to be defined from the very beginning, which is what we usually call the implementation of the class. Generally, our approach is to put the class definition in the header file and put the implementation code of the function members in a .cpp file. This is acceptable and is a very good approach. However, there is another approach: directly write the implementation code of the function members inside the class definition as well. In C++ classes, if a function member is defined within the class definition body, the compiler will treat this function as inline. Therefore, defining function members inside the class definition body and putting them together into the header file is legal. Note that if the definition of a function member is written in the header file of the class definition but not inside the class definition, this is illegal, because the function member is no longer inline at this point. Once the header file is included by two or more .cpp files, the function member will be redefined.

5. Protective measures in header files

Consider this: if the header file contains only declaration statements, then no matter how many times it is included by the same .cpp file, there is no problem—because the appearance of declaration statements is unrestricted. However, the three exceptions in header files discussed above are also a very common use of header files. So once any one of the above three exceptions appears in a header file, if it is included multiple times by a .cpp file, there will be a big problem. This is because the syntax elements in these three exceptions, although they "can be defined in multiple source files," "can only appear once in a single source file." Imagine that a.h contains the definition of class A, and b.h contains the definition of class B. Since the definition of class B depends on class A, b.h also #includes a.h. Now there is a source file that uses both class A and class B, so the programmer includes both a.h and b.h in this source file. At this point, the problem arises: the definition of class A appears twice in this source file! As a result, the entire program cannot be compiled. You might think this is the programmer's mistake—he should have known that b.h includes a.h—but in fact he should not have to know.

Using "#define" combined with conditional compilation can solve this problem well. In a header file, define a name via #define, and use the conditional compilation #ifndef...#endif so that the compiler can decide whether to continue compiling the rest of the header file based on whether this name is defined. This method is simple, but you must remember to include it when writing header files.


The difference between C++ header files and source files

1. How source files associate with header files based on #include

  • 1. System-provided header files are enclosed in angle brackets, so the compiler will search for them in the system file directory.
  • 2. User-defined files are enclosed in double quotes. The compiler first searches in the user directory, then searches in the C++ installation directory (for example, in VC you can specify and modify the library file search path, and in Unix and Linux you can set it through environment variables), and finally searches in system files.

#include "xxx.h" (I always thought "" and <> made no difference, but tinyxml.h is a non-system file, so "" must be used)

2. How header files associate with source files

This question actually means: given that the header file "a.h" declares a series of functions, and "b.cpp" implements these functions, if I want to use in "c.cpp" the functions declared in "a.h" and implemented in "b.cpp", I normally use #include "a.h" in "c.cpp". So how does c.cpp find the implementations in b.cpp?

In fact, the file names of .cpp and .h have no direct relationship. Many compilers can accept other extensions. For example, I now see in our company's source code that .cpp files have been replaced by .cc files.

In Turbo C, compilation is done by command line, and the command line parameter is the file name, whose default extensions are .cpp and .h, but they can also be customized to .xxx and so on.

In Teacher Tan Haoqiang's book "C Programming", it is mentioned that during compiler preprocessing, the #include command is processed as "file inclusion processing": the entire contents of file2.c are copied to the position of #include "file2.c". This also explains why many compilers do not care what the file's suffix is—because #include preprocessing is just doing a "copy and insert code" job.

During compilation, it does not look for the function implementations in b.cpp; this is only done at link time. Using #include "a.h" in b.cpp or c.cpp is actually introducing related declarations so that compilation can pass. The program does not care where the implementation is or how it is implemented. After the source files are compiled, object files (.o or .obj files) are generated. In these object files, these functions and variables are treated as symbols. At link time, the makefile needs to specify which .o or .obj file to link (here, the .o or .obj file generated by b.cpp). At this point, the linker goes to that .o or .obj file to find the functions implemented in b.cpp, and then builds them into the executable file specified in the makefile.

Under Unix, you can even avoid including header files in source files; you only need to specify them in the makefile (but this greatly reduces program readability, which is a bad habit, ^_^). In VC, in general you do not need to write a makefile yourself; you only need to include all required files in the project, and VC will automatically write the makefile for you.

Usually, the C++ compiler looks for the required symbols in every .o or .obj file, rather than only in a specific file, or stopping after finding one. Therefore, if the same function is implemented in several different files, or the same global variable is defined, the linker will report "redefined" during linking.

In summary

.h files can contain:

  • Declarations of class member data, but cannot assign values
  • Definitions and assignments of class static data members, but this is not recommended; just a declaration is fine.
  • Declarations of class member functions
  • Declarations of non-class member functions
  • Constant definitions: e.g., constint a=5;
  • Definitions of static functions
  • Definitions of class inline functions

Cannot contain:

  • 1. Declarations of all non-static variables (not class data members)
  • 2. Do not put default namespace declarations in header files; using namespace std; etc. should be placed in .cpp files. Use std::string in .h files.

Original URL: https://blog.csdn.net/qq_35038153/article/details/71293265