As a novice I just put the prototypes, functions and classes into the .h header file, which seemed to work, and the main.cpp had no trouble calling these functions. |
This is called a monolith -- and its fine if the code is small enough. Lets start there.
It is not wrong. It clearly works. Its bad design for large projects, but I have a couple dozen 'utility' programs that work exactly like this, and they are fine. No reason to break it up and make a mess of it.
As an aside, the names of the files are just convention. Its perfectly ok to have bar.cpp have its prototypes in foo.h. The compiler and linker don't give a rat's rear. You use the same name because its a convention programmers agree to so they don't have to kill each other.
But lets grow that small program until its too complicated to be easy to work on in one file and talk about THAT now. The first step is to understand how your tools (compiler, linker) work so you see what they need to have. I will lead you through a simple piece of that.
say you make all your functions at the top, and main at the bottom, but no prototypes? What happens?
It works fine!
now say one of your functions calls another of your functions. Ok, no problem... just move the one that is called above the one that calls it, and it still all works.
see what is happening here? The compiler and linker are going top-down (the linker does this to an extent but its the order the files produced by the compiler appear in)... it has to see it, or at least a placeholder for it, before it can unravel the calls.
so far so good, but when a calls b and b calls c and c calls a and ... at some point you can't put them all in front of each other! Then you need the prototypes. You will see the idea of a prototype again, for classes and other objects, in the form of a
"forward declaration" (look it up sometime). For the same reasons... it need to see one before the other, or a placeholder at least, but they depend on each other in a way that isn't possible, then you do that.
all that just grows as the code base grows. From here, your program gets bigger so you split it into a few files, but the dependency of things on each other force you to expose the prototypes. You can just paste the prototype you need anywhere you need it, eg if both foo.cpp and bar.cpp needs function funct() in funct.cpp then you can stuff the prototype at the top of both cpp files and that works. Ahh-ha, but this is what #include does! #include literally pastes the code in the file at that spot (you can exploit this to force inline functions and do other weirdness).
so that all that leads you eventually to the .h file idea because its the most sane way to handle the needs of the toolset in a useful, consistent, human comprehensible way :). Half the answer is you do what is needed to get it to compile. The other half is that convention dictates that we do things a certain way so that code can be understood by other programmers. As you may have gathered from above, for example, you can #include cpp files. You should not, but it works fine as long as you dodge the 'declared twice' bullets. Even though it works in some cases, convention and good practice demand you don't do this.