Pointers are useful for several reasons, none of which has anything to do with memory addresses.
The top five, in my opinion, are:
1) dynamic storage duration
Whenever you use the keyword "new", you get a pointer to the object that was created. That pointer is the only handle you have to keep track of that object, to access it, and, ultimately, to end its life.
2) Aggregation
Aggregation is an OOP relationship: it's when an object knows about, keeps track of several other objects, which exist elsewhere, e.g. are not just parts of the first object (that would be composition).
3) Shared ownership
This is another OOP concept: objects may share ownership of another object: the owned object needs to exist as long as any of its owners exist, but disappear when the last owner is gone.
4) polymorphic containers
Whenever you need to store a number of different objects that are all derived from the same base class, you can create a container of pointers to base, each set to point at one of those objects.
5) Rebindable references
Imagine you're implementing a balanced tree data structure: each node needs to refer to its descendants its parent, but it also needs to be able to switch these links whenever the tree is rebalanced. Such switchable references are exactly pointers.