C Functions - Complete Guide
Master C functions including types, user-defined functions, parameter passing techniques, arrays as arguments, and modular programming best practices.
Function Types
Built-in & User-defined
Parameter Passing
Value & Reference
Array Parameters
1D & 2D Arrays
Introduction to C Functions
Functions are the building blocks of C programming. They are self-contained blocks of code that perform specific tasks, enabling code reusability, modularity, and maintainability.
Why Use Functions?
- Code Reusability: Write once, use multiple times
- Modularity: Break complex problems into smaller parts
- Abstraction: Hide implementation details
- Maintainability: Easier debugging and updates
- Team Collaboration: Divide work among programmers
Function Components
- Function Declaration: Tells compiler about function
- Function Definition: Actual implementation
- Function Call: Invokes the function
- Parameters: Input to function
- Return Value: Output from function
Function Execution Flow:
Important Function Components
Every function consists of: Return Type (data type returned), Function Name (identifier), Parameters (input values), Function Body (implementation), and Return Statement (output value).
Types of Functions in C
Here is a comprehensive comparison of all function types in C with their key characteristics:
| Function Type | Description | Examples | Characteristics |
|---|---|---|---|
|
Built-in Functions
Library Functions
|
Pre-defined functions in C standard libraries |
printf(), scanf()
strlen(), strcpy()
sqrt(), pow()
|
Ready to use
Require headers
Optimized by compiler, part of standard library
|
|
User-Defined Functions
Custom Functions
|
Functions created by programmers |
int add(int a, int b)
void display()
float calculate()
|
Custom logic
Improve modularity
Project-specific, improve code organization
|
|
Functions with Return Value
Value-Returning
|
Functions that return a value to caller |
return a + b;
return result;
|
Return type
Use in expressions
Must specify return type, use return statement
|
|
Void Functions
No Return Value
|
Functions that don't return any value |
void printHello()
void displayMenu()
|
Return type void
No return
Return type is void, perform actions only
|
|
Recursive Functions
Self-Calling
|
Functions that call themselves |
int factorial(int n)
int fibonacci(int n)
|
Base case
Stack memory
Base case required, elegant for certain problems
|
#include <stdio.h>
#include <math.h> // For built-in functions
// User-defined function with return value
int add(int a, int b) {
return a + b;
}
// User-defined void function
void printLine(int length) {
for(int i = 0; i < length; i++) {
printf("-");
}
printf("\n");
}
// User-defined recursive function
int factorial(int n) {
if(n <= 1) return 1; // Base case
return n * factorial(n - 1); // Recursive call
}
int main() {
int x = 5, y = 3;
// Using built-in function
printf("Built-in sqrt(%d) = %.2f\n", x, sqrt(x));
// Using user-defined value-returning function
int sum = add(x, y);
printf("User-defined add(%d, %d) = %d\n", x, y, sum);
// Using user-defined void function
printf("Printing line:\n");
printLine(20);
// Using recursive function
printf("Recursive factorial(%d) = %d\n", x, factorial(x));
// Function calls in expressions
printf("Combined: add(%d, %d) * 2 = %d\n",
x, y, add(x, y) * 2);
return 0;
}
User-Defined Functions - Complete Guide
User-defined functions are created by programmers to perform specific tasks. They consist of function declaration, definition, and calling.
Syntax:
return_type function_name(parameter_list) {
// function body
return value; // optional
}
Function Execution Flow:
Key Characteristics:
- Modularity: Break programs into manageable pieces
- Reusability: Call functions multiple times
- Abstraction: Hide implementation details
- Testing: Test functions independently
Complete User-Defined Function Example:
#include <stdio.h>
// 1. FUNCTION DECLARATION (Prototype)
// Tells compiler about function name, return type, and parameters
int findMaximum(int a, int b);
void printTable(int number);
float calculateAverage(int arr[], int size);
// 2. MAIN FUNCTION
int main() {
int num1 = 45, num2 = 78;
int numbers[] = {85, 90, 78, 92, 88};
int size = 5;
// Calling function with return value
int max = findMaximum(num1, num2);
printf("Maximum of %d and %d is: %d\n\n", num1, num2, max);
// Calling void function
printf("Multiplication table of 5:\n");
printTable(5);
// Calling function with array parameter
float avg = calculateAverage(numbers, size);
printf("\nAverage of array elements: %.2f\n\n", avg);
// Calling functions multiple times (reusability)
printf("Checking maximum for different values:\n");
printf("findMaximum(10, 20) = %d\n", findMaximum(10, 20));
printf("findMaximum(100, 50) = %d\n", findMaximum(100, 50));
printf("findMaximum(-5, 10) = %d\n", findMaximum(-5, 10));
return 0;
}
// 3. FUNCTION DEFINITIONS
// Definition of findMaximum function
int findMaximum(int a, int b) {
if(a > b) {
return a;
} else {
return b;
}
}
// Definition of printTable function (void - no return value)
void printTable(int number) {
for(int i = 1; i <= 10; i++) {
printf("%d x %d = %d\n", number, i, number * i);
}
}
// Definition of calculateAverage function
float calculateAverage(int arr[], int size) {
int sum = 0;
for(int i = 0; i < size; i++) {
sum += arr[i];
}
return (float)sum / size;
}
Maximum of 45 and 78 is: 78
Multiplication table of 5:
5 x 1 = 5
5 x 2 = 10
... (table continues)
5 x 10 = 50
Average of array elements: 86.60
Checking maximum for different values:
findMaximum(10, 20) = 20
findMaximum(100, 50) = 100
findMaximum(-5, 10) = 10
Function Components Explained:
Function Prototype
return_type function_name(parameters);
Example:
int add(int a, int b);
Function Call
function_name(arguments);
Example:
result = add(5, 3);
Function Definition
return_type function_name(parameters) {
// function body
return value;
}
Return Statement
return expression;
Void functions: No return needed
Other functions: Must return value of specified type
Parameter Passing Methods
Parameter passing methods determine how arguments are passed to functions. C supports call by value and call by reference.
| Method | Description | Effect on Original | Example |
|---|---|---|---|
Call by Value |
Creates copy of actual parameter in formal parameter | No change - Original unchanged | void func(int a) |
Call by Reference |
Passes memory address of actual parameter | Can change - Original can be modified | void func(int *a) |
#include <stdio.h>
// Call by Value - receives copy of value
void swapByValue(int a, int b) {
int temp = a;
a = b;
b = temp;
printf("Inside swapByValue: a = %d, b = %d\n", a, b);
}
// Call by Reference - receives address
void swapByReference(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
printf("Inside swapByReference: *a = %d, *b = %d\n", *a, *b);
}
// Another Call by Value example
int squareByValue(int num) {
num = num * num; // Changes local copy only
return num;
}
// Call by Reference example
void squareByReference(int *num) {
*num = (*num) * (*num); // Changes original value
}
int main() {
int x = 10, y = 20;
printf("=== DEMONSTRATING CALL BY VALUE ===\n");
printf("Before swapByValue: x = %d, y = %d\n", x, y);
swapByValue(x, y); // Pass values
printf("After swapByValue: x = %d, y = %d\n", x, y);
printf("\n=== DEMONSTRATING CALL BY REFERENCE ===\n");
printf("Before swapByReference: x = %d, y = %d\n", x, y);
swapByReference(&x, &y); // Pass addresses
printf("After swapByReference: x = %d, y = %d\n", x, y);
printf("\n=== MORE EXAMPLES ===\n");
int num1 = 5;
int num2 = 5;
// Call by Value - returns new value
int result1 = squareByValue(num1);
printf("squareByValue(%d) = %d (num1 unchanged: %d)\n",
5, result1, num1);
// Call by Reference - modifies original
squareByReference(&num2);
printf("squareByReference(&num2): num2 is now %d\n", num2);
// Practical use cases
printf("\n=== PRACTICAL USE CASES ===\n");
printf("Use Call by Value when:\n");
printf(" - Function shouldn't modify original\n");
printf(" - Working with simple data types\n");
printf(" - Return value is sufficient\n\n");
printf("Use Call by Reference when:\n");
printf(" - Function needs to modify original\n");
printf(" - Passing large data structures\n");
printf(" - Need multiple return values\n");
printf(" - Implementing efficient algorithms\n");
return 0;
}
Call by Value Characteristics
- Actual parameters copied to formal parameters
- Changes inside function don't affect original
- Safe but uses extra memory
- Slower for large data
- Default in C for simple types
Call by Reference Characteristics
- Address of actual parameter passed
- Changes inside function affect original
- Efficient for large data
- Requires pointer syntax
- Can return multiple values
Parameter Passing Visualization:
Call by Value:
Function: a = 10 (Address: 2000) ← Copy
Function modifies a (Address: 2000)
x (Address: 1000) remains 10
Call by Reference:
Function: *a = address of x (1000)
Function modifies *a (Address: 1000)
x (Address: 1000) changes to new value
Arrays as Function Arguments
Arrays are always passed to functions by reference (as pointers), even though the syntax may look like call by value.
Syntax for Array Parameters:
// 1D Array
void processArray(int arr[], int size);
void processArray(int *arr, int size);
// 2D Array (must specify columns)
void processMatrix(int rows, int cols, int matrix[][cols]);
void processMatrix(int rows, int cols, int (*matrix)[cols]);
1D Arrays as Function Arguments:
#include <stdio.h>
// Method 1: Using array notation with size
void printArray(int arr[], int size) {
printf("Array elements: ");
for(int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
// Method 2: Using pointer notation
void modifyArray(int *arr, int size) {
printf("Doubling array elements...\n");
for(int i = 0; i < size; i++) {
arr[i] = arr[i] * 2; // Changes original array
}
}
// Method 3: Returning array through parameters
void getArrayStats(int arr[], int size, int *sum, float *avg, int *max, int *min) {
*sum = 0;
*max = arr[0];
*min = arr[0];
for(int i = 0; i < size; i++) {
*sum += arr[i];
if(arr[i] > *max) *max = arr[i];
if(arr[i] < *min) *min = arr[i];
}
*avg = (float)(*sum) / size;
}
// Method 4: Creating and returning new array (through parameters)
void reverseArray(int src[], int dest[], int size) {
for(int i = 0; i < size; i++) {
dest[i] = src[size - 1 - i];
}
}
int main() {
int numbers[] = {1, 2, 3, 4, 5};
int size = 5;
printf("=== PASSING 1D ARRAYS TO FUNCTIONS ===\n\n");
// Passing array to print function
printArray(numbers, size);
// Modifying original array
printf("\nBefore modification: ");
printArray(numbers, size);
modifyArray(numbers, size);
printf("After modification: ");
printArray(numbers, size);
// Getting array statistics
int total, maximum, minimum;
float average;
getArrayStats(numbers, size, &total, &average, &maximum, &minimum);
printf("\nArray Statistics:\n");
printf("Sum: %d\n", total);
printf("Average: %.2f\n", average);
printf("Maximum: %d\n", maximum);
printf("Minimum: %d\n", minimum);
// Reversing array
int reversed[5];
reverseArray(numbers, reversed, size);
printf("\nOriginal array: ");
printArray(numbers, size);
printf("Reversed array: ");
printArray(reversed, size);
// Important Note
printf("\n=== IMPORTANT NOTES ===\n");
printf("1. Arrays are ALWAYS passed by reference\n");
printf("2. Changes inside function affect original array\n");
printf("3. Size must be passed separately\n");
printf("4. arr[] and *arr are equivalent in parameter list\n");
return 0;
}
2D Arrays as Function Arguments:
#include <stdio.h>
// Method 1: Fixed column size (must specify columns)
void printMatrix(int rows, int cols, int matrix[][cols]) {
printf("Matrix (%dx%d):\n", rows, cols);
for(int i = 0; i < rows; i++) {
for(int j = 0; j < cols; j++) {
printf("%4d", matrix[i][j]);
}
printf("\n");
}
}
// Method 2: Using pointer to array (fixed column size)
void addMatrices(int rows, int cols, int A[][cols], int B[][cols], int result[][cols]) {
for(int i = 0; i < rows; i++) {
for(int j = 0; j < cols; j++) {
result[i][j] = A[i][j] + B[i][j];
}
}
}
// Method 3: Using single pointer (flattened array)
void multiplyMatrix(int *matrix, int rows, int cols, int multiplier) {
for(int i = 0; i < rows * cols; i++) {
matrix[i] = matrix[i] * multiplier;
}
}
// Method 4: Transpose matrix
void transposeMatrix(int rows, int cols, int src[][cols], int dest[][rows]) {
for(int i = 0; i < rows; i++) {
for(int j = 0; j < cols; j++) {
dest[j][i] = src[i][j];
}
}
}
int main() {
int rows = 3, cols = 4;
// Initialize matrices
int A[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
int B[3][4] = {
{12, 11, 10, 9},
{8, 7, 6, 5},
{4, 3, 2, 1}
};
printf("=== PASSING 2D ARRAYS TO FUNCTIONS ===\n\n");
// Print matrices
printf("Matrix A:\n");
printMatrix(rows, cols, A);
printf("\nMatrix B:\n");
printMatrix(rows, cols, B);
// Matrix addition
int C[3][4];
addMatrices(rows, cols, A, B, C);
printf("\nMatrix C = A + B:\n");
printMatrix(rows, cols, C);
// Matrix multiplication by scalar (using flattened array)
printf("\nMultiplying matrix A by 2:\n");
multiplyMatrix((int *)A, rows, cols, 2);
printMatrix(rows, cols, A);
// Transpose matrix
int transposed[4][3]; // Note: dimensions swapped
transposeMatrix(rows, cols, A, transposed);
printf("\nTranspose of modified A (4x3):\n");
printMatrix(cols, rows, transposed);
printf("\n=== KEY POINTS FOR 2D ARRAYS ===\n");
printf("1. Must specify column size in function parameters\n");
printf("2. int matrix[][cols] or int (*matrix)[cols] can be used\n");
printf("3. Row size can be passed as parameter\n");
printf("4. Memory is contiguous (row-major order)\n");
return 0;
}
Memory Layout of Array Parameters
1D Arrays: int arr[] is equivalent to int *arr in parameter list
2D Arrays: int matrix[][cols] is equivalent to int (*matrix)[cols]
Important: Array size information is lost when passed to functions. Always pass size as separate parameter.
Practical Function Applications
#include <stdio.h>
// Function prototypes
void displayMenu();
int getChoice();
void performOperation(int choice);
int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
float divide(int a, int b);
void processArray(int arr[], int size);
void processMatrix(int rows, int cols, int matrix[][cols]);
// Global constants
#define MAX_SIZE 10
#define ROWS 3
#define COLS 3
int main() {
int choice;
printf("=== FUNCTION-BASED PROGRAM DEMO ===\n\n");
do {
displayMenu();
choice = getChoice();
if(choice >= 1 && choice <= 4) {
performOperation(choice);
} else if(choice == 5) {
// Array processing
int arr[MAX_SIZE] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
printf("\n=== ARRAY PROCESSING ===\n");
processArray(arr, MAX_SIZE);
} else if(choice == 6) {
// Matrix processing
int matrix[ROWS][COLS] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
printf("\n=== MATRIX PROCESSING ===\n");
processMatrix(ROWS, COLS, matrix);
} else if(choice != 7) {
printf("Invalid choice! Please try again.\n");
}
} while(choice != 7);
printf("\nThank you for using the program!\n");
return 0;
}
// Function definitions
void displayMenu() {
printf("\n========== MAIN MENU ==========\n");
printf("1. Addition\n");
printf("2. Subtraction\n");
printf("3. Multiplication\n");
printf("4. Division\n");
printf("5. Process Array\n");
printf("6. Process Matrix\n");
printf("7. Exit\n");
printf("===============================\n");
}
int getChoice() {
int choice;
printf("Enter your choice (1-7): ");
scanf("%d", &choice);
return choice;
}
void performOperation(int choice) {
int a, b;
printf("Enter two numbers: ");
scanf("%d %d", &a, &b);
switch(choice) {
case 1:
printf("Result: %d + %d = %d\n", a, b, add(a, b));
break;
case 2:
printf("Result: %d - %d = %d\n", a, b, subtract(a, b));
break;
case 3:
printf("Result: %d × %d = %d\n", a, b, multiply(a, b));
break;
case 4:
if(b != 0) {
printf("Result: %d ÷ %d = %.2f\n", a, b, divide(a, b));
} else {
printf("Error: Division by zero!\n");
}
break;
}
}
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }
float divide(int a, int b) { return (float)a / b; }
void processArray(int arr[], int size) {
int sum = 0;
int max = arr[0];
int min = arr[0];
printf("Array elements: ");
for(int i = 0; i < size; i++) {
printf("%d ", arr[i]);
sum += arr[i];
if(arr[i] > max) max = arr[i];
if(arr[i] < min) min = arr[i];
}
printf("\nArray Statistics:\n");
printf("Size: %d\n", size);
printf("Sum: %d\n", sum);
printf("Average: %.2f\n", (float)sum / size);
printf("Maximum: %d\n", max);
printf("Minimum: %d\n", min);
// Reverse array
printf("Reversed array: ");
for(int i = size-1; i >= 0; i--) {
printf("%d ", arr[i]);
}
printf("\n");
}
void processMatrix(int rows, int cols, int matrix[][cols]) {
printf("Matrix (%dx%d):\n", rows, cols);
for(int i = 0; i < rows; i++) {
for(int j = 0; j < cols; j++) {
printf("%4d", matrix[i][j]);
}
printf("\n");
}
// Calculate row sums
printf("\nRow sums:\n");
for(int i = 0; i < rows; i++) {
int rowSum = 0;
for(int j = 0; j < cols; j++) {
rowSum += matrix[i][j];
}
printf("Row %d sum: %d\n", i+1, rowSum);
}
// Calculate column sums
printf("\nColumn sums:\n");
for(int j = 0; j < cols; j++) {
int colSum = 0;
for(int i = 0; i < rows; i++) {
colSum += matrix[i][j];
}
printf("Column %d sum: %d\n", j+1, colSum);
}
}
Common Mistakes and Best Practices
Function Best Practices:
- Always declare function prototypes at the top
- Use meaningful function names (verbs for actions)
- Keep functions small and focused (single responsibility)
- Document functions with comments (purpose, parameters, return)
- Validate input parameters inside functions
- Pass array size as separate parameter
- Use const for parameters that shouldn't be modified
- Return meaningful error codes or use error handling
- Avoid global variables; use parameters instead
- Test functions independently (unit testing)
Good Function Design
- Clear, descriptive names
- Small, focused functionality
- Proper parameter validation
- Meaningful return values
- No side effects (when possible)
Poor Function Design
- Vague, cryptic names
- Too many responsibilities
- No parameter checking
- Unnecessary global variables
- Too many parameters (> 4)
Key Takeaways
- Functions enable modular programming and code reusability
- C supports built-in and user-defined functions
- Call by value passes copies; call by reference passes addresses
- Arrays are always passed by reference (as pointers)
- For 2D arrays, must specify column size in function parameters
- Always pass array size as separate parameter
- Function prototypes should be declared before use
- Functions should have single responsibility
- Use const keyword for parameters that shouldn't be modified
- Proper error handling is essential in functions