C Language Functions: Core Concepts, Parameter Passing, and Recursion with Practical Examples

Understanding Functions in C

Functions represent the fundamental building blocks of C programs. Each function encapsulates a specific operation, enabling modular design, code reuse, and logical organization. The language provides two categories: predefined library functions and user-defined custom functions.

Library Functions

Compiler vendors supply implementations for standardized functions defined by the ANSI C standard. These functions cover common operations such as input/output, string manipulation, memory operations, and mathematical calculations. For instance, strcpy copies one string to another, and memset fills a memory block with a specified value.

#include <stdio.h>
#include <string.h>

void demo_strcpy(void) {
    char target[20] = {"xxxxxxxxx"};
    char source[] = {"sample text"};
    strcpy(target, source);
    printf("%s", target);
}

void demo_memset(void) {
    char buffer[] = "hello world";
    memset(buffer, '#', 4);
    printf("%s\n", buffer);
}

Always include the appropriate header file when using library functions. Reference resources like cppreference.com provide documentation on required headers and function signatures.

Custom Functions

Programmers define their own functions when library functions do not meet specific requirements. A custom function consists of a return type, a name, a parameter list, and a body.

return_type function_name(parameter_declarations) {
    // statements
}

Consider a function that returns the larger of two integers:

int max_of_two(int first, int second) {
    return first > second ? first : second;
}

Attempting to swap two variables using a function highlights a critical concept. The following implementation fails because parameters receive copies of the argument values.

void swap_values(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
}

Function Parameters

Actual Arguments (Arguments)

Arguments passed to a function can be constants, variables, expressions, or even return values from other functions. Each argument must resolve to a concrete value before the call executes.

Formal Parameters (Parameters)

Parameters are the variables declared in the function definition. They are instantiated when the function is called and destroyed upon return. Modificationss to parameters do not affect the original arguments unless addresses are passed.

Calling Conventions

Pass by Value

When arguments are past by value, the function operates on independent copies. Changes inside the function have no external effect.

Pass by Reference (via Pointers)

Passing the memory address of a variable establishes a direct link between the function and the caller's data. This technique enables a function to modify external variables.

void swap_elements(int *ptr_a, int *ptr_b) {
    int temp = *ptr_a;
    *ptr_a = *ptr_b;
    *ptr_b = temp;
}

int main(void) {
    int x = 10, y = 20;
    printf("Before: x=%d, y=%d\n", x, y);
    swap_elements(&x, &y);
    printf("After:  x=%d, y=%d\n", x, y);
    return 0;
}

Practical Examples

Prime Number Checking

A prime number is greater than 1 and divisible only by 1 and itself. To test a number, check divisors only up to its square root.

#include <math.h>
#include <stdio.h>

int is_prime_candidate(int candidate) {
    int divisor;
    for (divisor = 2; divisor <= sqrt(candidate); divisor++) {
        if (candidate % divisor == 0)
            return 0;
    }
    return 1;
}

int main(void) {
    int num;
    for (num = 100; num <= 200; num++) {
        if (is_prime_candidate(num))
            printf("%d is prime\n", num);
    }
    return 0;
}

Leap Year Detection

A year qualifies as a leap year if it is divisible by 4 but not by 100, unless it is also divisible by 400.

int check_leap_year(int year) {
    return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0);
}

Binary Search Implementation

Binary search operates on sorted arrays by repeatedly dividing the search interval in half.

int binary_search_array(int dataset[], int size, int target) {
    int low = 0;
    int high = size - 1;
    int middle;

    while (low <= high) {
        middle = low + (high - low) / 2;
        if (target < dataset[middle])
            high = middle - 1;
        else if (target > dataset[middle])
            low = middle + 1;
        else
            return middle;
    }
    return -1;
}

Modifying Values Through Pointers

void increase_value(int *value_ptr) {
    (*value_ptr)++;
}

int original = 0;
increase_value(&original);
printf("original = %d\n", original); // Outputs 1

Operator precedence requires parentheses around *value_ptr before applying increment.

Nested and Chained Function Calls

Functions can call other functions, forming nested invocations. However, C does not permit function definitions inside other functions.

Chained access uses the return value of one function directly as an argument to another. For example:

char text[20] = "hello";
int combined_length = strlen(strcat(text, " world"));
printf("Combined length: %d\n", combined_length);

An interesting pattern emerges with printf returning the count of printed characters:

printf("%d", printf("%d", printf("42")));
// The innermost call prints "42" (2 chars) and returns 2.
// The next call prints "2" and returns 1.
// The outermost call prints "1".
// Total output: "4221"

Declaration and Definition

Declarations inform the compiler about a function's name, parameters, and return type before its actual use. Definitions provide the implementation. In multi-file projects, declarations reside in header files (.h), while definitions live in source files (.c).

// header.h
int add(int a, int b);

// implementation.c
#include "header.h"
int add(int a, int b) {
    return a + b;
}

Recursion

Recursion occurs when a function invokes itself. It transforms complex problems by breaking them into simpler subproblems until a base case is reached. Every recursive solution requires a termination condition and progression toward that condition.

Digit Printing

Printing digits of a number sequentially can be accomplished without loops using recursion:

void print_digits(int number) {
    if (number > 9)
        print_digits(number / 10);
    printf("%d ", number % 10);
}

String Length Without Counters

A recursive length function adds 1 for each character until the null terminator is encountered.

int recursive_strlen(char *str) {
    if (*str == '\0')
        return 0;
    return 1 + recursive_strlen(str + 1);
}

Factorial Calculation

Factorial n! equals n * (n-1)! with the base case 0! = 1.

int factorial(int n) {
    if (n <= 1)
        return 1;
    return n * factorial(n - 1);
}

An iterative equivalent uses a loop accumulator:

int factorial_iterative(int n) {
    int product = 1;
    for (int factor = 1; factor <= n; factor++) {
        product *= factor;
    }
    return product;
}

Fibonacci Sequence

The sequence starts with 1, 1, and each subsequent term is the sum of the two preceding ones.

int fibonacci(int term_index) {
    if (term_index <= 2)
        return 1;
    return fibonacci(term_index - 1) + fibonacci(term_index - 2);
}

Digit Summation Until Single Digit

Repeatedly sum the digits of a number until a single-digit result remains.

#include <stdio.h>

int collapse_to_single_digit(int value) {
    int sum = (value % 10) + (value / 10);
    if (sum < 10)
        return sum;
    return collapse_to_single_digit(sum);
}

int main(void) {
    int input = 123;
    int result = collapse_to_single_digit(input);
    printf("Result: %d", result);  // Outputs 6
    return 0;
}

Tags: C Language functions parameters Recursion pointer

Posted on Mon, 22 Jun 2026 18:56:34 +0000 by rane500