Chapter 4: Functions: Building Modular Code
As your programs grow larger and more complex, simply writing all your code sequentially in the main function becomes unwieldy and hard to manage. This is where functions come in. Functions are self-contained blocks of code that perform a specific task. They are the cornerstone of modular programming, allowing you to break down a large problem into smaller, more manageable sub-problems.
In this chapter, you will learn:
- What a function is and why they are important.
- How to define and declare your own functions.
- How to call functions and pass data to them.
- Different ways functions can return values.
4.1 What are Functions and Why Use Them?
A function in C is a group of statements that together perform a specific task. You’ve already used functions, like printf() for output and scanf() for input, and main() itself is a function!
Why use functions?
- Modularity: Break down complex problems into smaller, more manageable parts. Each function focuses on a single, well-defined task.
- Code Reusability: Write a piece of code once and use it multiple times throughout your program (or even in different programs). This avoids redundant code.
- Readability: Programs with well-named functions are easier to understand, as the function name clearly describes its purpose.
- Easier Debugging: When an error occurs, you can narrow down the potential source to a specific function, rather than searching through hundreds of lines of code.
- Maintainability: Changes or updates to a specific functionality can often be isolated to a single function, reducing the risk of introducing bugs elsewhere.
4.2 Function Anatomy: Declaration, Definition, Call
Every function typically involves three aspects:
- Function Declaration (Prototype): Tells the compiler about a function’s name, return type, and parameters before the function is actually defined.
- Function Definition: Contains the actual code (body) of the function that performs the task.
- Function Call: Executes the function from another part of the program.
4.2.1 Function Declaration (Prototype)
A function declaration, also known as a function prototype, informs the compiler about a function. It’s like telling the compiler, “Hey, a function named add_numbers exists, it takes two integers, and it will return an integer.”
Syntax:
returnType functionName(parameterType1 parameterName1, parameterType2 parameterName2, ...);
returnType: The data type of the value the function will return. If the function doesn’t return anything, usevoid.functionName: A unique identifier for the function.parameterTypeX: The data type of each parameter.parameterNameX: A name for each parameter (optional in declaration, but good practice).
Example Declarations:
int add_numbers(int a, int b); // Returns an int, takes two ints
void greet(char* name); // Returns nothing, takes a character pointer (string)
double calculate_area(double radius); // Returns a double, takes a double
int get_max(int num1, int num2); // Returns an int, takes two ints
void print_message(); // Returns nothing, takes no parameters
Function declarations are usually placed at the top of the C file (before main()) or in a header file (.h).
4.2.2 Function Definition
The function definition provides the actual implementation of the function. It tells the compiler what the function does.
Syntax:
returnType functionName(parameterType1 parameterName1, parameterType2 parameterName2, ...) {
// Function body:
// Local variable declarations
// Statements to perform the task
// return value; // If returnType is not void
}
parameterNameX: The names used within the function body to refer to the arguments passed to it. These are local variables.return value;: Thereturnstatement sends a value back to the caller. Thevaluemust match thereturnTypeof the function. If thereturnTypeisvoid, areturn;statement can be used without a value, or implicitly at the end of the function.
Example Definitions:
// Definition of add_numbers
int add_numbers(int num1, int num2) {
int sum = num1 + num2;
return sum; // Return the sum
}
// Definition of greet
void greet(char* name) {
printf("Hello, %s!\n", name);
// No return statement needed for void functions if nothing is returned
}
// Definition of calculate_area
double calculate_area(double radius) {
// Use M_PI for pi, often found in <math.h>
// Or define it yourself: #define PI 3.1415926535
double area = 3.1415926535 * radius * radius;
return area;
}
4.2.3 Function Call
To use a function, you “call” it from another part of your program. When a function is called, the program’s control transfers to the called function. Once the function finishes its task (either by a return statement or by reaching the end of its body), control returns to the point where it was called.
Syntax:
// If the function returns a value:
variable = functionName(argument1, argument2, ...);
// If the function doesn't return a value (void):
functionName(argument1, argument2, ...);
argumentX: These are the actual values passed to the function when it is called. They must match the type and order of the parameters defined in the function’s declaration/definition.
Example Calls:
int result = add_numbers(5, 3); // result will be 8
printf("Sum: %d\n", result);
greet("Alice"); // Prints "Hello, Alice!"
double circle_area = calculate_area(2.5);
printf("Area of circle: %.2lf\n", circle_area);
Code Example: Function Declaration, Definition, and Call
#include <stdio.h> // For printf
#include <math.h> // For M_PI constant (often available, but not standard C)
// Function Declarations (Prototypes)
// It's good practice to declare functions before main,
// especially if their definitions are after main.
int multiply(int a, int b);
void print_stars(int count);
double calculate_cylinder_volume(double radius, double height);
int is_even(int number); // C23: can use bool instead of int for return type
int main() {
printf("--- Function Calls ---\n");
// Call multiply function
int product = multiply(12, 5);
printf("Product of 12 and 5: %d\n", product); // Output: 60
// Call print_stars function
print_stars(10); // Prints 10 stars on a line
print_stars(3); // Prints 3 stars
// Call calculate_cylinder_volume
double vol = calculate_cylinder_volume(2.0, 5.0);
printf("Volume of cylinder (r=2.0, h=5.0): %.2lf\n", vol); // Output: 62.83 (approx)
// Call is_even function
int number_to_check = 7;
// In C99/C11/C17, is_even returns int (0 or 1).
// In C23, it could return bool. printf still uses %d.
if (is_even(number_to_check)) {
printf("%d is even.\n", number_to_check);
} else {
printf("%d is odd.\n", number_to_check);
}
number_to_check = 10;
if (is_even(number_to_check)) {
printf("%d is even.\n", number_to_check);
} else {
printf("%d is odd.\n", number_to_check);
}
return 0;
}
// Function Definitions
// Multiplies two integers and returns the product
int multiply(int a, int b) {
return a * b;
}
// Prints a specified number of stars
void print_stars(int count) {
for (int i = 0; i < count; i++) {
printf("*");
}
printf("\n"); // Newline after printing stars
}
// Calculates the volume of a cylinder
double calculate_cylinder_volume(double radius, double height) {
// If M_PI is not defined, you can define it manually:
// #define PI 3.141592653589793
// return PI * radius * radius * height;
return M_PI * radius * radius * height;
}
// Checks if a number is even
int is_even(int number) {
// In C23, you might write: return (bool)(number % 2 == 0);
return (number % 2 == 0); // Returns 1 if even, 0 if odd
}
Compile and Run:
# Compile and link with math library if M_PI is used (requires -lm)
gcc functions_example.c -o functions_example -lm
./functions_example
4.3 Parameters and Arguments
- Parameters: Variables declared in the function’s definition (and prototype) that receive the values passed to the function. They act as placeholders.
- Arguments: The actual values passed to the function when it is called.
Value vs. Reference (a sneak peek): In C, arguments are typically passed to functions by value. This means a copy of the argument’s value is passed to the function’s parameter. Any changes made to the parameter inside the function do not affect the original argument in the calling function.
We will explore pass by reference using pointers in a later chapter, which allows functions to modify the original variables of the caller.
#include <stdio.h>
void modify_value(int num) { // num is a parameter
printf("Inside function: num before modification = %d\n", num);
num = num + 10; // This changes the LOCAL copy of num
printf("Inside function: num after modification = %d\n", num);
}
int main() {
int x = 5; // x is an argument
printf("In main: x before function call = %d\n", x); // Output: 5
modify_value(x); // Pass x by value
printf("In main: x after function call = %d\n", x); // Output: 5 (x remains unchanged)
return 0;
}
Explanation: When modify_value(x) is called, a copy of x’s value (which is 5) is passed to the num parameter. num gets its own memory space and initially holds 5. When num = num + 10; executes, only num (the local copy) changes to 15. The original x in main remains untouched.
4.4 main() Function Revisited
You’ve been using main() since your first “Hello, World!” program. main() is a special function:
- It is the entry point of every C program. Execution always begins here.
- It returns an
int(integer), typically0to indicate successful execution, or a non-zero value to indicate an error. - It can optionally take two arguments:
argc(argument count) andargv(argument vector), which allow your program to receive command-line arguments. We’ll cover this in a later chapter.
int main() {
// ... program code ...
return 0; // Success
}
4.5 Scope of Variables
The scope of a variable refers to the region of the program where that variable can be accessed.
- Local Variables: Declared inside a function or a block (
{}). They are only accessible within that function/block. They are created when the function/block is entered and destroyed when it is exited. Parameters are also local to their function. - Global Variables: Declared outside of any function, typically at the top of the file. They are accessible from any function in the program from the point of declaration onwards. While sometimes convenient, overuse of global variables can lead to harder-to-debug code and reduce modularity.
Example:
#include <stdio.h>
int global_var = 100; // Global variable
void function_a() {
int local_a = 10; // Local to function_a
printf("Inside function_a: global_var = %d, local_a = %d\n", global_var, local_a);
// printf("Inside function_a: local_b = %d\n", local_b); // ERROR: local_b is not visible here
}
void function_b() {
int local_b = 20; // Local to function_b
printf("Inside function_b: global_var = %d, local_b = %d\n", global_var, local_b);
}
int main() {
int local_main = 5; // Local to main
printf("Inside main: global_var = %d, local_main = %d\n", global_var, local_main);
function_a();
function_b();
// printf("Inside main: local_a = %d\n", local_a); // ERROR: local_a is not visible here
return 0;
}
Exercise 4.1: Custom Math Functions
Create a C program that defines and uses the following functions:
double calculate_average(double val1, double val2, double val3): Takes threedoublevalues and returns their average.void print_max(int num1, int num2): Takes two integers and prints which one is greater, or if they are equal. It should not return any value.int factorial(int n): Calculates the factorial of a given integern(n!). Recall that factorial of 0 is 1. Ifnis negative, return -1 to indicate an error.
In your main function, call these functions with different arguments and print their results (if they return a value).
Example Calls:
double avg = calculate_average(10.0, 20.0, 30.0);
printf("Average: %.2lf\n", avg); // Expected: 20.00
print_max(5, 12); // Expected: 12 is greater than 5.
print_max(7, 7); // Expected: Both numbers are equal.
int fact5 = factorial(5);
printf("Factorial of 5: %d\n", fact5); // Expected: 120
int fact_neg = factorial(-3);
printf("Factorial of -3: %d\n", fact_neg); // Expected: -1 (Error)
Exercise 4.2: Simple Unit Converter (Mini-Challenge)
Create a C program that converts units. Define separate functions for each conversion:
double celsius_to_fahrenheit(double celsius)double fahrenheit_to_celsius(double fahrenheit)double kilometers_to_miles(double kilometers)double miles_to_kilometers(double miles)
In your main function, implement a simple menu using switch and do-while loops (from Chapter 3) to allow the user to choose which conversion they want to perform. Continuously ask for conversions until the user chooses to exit.
Conversion Formulas:
- Fahrenheit = (Celsius * 9/5) + 32
- Celsius = (Fahrenheit - 32) * 5/9
- Miles = Kilometers / 1.60934
- Kilometers = Miles * 1.60934
Expected interaction:
Unit Converter Menu:
1. Celsius to Fahrenheit
2. Fahrenheit to Celsius
3. Kilometers to Miles
4. Miles to Kilometers
5. Exit
Enter your choice: 1
Enter temperature in Celsius: 25.0
25.0 Celsius is 77.00 Fahrenheit.
Unit Converter Menu:
...
Enter your choice: 5
Exiting program.
You’ve now learned how to structure your C programs using functions, a critical step towards writing clean, organized, and reusable code. This understanding will be crucial as we move into more complex topics like pointers and memory management. In the next chapter, we will tackle the heart of C: Pointers.