pointers are a lot easier than many think, but they're often taught using clumsy analogies, which frankly are unhelpful.
A pointer type is a basic type, just like any other (e.g. int, double, char). You create one and it exists somewhere in memory.
int*, char*, double*.... they all take up the same space in memory and essentially they are just a number. Ignore for the moment that they are int pointers or char pointers or double pointers. In memory, they all look the same.
1 2 3 4 5 6
|
int* p_x; // You have now created a space in memory the size of a pointer.
// It's often the same size as an int. Right now, if you look at that memory that
// effectively is p_x, it'll have some garbage value in it.
p_x = 0 ; // Now, if you look in that piece of memory, it'll have the number 0 in it.
p_x = 3425 ; // Now, if you look in that piece of memory, it'll have the number 3425 in it.
|
Note that in none of the above steps did you actually create a new int; you have not made an int, you have made an int*.
1 2 3 4
|
int y = 12; // Now we HAVE made an int somewhere in memory, with the value 12
// If only we knew its address in memory, we could put that address value in p_x
p_x = &y; // The '&' operator here returns the memory address of y, so now the value of p_x
// is the address of y
|
There is an operation you can carry out on a pointer called "dereferencing". This operation reaches the contents of an address in memory. The address is just a number; the number used for the address is the value of the pointer.
1 2
|
*p_x; // Returns (and in this case doesn't do anything with) the contents of memory address &y
// which in this case we know to be the number 12
|
But wait, you cry. There could be anything in that memory. Anything at all. How does the compiler know how to treat it? Good question. It knows because you explained when you created p_x what kind of things it would point at. We made an int pointer, so the value returned will be treated as an int.
1 2 3 4
|
int z = *p_x; // Z is now whatever value was at memory address &y, interpreted as an int
// So now we've got three objects in memory (two integers, y and z, and an
// int pointer, p_x
// z is 12, y is 12, and p_x is a number that is the address of y in memory
|
If you try something like this:
double z = *p_z;
the compiler should warn that you're trying to assign an integer value to a double; it may carry out some kind of conversion for you, it may just refuse outright, depending on your setup. There are some special values of pointer; if your pointer has a value of 0, it is a null pointer and deliberately does not point anywhere. This is also known as a NULL pointer.
If you understand pointers like this, by actually knowing what they do in terms of the memory, they become an absolute walk in the park. Pointer arithmetic and the relationship between pointers and array follow quite easily if you recall that a pointer is just the number of some other piece of memory somewhere.
The other half of your question - why do we need pointers at all?
C and C++ trust you. If you want to mess around with raw data, the actual numbers, you can. If that raw data is hidden from you because you're using a programming language that doesn't want you to see it, that's great; it's probably safer, but don't fool yourself - some control has been wrested from your hands. C (and C++) trust the coder to a great degree; if you want to mess around at the level of specific memory addresses, you can.
Your hardware uses hard-wired address locations for various things. It is common, for example, on an embedded system for LEDs to be hard-wired to specific memory addresses. To alter the state of those LEDs, you MUST be able to specify the value in specific memory locations. If you can think of a sensible way to do that without pointers, I'd like to hear it. This holds true on the hardware in your PC. Your sound card. Your graphics card. The people who wrote those drivers couldn't do it without pointers.
When you access an array, don't fool yourself; you're using pointers. When you enter
a[10]
that's translated as
which means "get the value of the pointer object called 'a', which is a memory address and points at a certain kind of object, and add to that value however much memory 10 of those objects would take up, and then give me the object at that memory address". Off the top of my head, I can't think of a sensible way to implement arrays without a system of just keeping track of the start location and the size of the objects.
There are more, many many more. I suspect the real problem here is that you learnt to programme using some other language that did not have these features, and the issue now is not that you can't understand these new constructs - it's that they're not part of your mental arsenal for solving problems in programming, so you never use them and thus can't understand why they exist.
When learning a new programming language, learning the new syntax is only part of it; you have to learn to think about problems using the new tools that language gives you, and that is often much, much harder. Many times, I've come across C++ code that is quite clearly just Algol or Python or Java or BASIC, but has been written in C++ (or, more commonly, written in C and called C++).
Here's an example of a real application that might make it a bit clearer. Let's say you've got an image to process, and you need to do it fast. If you read each pixel, one at a time, you've now got that many pixel objects. There could be millions of them. Millions of individual objects to now deal with. Maybe you could read in a few at a time, and deal with them, and then read the next few, but that's going to add huge amounts of time in fetching data from storage.
You could have a structure to help you keep track of them; something like image.pixel1, image.pixel2, image.pixel3..... image.pixel1000000..... lots and lots of objects. You just had to go through the construction process for each of those objects, which took time. You'll have to destruct them too; more time.
If you allocate a long stretch of memory and then just read all that pixel data directly into that memory, you've skipped a lot of construction time. You've then got a pointer to the start of the memory, so you can do this and run through the whole image a lot faster:
processPixel(*pointer_to_image++);
Additionally, you now have a mental model of the image as a long, unrolled line of pixels, that actually matches how it is in memory. You can develop your image processing algorithms based on this mental model and directly apply them. If you need to process a pixel and the output depends on the pixel to the right and left of it, that's astonishingly easy and faster than having to do some kind of structure-based fetch to get the values of those pixels.