Erin's CS stuff

CS stuff!

Functions

Functions allow for modularity in our programs.

"Modularity in computing and programming refers to dividing a system into separate modules or components. Each module handles a specific functionality and operates independently. It simplifies design, development, testing, and maintenance by allowing you to focus on one part at a time without affecting the rest of the system."

Functions empower us to package up or enclose bits of code that will accomplish a particular task or BEHAVIOR. That function can be re-used whenever we need to execute that package of code in different parts of our programs, under different circumstances or STATE. For example, when drawing a table in our program to present some data, one of the tasks that the computer must perform could be drawing horizontal lines, like below. How many horizontal lines do you see?

I counted 4!

The top and bottom lines (in orange) are 15 characters wide and use the equal sign =. It's important to note that the middle divider (in blue) is actually two different lines! While they both use the dash character -, the first line is 10 characters wide and the second is 5 characters wide. Since the things that vary between the lines are its width and the character used, we can identify those as the different circumstances (STATE). Also, since we can see it's useful to be able to display two lines without moving down the screen, moving to the next line is not part of the task of drawing a horizontal line (BEHAVIOR)! If we spend some time considering these conditions and circumstances, we can make our functions SPECIFIC ENOUGH TO ACCOMPLISH THE TASK, BUT GENERALIZABLE ENOUGH TO DO IT UNDER DIFFERENT CIRCUMSTANCES.

Syntax

Definitions

Although there are many parts of incorporating functions into our programs, function definitions are at the core. They are where we put the code meant to accomplish the function's task or BEHAVIOR. Each function definition should include the return type of the function, the name of the function, a list of parameters and their data types, and the body of the function.

The return type should match the data type of any value that is sent out of the function back to another part of the program. If no value is sent back, the return type should be void, as we see below:

void printHorizLine(int width, char symbol){
  for(int i = 0; i < width; i++){
    printf("%c", symbol);
  }
}

The function name should be related to the task or BEHAVIOR the function performs, which helps our code read like a beautiful story!

Parameters are like special variables that save the values (STATE) sent into the function; they represent the circumstances. When we're writing a function definition, we just need to understand what the parameters represent. In our example above, the first parameter is a whole number that represents how many characters wide we want the line to be and the second parameter represents which character we'll be using for our line. Since these are the circumstances (STATE) that can change each time the function is used, the parameters are treated exactly like variables that already have a value stored in them; we don't have to worry about any actual values, but considering possible circumstances can help us write the code and catch any "edge cases".

The function body is all of the code needed to accomplish the function's task (BEHAVIOR) and goes between the curly braces. Anything can go here, even other function calls!

Calls

Speaking of function calls, let's get into them since we use them in our code where we want to see the actual BEHAVIOR of the function. Each function call should appropriately handle the returned value of the function (if there is one), reference the name of the function, and include a list of arguments to send into the function.

If the function returns a value, the function call will evaluate to (be replaced by) it. Often the assignment operator = is used to store the returned value in another variable (although sometimes it can just be used directly). If the return type is void, then there's no returned value to consider, as demonstrated below:

printHorizLine(15, '=');

Arguments are the actual values (STATE) sent into the function; they are the specific circumstances for that instance or version of the function. In our example above, the first argument is the actual width of the line outlined in orange and the second argument is the actual character we want to use. Since the function definition is general enough to execute the task (BEHAVIOR) each time we send in different arguments, we get to re-use the function code each time we call it:

printHorizLine(10, '-');
This function call displays the first line outlined in blue!

It's important to note that, while the data types of the arguments should match that of the function definition, data type keywords (like int or char should NOT be included in function calls.

Prototypes

The final component of using functions in our programs will be function prototypes. While they aren't strictly necessary, they are considered a "best practice"; since using and understanding them will help students in future CSE courses, they will be required in this class. Luckily, they're pretty easy to incorporate!

We just need to include all of the parts of function definitions before the body with a semicolon at the end:

void printHorizLine(int width, char symbol);

These new components change the overall structure of our program files:

  1. header comments
  2. preprocessor directives
  3. function prototypes
  4. main function
  5. function definitions
We can apply this new structure to put all the pieces of our example together:

Metaphor: Like a Thanksgiving Dinner

We've examined how programs are very similar to food recipes and we can take that comparison even further by considering preparing an entire meal. To prepare our meal we gather multiple recipes for multiple dishes, depending on what we want to serve that year. Some years we skip the cranberry sauce, some years we try a new recipe for cinnamon carrots (at least in my house!). Our meal system is more flexible when we keep the dishes modular and we can more easily assign the recipes to different family members to work on. As an added bonus, it would be easier to test an individual recipe without having to make the entire meal!

While this comparison can help us understand why functions are important, it can also help us understand how to incorporate them into our programs if we imagine these recipes in a recipe book! The actual recipes themselves are like function definitions, since they contain the necessary instructions. The function call would be similar to when we actually use the recipe to make the dish. The function prototypes are like the table of contents at the beginning of the book, which is very convenient for finding the recipe pages when we need them.

Finally, if we consider ingredients to be like arguments into the functions and the final dish as the returned output, the main function/chef becomes responsible for coordinating the larger aspects of the meal. They must guarantee the right ingredients are already in the kitchen (it would be strange to ask your uncle to make pumpkin pie, then tell him he has to go to the store first). They must make decisionns to synchronize the timing of when to start the dishes so everything can be ready to serve at roughly the same time. Finally, they must ensure there are enough serving bowls and platters to store finished dishes for the meal itself.