[C Series 6] Breaking Code into Reusable Functions

한국어 버전

If part 5 taught you how to guide the control flow with conditionals and loops, this chapter is where you wrap that flow into named chunks. A function lets you wrap the "input -> processing -> output" pattern with a name so that you can reuse it whenever you like. This matters because you do not want to type the same block ten times. We will separate declarations from definitions and see how to design parameters and return values.

New Terms in This Chapter

  1. Function Declaration: The statement that tells the compiler a function's name, parameters, and return type
  2. Function Definition: The code block that implements the actual behavior
  3. Parameter: The placeholder inside a function that receives input when the function is called
  4. Return Value: The value a function hands back after it finishes its computation
  5. Function Prototype: A declaration written above main so the compiler knows the function signature in advance

Key Ideas

Study Notes

  • Time: about 50–60 minutes
  • Prep: a few conditional and loop examples ready to modify
  • Goal: understand how to declare, define, and call functions, and how to design parameters and return values

Functions are tools for naming repeated code. Even if you only use a function once, a good name explains "what it does," which makes reading much easier. Think of each function as having five parts. In int add_two_numbers(int a, int b) { return a + b; }, the return type is int, the function name is add_two_numbers, the parameters are int a, int b, the body is inside { }, and the return statement sends the result back.

  1. Return type: the data type of the result (int, void, double, and so on)
  2. Function name: verbs such as print_line or calculate_total work well
  3. Parameter list: the inputs the function needs
  4. Body: the block of code inside braces that does the work
  5. return statement: required whenever the return type is not void

Follow Along with Code

Creating the Smallest Function

#include <stdio.h>

void print_separator(void) {
    printf("----------\n");
}

int main(void) {
    print_separator();
    printf("Starting the C function lesson\n");
    print_separator();
    return 0;
}

print_separator uses void because it does not return anything. Calling the name inside main executes the code. Grouping repeated output into one function reduces mistakes and makes the intent obvious.

Accepting Input with Parameters

#include <stdio.h>

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

int main(void) {
    int result = add_two_numbers(12, 30);
    printf("Result: %d\n", result);
    return 0;
}

a and b only exist inside the function. When you call add_two_numbers(12, 30), the parameters are filled just for that call and disappear when the function ends. That is what makes the same function reusable with many inputs.

Returning a Result

#include <stdio.h>

double average_score(int sum, int count) {
    if (count == 0) {
        return 0.0;
    }
    return (double)sum / count;
}

int main(void) {
    double avg = average_score(273, 3);
    printf("Average: %.2f\n", avg);
    return 0;
}

Because the return type is double, the return statement must produce a double. When parameter and return types disagree, the compiler can emit warnings or errors, so keeping the types aligned matters.

Splitting Declaration and Definition with a Prototype

#include <stdio.h>

double celsius_to_fahrenheit(double c); // function prototype

int main(void) {
    double result = celsius_to_fahrenheit(24.0);
    printf("24°C = %.1f°F\n", result);
    return 0;
}

double celsius_to_fahrenheit(double c) {
    return (c * 9.0 / 5.0) + 32.0;
}

If you prefer to define the function below main, place the prototype above so the compiler already knows the signature. Later, when you split code into multiple files, these prototypes move into header files.

Practical Example: Grouping Grade Helpers

#include <stdio.h>

int is_pass(int score) {
    return score >= 60;
}

void print_report(int score) {
    if (is_pass(score)) {
        printf("Score %d: pass\n", score);
    } else {
        printf("Score %d: extra practice\n", score);
    }
}

int main(void) {
    print_report(85);
    print_report(42);
    print_report(73);
    return 0;
}

is_pass returns 1 or 0 based on the score, and print_report uses that result to choose a message. Combining small functions like this keeps conditionals and loops easy to read.

Why It Matters

  • Functions give names to repeated control-flow patterns, which cuts down on bugs.
  • Clear parameters and return values make testing easier and help you guard against bad input.
  • Separating declarations from definitions prepares you for working with multiple .c files and headers.
  • Thinking in terms of functions narrows down where a bug might live: "which behavior is broken?"

Practice in CodeSandbox

The sandbox below uses CodeSandbox's Universal starter. For C, the key learning loop is still compile and run in the terminal, so recreate the lesson code as a source file and repeat that cycle directly.

Live Practice

C Practice Sandbox

CodeSandbox

Run the starter project in CodeSandbox, compare it with the lesson code, and keep experimenting.

Universal starterCterminal
  1. Fork the starter and create a practice file such as hello.c
  2. Paste in the lesson code and compile it if clang or gcc is available
  3. Edit the code, rebuild it, and compare the new output

For C, the terminal build loop matters more than a browser preview. Compiler availability can vary by environment, so first confirm that clang or gcc is present in the Universal starter.

Practice

  • Follow: Type out print_separator, add_two_numbers, and average_score, then confirm the output.
  • Extend: Create two functions that take an array of scores and return the average and the highest value, then call them in different orders inside main.
  • Debug: Remove a function prototype on purpose and observe what compile-time errors appear.
  • Done When: You can explain the difference between a declaration and a definition and write your own functions with parameters and return values.

Wrap-Up

We covered how to bundle control-flow logic into functions. Functions stay at the center when you move on to arrays, strings, and pointers. Next time, we will store multiple values at once with arrays and strings so we can reason about batches of data.

💬 댓글

이 글에 대한 의견을 남겨주세요