DEV Community

Shin
Shin

Posted on

// pointers: the computer's fingers

In the last post, I dove into functions and how it allows us to make recursions with its syntax. Recursion is more logic-based, showcasing the human side of functions in the sense that we, the programmers, have to think in the shoes of a problem-solver.

Diving deeper into functions, let's move on to the technical side of things, the computer's perspective. There are two ways of passing parameters into functions, both of which taps into various things about variables, memory addresses, and pointers.

So let's talk about them.

The Problem

Create a program that gets three (3) largest prime numbers from x to y, x and y are given by the user.

If x is greater than y, swap x and y.

If there are no prime numbers in the given range, the program must print zero (0). If there is only one prime number, the program must print that number. If there are two prime numbers in the given range, then both prime numbers are printed.

Right off the bat, the problem seems complicated. Breaking it down into functions with specific purposes, I will need:

  • function that will swap x and y
  • function that will check if a number n is prime
  • function that will print the three largest primes between an interval x, y

For the variables, I declared two ints, min for x and min for y.

Preliminaries
Before heading straight to coding, I want to talk about the concepts that I will be using. In this topic, I've learned that there are two ways of passing variables to functions: 1) passing it purely by its value and 2) passing it using its reference. Passing by value is easy as it's the parameter passing type that we've been using since the beginning; it takes the value of the variable and does computations with it. On the other hand, what does passing by reference mean?

Well first, we need to know memory addresses. In C, variables are stored in free memory addresses so the more variables you store, the more addresses are going to be occupied. Conceptually, it should look like this:

Address Value Variable Name
000 8 x
001 10 y
... ... ...

Question: why is this relevant?

To pass by reference, we have to pass the address of the variable itself into the function instead of the value that it holds. In concept, those addresses are assigned arbitrarily and they usually look something like a hexadecimal value, so to get their value, they are stored in variables called pointers which does exactly as the name suggests: point to the address of another variable. You can do this by the following syntax:

int *p;
int var = 1;
p = &var;
Enter fullscreen mode Exit fullscreen mode

In this code, pointer p is initialized using *, and then the address of var is taken using the unary operator &, which is then stored in p. The data type of the pointer should be the same as the data type of the variable you're trying to get the address of.

The & is only one of the two operators that are used when working with pointers. Obviously, the other one is *, but it has another use aside from just initialization.

x = *p;
printf("x is: %d", x);
// x is: 1
printf("%d is the same as %d", var, x);
// 1 is the same as 1
Enter fullscreen mode Exit fullscreen mode

The * checks the address stored in the pointer and then takes the value of the variable in that address. Essentially what I've done in the example is take var's address using & then store it into p, then take the value of var from p using *. This is (sort of) what passing by reference is.

It's pretty complicated at first sight, but with this in mind, I can finally move on to making the program.

The Swapper Function
There are situations where using passing by reference is more preferred over passing by value (and vice versa) and it is on the hands of the programmer to decide which is more appropriate for the problem.

Passing by value is suitable in many cases and you can get by with just using it. However, since only the value is only needed, the function needs to copy it first, which can be expensive in the performance with larger variables. Aside from that, pass by value only works on the copy, so on occasions that you'd want to perform computations on the variable itself, it would be more appropriate to pass by reference.

Since I'm required to swap the values of two variables, I think it would be better to pass by reference directly. Simply enough, all the function does is take the pointers of the numbers as parameters, store the value of one address in a temporary variable, and then swap them as usual. The function will be void as well since I've already swapped the numbers and don't need to return anything else.

void swap(int *n, int *m) {
    int temp = *n;
    *n = *m;
    *m = temp;
}
Enter fullscreen mode Exit fullscreen mode

The Prime Checker Function
Initially, I thought of just adding this bit to the function that prints the primes, however, it would be difficult to implement (and read) as the function is doing two different things, counterproductive for a function. In this case, I'm just checking whether the number is prime or not, so there would be no pass by reference.

As mentioned in the previous post, to find primes, all I need to do is check if the number is not divisible by at least one smaller number except 1. This version of the function is implemented with an iterative approach using a for loop starting from n - 1 down to 2. When the loop finishes with no divisor found, it returns the value of flag, a local variable set to 1 by default, indicating that it is prime. Whereas if a smaller number is found, the loop breaks, changing the value of flag to 0 and returning it.

int isPrime(int n) {
    int flag = 1;
    for (int i = n - 1; i >= 2; i--) {
        if (n % i == 0) {
            flag = 0;      // If divisor is found,
            break;         // break loop and return 0
        }
    }
    return flag;
}
Enter fullscreen mode Exit fullscreen mode

The Prime Printer Function
For this function, I'll only be printing numbers, so I made the return type void. It works is by checking if the number is prime using the isPrime function, starting from the upper bound of the interval, allowing the function to print the largest primes first. It also has a local variable counter that records how many primes have been found and breaking the loop when three are found. Any less than that will be printed regardless, but if nothing is found, it will print 0.

void firstPrimes(int max, int min) {
    int primes = 0;
    for (int i = max; i >= min; i--) {
        if (isPrime(i) == 1) {
            if (primes < 3) {
                primes++;
                printf("%d ", i);
            } else {
                break;     // If there are 3 primes already,
            }              // stop printing
        }
    }
    if (primes == 0) {
        printf("0");
    }
}
Enter fullscreen mode Exit fullscreen mode

The Other Parts
Lastly, the only thing left is to place the function calls wherever necessary in the main. Before that, I added an input prompt for the two numbers x and y, with x as the lower bound and y as the upper bound. Once the user has placed two numbers, the program will check if x is greater than y, calling swap. If all goes well, the program then calls firstPrimes, printing the largest three primes.

Conclusion
Overall, this has been a new experience for me. It's interesting to see how C deals with its memory management on a deeper level. Learning pointers, to me, feels like a great way to understand how the computer hardware handles the variables and calculations that we add to our programs. Admittedly, this is something that I tend to overlook with other programming languages, and I guess this topic acts like a reminder that "Hey! All this is being processed by your computer's memory." I think that is one of the greatest things about low-level languages.

Top comments (0)