Being able to treat functions as data (a.k.a. function pointers) is very useful in certain situations. For example, let's say that you're making a drawing program, and you want to have a little dropdown menu that lets the user choose between drawing a rectangle, a circle, or a pentagon. Drawing is done by clicking on the canvas and then dragging out to a point, so your drawRectangle, drawCircle, and drawPentagon functions all take the same arguments: the center, and the position of the mouse.
void drawRectangle(Coord center, Coord mouse);
void drawCircle(Coord center, Coord mouse);
void drawPentagon(Coord center, Coord mouse);
Now, when the user tries to draw something, you could look at the current drawing mode and call the relevant function:
if (drawMode == RECTANGLE) {
drawRectangle(center, mouse);
}
else if (drawMode == CIRCLE) {
drawCircle(center, mouse);
}
else {
drawPentagon(center, mouse);
}
Or when the user chooses their draw mode, you could just set a variable to hold the relevant draw function:
drawFunc = drawRectangle;
...
drawFunc(center, mouse);
Get the idea?
As for the null pointer, NULL is a convenient "always invalid" value. There are situations where you want to know that something has not been initialized, or cannot be used; in those situations, you can simply set the value to NULL. You often can't use 0 or -1 or the empty string because those could be valid values; NULL is never valid.