There are two stages to building a library:
- The '.cpp' files are compiled to '.o' files. These intermediate files are not included in the library distribution.
- The .o files are collected together to form a '.a' or '.so' library file. These are typically in /usr/lib or /usr/local/lib.
As you say, when you include the '.h' files, you are typically including the prototypes for the functions you will be calling. And then you pass in the -llibname to the linker so that it knows where to find the code for those functions.
However, it is possible to define the entire function in the header, in which case the code for the function is not in a library file, but included into your own object files. Examples of this are preprocessor functions in C (#define), and template functions in C++.
The reason for this is that templated code cannot be compiled without knowing the types that are being supplied to the templates, so the code is included in the header and compiled when the application code uses it. It would be possible to compile particular instantiations of the templates and include them in the libraries, but it isn't really necessary.
There are some template libraries that make use of helper code that is included as a library, but the bulk of the code is typically still in the header files.
The distinction between static and dynamic libraries is unrelated to this. When using a static library, the components that your code uses are 'copied' (linked) into the application binary at compile time. When using a dynamic library, the components that your code uses are loaded by the application from the library binary at run time.
There are two different ways to invoke dynamic libraries, but I won't go into that detail here.