C Programming Tutorial 5: Functions in C gives a short introduction to functions and also quickly glosses over function definitions and writing functions in C.
While this is alright, I realize that there are many people new to C programming and that a better, more in-depth treatment of writing functions in C is in order. Enter this article, which goes into a bit more detail on writing your own C functions.
Writing Functions in C
We know that C comes with a lot of built-in functions which can save you time and work, but what if none of them do what you need?
The answer is to write your own C function.
These are referred to as user defined functions and this lesson will be a more detailed introduction to user defined functions, or simply, writing your own functions.
The functions you write will be the bulk of a lot of your programs so it’s important to understand how this works. Luckily, creating your own functions is not that difficult.
Quick Review on Functions in C and Abstractions
Let’s start by doing a quick review on functions and say a few ’abstract’ words. If you need more of a review, I urge you to go back to the first lesson on functions.
A common abstraction in the world of programming, computers, and electronics is the black box model. This black box has an input and an output. We don’t know what sort of circuitry, code, or combination of both is inside the box, all we know is the box takes an input, does some processing, and produces an output, sort of like a typical function.
Figure 1: thinking of functions as a black box with an input and an output is a good abstraction.
Another abstract way to visualize functions that many of us may be familiar with is to think of them as kind of like a cake mix. When you buy a cake mix, you just add water and maybe some butter, and using the stuff in the box, you end up with a cake. If you’re a novice baker, you may not know what is inside the cake mix, nor care.
Sure, you could grab some flour, sugar, salt and some other ingredients and mix them all together in the right proportions yourself — especially if you’re a baker with some experience, but it’s way easier to use the cake mix. The mix ‘abstracts away’ most of the complexity of baking a cake.
But, since the black box model seems to be the dominant abstraction in the world of software, we’ll stick with that.
Our black box has an input and an output. We can’t see what goes on inside the box and we don’t necessarily care. All we know is that our box takes an input, performs some sort of processing on the inside, and then gives us an output.
Writing Your Own Functions in C
But now we want to write our own functions, so it’s time to take a peek inside the box. We’ll not only see what’s going on but design how the machinery on the inside works and get it to do what we want it to do, because there’s a certain satisfaction about being a control freak.
To write our own functions, we’ll need to think about the inputs, the outputs, and most importantly, the algorithm which is the ‘machinery’ so to speak inside of the box that makes the magic happen.
Before we start writing our own functions, we need to know the proper syntax for user defined functions, so let’s jump right into that.
Take a peek at figure 2. What we see here is the basic form for a function definition. The function definition tells us how our functions works. You can say that the function definition is the function itself. It defines the inputs, outputs, and contains the algorithm for how the function operates.
Figure 2: function definition template/model.
All the functions you use have a function definition living somewhere, even the ones that come with the compiler.
Refer to figure 2. Let’s start our discussion on function definition syntax by looking at the function’s output, which is also known as the return type.
When we say type, we’re just referring to the data type it returns such as an int or float. By now you should be familiar with many of C’s data types.
By return, we mean the stuff the function returns to the calling function. So, if the main() function in your program calls or invokes another function, which is quite common, the called function will often return some sort of value to main() or whatever function called it. So, when a function creates an output, or a value, it gives that value back to the caller.
It’s kind of like calling a friend and leaving them a message asking a question. They’ll get the call and the message, think of a response, and then call you back with that response.
So, the return type is just the data type the function returns. For example, sqrt() which is part of the math.h header file, returns a double. In case you didn’t know, this function takes the square root of a number.
So, when we’re writing a function in C, we need to define what data type it’s going to return.
However, not all functions return a value. Sometimes they just do something and that’s it. If you’re into Arduino, you know you program in a language similar to C and you’re familiar with setup() and loop(). These two essential Arduino functions don’t return anything. Another example is the Arduino delay() function. It doesn’t return anything — all it does is wait a certain number of milliseconds and that’s it.
Become the Maker you were born to be. Try Arduino Academy for FREE!
When writing a function definition for a function that doesn’t return anything , we’ll use the word void instead of a data type to indicate the function has no return. The snippet below shows such an example.
void example_function (int number) { //function body goes here }
Next, we can see the name of the function in figure 2. It’s a good idea to pick something meaningful, maybe like something that describes what the function does instead of using a generic name like function1 or my_function. For example, if I want to create a function that prints smiley faces, I may call it something like smileyPrinter.
Next, we see a set of parentheses. Inside dwell the function’s parameters. This is the stuff the function takes in and does its magic on. In other words, these are the function’s inputs. Some functions, like getchar() take no inputs. Arduino functions setup() and loop() also take no inputs. Our old friend void shows up again here. When writing a function like this, we just put the word void in the parentheses, like below.
int another_example_function (void) { //function body goes here }
Some functions neither return a value nor take any inputs. We can write such a function like this:
void sad_function (void) { //function body goes here }
Many functions do take inputs. For example, sqrt() in C takes a double and delay() in Arduino takes an integer.
When writing your own user defined function, you’ll need to specify what inputs it will take.
First, it’s interesting to know that you can write a function that takes more than one input or parameter.
Second, each input has two parts: the data type and the name. As before, the data type can be any valid type such as an int, float, char etc. The name is how you’ll refer to the value when you write the body, or the actual algorithm, of the function. If you need more than one parameter, separate them by commas.
Refer back to figure 2. Next, we see a set of curly braces. Inside those braces is the body of the function which can be as short as a few lines of code or hundreds of lines or more. This is the algorithm and is the meat of the function. It’s the stuff inside the black box we keep talking about.
Let’s do an easy example similar to the example from the last C tutorial on functions. Say we want to write a function that prints an arbitrary number of plus signs for some reason. Maybe the number of plus signs comes from a sensor reading, or maybe a user enters the number on a keyboard or touchscreen — it really doesn’t matter.
void plusPrinter (int plus_count) { for (int i=0; i< plus_count; i++) putchar('+'); }
Our function doesn’t really need to return anything, so we’re just going to put void in front as the return type.
Next, we give it a descriptive name (plusPrinter) that gives us an idea of what our function will do.
Now, we need to tell it what kind of datatype it will take in. Since we can’t print a fraction of a plus sign it makes sense to pick an integer data type. We’ll also give that integer a name that is somewhat descriptive. In the body of our function, when we need to refer to the value passed into that function, we’ll use the name plus_count.
Then we have our first curly brace followed by the body of the function which is the actual algorithm that does the work. By now you should know all about for loops and printing things, so I won’t rehash that. Finally, we close with the 2nd curly brace.
Let’s write another, more useful function, because, you know, printing a bunch of plus signs is boring.
Say we have two temperature sensors in two different locations like one in the back of your refrigerator and another in the front, because, for some reason in the 2020’s it’s still impossible to build a fridge without wild temperature gradients. It really sucks when the food in the back is frozen while the stuff in the front is warm. Because of this, we want to take the temperature in each location and then average the two different temperatures. We have an ingenious idea on how to fix this age-old problem and will make millions if we solve it, so let’s go for it.
We want to be more exact, so instead of using integers we’ll use floats. Also, our user defined function will need to take in 2 floats and return the result to the calling function. Here’s our function definition:
float fridge_temp_monitor (float reading1, float reading2) { float average = (reading1 + reading2) / 2; return average; }
Let’s step through it.
First, we specify a return type. This time, it’s a float.
Next, we give the function a meaningful name.
Now we define the parameters. Since it has more than one parameter, we need to separate each one with a comma. We also use floats for the parameter and give them meaningful names.
Unlike the last function, this one actually returns something. It returns the average of the two sensor readings, so we use the return statement. It’s important to note the function returns the value of the variable average, not the name of the variable.
Let’s use our function in a real C program. I don’t have a circuit with sensors wired up in my refrigerator, so I’ll just assign reasonable values to the two variables and pretend they’re coming from the sensors.
#include <stdio.h> float fridge_temp_monitor (float reading1, float reading 2) { float average = (reading 1 + reading 2) / 2; return average; } int main(void) { float num1 = 35.0; float num2 = 40.0; float average = fridge_temp_monitor (num1, num2); printf("%f", average); return 0; }
There are a few things to note about this short program.
First, there’s no function prototype. It turns out that we don’t have to use one if we have the function definition appear before the first function call, though it still may not be a bad idea to use a prototype.
Second, the function call appears on the right of the assignment operator and assigns whatever value it returns to the variable average.
Last, in the definition we named the parameters reading1 and reading2 but when we called the function, we passed it num1 and num2, which the function interprets as reading1 and reading2 when it does its processing.
Function Definition Recap
Let’s do a quick review of writing functions in C (a.k.a. user defined functions) and function definitions.
- First, we have the return type which is the data type of the value that the function we write returns. Sometimes, our function may not return anything so we use void instead of a data type in this case.
- Next, we have the function name. Try to make it meaningful.
- Then, there’s the parameter list, which are the inputs the function takes in or accepts. Here, we define the data types and names of the inputs. When there’s more than one parameter, we separate them with a comma. If our function doesn’t take any parameters, we just put void in the parentheses.
- Then, there is the body of the function. This is the actual algorithm that does the work. If we want to return a value, we use the keyword return.
Function Definitions in C: Closing Thoughts
Now we’ve taken a closer look at function definitions in C and writing functions. But there’s still more to say on functions. We’ll pick functions up again at a later time.
Meanwhile, drop a comment and tell me about the functions you write. What are they for? How many lines of code? Or, just tell me about your latest project. I’d love to hear from you either way!
Become the Maker you were born to be. Try Arduino Academy for FREE!
Electronics Tips & Tutorials Sent Directly to Your Inbox
Submit your email & you'll get:
- Exclusive content that I don't put on the blog
- The checklist 10 mistakes all electronics enthusiasts make (& how to avoid them)
- And more!
Leave a Reply