C Complete Course — CodeStudio Vault
0 / 57 topics
C Language Fundamentals

Complete C Reference Course

From Hello World and variables to pointers, file I/O, and the preprocessor — every concept explained line-by-line with real code, step-by-step breakdowns, and interactive progress tracking. No need to go anywhere else.

7Modules
57Topics
C89/C99+Standard
100%Free
Overall Progress — 0 of 57 topics completed
0Completed
57Remaining
0%Progress
01
Module 1 — Getting Started
Hello World, variables, constants, print, strings, conditions, loops, arrays, enums, pointers, user input
0% · 0/17 done
1.1 — Hello World & Compilation
Expand →

Your first C program

C is a compiled language — you write source code in a .c file, then use a compiler (typically gcc) to turn it into a machine-executable binary. #include <stdio.h> pulls in the Standard I/O library which provides printf(). Every C program must have a main() function — it is the entry point where execution begins. Returning 0 signals success to the OS.

1Write hello.c
hello.c
#include <stdio.h>

int main(void) {
  printf("Hello World!\n");

  return 0;
}
2Compile and run
Terminal
# Compile: gcc takes source file, -o sets the output binary name
$ gcc hello.c -o hello

# Run the compiled binary
$ ./hello
Output => Hello World!
1.2 — Variables
Expand →

What is a Variable?

A variable is a named memory location that stores a value. In C you must declare the type before use. You can declare and assign simultaneously, or declare first and assign later. Variables can be reassigned any number of times after creation. You can also declare multiple variables of the same type in one line.

C
int myNum = 15;           // declare and assign immediately

int myNum2;               // declare without assigning
myNum2 = 15;             // then assign later

int myNum3 = 15;          // myNum3 is 15
myNum3 = 10;             // myNum3 is now 10 (reassigned)

float myFloat  = 5.99;    // floating point number
char  myLetter = 'D';    // character (single quotes)

int x = 5;
int y = 6;
int sum = x + y;         // add variables together

// Declare multiple variables on one line
int a = 5, b = 6, c = 50;
1.3 — Constants
Expand →

Values That Never Change

The const keyword makes a variable read-only — its value cannot be changed after initialization. Constants are useful for fixed values like mathematical constants, configuration values, or limits. By convention, constant names are written in UPPER_CASE to make them easy to spot.

C
const int   minutesPerHour = 60;
const float PI             = 3.14;

// Best practice: UPPER_CASE naming for constants
const int BIRTHYEAR = 1980;
Warning

Attempting to modify a constant after declaration causes a compile error. Always initialize constants when you declare them — you cannot assign to them later.

1.4 — Comments
Expand →

Documenting Your Code

Comments are ignored by the compiler — they exist purely for human readers. C supports two styles: single-line (//) and multi-line (/* */). Single-line comments can appear anywhere on a line. Multi-line comments can span many lines and are great for longer explanations or temporarily disabling blocks of code.

C
// This is a single-line comment

printf("Hello World!"); // inline comment — appears after code

/* Multi-line comment,
   can span multiple lines,
   compiler ignores all of this */
1.5 — printf() and Format Specifiers
Expand →

Printing Output

printf() outputs formatted text to the terminal. You embed format specifiers (like %d, %f, %s) as placeholders inside the string — each one is replaced by the corresponding argument. C can also print numbers in different bases (decimal, octal, hexadecimal) and control column widths using format flags.

1Basic printing with format specifiers
C
printf("I am learning C.");

int testInteger = 5;
printf("Number = %d", testInteger);   // %d for integers

float f = 5.99;
printf("Value = %f", f);             // %f for floats
2Printing in different number bases (octal, hex)
C
short a = 0b1010110;   // binary literal
int   b = 02713;        // octal literal (prefix 0)
long  c = 0X1DAB83;     // hexadecimal literal (prefix 0x)

// Octal output: %ho %o %lo
printf("a=%ho, b=%o, c=%lo\n", a, b, c);
// => a=126, b=2713, c=7325603

// Decimal output: %hd %d %ld
printf("a=%hd, b=%d, c=%ld\n", a, b, c);
// => a=86, b=1483, c=1944451

// Hex lowercase: %hx %x %lx
printf("a=%hx, b=%x, c=%lx\n", a, b, c);
// => a=56, b=5cb, c=1dab83

// Hex uppercase: %hX %X %lX
printf("a=%hX, b=%X, c=%lX\n", a, b, c);
// => a=56, b=5CB, c=1DAB83
3Controlling column width (%-9d)
C — aligned table output
int a1 = 20,  a2 = 345,  a3 = 700;
int b1 = 56720, b2 = 9999, b3 = 20098;

printf("%-9d %-9d %-9d\n", a1, a2, a3);
printf("%-9d %-9d %-9d\n", b1, b2, b3);

// Output (each column 9 chars wide, left-aligned):
// 20        345       700
// 56720     9999      20098
Note

In %-9d: d = decimal output, 9 = minimum 9 character width (padded with spaces), - = left-align. Without -, output is right-aligned by default.

1.6 — Strings (char arrays)
Expand →

C Has No String Type

Unlike C++ or Python, C has no built-in string type. Strings are simply arrays of char ending with a null terminator '\0'. You print them with %s, access individual characters by index, and modify them by changing individual elements. A character pointer char * can also point to a string literal, but string literals are read-only.

1Create, print, access, and modify
C
// Create a string (char array)
char greetings[] = "Hello World!";
printf("%s", greetings);              // print whole string

// Access a single character by index
printf("%c", greetings[0]);           // => H

// Modify a character
greetings[0] = 'J';
printf("%s", greetings);              // => "Jello World!"

// Build string from character array manually (null-terminated)
char greetings2[] = {'H','e','l','l','\0'};
printf("%s", greetings2);             // => "Hell"
2String literal via character pointerRead-only
C
char *greetings = "Hello";
printf("%s", greetings);   // => Hello
// WARNING: do NOT modify — stored in read-only memory!
// greetings[0] = 'J';  // undefined behavior!
1.7 — Conditions (if / else if / else)
Expand →

Branching Logic

C uses curly-brace blocks for conditional execution. The if block runs when the condition is true. else if adds additional conditions. else is a catch-all that runs when no prior condition matched. Only one branch executes per if-else chain.

C
int time = 20;
if (time < 18) {
  printf("Goodbye!");
} else {
  printf("Good evening!");  // runs — 20 is not < 18
}

// Multi-branch else-if chain
int hour = 22;
if (hour < 10) {
  printf("Good morning!");
} else if (hour < 20) {
  printf("Goodbye!");
} else {
  printf("Good evening!");   // => "Good evening!"
}
1.8 — Ternary Operator & Switch
Expand →

Compact Branching

The ternary operator condition ? expr_true : expr_false is a one-line if-else that produces a value. switch compares a variable against multiple exact integer or char constants — cleaner than a long if-else chain for this pattern. Always include break to prevent fall-through to the next case. default runs when no case matches.

1Ternary operator
C
int age = 20;
// condition ? value_if_true : value_if_false
(age > 19) ? printf("Adult") : printf("Teenager");
// => Adult
2Switch statement
C
int day = 4;

switch (day) {
  case 3: printf("Wednesday"); break;
  case 4: printf("Thursday");  break;  // runs — day is 4
  default:
    printf("Weekend!");
}
// => "Thursday"
1.9 — while, do/while, for Loops
Expand →

Repeating Code

C has three loop types. while: checks condition before each iteration — may never run if condition is false initially. do/while: runs the body first then checks — always runs at least once. for: packs init, condition, and increment into one line — best when you know the count upfront.

1while loop
C
int i = 0;
while (i < 5) {
  printf("%d\n", i);
  i++;    // MUST increment — or infinite loop!
}
// prints: 0 1 2 3 4
2do/while loop — runs at least once
C
int i = 0;
do {
  printf("%d\n", i);
  i++;
} while (i < 5);   // semicolon required after while()
// prints: 0 1 2 3 4
3for loop
C
// for(init; condition; increment)
for (int i = 0; i < 5; i++) {
  printf("%d\n", i);
}
// prints: 0 1 2 3 4
1.10 — break & continue
Expand →

Controlling Loop Flow

break exits the entire loop immediately. continue skips the rest of the current iteration and jumps to the next one. Both work with for, while, and do/while loops.

1break — exit when i == 4
C
for (int i = 0; i < 10; i++) {
  if (i == 4) {
    break;   // exits the entire for loop
  }
  printf("%d\n", i);
}
// prints: 0 1 2 3  (stops before printing 4)
2continue — skip when i == 4
C
for (int i = 0; i < 10; i++) {
  if (i == 4) {
    continue;  // skips 4, continues from i=5
  }
  printf("%d\n", i);
}
// prints: 0 1 2 3 5 6 7 8 9  (4 is skipped)
3while + break and while + continue
C
// while + break
int i = 0;
while (i < 10) {
  if (i == 4) { break; }
  printf("%d\n", i);
  i++;
}

// while + continue
i = 0;
while (i < 10) {
  i++;
  if (i == 4) { continue; }
  printf("%d\n", i);
}
1.11 — Arrays
Expand →

Fixed-size Collections

An array is a contiguous block of memory holding multiple values of the same type. Declare with type name[size] or let the compiler count from an initializer list. Elements are zero-indexed. Arrays have a fixed size set at declaration time.

C
// Declare and initialize (compiler counts size)
int myNumbers[] = {25, 50, 75, 100};

printf("%d", myNumbers[0]);   // => 25 (index starts at 0)

// Modify an element
myNumbers[0] = 33;
printf("%d", myNumbers[0]);   // => 33

// Loop through the array
int i;
for (i = 0; i < 4; i++) {
  printf("%d\n", myNumbers[i]);
}

// Declare size explicitly, then fill element by element
int arr[4];
arr[0] = 25;
arr[1] = 50;
arr[2] = 75;
arr[3] = 100;
1.12 — Enumeration (enum)
Expand →

Named Integer Constants

An enum (enumeration) lets you define a set of named integer constants. By default the first name is 0, but you can assign custom starting values — subsequent names increment from the last assigned value. Enums make code more readable than using raw magic numbers.

1Defining and using enums
C
// Define enum — Mon=1, Tues=2, Wed=3 ... (auto-increments)
enum week { Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun };

// Declare enum variables separately
enum week a, b, c;

// Assign values from the enum list
enum week a = Mon, b = Wed, c = Sat;
2Enum with scanf and switch
C — practical enum usage
enum week {Mon = 1, Tues, Wed, Thurs} day;

scanf("%d", &day);

switch(day) {
  case Mon:   puts("Monday");    break;
  case Tues:  puts("Tuesday");   break;
  case Wed:   puts("Wednesday"); break;
  case Thurs: puts("Thursday");  break;
  default:    puts("Error!");
}
1.13 — User Input (scanf)
Expand →

Reading from the Keyboard

scanf() reads formatted input from stdin (keyboard). The format string uses the same specifiers as printf(). Pass variables by address using & — this gives scanf a pointer to where to store the value. For strings, the array name is already a pointer so & is optional for char arrays, though many examples include it.

C
// Read an integer from the user
int myNum;
printf("Please enter a number: \n");
scanf("%d", &myNum);       // & gives scanf the address of myNum
printf("You entered: %d", myNum);

// Read a string (stops at whitespace)
char firstName[30];
printf("Enter your name: \n");
scanf("%s", &firstName);
printf("Hello %s.", firstName);
1.14 — Memory Addresses (&)
Expand →

Where Variables Live in Memory

Every variable is stored at a specific location in RAM. The address-of operator & retrieves that memory address as a hexadecimal number. Print addresses with %p. Understanding addresses is the foundation of pointer programming.

C
int myAge = 43;

printf("%p", &myAge);
// Output: 0x7ffe5367e044  (the actual address — varies per run)

// %p is the format specifier for printing pointer/address values
1.15 — Pointers
Expand →

Variables That Hold Addresses

A pointer is a variable whose value is a memory address. Declare with type *name. Assign the address of another variable using &. Print the address stored in the pointer with %p. A pointer pointing to the same address as &myAge will print the same value.

C
int myAge = 43;             // a normal int variable
printf("%d", myAge);        // => 43 (the value)
printf("%p", &myAge);       // => 0x7ffe... (the address)

// Declare a pointer — it holds the ADDRESS of myAge
int *ptr = &myAge;

printf("%p\n", ptr);        // => same address as &myAge
printf("%p\n", &myAge);     // => same address
1.16 — Dereferencing Pointers (*ptr)
Expand →

Reading the Value at an Address

Dereferencing means following a pointer to access the value it points to. Use the * operator on a pointer to get or set the value at that address. This is distinct from the * used in declaration.

C
int myAge = 43;
int *ptr  = &myAge;   // ptr holds the address of myAge

// Reference: print the address stored in ptr
printf("%p\n", ptr);   // => 0x7ffe5367e044

// Dereference: follow the pointer to get the VALUE at that address
printf("%d\n", *ptr);  // => 43  (*ptr is the value myAge holds)
Tip

Think of ptr as a street address, and *ptr as what is inside that house. The address (ptr) tells you where to go; the dereference (*ptr) shows you what is there.

1.17 — Pointer Summary (Reference vs Dereference)
Expand →

Pointer Operators at a Glance

Two operators are central to pointers: & (address-of / reference) gives the memory address of a variable. * (dereference) reads or writes the value at the address stored in a pointer. Both operators appear in different contexts — learn to distinguish declaration from usage.

C — complete pointer example
int myAge = 43;

// DECLARATION: int*ptr declares ptr as a pointer-to-int
int *ptr = &myAge;

printf("%d\n",  myAge);   // => 43        (value of myAge directly)
printf("%p\n", &myAge);   // => 0x7ffe..  (address of myAge)
printf("%p\n",  ptr);     // => 0x7ffe..  (ptr stores same address)
printf("%d\n", *ptr);     // => 43        (dereference: value at address)
02
Module 2 — Operators
Arithmetic, assignment, comparison, logical, bitwise
0% · 0/4 done
2.1 — Arithmetic Operators
Expand →

Math Operations

C supports all standard math operations. % is the modulo (remainder) operator. ++ and -- increment or decrement a value by 1. Used as a prefix (++x) the change happens before the expression is evaluated; as a postfix (x++) the original value is used first, then incremented.

C
int myNum = 100 + 50;            // 150
int sum1  = 100 + 50;            // 150
int sum2  = sum1 + 250;          // 400
int sum3  = sum2 + sum2;         // 800
x + yAdd
x - ySubtract
x * yMultiply
x / yDivide
x % yModulo (remainder)
++xIncrement by 1
--xDecrement by 1
2.2 — Assignment & Comparison Operators
Expand →

Assigning and Comparing Values

Assignment operators combine an operation with assignment — x += 3 means x = x + 3. They are shorthand for modifying a variable in place. Comparison operators compare two values and return 1 (true) or 0 (false) in C.

1Assignment operators
x = 5Assign value 5
x += 3x = x + 3
x -= 3x = x - 3
x *= 3x = x * 3
x /= 3x = x / 3
x %= 3x = x % 3
x &= 3x = x & 3
x |= 3x = x | 3
x ^= 3x = x ^ 3
x >>= 3x = x >> 3
x <<= 3x = x << 3
2Comparison operators (return 1 or 0)
C
int x = 5, y = 3;
printf("%d", x > y);   // => 1 (true, 5 > 3)
x == yEqual to
x != yNot equal to
x > yGreater than
x < yLess than
x >= yGreater than or equal
x <= yLess than or equal
2.3 — Logical Operators (&&, ||, !)
Expand →

Combining Boolean Conditions

Logical operators combine two or more conditions into a single boolean result. && (AND): both must be true. || (OR): at least one must be true. ! (NOT): inverts the result. They short-circuit — && stops early if the first is false; || stops early if the first is true.

&&AND — both conditions true
||OR — one condition true
!NOT — inverts result
C — examples
int x = 5;
x < 5 && x < 10    // false && true  = 0 (false)
x < 5 || x < 10    // false || true  = 1 (true)
!(x < 5 && x < 10) // !(false)        = 1 (true)
2.4 — Bitwise Operators
Expand →

Operating on Individual Bits

Bitwise operators work directly on the binary representation of integers, bit by bit. They are extremely fast and commonly used in systems programming, embedded development, and flag manipulation. Understanding binary representation is key to using them correctly.

C — bitwise example (a=60, b=13)
unsigned int a = 60; /* 60  = 0011 1100 */
unsigned int b = 13; /* 13  = 0000 1101 */
int c = 0;

c = a & b;   /* 12  = 0000 1100 — AND */
c = a | b;   /* 61  = 0011 1101 — OR  */
c = a ^ b;   /* 49  = 0011 0001 — XOR */
c = ~a;      /* -61 = 1100 0011 — NOT (one's complement) */
c = a << 2; /* 240 = 1111 0000 — left shift (×4) */
c = a >> 2; /* 15  = 0000 1111 — right shift (÷4) */
OperatorNameEffect
&AND1 only where both bits are 1
|OR1 where either bit is 1
^XOR1 where bits differ
~NOTFlips all bits (one's complement)
<<Left shiftShift bits left (multiply by 2 per shift)
>>Right shiftShift bits right (divide by 2 per shift)
03
Module 3 — Data Types
Primitive types, sizes, ranges, format specifiers reference
0% · 0/4 done
3.1 — Basic Data Types & Sizes
Expand →

C's Type System

C is a statically typed language — every variable must have its type declared. The sizes and ranges below are the standard minimum values; actual sizes may vary by platform and compiler. Use sizeof(type) to get the exact size on your system.

TypeSizeRange
char1 byte-128 to 127
signed char1 byte-128 to 127
unsigned char1 byte0 to 255
int2–4 bytes-32,768 to 32,767
signed int2 bytes-32,768 to 32,767
unsigned int2 bytes0 to 65,535
short int2 bytes-32,768 to 32,767
unsigned short int2 bytes0 to 65,535
long int4 bytes-2,147,483,648 to 2,147,483,647
signed long int4 bytes-2,147,483,648 to 2,147,483,647
unsigned long int4 bytes0 to 4,294,967,295
float4 bytes3.4E-38 to 3.4E+38
double8 bytes1.7E-308 to 1.7E+308
long double10 bytes3.4E-4932 to 1.1E+4932
3.2 — Declaring Variables of Each Type
Expand →

Using Types in Practice

Each type is suited for different data. Use int for whole numbers, float for single-precision decimals, double for higher-precision decimals, and char for single characters. Always use the correct format specifier when printing with printf().

C
int    myNum       = 5;         // integer
float  myFloatNum  = 5.99;      // floating point number
char   myLetter    = 'D';       // single character
double myDouble    = 3.2325467; // high precision floating point

printf("%d\n",  myNum);        // %d for int
printf("%f\n",  myFloatNum);   // %f for float
printf("%c\n",  myLetter);     // %c for char
printf("%lf\n", myDouble);     // %lf for double
3.3 — Format Specifiers Reference
Expand →

printf / scanf Format Codes

Format specifiers tell printf() and scanf() how to interpret each argument. Using the wrong specifier is a common source of bugs and undefined behavior in C.

SpecifierTypeNotes
%d or %iintSigned decimal integer
%ffloatSingle-precision decimal
%lfdoubleHigh precision floating point
%ccharSingle character
%schar[]String (char array)
%ppointerMemory address in hex
%uunsigned intUnsigned decimal
%ldlong intLong decimal
Baseshortintlong
Octal%ho%o%lo
Decimal%hd%d%ld
Hex (lower)%hx%x%lx
Hex (upper)%hX%X%lX
3.4 — Type Keywords Summary
Expand →

C Type Keywords at a Glance

A quick reference for all the basic type keywords available in standard C, with their common use cases.

KeywordMeaning
charCharacter type — single ASCII character
shortShort integer (2 bytes)
intInteger — most common whole number type
longLong integer (4 bytes or more)
floatSingle-precision floating-point (~7 digits)
doubleDouble-precision floating-point (~15 digits)
voidNo type — used for functions that return nothing
04
Module 4 — C Preprocessor
Directives, predefined macros, continuation, stringification, token-paste, parameterized macros
0% · 0/8 done
4.1 — Preprocessor Directives Overview
Expand →

Before Compilation

The C preprocessor runs before the compiler and performs text substitutions, file inclusions, and conditional compilation. All preprocessor directives start with #. They are not C statements — no semicolon needed.

DirectivePurpose
#defineDefine a macro or constant substitution
#includeInclude a source code or header file
#undefUndefine (remove) a macro
#ifdefTrue if the macro IS defined
#ifndefTrue if the macro is NOT defined
#ifCompile if given condition is true
#elseAlternative to #if
#elifElse-if for preprocessor conditionals
#endifEnd a conditional block
#errorEmit error message and stop compilation
#pragmaIssue special commands to the compiler
C — common directive usage
// Replace all MAX_ARRAY_LENGTH occurrences with 20
#define MAX_ARRAY_LENGTH 20

// Include standard library header (angle brackets = system path)
#include <stdio.h>

// Include your own header (quotes = current directory)
#include "myheader.h"

#undef FILE_SIZE
#define FILE_SIZE 42   // undefine then redefine
4.2 — Predefined Macros
Expand →

Built-in Compiler Macros

ANSI C defines a set of predefined macros that the compiler fills in automatically. They are read-only — you cannot redefine them. They are extremely useful for logging and debugging because they automatically embed file name, line number, date, and time into your output.

MacroValue
__DATE__Current date as "MMM DD YYYY"
__TIME__Current time as "HH:MM:SS"
__FILE__Current source filename (string)
__LINE__Current line number (decimal)
__STDC__1 when compiled per ANSI standard
C
#include <stdio.h>

int main() {
  printf("File :%s\n", __FILE__);
  printf("Date :%s\n", __DATE__);
  printf("Time :%s\n", __TIME__);
  printf("Line :%d\n", __LINE__);
  printf("ANSI :%d\n", __STDC__);
}
4.3 — Macro Continuation Operator (\)
Expand →

Multi-line Macros

Macros must normally fit on a single line. When a macro definition is too long, use the backslash \ continuation character at the end of each line (except the last) to span multiple lines. The backslash must be the very last character before the newline — no trailing spaces.

C
// Backslash \ continues the macro to the next line
#define message_for(a, b) \
    printf(#a " and " #b ": We love you!\n")
4.4 — String Constantization Operator (#)
Expand →

Turning a Token into a String

The # operator inside a macro converts its argument into a string literal by wrapping it in double quotes. This is called stringification. It happens at the preprocessor level, before compilation.

C
#include <stdio.h>

#define message_for(a, b) \
  printf(#a " and " #b ": We love you!\n")

int main(void) {
  message_for(Carole, Debra);

  return 0;
}
// Output: Carole and Debra: We love you!
4.5 — Token Paste Operator (##)
Expand →

Joining Two Tokens Together

The ## operator (token pasting / concatenation) merges two tokens into one during preprocessing. It is useful for generating variable names, function names, or identifiers dynamically from macro parameters.

C
#include <stdio.h>

// token34 is built by pasting "token" and the argument "34"
#define tokenpaster(n) printf("token" #n " = %d", token##n)

int main(void) {
  int token34 = 40;
  tokenpaster(34);   // expands to: printf("token34 = %d", token34)

  return 0;
}
4.6 — defined() Operator
Expand →

Checking if a Macro Exists

The defined() operator returns 1 if a macro name has been defined (even if defined as empty), 0 otherwise. Commonly used with #if !defined() to set a default value only when something has not been defined — this is the basis of include guards.

C
#include <stdio.h>

// If MESSAGE is not yet defined, define it with a default value
#if !defined (MESSAGE)
   #define MESSAGE "You wish!"
#endif

int main(void) {
  printf("Here is the message: %s\n", MESSAGE);
  return 0;
}
4.7 — Parameterized Macros (Function-like)
Expand →

Macros That Take Arguments

Macros can accept parameters like functions. The preprocessor performs simple text substitution — unlike functions, there is no type checking and no function call overhead. Always wrap arguments in parentheses inside the macro body to avoid operator precedence bugs.

C
// Regular function version
int square(int x) {
  return x * x;
}

// Macro version — no type, no call overhead, but no type checking
// Wrap args in () to prevent precedence bugs
#define square(x) ( (x) * (x) )

// No space between macro name and ( — space would break it!
4.8 — Macro with MAX Example
Expand →

Classic MAX Macro

A common real-world macro: finding the maximum of two values using the ternary operator. This avoids a function call and works for any comparable type. Always parenthesize arguments and the entire expression.

C
#include <stdio.h>

// MAX returns the larger of x and y — works for any type
#define MAX(x,y) ( (x) > (y) ? (x) : (y) )

int main(void) {
  printf("Max between 20 and 10 is %d\n", MAX(10, 20));
  // => Max between 20 and 10 is 20

  return 0;
}
05
Module 5 — Functions
Declaration, definition, parameters, return values, recursion, math.h
0% · 0/7 done
5.1 — Function Declaration, Definition & Calling
Expand →

Structure of a C Function

Every function has two parts: a declaration (prototype) that tells the compiler the function's name, return type, and parameter types — and a definition that contains the actual code. In C you must declare a function before you call it. The main() function is the program's entry point and is always defined.

C — declaration before use
// DECLARATION (prototype) — tell compiler this function exists
void myFunction();

// main — calls myFunction before it is defined below
int main() {
  myFunction();   // CALL the function
  return 0;
}

// DEFINITION — actual code for the function
void myFunction() {
  printf("Good evening!");
}
Note

If you define the function above main() in the file, the declaration (prototype) is optional — the compiler already sees the full definition first.

5.2 — Calling Functions Multiple Times
Expand →

Code Reuse

The main benefit of functions is reusability — define once, call many times. Every call executes the function body independently.

C
void myFunction() {
  printf("Good evening!\n");
}

int main() {
  myFunction();   // first call
  myFunction();   // second call — same output again

  return 0;
}
// Output:
// Good evening!
// Good evening!
5.3 — Function Parameters
Expand →

Passing Data Into Functions

Parameters are variables declared in the function signature that receive values from the caller (arguments). C passes arguments by value — the function gets a copy. Multiple parameters are separated by commas.

1Single parameter
C
void myFunction(char name[]) {
  printf("Hello %s\n", name);
}

int main() {
  myFunction("Liam");   // => Hello Liam
  myFunction("Jenny");  // => Hello Jenny
  return 0;
}
2Multiple parameters
C
void myFunction(char name[], int age) {
  printf("Hi %s, you are %d years old.\n", name, age);
}
int main() {
  myFunction("Liam",  3);   // => Hi Liam, you are 3 years old.
  myFunction("Jenny", 14);  // => Hi Jenny, you are 14 years old.
  return 0;
}
5.4 — Return Values
Expand →

Sending Data Back to the Caller

Functions can return a single value using the return statement. Declare the return type before the function name. Use void when the function does not return anything. The returned value can be used directly or stored in a variable.

C
// One parameter, returns int
int myFunction(int x) {
  return 5 + x;
}
int main() {
  printf("Result: %d", myFunction(3));  // => 8
  return 0;
}

// Two parameters
int add(int x, int y) {
  return x + y;
}
int main() {
  printf("Result: %d", add(5, 3));   // => 8
  int result = add(5, 3);
  printf("Result = %d", result);         // => 8
  return 0;
}
5.5 — Recursion
Expand →

Functions Calling Themselves

Recursion is when a function calls itself. Every recursive function needs a base case — a condition that stops the recursion — otherwise it would call itself forever (stack overflow). Great for problems with naturally recursive structure: factorial, Fibonacci, tree traversal.

C — sum of 1 to k
int sum(int k);

int main() {
  int result = sum(10);
  printf("%d", result);   // => 55  (1+2+...+10)
  return 0;
}

int sum(int k) {
  if (k > 0) {
    return k + sum(k - 1);  // recursive call with k-1
  } else {
    return 0;               // base case — stops recursion
  }
}
5.6 — Mathematical Functions (math.h)
Expand →

Built-in Math Library

Include <math.h> for access to common mathematical functions. When compiling, link with -lm flag: gcc program.c -o program -lm.

C
#include <math.h>

void main(void) {
  printf("%f", sqrt(16));   // => 4.000000  (square root)
  printf("%f", ceil(1.4));  // => 2.000000  (round up)
  printf("%f", floor(1.4)); // => 1.000000  (round down)
  printf("%f", pow(4, 3));   // => 64.000000 (4 to the power 3)
}
FunctionDescription
abs(x)Absolute value
acos(x)Arc cosine
asin(x)Arc sine
atan(x)Arc tangent
cbrt(x)Cube root
cos(x)Cosine
exp(x)e raised to power x
sin(x)Sine
tan(x)Tangent
5.7 — Function Anatomy Summary
Expand →

Two-Part Structure

A C function has a declaration and a definition. The declaration (prototype) ends with a semicolon. The definition contains the body in curly braces.

C — anatomy
// Declaration (prototype) — return_type name(param_types);
void myFunction();

// Definition — same signature + body in { }
void myFunction() {
  // code to execute (definition)
}
06
Module 6 — Structures
struct definition, members, strings, access, copy, modify
0% · 0/7 done
6.1 — Creating a Structure
Expand →

Grouping Related Data

A struct (structure) is a user-defined type that groups variables of different types under one name. Unlike arrays, members can have different types. Declare with the struct keyword followed by a tag name and member list inside braces. Always end with a semicolon.

C
// Declare a struct type called MyStructure
struct MyStructure {
  int  myNum;     // member: integer
  char myLetter;  // member: character
};                // MUST end with semicolon
6.2 — Creating Struct Variables & Accessing Members
Expand →

Instantiating and Using Structs

A struct definition is just a blueprint — you must create a variable of that struct type to actually use it. Access members with the dot operator .. You can also initialize all members at once with a brace initializer.

C
struct myStructure {
  int  myNum;
  char myLetter;
};

int main() {
  // Create variable s1, assign members individually
  struct myStructure s1;
  s1.myNum    = 13;
  s1.myLetter = 'B';

  // Create variable s2, initialize with brace initializer
  struct myStructure s2 = {13, 'B'};

  printf("My number: %d\n", s1.myNum);    // => 13
  printf("My letter: %c\n", s1.myLetter);  // => B
  return 0;
}
6.3 — Strings in Structures (strcpy)
Expand →

String Members Need strcpy

You cannot assign a string to a char array member using = after declaration — you must use strcpy() from <string.h>. You can only use the direct initializer {"text"} at declaration time.

C
#include <string.h>

struct myStructure {
  int  myNum;
  char myLetter;
  char myString[30];   // string member (char array)
};

int main() {
  struct myStructure s1;

  // Must use strcpy() to assign a string after declaration
  strcpy(s1.myString, "Some text");

  printf("my string: %s", s1.myString);
  return 0;
}
6.4 — Multiple Struct Variables
Expand →

Many Instances of One Type

You can create multiple variables from the same struct type, each with independent data. This is analogous to creating multiple objects from a class in OOP languages.

C
struct myStructure s1;
struct myStructure s2;

s1.myNum    = 13;
s1.myLetter = 'B';

s2.myNum    = 20;   // completely independent data
s2.myLetter = 'C';
6.5 — Copying Structs
Expand →

Struct Assignment Copies All Members

Assigning one struct variable to another with = performs a shallow copy — all member values are copied from source to destination. This is a full value copy, unlike in some higher-level languages.

C
struct myStructure s1 = {
  13, 'B', "Some text"
};

struct myStructure s2;
s2 = s1;   // s2 now has an exact copy of all s1 values
6.6 — Modifying Struct Members
Expand →

Changing Values After Creation

Individual struct members can be read or written at any time using the dot operator. Simply assign a new value to the specific member you want to change.

C
struct myStructure s1 = {13, 'B'};

// Modify individual members
s1.myNum    = 30;    // changed from 13 to 30
s1.myLetter = 'C';  // changed from B to C

printf("%d %c", s1.myNum, s1.myLetter);
// => 30 C
6.7 — Struct Summary & Best Practices
Expand →

Key Rules to Remember

A summary of everything to keep in mind when working with C structs.

RuleDetail
Semicolon after closing bracestruct Name { ... }; — required
Dot operator for accesss1.myNum — use . for struct variables
Arrow operator for pointersptr->myNum — use -> for struct pointers
String assignmentUse strcpy(), not =, for char array members
Brace init at declarationstruct S s = {val1, val2}; — works at declaration only
Assignment copiess2 = s1; copies all member values (shallow copy)
07
Module 7 — File Processing
fopen, fprintf, fscanf, fputc, fgetc, fputs, fgets, fseek, rewind, ftell
0% · 0/10 done
7.1 — File Functions & Open Modes Reference
Expand →

File I/O Overview

C provides a rich set of functions in <stdio.h> for file operations. All file operations use a FILE * pointer returned by fopen(). Always close files with fclose() when done to flush buffers and release the file handle.

FunctionPurpose
fopen()Open a new or existing file
fprintf()Write formatted data to a file
fscanf()Read formatted data from a file
fputc()Write a single character to a file
fgetc()Read a single character from a file
fputs()Write a string to a file
fgets()Read a string from a file
fclose()Close the file and flush buffers
fseek()Move file pointer to a given position
ftell()Returns the current file pointer position
rewind()Reset file pointer to the beginning
fputw()Write an integer to a file
fgetw()Read an integer from a file
ModeDescription
rOpen text file for reading
wOpen text file for writing (creates/overwrites)
aAppend to text file (creates if needed)
r+Open text file for reading and writing
w+Open text file for read/write (creates/overwrites)
a+Open text file for read/append
rbOpen binary file for reading
wbOpen binary file for writing
abOpen binary file for appending
rb+Open binary file for read/write
wb+Open binary file for read/write (creates/overwrites)
ab+Open binary file for read/append
7.2 — fopen() and fgetc() — Read a File
Expand →

Opening and Reading Character by Character

fopen() opens a file and returns a FILE * pointer. Pass this pointer to all subsequent file functions. fgetc() reads one character at a time. When it reaches the end of the file it returns the special constant EOF (End Of File). Always call fclose() after all operations.

C
#include <stdio.h>

void main() {
  FILE *fp;
  char ch;

  fp = fopen("file_handle.c", "r");  // open for reading

  while (1) {
    ch = fgetc(fp);       // read one character
    if (ch == EOF) break; // stop at end of file
    printf("%c", ch);
  }
  fclose(fp);   // always close after done
}
7.3 — fprintf() — Write to File
Expand →

Writing Formatted Text

fprintf() works exactly like printf() but writes to a file instead of stdout. The first argument is the FILE * pointer. Opening with mode "w" creates the file if it does not exist, or overwrites it if it does.

C
#include <stdio.h>

void main() {
  FILE *fp;
  fp = fopen("file.txt", "w");    // open for writing

  fprintf(fp, "Hello file for fprintf..\n");

  fclose(fp);
}
7.4 — fscanf() — Read Formatted Data
Expand →

Reading Word by Word

fscanf() reads formatted input from a file — like scanf() but from a file pointer. By default %s reads one whitespace-delimited word at a time. Returns EOF when the file ends.

C
#include <stdio.h>

void main() {
  FILE *fp;
  char buff[255];   // buffer to store each word

  fp = fopen("file.txt", "r");

  while(fscanf(fp, "%s", buff) != EOF) {
    printf("%s ", buff);  // print each word
  }
  fclose(fp);
}
7.5 — fputc() & fgetc() — Single Character I/O
Expand →

One Character at a Time

fputc(char, fp) writes a single character to the file. fgetc(fp) reads one character. Both are the lowest-level text file I/O functions in C.

1fputc — write a character
C
#include <stdio.h>

void main() {
  FILE *fp;
  fp = fopen("file1.txt", "w");
  fputc('a', fp);   // write single character 'a'
  fclose(fp);
}
2fgetc — read characters until EOF
C
#include <stdio.h>

void main() {
  FILE *fp;
  char c;

  fp = fopen("myfile.txt", "r");

  while( (c = fgetc(fp)) != EOF) {
    printf("%c", c);
  }
  fclose(fp);
}
7.6 — fputs() & fgets() — String I/O
Expand →

Reading and Writing Whole Strings

fputs(string, fp) writes a null-terminated string to the file. fgets(buffer, n, fp) reads at most n-1 characters into the buffer, stopping at a newline or EOF — safer than fscanf() for reading lines.

1fputs — write a string
C
#include <stdio.h>

void main() {
  FILE *fp;
  fp = fopen("myfile2.txt", "w");
  fputs("hello c programming", fp);
  fclose(fp);
}
2fgets — read a string (up to n chars)
C
#include <stdio.h>

void main() {
  FILE *fp;
  char text[300];

  fp = fopen("myfile2.txt", "r");
  printf("%s", fgets(text, 200, fp));
  // reads up to 199 chars from fp into text
  fclose(fp);
}
7.7 — fseek() — Seeking to a Position
Expand →

Moving the File Pointer

fseek(fp, offset, whence) moves the file position indicator. whence can be: SEEK_SET (from file start), SEEK_CUR (from current position), or SEEK_END (from file end). The offset is in bytes. After seeking, the next read or write starts from that position.

C
#include <stdio.h>

void main(void) {
  FILE *fp;

  fp = fopen("myfile.txt", "w+");
  fputs("This is Book", fp);

  // Move pointer to 7 bytes from the start
  fseek(fp, 7, SEEK_SET);

  // Write starting from position 7 (overwrites " Book")
  fputs("Kenny Wong", fp);

  fclose(fp);
}
7.8 — rewind() — Reset to Beginning
Expand →

Reading a File Twice

rewind(fp) moves the file pointer back to the beginning of the file — equivalent to fseek(fp, 0, SEEK_SET). Useful when you need to read through a file multiple times.

C
#include <stdio.h>

void main() {
  FILE *fp;
  char c;

  fp = fopen("file.txt", "r");

  // First pass — read entire file
  while( (c = fgetc(fp)) != EOF) { printf("%c", c); }

  rewind(fp);  // move pointer back to start

  // Second pass — read it again
  while( (c = fgetc(fp)) != EOF) { printf("%c", c); }

  fclose(fp);
}
// Output: Hello World! Hello World!
7.9 — ftell() — Get Current Position
Expand →

Measuring the File

ftell(fp) returns the current position of the file pointer as a long int (number of bytes from the start). A common trick for finding file size: seek to the end with fseek(fp, 0, SEEK_END) then call ftell().

C
#include <stdio.h>

void main() {
  FILE *fp;
  int length;

  fp = fopen("file.txt", "r");

  fseek(fp, 0, SEEK_END);   // seek to end of file
  length = ftell(fp);         // position at end = file size

  fclose(fp);

  printf("File size: %d bytes", length);
}
// => File size: 18 bytes
7.10 — File I/O Best Practices & Summary
Expand →

Safe File Handling Rules

A summary of important rules to follow when doing file I/O in C to avoid bugs, data corruption, and resource leaks.

PracticeWhy
Always check if fopen() returns NULLFile may not exist or permissions denied
Always call fclose()Flushes write buffers; releases file handle
Use fgets() over fscanf() for linesPrevents buffer overflow; handles spaces
Check for EOF in read loopsPrevents reading past the end of file
Use binary mode (rb/wb) for binary dataAvoids newline translation on Windows
Use SEEK_SET, SEEK_CUR, SEEK_ENDPortable constants for fseek whence parameter
Tip

To check for a failed fopen(): if (fp == NULL) { printf("Error opening file\n"); return 1; } — always validate before reading or writing.