It sends the End Of File character. In most terminal applications, this ends an interactive session. E.g. if you're in a Python REPL and are done with it, you can press Ctrl-D and it will close and return to Bash. Press Ctrl-D once more and Bash will exit as well (and in most cases the terminal emulator will then close, too).
Ctrl-C sends the SIGINT signal instead. It can be handled, too, and by default it will terminate the program. Semantics are a little different: you use Ctrl-D when the app expects input, and Ctrl-C when it's actively doing something and you want to interrupt it.
For example, in Python REPL, you can press ^C to interrupt a running piece of code:
>>> while True:
... pass
...
^CTraceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyboardInterrupt
You can't however exit the REPL with ^C:
>>> (^C)
KeyboardInterrupt
But if you press ^D it exits just fine.
Ruby's irb works the same (and of course Bash/zsh do, too). In Node.js, ^D works right away, and ^C first triggers a warning but you can press it the second time to exit.