Thank you for the explanation! It is very helpful.
It does seem odd at first that createrawtransaction needs prior transactions as inputs and addresses as outputs. It is a lot more intuitive to think of each transaction as transferring funds from one set of addresses into another set of addresses. But, in light of how you explained it, it does make sense.
Thank you again!
At the protocol level addresses are never used. To create a transaction you need the PubKeyHash of the output. There is a 1:1 relationship between the PubKeyHash and Address which is why clients (both in createrawtx and in a high level GUI client) can "send coins" to an address. The first thing the client does is validate the address and then convert it to the PubKeyHash. The protocol could care less about addresses however humans can make errors and the Address being the PubKeyHash encoded, versioned, and checked is useful for catching errors (like trying to send Bitcoins to a LiteCoin address, or catching a typo because the checksum now doesn't validate, or catching a truncated address because browser form cut off the last digit, etc).
On edit: Danny beat me again.
The "addresses as outputs" are just a shorthand way to talk about a particular type of transaction.
To emphasis the important point Danny made, the "OP_DUP OP_HASH160 BYTES_TO_PUSH <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG" (i.e. "sending coins to an address") is by far the most common type of transaction output but it isn't the only type of output.
Are you sure you need to use raw transactions? There are higher level RPC calls. If you don't care which specific unspent output is being referenced in the transaction, you could just use
sendtoaddress (or
sendmany) RPC call. The created transaction still references the unspent output(s) of prior transaction(s) however it is handled internally by the client ("coin selection").