next up previous contents
Next: 8.5 The Function main() Up: 8. A More Modular Previous: 8.3 Introductory Comments

8.4 Include Statements, Function Declarations, etc.

The first lines of real code start right after the introductory comments:

\begin{Code}[nbody\_sh1.C: premain]
\small\verbatiminput{chap8/nbody_sh1.C.2_premain} \end{Code}

We start with #include statements to various libraries. The comments on each line mention some of the functions used from those libraries. If we would leave out one of these include statements, the corresponding functions listed could not be linked, and the compiler would issue an error.

The next statement indicates that we used the standard C++ namespace. Later, when gravitylab will have grown sufficiently large, it may be useful to create our own namespaces, in order to avoid collisions with other programs that may use names that are the same as we have chosen. Right now it is too early to worry about such complications.

The typedef statement defines the word real as an alternative for the build-in function type double. From now on we will only use the name real to indicate the standard floating point type double. It is far more logical to talk about real numbers of type real, together with the integers of type int, without using the archaic term `double' that stems from the expression `double precision' (long ago, the standard precision for floating point calculations used four bytes per floating point word, leading to the expression double precision for the now-standard eight-byte word length).

Next we introduce the symbol NDIM for the number of dimensions. So far we have simply used the number 3 in our loops over Cartesian coordinates, but it is much better not to have any magic numbers in a code, where a magic number is defined as anything that is not 0 or 1. The term ``NDIM'' for the number of dimensions is far clearer than a blind ``3'' in the middle of a piece of code. A second advantage of introducing a symbol, rather than magic numbers, is that we can change the symbol at one place, while guaranteeing its substitution everywhere else in the code. In the vast majority of cases, we will do our simulations in three spatial dimensions, hence the assignment here of the number 3 to NDIM here, but we will also encounter cases where we want to do some experimentation in one or two dimensions. In that case, changing 3 to 1 or 2 in this line is all we need to do (apart from making sure that we have not used uniquely three-dimensional constructs elsewhere in the code, such as for example the use of 3D spherical harmonics).

Note that older C-style usage would have defined NDIM through the macro definition ``#define NDIM 3'' . Nowadays, however, it is considered good form to use the C++ expression ``const int NDIM = 3;'' . Although the use of a #define macro in this case is quite innocent, there are many other cases where the use of macros can lead to code that is prone to confusing errors that are hard to debug. Therefore, as a matter of style it is a good idea to avoid them as much as possible.

The following nine function declarations are necessary if we want to have the freedom to define them in an arbitrary way in the rest of the file. The problem is that the C compiler goes through the file in one single pass, from top to bottom. As long as each function is invoked only after it has been seen by the compiler, there is no problem. In the codes hermite4.C through hermite6.C, the two functions listed at the top of the files were invoked only by main(), which was listed last. In general, however, with many functions there may not be a unique flow of functions calls. Besides, it is easier to follow the logic of the code if we can start with main() at the top of the file. The latter immediately implies that we will have to declare all functions mentioned in main().

This need for redundant information in the form of declarations is a weakness of C++. In general, any time that a computer language forces you to duplicate information, it brings with it the danger of errors creeping in. It is easy to change the definition of a function without changing the declaration, or vice versa. In some cases, the compiler may catch this, but there may be other cases where overloading of function names with different argument sets makes it impossible for the compiler to catch such mistakes. Unfortunately, we will have to live with this situation.

Another example of redundant information in our program is the description of the command line options. Almost the same words appear once in the `usage' part of the initial commments, and twice in the function read_options() (for the help option and the unknown option). It is possible to capture that information in a string at the top of the program, and to echo that string in read_options(). We will make such a modification later.

next up previous contents
Next: 8.5 The Function main() Up: 8. A More Modular Previous: 8.3 Introductory Comments
Jun Makino