#ifdef ("if defined") works like #if; the only difference is that the argument it takes is evaluated solely on whether that argument is a valid symbol that has been #defined.

This is generally used to determine which header files are included at compile time. For example, let's say i was going to distribute a library with a header file that i am going to call squarepusher.h. I would say at the top of squarepusher.h

#define __SQUAREPUSHER__
People would then be able, in code they wrote, to say things like
then put (underneath that #ifdef) code that only makes sense if squarepusher.h has been successfully #included and __SQUAREPUSHER__ is defined, without fear of causing some kind of compilation error in those cases where squarepusher.h is not available-- because systems without squarepusher.h will ignore squarepusher.h-specific code. They can then have an #else block in which they put simpler code which does not rely on squarepusher.h. Remember to use #endif.

There is, by the way, a #ifndef ("if not defined"), which acts as a boolean-negated #ifdef.