I am not an expert, but a reference is like an alias of the variable, it has to be initialized, never can be set to reference a different variable and can’t be null. While a pointer stores the the memory address of the value is pointing at, can be changed to point to another value, and can be null. So…
With int* ptr = &i; you declare a pointer, that stores the memory addres of i.
With &ref = i; you create a reference (like an alias) of i.
This is a bit weird, but * and & are different based on where they are, basically if they are after a type of object, like int, is to declare a * pointer or a &reference. But when they aren’t after a type of object, they are indirection and address-of operator, this means with * you get the value inside a memory address (of variable), with & you get the address of a variable. So…
int* ptr ← you declare a pointer
int& ref ← you declare a reference
i ← just a variable
ref ← just as i (3 in your example)
&i ← memory address of i
ptr ← a memory address (in your example, the memory address of i)
&ptr ← the memory address of ptr (its own memory address, not the address it is pointing at)
*ptr ← the indirection of the memory address it is pointing at (in your example, the indirection of the memory address of i, which is 3)
A weird example is you can do *(&i), so you get memory address of i, and then indirect its memory address, so you get 3.
Example of results:
As I said, this is how I understand em, maybe someone can explain it in a better way.
After my explanation, you can watch this video, that explains it in a more visual way What is the Difference Between a Pointer and a Reference C++ - YouTube