An array is a big continuous chunk of memory. To get the first item in an array of 1-byte objects, we just look at the first byte of that array. The second item is the second byte. And in general the nth item is the nth byte in that chunk of memory.
If we have an array of 4-byte objects like
int, then the first item is the first four bytes of memory. The second item is bytes five to eight. And so on.
Another way to view it is if we have a pointer to the first byte of memory in that array, then we can find a simple formula that makes the pointer point to the nth byte of that array. If we have 1-byte objects, we only need to look at
ptr + (n-1).
Extending this idea, you would think that if we have 4-byte objects, to get the nth item in the array, we want to look at bytes
ptr + 4*(n-1) until
ptr + 4*(n-1) + 3. But it’s not!
If you have a pointer and you add an integer to it, it automatically multiplies the added integer by the size of type of the pointer. So an
int pointer, if you add 1 to it, will actually get incremented by 4. If you add 3 to it, will get incremented by 12. In some way, this makes pointers easier to work with if you think about them as arrays. Some people might find this strange, but that’s just the way C++ was designed. This is called pointer arithmetic, by the way. Most of the time, you just want to increment and decrement pointers, and it generally works out really well. In case you really need to mess with memory addresses (which you almost never should), you can just typecast your pointers into
long long before doing the operations, then typecast them back to your prefered pointer type.
Earlier we mentioned that to get the nth item in an array, we look at
ptr + (n-1). But if we just started counting from 0, then the problem solves itself! The 0th item is at address
ptr, the 1st item is at address
ptr + 1, the 2nd item is at address
ptr + 2. And the kth item is at address
ptr + k. And this simplification is the reason why programmers like to count from 0.
As a side note, some other programming languages are not zero-indexed and their compilers automatically add
-1 when arrays are accessed.
By now you might be thinking: "Wait a minute! I've been doing array access without all this pointer stuff. It's just
arr[x] right?" And I’m here to tell you that array access by doing
arr[x] is completely the same as
*(arr+x). The compiler turns them into the exact same processor instructions. So if you have a pointer that points to a single variable and not an array, instead of doing
*ptr you can just as well do
ptr. It might not make intuitive sense when you're reading some code that’s not yours though. Most people expect
 for arrays and
* for single values.