Never ask about pointers again

Never ask about pointers again

Pointers are infuriating but pointless. If you're into c or c++, you may have heard about pointers because direct memory manipulation is common in these languages. If you're coming from any high-level programming language like Java or Python, you may have heard about reference variables. Yes! Everything that happens behind reference variables is pointers, as references in higher-level programming languages are essentially a more abstract and simplified way of manipulating pointers to access and manipulate data in memory.

So what is a pointer?

A pointer is just a variable that stores the memory address of another variable. Yes! That's it. But where is the complex part I was talking about? Below.

How to declare a pointer?

int *p;

or

int* p;

You just have to use an asterisk * symbol either with the data type or the variable name. Here p is a pointer variable which can store the memory address of an integer-type variable.

Initialising a pointer

int a = 10;
int *p = &a;

or

int a = 10;
int *p;
p = &a;

To initialize the pointer to point to a specific variable, we would use the & operator, which returns the memory address of the variable.

This assigns the value of a to the p pointer.

The & symbol is for referencing means it tells the address. Putting & in front of a variable means 'address of' variable.

The * symbol is for dereferencing. Putting it in front of a variable means it tells the value inside the variable the pointer is pointing to.

int a = 10;
int *p = &a;
printf("%d",*p); //prints 10

To access the value of the variable our pointer p is pointing to, we put the * symbol in front of the pointer variable. Pointer p holds the address of the variable a. By putting '*' in front of p, it'll dereference p and returns the value residing in a.

You can do this with the variable of any data type.

Pointers with Arrays

When an array is declared, it is allocated a contiguous block of memory. The name of the array is a pointer to the first element of the array.

int arr[5] = {1, 2, 3, 4, 5};
int *p = arr; // equivalent to int *p = &arr[0];

Here, p is a pointer to the first element of the array arr. We can access the elements of the array using pointer arithmetic. For example, to access the second element of the array, we can use:

printf("%d", *(p+1)); // prints 2

Pointers with strings

In C, a string is simply an array of characters terminated by a null character ('\0'). The name of the string is a pointer to the first character of the array. For example:

char str[] = "Hello";
char *p = str; // equivalent to char *p = &str[0];

Here, p is a pointer to the first character of the string str. We can access the characters of the string using pointer arithmetic. For example, to access the second character of the string, we can use:

printf("%c", *(p+1)); // prints 'e'

Here, we are adding 1 to the pointer p, which points to the second character of the string.

One useful application of pointers with strings is the ability to dynamically allocate memory for a string using the malloc function. For example:

char *str = (char*) malloc(6 * sizeof(char)); // allocate memory for a string of length 5
strcpy(str, "Hello"); // copy the string "Hello" to the dynamically allocated memory

Here, we are allocating memory for a string of length 5 (plus one byte for the null character) and then copying the string "Hello" to that memory using the strcpy function. We can then manipulate the string as needed using pointer arithmetic.

Another useful application of pointers with strings is the ability to pass strings as arguments to functions using pointers. When we pass a string to a function, we are actually passing a pointer to the first character of the string. For example:

void print_string(char *str) {
    printf("%s", str);
}

char str[] = "Hello";
print_string(str); // prints "Hello"

Here, we are passing the string str to the function print_string as a pointer. The function then prints the string using the %s format specifier.

Pointer as return type for functions

To declare a function that returns a pointer, we use the following syntax:

data_type *function_name(arguments);

Here, data_type is the type of data that the pointer points to, function_name is the name of the function, and arguments are the arguments that the function takes. For example, to declare a function that returns a pointer to an integer, we use the following syntax:

int *create_array(int size);

Here, create_array is a function that takes an integer size as an argument and returns a pointer to an array of integers.

To allocate memory for the pointer in the function, we use the malloc function. For example:

int *create_array(int size) {
    int *arr = (int *) malloc(size * sizeof(int));
    return arr;
}

Here, we are allocating memory for an array of integers of size size using malloc, and returning a pointer to the first element of the array.

We can then use the returned pointer in another function or in the main program. For example:

int *arr = create_array(5);
arr[0] = 10;
arr[1] = 20;
arr[2] = 30;
arr[3] = 40;
arr[4] = 50;

Here, we are creating an array of integers of size 5 using the create_array function, and then assign values to the elements of the array.

Function Pointers

To declare a function pointer, we use the following syntax:

return_type (*pointer_name)(parameter_list);

Here, return_type is the type of the value returned by the function, pointer_name is the name of the function pointer, and parameter_list is a comma-separated list of the types of the parameters that the function takes. For example, to declare a function pointer that points to a function that takes two integers as parameters and returns an integer, we use the following syntax:

int (*sum)(int, int);

Here, sum is the name of the function pointer, which can hold the memory address of a function that takes two integers as parameters and returns an integer.

To assign a function to a function pointer, we simply use the function name without parentheses. For example:

int add(int a, int b) {
    return a + b;
}

int (*sum)(int, int) = add;

Here, we are assigning the function add to the function pointer variable sum. We can then call the function indirectly using the function pointer like this:

int result = (*sum)(2, 3); // equivalent to int result = add(2, 3);

Here, we are calling the function add indirectly using the function pointer sum.

Function pointers are particularly useful when working with callback functions. A callback function is a function that is passed as an argument to another function and is called by that function. For example:

void iterate(int arr[], int n, void (*callback)(int)) {
    for (int i = 0; i < n; i++) {
        callback(arr[i]);
    }
}

Here, iterate is a function that takes an array arr of length n and a function pointer callback as arguments. The function iterate then calls the callback function for each element of the array.

To use the iterate function with a custom callback function, we can define a new function that takes an integer as a parameter and performs some operation on it, such as printing it:

void print_int(int n) {
    printf("%d\n", n);
}

We can then pass this function as an argument to the iterate function using a function pointer:

int arr[] = {1, 2, 3, 4, 5};
int n = 5;
iterate(arr, n, print_int);

Here, we are calling the iterate function with the print_int function pointer as the callback function. The iterate function then calls the print_int function for each element of the arr array.

Void pointers

In C, a void pointer is a pointer that has no associated data type. Void pointers are also known as generic pointers, because they can be used to point to objects of any data type.

The syntax for declaring a void pointer is:

void *ptr;

Here, ptr is a void pointer variable that can point to any data type.

Void pointers are useful in situations where we don't know the data type of a variable at compile time, but we still need to pass it to a function or store it in a data structure. For example, we might use void pointers in a linked list implementation, where each node can store data of any data type.

To use a void pointer, we need to first cast it to the appropriate data type before dereferencing it. For example, if we have a void pointer ptr that points to an integer, we can cast it to an integer pointer and then dereference it like this:

void *ptr;
int num = 42;
ptr = &num;
int *int_ptr = (int *)ptr;
printf("%d\n", *int_ptr); // prints 42

Here, we are first assigning the memory address of the integer variable num to the void pointer ptr. We then cast ptr to an integer pointer using (int *), and dereference it using the * operator to print the value of num.

One important thing to note is that we should only cast a void pointer to a data type that matches the type of the object it points to. If we cast a void pointer to an incompatible data type, we might get unexpected results or even cause a segmentation fault.

Conclusion

Well, here is everything about pointers. However to understand the core of it needs practice.