Welcome to C Programming
C is a powerful, efficient, and widely-used programming language that forms the foundation of many modern programming languages and operating systems. Developed by Dennis Ritchie at Bell Labs in the early 1970s, C provides low-level access to memory and hardware while maintaining portability across different platforms.
Known for its performance and simplicity, C is the language of choice for system programming, embedded systems, operating systems, and performance-critical applications. Many popular software like the Linux kernel, Windows operating system components, and database systems are written in C.
C strikes a perfect balance between high-level abstraction and low-level control, making it an excellent language for understanding how computers work at a fundamental level.
Introduction to C Programming
How to set up a C development environment and write your first "Hello, World!" program.
C is one of the most influential programming languages, known for its efficiency and close-to-hardware capabilities. Setting up a C development environment is straightforward and works on all major operating systems.
Step 1: Install a C Compiler
-
Windows
Install MinGW-w64 or Microsoft Visual Studio with C support. For MinGW, download from mingw-w64.org and add the bin directory to your PATH. -
macOS
Install Xcode from the App Store, which includes the Clang compiler. Alternatively, install Command Line Tools by runningxcode-select --installin Terminal. -
Linux
Use your distribution's package manager. For Ubuntu/Debian:sudo apt install gcc
Step 2: Verify Installation
Open a terminal or command prompt and type:
gcc --version
This should display the installed compiler version.
Step 3: Write and Compile Your First C Program
-
Create a file named
hello.cwith the following content:#include <stdio.h> int main() { printf("Hello, World!\n"); return 0; } -
Compile the program:
gcc hello.c -o hello -
Run the executable:
./hello # On Linux/macOS hello.exe # On WindowsYou should see the output:
Hello, World!
Congratulations! You have successfully set up a C environment and run your first program. 🎉
C Syntax Basics
C has a simple and consistent syntax that has influenced many other programming languages. Understanding the basic syntax is crucial for writing correct C programs.
1. Basic Structure of a C Program
Every C program has a specific structure:
#include <stdio.h> // Preprocessor directive for standard I/O
int main() { // Main function - program entry point
// Program statements go here
printf("Hello, C!\n");
return 0; // Return 0 to indicate successful execution
}
2. Semicolons and Braces
C uses semicolons to terminate statements and braces {} to define code blocks:
int x = 5; // Statement ends with semicolon
if (x > 3) { // Braces define the if block
printf("x is greater than 3\n");
} // Closing brace
3. Comments
C supports single-line and multi-line comments:
// This is a single-line comment (C99 and later)
/*
This is a multi-line comment
It can span multiple lines
*/
4. Case Sensitivity
C is case-sensitive, meaning it distinguishes between uppercase and lowercase letters:
int myVar = 5; // Different from myvar
int MyVar = 10; // Different from myVar
printf("%d\n", myVar); // Outputs: 5
printf("%d\n", MyVar); // Outputs: 10
5. Variables and Static Typing
C is statically typed, meaning you must declare the type of a variable explicitly:
int x = 10; // Integer
float y = 3.14; // Floating-point number
char z = 'A'; // Character
char name[] = "C"; // String (character array)
printf("Type of x: integer\n");
Conclusion
Understanding C syntax is essential for writing correct and efficient programs. Key takeaways include:
- C programs start execution from the
main()function - Statements end with semicolons
- Code blocks are defined with braces
{} - C is case-sensitive
- C uses static typing - variable types must be declared
Output with printf
The printf function in C is used to display formatted output on the screen. It is part of the stdio.h library and is one of the most commonly used functions for basic output and debugging.
1. Basic printf Usage
The simplest way to use printf is with a format string:
#include <stdio.h>
int main() {
printf("Hello, World!\n"); // Outputs: Hello, World!
printf("%d\n", 42); // Outputs: 42
return 0;
}
2. Format Specifiers
printf uses format specifiers to output different data types:
int age = 25;
float height = 5.9;
char grade = 'A';
char name[] = "John";
printf("Name: %s\n", name); // %s for strings
printf("Age: %d\n", age); // %d for integers
printf("Height: %.1f\n", height); // %f for floats, .1 for 1 decimal
printf("Grade: %c\n", grade); // %c for characters
3. Common Format Specifiers
| Specifier | Description |
|---|---|
%d |
Signed decimal integer |
%f |
Floating-point number |
%c |
Character |
%s |
String |
%p |
Pointer address |
%x |
Hexadecimal integer |
4. Formatting Options
double pi = 3.14159265359;
printf("Default: %f\n", pi); // 3.141593
printf("2 decimals: %.2f\n", pi); // 3.14
printf("10 width: %10.2f\n", pi); // " 3.14"
printf("Left align: %-10.2f\n", pi); // "3.14 "
5. Printing Multiple Values
char product[] = "Computer";
int quantity = 5;
float price = 999.99;
printf("Product: %s, Quantity: %d, Price: $%.2f\n",
product, quantity, price);
Conclusion
The printf function is a fundamental tool for output in C. Key points:
- Use format specifiers to output different data types
- Control decimal places with
.precision - Align output with width specifiers
- Always include
\nfor new lines
Arithmetic Operators in C
C provides standard arithmetic operators for mathematical calculations. These operators work with numeric data types like integers and floating-point numbers.
Basic Arithmetic Operators
#include <stdio.h>
int main() {
int a = 15, b = 4;
printf("a + b = %d\n", a + b); // Addition: 19
printf("a - b = %d\n", a - b); // Subtraction: 11
printf("a * b = %d\n", a * b); // Multiplication: 60
printf("a / b = %d\n", a / b); // Division: 3 (integer division)
printf("a %% b = %d\n", a % b); // Modulus: 3
float x = 15.0, y = 4.0;
printf("x / y = %.2f\n", x / y); // Division: 3.75 (float division)
return 0;
}
Increment and Decrement Operators
int count = 5;
printf("count = %d\n", count); // 5
printf("++count = %d\n", ++count); // 6 (pre-increment)
printf("count++ = %d\n", count++); // 6 (post-increment)
printf("count = %d\n", count); // 7
printf("--count = %d\n", --count); // 6 (pre-decrement)
Common Pitfalls
- Integer division truncates the fractional part:
5 / 2equals2, not2.5 - Modulus operator
%only works with integer types - Division by zero causes undefined behavior
- Operator precedence matters:
a + b * cisa + (b * c)
Comparison Operators in C
Comparison operators compare two values and return an integer result (1 for true, 0 for false). These are essential for conditional statements and loops.
Comparison Operators
#include <stdio.h>
int main() {
int x = 7, y = 10;
printf("x == y: %d\n", x == y); // Equal to: 0 (false)
printf("x != y: %d\n", x != y); // Not equal to: 1 (true)
printf("x > y: %d\n", x > y); // Greater than: 0
printf("x < y: %d\n", x < y); // Less than: 1
printf("x >= 7: %d\n", x >= 7); // Greater than or equal: 1
printf("y <= 7: %d\n", y <= 7); // Less than or equal: 0
return 0;
}
Using Comparisons in Conditions
int age = 18;
if (age >= 18) {
printf("You are an adult\n");
} else {
printf("You are a minor\n");
}
Common Pitfalls
- Confusing assignment
=with equality== - Comparing floating-point numbers for exact equality can be problematic due to precision issues
- In C, any non-zero value is considered true in boolean contexts
Logical Operators in C
Logical operators combine boolean expressions and return 1 (true) or 0 (false). C provides three logical operators: && (AND), || (OR), and ! (NOT).
Logical Operators
#include <stdio.h>
int main() {
int isSunny = 1; // true
int isWarm = 0; // false
printf("isSunny && isWarm: %d\n", isSunny && isWarm); // AND: 0
printf("isSunny || isWarm: %d\n", isSunny || isWarm); // OR: 1
printf("!isWarm: %d\n", !isWarm); // NOT: 1
// Complex conditions
int age = 25;
int hasLicense = 1;
if (age >= 18 && hasLicense) {
printf("You can drive\n");
}
return 0;
}
Short-Circuit Evaluation
// In AND (&&), if first condition is false, second isn't evaluated
// In OR (||), if first condition is true, second isn't evaluated
int x = 5;
if (x != 0 && 10/x > 1) { // Safe because x != 0 is checked first
printf("Condition satisfied\n");
}
Common Pitfalls
- Using bitwise operators (
&,|) instead of logical operators (&&,||) - Forgetting that logical operators have lower precedence than comparison operators
- Using
=instead of==in logical expressions
Bitwise Operators in C
Bitwise operators perform operations on individual bits of integer types. They are used in low-level programming, embedded systems, and performance optimization.
Bitwise Operators
#include <stdio.h>
int main() {
unsigned int a = 6; // binary: 0110
unsigned int b = 3; // binary: 0011
printf("a & b = %d\n", a & b); // AND: 0010 (2)
printf("a | b = %d\n", a | b); // OR: 0111 (7)
printf("a ^ b = %d\n", a ^ b); // XOR: 0101 (5)
printf("~a = %u\n", ~a); // NOT: 11111111111111111111111111111001
printf("a << 1 = %d\n", a << 1); // Left shift: 1100 (12)
printf("b >> 1 = %d\n", b >> 1); // Right shift: 0001 (1)
return 0;
}
Practical Applications
// Setting flags
#define FLAG_A (1 << 0) // 0001
#define FLAG_B (1 << 1) // 0010
#define FLAG_C (1 << 2) // 0100
unsigned int settings = 0;
settings |= FLAG_A; // Set flag A
settings |= FLAG_C; // Set flag C
// Check if flag B is set
if (settings & FLAG_B) {
printf("Flag B is set\n");
} else {
printf("Flag B is not set\n");
}
// Toggle a flag
settings ^= FLAG_A; // Toggle flag A
Common Pitfalls
- Confusing bitwise AND
&with logical AND&& - Right-shifting negative numbers has implementation-defined behavior
- Shifting beyond the bit width causes undefined behavior
- Forgetting that bitwise NOT
~flips all bits, not just the significant ones
Assignment Operators in C
Assignment operators assign values to variables. C provides compound assignment operators that combine assignment with arithmetic or bitwise operations.
Assignment Operators
#include <stdio.h>
int main() {
int x = 10; // Simple assignment
printf("x = %d\n", x);
x += 5; // Equivalent to x = x + 5
printf("x += 5: %d\n", x); // 15
x -= 3; // Equivalent to x = x - 3
printf("x -= 3: %d\n", x); // 12
x *= 2; // Equivalent to x = x * 2
printf("x *= 2: %d\n", x); // 24
x /= 4; // Equivalent to x = x / 4
printf("x /= 4: %d\n", x); // 6
x %= 4; // Equivalent to x = x % 4
printf("x %%= 4: %d\n", x); // 2
// Bitwise assignment operators
unsigned int y = 5;
y &= 3; // AND assignment: y = y & 3
y |= 8; // OR assignment: y = y | 8
y ^= 4; // XOR assignment: y = y ^ 4
return 0;
}
Multiple Assignment
int a, b, c;
a = b = c = 10; // All variables get value 10
// Compound assignment in loops
int sum = 0;
for (int i = 1; i <= 5; i++) {
sum += i; // Add i to sum
}
printf("Sum: %d\n", sum); // 15
Common Pitfalls
- Confusing
=(assignment) with==(equality comparison) - Assignment has right-to-left associativity:
a = b = cmeansa = (b = c) - Forgetting that assignment returns the assigned value
Integer Data Types in C
C provides several integer types with different sizes and ranges. Choosing the right integer type depends on the required range and memory constraints.
Basic Integer Types
#include <stdio.h>
#include <limits.h>
int main() {
// Signed integers (can represent negative numbers)
short s = 100; // Typically 2 bytes
int i = 100000; // Typically 4 bytes
long l = 100000L; // Typically 4 or 8 bytes
long long ll = 100000LL; // Typically 8 bytes
// Unsigned integers (only non-negative numbers)
unsigned short us = 100;
unsigned int ui = 100000;
unsigned long ul = 100000UL;
unsigned long long ull = 100000ULL;
printf("short range: %d to %d\n", SHRT_MIN, SHRT_MAX);
printf("int range: %d to %d\n", INT_MIN, INT_MAX);
printf("unsigned int range: 0 to %u\n", UINT_MAX);
return 0;
}
Integer Operations
int a = 10, b = 3;
printf("a + b = %d\n", a + b); // 13
printf("a - b = %d\n", a - b); // 7
printf("a * b = %d\n", a * b); // 30
printf("a / b = %d\n", a / b); // 3 (integer division)
printf("a %% b = %d\n", a % b); // 1 (modulus)
Type Conversion
// Implicit conversion
int num = 3.14; // num becomes 3 (truncation)
// Explicit conversion (C-style)
int x = (int)3.14;
// String to integer
#include <stdlib.h>
char str[] = "123";
int z = atoi(str);
Common Pitfalls
- Integer overflow when result exceeds type's maximum value
- Integer division truncates fractional parts
- Mixing signed and unsigned types can cause unexpected behavior
- Size of integer types can vary between platforms
Floating-Point Data Types in C
Floating-point types represent real numbers with fractional parts. C provides three floating-point types with different precision levels.
Floating-Point Types
#include <stdio.h>
#include <math.h>
int main() {
float f = 3.14159f; // Single precision (typically 4 bytes)
double d = 3.14159265358979; // Double precision (typically 8 bytes)
long double ld = 3.141592653589793238L; // Extended precision
printf("float: %.10f\n", f);
printf("double: %.10f\n", d);
printf("long double: %.10Lf\n", ld);
// Special values
printf("sqrt(-1): %f\n", sqrt(-1)); // NaN (Not a Number)
printf("1.0/0.0: %f\n", 1.0/0.0); // inf (Infinity)
printf("-1.0/0.0: %f\n", -1.0/0.0); // -inf
return 0;
}
Floating-Point Operations
double a = 2.5, b = 1.5;
printf("a + b = %.2f\n", a + b); // 4.0
printf("a - b = %.2f\n", a - b); // 1.0
printf("a * b = %.2f\n", a * b); // 3.75
printf("a / b = %.2f\n", a / b); // 1.67
// Mathematical functions
printf("sqrt(a) = %.2f\n", sqrt(a));
printf("pow(a, b) = %.2f\n", pow(a, b));
printf("sin(pi/2) = %.2f\n", sin(M_PI/2));
Type Conversion
// String to floating-point
#include <stdlib.h>
char str[] = "3.14159";
double pi = atof(str);
// Floating-point to integer (truncation)
double d = 3.99;
int i = (int)d; // i becomes 3
// Integer to floating-point
int x = 5;
double y = (double)x; // y becomes 5.0
Common Pitfalls
- Floating-point numbers have limited precision and rounding errors
- Comparing floating-point numbers for exact equality is often problematic
- Operations with very large or very small numbers can cause overflow/underflow
- Floating-point arithmetic is generally slower than integer arithmetic
Strings in C
In C, strings are arrays of characters terminated by a null character (\0). C provides many functions in the string.h library for working with strings.
Creating and Using Strings
#include <stdio.h>
#include <string.h>
int main() {
// Different ways to create strings
char s1[] = "Hello";
char s2[6] = {'W', 'o', 'r', 'l', 'd', '\0'};
char s3[10];
strcpy(s3, "C Programming"); // Copy string
printf("s1: %s\n", s1);
printf("s2: %s\n", s2);
printf("s3: %s\n", s3);
// Accessing characters
printf("First character: %c\n", s1[0]); // 'H'
printf("Second character: %c\n", s1[1]); // 'e'
// String length
printf("Length of s1: %zu\n", strlen(s1));
return 0;
}
String Comparison and Searching
char str1[] = "apple", str2[] = "banana";
// Comparison
if (strcmp(str1, str2) == 0) {
printf("Strings are equal\n");
} else if (strcmp(str1, str2) < 0) {
printf("%s comes before %s\n", str1, str2);
} else {
printf("%s comes after %s\n", str1, str2);
}
// Searching
char text[] = "Hello World";
char *pos = strstr(text, "World");
if (pos != NULL) {
printf("Found 'World' at position %ld\n", pos - text);
}
String Modification
char str[20] = "Hello";
// Concatenation
strcat(str, " World");
printf("After concatenation: %s\n", str);
// String copying with length limit
char dest[10];
strncpy(dest, "This is a long string", sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0'; // Ensure null termination
printf("Safe copy: %s\n", dest);
Common Pitfalls
- Forgetting the null terminator
\0 - Buffer overflow when copying strings without bounds checking
- Not allocating enough space for string operations
- Modifying string literals (which are read-only)
String Functions in C
The C standard library provides many useful functions for string manipulation in the string.h header, including copying, concatenation, comparison, and searching.
Common String Functions
#include <stdio.h>
#include <string.h>
int main() {
char str1[20] = "Hello";
char str2[20] = "World";
char result[50];
// String length
printf("Length of str1: %zu\n", strlen(str1));
// String copy
strcpy(result, str1);
printf("After strcpy: %s\n", result);
// String concatenation
strcat(result, " ");
strcat(result, str2);
printf("After strcat: %s\n", result);
// String comparison
int cmp = strcmp(str1, str2);
if (cmp == 0) {
printf("Strings are equal\n");
} else if (cmp < 0) {
printf("str1 comes before str2\n");
} else {
printf("str1 comes after str2\n");
}
// String search
char text[] = "The quick brown fox";
char *found = strstr(text, "brown");
if (found) {
printf("Found 'brown' at position %ld\n", found - text);
}
return 0;
}
Safe String Functions
// Use strncpy, strncat for bounds checking
char dest[10];
char src[] = "This is too long";
// Safe copy with length limit
strncpy(dest, src, sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0'; // Ensure null termination
printf("Safe copy: %s\n", dest);
// Safe concatenation
char buffer[20] = "Hello";
strncat(buffer, " World!!!", sizeof(buffer) - strlen(buffer) - 1);
printf("Safe concatenation: %s\n", buffer);
Character Classification
#include <ctype.h>
char str[] = "Hello123";
int i = 0;
while (str[i]) {
if (isalpha(str[i])) {
printf("%c is a letter\n", str[i]);
} else if (isdigit(str[i])) {
printf("%c is a digit\n", str[i]);
} else if (isspace(str[i])) {
printf("%c is whitespace\n", str[i]);
}
i++;
}
// Character conversion
char lower[] = "HELLO";
i = 0;
while (lower[i]) {
lower[i] = tolower(lower[i]);
i++;
}
printf("Lowercase: %s\n", lower);
Common Pitfalls
- Forgetting to check return values of string functions
- Not ensuring null termination after using strncpy
- Buffer overflow from unsafe string operations
- Assuming string functions work with uninitialized memory
String Formatting in C
C provides powerful string formatting capabilities through functions like printf, sprintf, and snprintf. These functions use format specifiers to control how data is formatted.
Using sprintf and snprintf
#include <stdio.h>
int main() {
char name[] = "Alice";
int age = 25;
double score = 95.5;
char buffer[100];
// Using sprintf (be careful of buffer overflow)
sprintf(buffer, "Name: %s, Age: %d, Score: %.1f", name, age, score);
printf("%s\n", buffer);
// Using snprintf (safer - specifies buffer size)
snprintf(buffer, sizeof(buffer),
"Name: %s, Age: %d, Score: %.1f", name, age, score);
printf("%s\n", buffer);
return 0;
}
Common Format Specifiers
| Specifier | Description | Example |
|---|---|---|
%d |
Signed decimal integer | printf("%d", 42) → "42" |
%f |
Floating-point number | printf("%.2f", 3.14159) → "3.14" |
%c |
Character | printf("%c", 'A') → "A" |
%s |
String | printf("%s", "hello") → "hello" |
%x |
Hexadecimal integer | printf("%x", 255) → "ff" |
%p |
Pointer address | printf("%p", &var) |
Formatting Options
int number = 123;
double value = 3.14159265359;
printf("Default integer: %d\n", number);
printf("8 width: %8d\n", number); // " 123"
printf("8 width left: %-8d\n", number); // "123 "
printf("With zeros: %08d\n", number); // "00000123"
printf("Default float: %f\n", value);
printf("2 decimals: %.2f\n", value); // "3.14"
printf("10 width, 3 decimals: %10.3f\n", value); // " 3.142"
Reading Formatted Input
#include <stdio.h>
int main() {
char name[50];
int age;
double salary;
printf("Enter name, age, and salary: ");
scanf("%s %d %lf", name, &age, &salary);
printf("Name: %s, Age: %d, Salary: $%.2f\n", name, age, salary);
// Reading line with spaces
char line[100];
printf("Enter a line: ");
getchar(); // Consume newline from previous input
fgets(line, sizeof(line), stdin);
printf("You entered: %s", line);
return 0;
}
Common Pitfalls
- Buffer overflow with
sprintf- always usesnprintf - Format specifiers not matching variable types
- Forgetting
&with variables inscanf - Not checking return value of
scanf
Arrays in C
Arrays are collections of elements of the same type stored in contiguous memory. C supports both fixed-size arrays and dynamic arrays using pointers.
Fixed-Size Arrays
#include <stdio.h>
int main() {
// Declaration and initialization
int numbers[5] = {1, 2, 3, 4, 5};
double prices[] = {1.99, 2.99, 3.49}; // Size inferred
// Accessing elements
printf("First element: %d\n", numbers[0]);
printf("Last element: %d\n", numbers[4]);
// Modifying elements
numbers[2] = 100;
// Array size
int size = sizeof(numbers) / sizeof(numbers[0]);
printf("Array size: %d\n", size);
// Iterating through array
for (int i = 0; i < size; i++) {
printf("numbers[%d] = %d\n", i, numbers[i]);
}
return 0;
}
Multi-dimensional Arrays
// 2D array (matrix)
int matrix[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
// Accessing elements
printf("Element at [1][2]: %d\n", matrix[1][2]); // 6
// Nested loops for 2D array
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", matrix[i][j]);
}
printf("\n");
}
Array Limitations
// Arrays have fixed size
// Cannot return arrays from functions directly
// Arrays decay to pointers when passed to functions
int arr[5] = {1, 2, 3, 4, 5};
// arr[10] = 100; // Undefined behavior - no bounds checking!
Common Pitfalls
- Array indices start at 0, not 1
- No bounds checking - accessing out-of-bounds causes undefined behavior
- Arrays decay to pointers when passed to functions
- Cannot return arrays from functions directly
Array Indexing in C
Array indexing allows access to individual elements in an array using their position. C uses zero-based indexing, meaning the first element is at index 0.
Basic Array Indexing
#include <stdio.h>
int main() {
int numbers[5] = {10, 20, 30, 40, 50};
// Accessing elements by index
printf("numbers[0] = %d\n", numbers[0]); // First element: 10
printf("numbers[2] = %d\n", numbers[2]); // Third element: 30
printf("numbers[4] = %d\n", numbers[4]); // Last element: 50
// Modifying elements
numbers[1] = 25; // Change second element from 20 to 25
numbers[3] = 45; // Change fourth element from 40 to 45
// Display modified array
for (int i = 0; i < 5; i++) {
printf("numbers[%d] = %d\n", i, numbers[i]);
}
return 0;
}
Multi-dimensional Array Indexing
// 2D array indexing
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
printf("matrix[0][1] = %d\n", matrix[0][1]); // 2
printf("matrix[1][2] = %d\n", matrix[1][2]); // 6
// 3D array indexing
int cube[2][2][2] = {
{{1, 2}, {3, 4}},
{{5, 6}, {7, 8}}
};
printf("cube[1][0][1] = %d\n", cube[1][0][1]); // 6
Character Array Indexing
char word[] = "Hello";
printf("word[0] = %c\n", word[0]); // 'H'
printf("word[4] = %c\n", word[4]); // 'o'
// Strings are null-terminated
printf("word[5] = %d\n", word[5]); // 0 (null character)
Common Pitfalls
- Accessing out-of-bounds indices causes undefined behavior
- Array indices start at 0, not 1
- Negative indices are not allowed in standard arrays
- No bounds checking by default
Array Functions in C
C provides several ways to work with arrays, including standard library functions and custom functions that operate on arrays.
Passing Arrays to Functions
#include <stdio.h>
// Function that takes an array and its size
void printArray(int arr[], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
// Function that modifies array elements
void doubleArray(int arr[], int size) {
for (int i = 0; i < size; i++) {
arr[i] *= 2;
}
}
int main() {
int numbers[] = {1, 2, 3, 4, 5};
int size = sizeof(numbers) / sizeof(numbers[0]);
printf("Original array: ");
printArray(numbers, size);
doubleArray(numbers, size);
printf("Doubled array: ");
printArray(numbers, size);
return 0;
}
Array Manipulation Functions
#include <stdio.h>
#include <string.h>
// Working with character arrays
void stringOperations() {
char str1[20] = "Hello";
char str2[20] = "World";
// Copy strings
strcpy(str1, "Hello"); // Copy "Hello" to str1
strcat(str1, " "); // Append space
strcat(str1, str2); // Append "World"
printf("Concatenated: %s\n", str1);
// Compare strings
if (strcmp(str1, str2) == 0) {
printf("Strings are equal\n");
} else {
printf("Strings are different\n");
}
// Get string length
printf("Length: %zu\n", strlen(str1));
}
// Array sorting (bubble sort)
void bubbleSort(int arr[], int n) {
for (int i = 0; i < n-1; i++) {
for (int j = 0; j < n-i-1; j++) {
if (arr[j] > arr[j+1]) {
// Swap elements
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
Array Algorithms
// Find maximum element
int findMax(int arr[], int n) {
int max = arr[0];
for (int i = 1; i < n; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
return max;
}
// Calculate sum of elements
int arraySum(int arr[], int n) {
int sum = 0;
for (int i = 0; i < n; i++) {
sum += arr[i];
}
return sum;
}
// Search for element
int linearSearch(int arr[], int n, int target) {
for (int i = 0; i < n; i++) {
if (arr[i] == target) {
return i; // Return index if found
}
}
return -1; // Return -1 if not found
}
Common Pitfalls
- C-style arrays don't know their own size - you must track it separately
- Arrays decay to pointers when passed to functions
- C-string functions don't check buffer sizes, leading to potential overflows
- Always ensure arrays are large enough for operations
Pointers in C
Pointers are variables that store memory addresses. They provide direct access to memory and are essential for dynamic memory allocation and efficient data structures.
Basic Pointer Operations
#include <stdio.h>
int main() {
int number = 42;
int* ptr = &number; // ptr stores address of number
printf("Value of number: %d\n", number);
printf("Address of number: %p\n", &number);
printf("Value of ptr: %p\n", ptr);
printf("Value at ptr: %d\n", *ptr); // Dereferencing
// Modifying through pointer
*ptr = 100;
printf("New value of number: %d\n", number);
// Pointer to different types
double pi = 3.14159;
double* dptr = π
printf("Value at dptr: %.2f\n", *dptr);
return 0;
}
Pointers and Arrays
int arr[] = {10, 20, 30, 40, 50};
int* ptr = arr; // Array name decays to pointer to first element
printf("First element: %d\n", *ptr); // 10
printf("Second element: %d\n", *(ptr + 1)); // 20
printf("Third element: %d\n", ptr[2]); // 30 (array syntax works)
// Pointer arithmetic
ptr++; // Move to next element
printf("After increment: %d\n", *ptr); // 20
ptr += 2; // Move two elements forward
printf("After +2: %d\n", *ptr); // 40
Null Pointers and Safety
// Different ways to create null pointers
int* ptr1 = NULL; // Preferred way
int* ptr2 = 0;
// Always check pointers before use
if (ptr1 != NULL) {
printf("%d\n", *ptr1);
} else {
printf("Pointer is null\n");
}
// Dangerous: dereferencing null pointer
// *ptr1 = 5; // CRASH! Undefined behavior
Common Pitfalls
- Dereferencing null or uninitialized pointers causes crashes
- Pointer arithmetic can easily go out of bounds
- Memory leaks if dynamically allocated memory isn't freed
- Dangling pointers after memory is freed
Pointer Arithmetic in C
Pointer arithmetic allows you to navigate through memory by adding or subtracting from pointers. This is particularly useful with arrays and dynamic memory.
Basic Pointer Arithmetic
#include <stdio.h>
int main() {
int arr[] = {10, 20, 30, 40, 50};
int* ptr = arr;
printf("Pointer at: %p\n", ptr);
printf("Value: %d\n", *ptr); // 10
ptr++; // Move to next int (4 bytes forward typically)
printf("After ++: %p\n", ptr);
printf("Value: %d\n", *ptr); // 20
ptr += 2; // Move two ints forward
printf("After += 2: %p\n", ptr);
printf("Value: %d\n", *ptr); // 40
ptr--; // Move one int backward
printf("After --: %p\n", ptr);
printf("Value: %d\n", *ptr); // 30
return 0;
}
Pointer Differences and Comparisons
int numbers[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int* start = numbers;
int* end = numbers + 10; // Pointer one past the end
// Calculate number of elements between pointers
int distance = end - start;
printf("Distance: %d elements\n", distance);
// Pointer comparisons
int* middle = numbers + 5;
if (start < middle) {
printf("start comes before middle\n");
}
if (middle < end) {
printf("middle comes before end\n");
}
// Iterate using pointers
for (int* p = start; p < end; p++) {
printf("%d ", *p);
}
printf("\n");
Pointer Arithmetic with Different Types
// Different types have different sizes
char chars[] = {'a', 'b', 'c', 'd', 'e'};
double doubles[] = {1.1, 2.2, 3.3, 4.4, 5.5};
char* cptr = chars;
double* dptr = doubles;
printf("char pointer: %p\n", cptr);
cptr++;
printf("after ++: %p\n", cptr); // Moves 1 byte
printf("double pointer: %p\n", dptr);
dptr++;
printf("after ++: %p\n", dptr); // Moves 8 bytes (typically)
Practical Applications
// Finding array length
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
int data[] = {1, 2, 3, 4, 5};
printf("Array size: %zu\n", ARRAY_SIZE(data));
// String length using pointers
const char* str = "Hello";
const char* p = str;
while (*p != '\0') {
p++;
}
printf("String length: %ld\n", (p - str));
Common Pitfalls
- Pointer arithmetic only valid within same array or one past the end
- Adding two pointers is not allowed
- Pointer arithmetic on void pointers is not allowed (unknown size)
- Going beyond array bounds causes undefined behavior
Structures in C
Structures (structs) allow you to group related variables of different types together. They are used to create custom data types that can represent real-world entities.
Basic Structure Definition
#include <stdio.h>
#include <string.h>
// Define a structure
struct Person {
char name[50];
int age;
float height;
};
int main() {
// Create structure variables
struct Person person1;
// Initialize structure members
strcpy(person1.name, "Alice");
person1.age = 25;
person1.height = 5.6;
// Access structure members
printf("Name: %s\n", person1.name);
printf("Age: %d\n", person1.age);
printf("Height: %.1f\n", person1.height);
// Initialize during declaration
struct Person person2 = {"Bob", 30, 5.9};
return 0;
}
Structures with Pointers
struct Point {
int x;
int y;
};
void printPoint(struct Point* p) {
printf("Point: (%d, %d)\n", p->x, p->y);
}
int main() {
struct Point p1 = {10, 20};
struct Point* ptr = &p1;
// Access members through pointer
printf("x: %d\n", ptr->x); // Arrow operator
printf("y: %d\n", ptr->y);
printPoint(&p1);
return 0;
}
Nested Structures
struct Date {
int day;
int month;
int year;
};
struct Employee {
char name[50];
int id;
struct Date hireDate;
float salary;
};
int main() {
struct Employee emp = {
"John Doe",
1001,
{15, 3, 2020},
75000.50
};
printf("Employee: %s\n", emp.name);
printf("Hire date: %d/%d/%d\n",
emp.hireDate.day, emp.hireDate.month, emp.hireDate.year);
return 0;
}
Typedef with Structures
// Use typedef to create an alias
typedef struct {
char title[100];
char author[50];
int year;
float price;
} Book;
int main() {
// Now we can use Book instead of struct Book
Book book1 = {"The C Programming Language", "K&R", 1978, 49.99};
printf("Title: %s\n", book1.title);
printf("Author: %s\n", book1.author);
printf("Year: %d\n", book1.year);
return 0;
}
Common Pitfalls
- Forgetting the
structkeyword when declaring variables - Not using
typedeffor cleaner syntax - Comparing structures directly (must compare member by member)
- Copying structures with pointers can lead to shallow copies
Unions in C
Unions are similar to structures but all members share the same memory location. Only one member can contain a value at any given time.
Basic Union Usage
#include <stdio.h>
union Data {
int i;
float f;
char str[20];
};
int main() {
union Data data;
data.i = 10;
printf("data.i: %d\n", data.i);
data.f = 3.14;
printf("data.f: %.2f\n", data.f);
// Now data.i is overwritten!
printf("data.i after setting f: %d\n", data.i); // Undefined behavior
return 0;
}
Practical Use Cases
// Union for representing different data types
typedef union {
int intValue;
float floatValue;
char stringValue[20];
} Variant;
typedef struct {
int type; // 0 = int, 1 = float, 2 = string
Variant value;
} TaggedUnion;
void printVariant(TaggedUnion* var) {
switch (var->type) {
case 0:
printf("Integer: %d\n", var->value.intValue);
break;
case 1:
printf("Float: %.2f\n", var->value.floatValue);
break;
case 2:
printf("String: %s\n", var->value.stringValue);
break;
}
}
int main() {
TaggedUnion var1 = {0, .value.intValue = 42};
TaggedUnion var2 = {1, .value.floatValue = 3.14};
TaggedUnion var3 = {2, .value.stringValue = "Hello"};
printVariant(&var1);
printVariant(&var2);
printVariant(&var3);
return 0;
}
Union vs Structure
struct S {
int a;
float b;
char c;
}; // Size = sizeof(int) + sizeof(float) + sizeof(char) + padding
union U {
int a;
float b;
char c;
}; // Size = size of largest member
printf("Size of struct: %zu\n", sizeof(struct S));
printf("Size of union: %zu\n", sizeof(union U));
Common Pitfalls
- Accessing the wrong member of a union leads to undefined behavior
- Forgetting which member was last set
- Unions don't track which member contains valid data
- Portability issues with different byte orders (endianness)
Variables in C
Variables are named storage locations in memory that hold data. C is a statically-typed language, meaning variable types must be declared explicitly.
Variable Declaration and Initialization
#include <stdio.h>
int main() {
// Declaration and initialization
int age = 25;
double price = 19.99;
char grade = 'A';
char name[] = "Alice";
// Multiple variables
int x = 5, y = 10, z = 15;
// Different initialization styles
int a = 10; // Copy initialization
int b = 20; // Another initialization
printf("a: %d, b: %d\n", a, b);
return 0;
}
Variable Scope
int globalVar = 100; // Global variable
void demoFunction() {
int localVar = 50; // Local variable
printf("Local: %d, Global: %d\n", localVar, globalVar);
{
int blockVar = 25; // Block scope
printf("Block: %d\n", blockVar);
}
// printf("%d\n", blockVar); // Error: blockVar not accessible here
}
int main() {
demoFunction();
// printf("%d\n", localVar); // Error: localVar not accessible here
return 0;
}
Constants and Type Modifiers
// Constants
const double PI = 3.14159;
const int MAX_SIZE = 100;
// PI = 3.14; // Error: const variable cannot be modified
// Type modifiers
unsigned int positiveOnly = 4000000000;
short smallNumber = 32767;
long bigNumber = 2147483647;
long long veryBigNumber = 9223372036854775807LL;
// Volatile (tells compiler variable may change unexpectedly)
volatile int hardwareRegister = 0;
// Register hint (suggests using CPU register)
register int counter = 0;
Variable Lifetime and Storage Classes
// Automatic (default) - created when block entered, destroyed when exited
void automaticDemo() {
int x = 10; // Automatic storage
}
// Static - persists for program lifetime
void staticDemo() {
static int count = 0; // Initialized only once
count++;
printf("Count: %d\n", count);
}
// External - accessible across files
extern int externalVar; // Defined in another file
int main() {
automaticDemo();
staticDemo(); // Count: 1
staticDemo(); // Count: 2
staticDemo(); // Count: 3
return 0;
}
Common Pitfalls
- Uninitialized variables contain garbage values
- Using variables outside their scope
- Name shadowing (local variables hiding global ones)
- Forgetting that
charcan be signed or unsigned depending on implementation
Conditional Statements: if, else if, else
Conditional statements allow your program to make decisions and execute different code blocks based on conditions.
Basic if Statement
#include <stdio.h>
int main() {
int number;
printf("Enter a number: ");
scanf("%d", &number);
// Simple if statement
if (number > 0) {
printf("The number is positive\n");
}
// if-else statement
if (number % 2 == 0) {
printf("The number is even\n");
} else {
printf("The number is odd\n");
}
return 0;
}
if-else if-else Chain
int score;
printf("Enter your score (0-100): ");
scanf("%d", &score);
if (score >= 90) {
printf("Grade: A\n");
} else if (score >= 80) {
printf("Grade: B\n");
} else if (score >= 70) {
printf("Grade: C\n");
} else if (score >= 60) {
printf("Grade: D\n");
} else {
printf("Grade: F\n");
}
Nested if Statements
int age;
int hasLicense;
printf("Enter your age: ");
scanf("%d", &age);
printf("Do you have a license? (1 for yes, 0 for no): ");
scanf("%d", &hasLicense);
if (age >= 18) {
if (hasLicense) {
printf("You can drive legally\n");
} else {
printf("You need to get a license first\n");
}
} else {
printf("You are too young to drive\n");
}
Conditional Operator (Ternary)
int a = 10, b = 20;
// Traditional if-else
int max;
if (a > b) {
max = a;
} else {
max = b;
}
// Using ternary operator
int max2 = (a > b) ? a : b;
printf("Maximum: %d\n", max2);
// Nested ternary (use sparingly!)
int x = 5, y = 10, z = 15;
int largest = (x > y) ? ((x > z) ? x : z) : ((y > z) ? y : z);
printf("Largest: %d\n", largest);
Common Pitfalls
- Using assignment
=instead of equality==in conditions - Forgetting braces
{}for multi-statement blocks - Dangling else problem (else matches with nearest if)
- Overusing nested ternary operators reduces readability
for Loop in C
The for loop is used when you know exactly how many times you want to iterate. It has three parts: initialization, condition, and increment.
Basic for Loop
#include <stdio.h>
int main() {
// Count from 1 to 5
for (int i = 1; i <= 5; i++) {
printf("Count: %d\n", i);
}
// Countdown from 10 to 1
for (int i = 10; i >= 1; i--) {
printf("%d ", i);
}
printf("\n");
// Iterate through array
int numbers[] = {2, 4, 6, 8, 10};
for (int i = 0; i < 5; i++) {
printf("%d ", numbers[i]);
}
printf("\n");
return 0;
}
Advanced for Loop Variations
// Multiple variables in initialization
for (int i = 0, j = 10; i < j; i++, j--) {
printf("i=%d, j=%d\n", i, j);
}
// Omitting parts (infinite loop with break)
int count = 0;
for (;;) {
printf("%d ", count);
count++;
if (count >= 5) break;
}
printf("\n");
// Complex condition
for (int i = 0; i < 100; i += 5) {
printf("%d ", i);
}
printf("\n");
Nested for Loops
// Multiplication table
for (int i = 1; i <= 5; i++) {
for (int j = 1; j <= 5; j++) {
printf("%d\t", i * j);
}
printf("\n");
}
// Pattern printing
for (int i = 1; i <= 5; i++) {
for (int j = 1; j <= i; j++) {
printf("*");
}
printf("\n");
}
Common Pitfalls
- Off-by-one errors (using
<=instead of<) - Modifying loop counter inside the loop body
- Infinite loops when condition never becomes false
- Variable scope issues with loop counter declaration
while Loop in C
The while loop repeats a block of code as long as a condition is true. It's useful when you don't know in advance how many iterations are needed.
Basic while Loop
#include <stdio.h>
int main() {
// Count from 1 to 5
int i = 1;
while (i <= 5) {
printf("Count: %d\n", i);
i++;
}
// User input validation
int number;
printf("Enter a positive number: ");
scanf("%d", &number);
while (number <= 0) {
printf("Invalid input. Enter a positive number: ");
scanf("%d", &number);
}
printf("Thank you! You entered: %d\n", number);
return 0;
}
do-while Loop
// do-while executes at least once
char choice;
do {
printf("Menu:\n");
printf("1. Option One\n");
printf("2. Option Two\n");
printf("q. Quit\n");
printf("Enter your choice: ");
scanf(" %c", &choice);
switch (choice) {
case '1': printf("Option One selected\n"); break;
case '2': printf("Option Two selected\n"); break;
case 'q': printf("Goodbye!\n"); break;
default: printf("Invalid choice\n");
}
} while (choice != 'q');
Practical while Loop Examples
// Reading until sentinel value
int value, sum = 0;
printf("Enter numbers (0 to stop): ");
scanf("%d", &value);
while (value != 0) {
sum += value;
scanf("%d", &value);
}
printf("Sum: %d\n", sum);
// File reading
#include <stdio.h>
FILE* file = fopen("data.txt", "r");
char line[100];
while (fgets(line, sizeof(line), file) != NULL) {
printf("%s", line);
}
fclose(file);
// Infinite loop with break
while (1) {
int num;
printf("Enter a number (negative to quit): ");
scanf("%d", &num);
if (num < 0) break;
printf("Square: %d\n", num * num);
}
Common Pitfalls
- Forgetting to update the loop control variable
- Infinite loops when condition never becomes false
- Using assignment
=instead of comparison==in condition - Off-by-one errors in loop conditions
Loop Control Statements
C provides three loop control statements: break, continue, and goto (use sparingly). These statements alter the normal flow of loops.
break Statement
#include <stdio.h>
int main() {
// break exits the loop immediately
for (int i = 1; i <= 10; i++) {
if (i == 5) {
break; // Exit loop when i reaches 5
}
printf("%d ", i);
}
printf("\n"); // Output: 1 2 3 4
// Search example
int numbers[] = {2, 4, 6, 8, 10, 12, 14};
int target = 8;
for (int i = 0; i < 7; i++) {
if (numbers[i] == target) {
printf("Found %d at position %d\n", target, i);
break; // Stop searching once found
}
}
return 0;
}
continue Statement
// continue skips the rest of current iteration
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) {
continue; // Skip even numbers
}
printf("%d ", i); // Only odd numbers printed
}
printf("\n"); // Output: 1 3 5 7 9
// Input validation with continue
int sum = 0;
for (int i = 0; i < 5; i++) {
int num;
printf("Enter positive number %d: ", i + 1);
scanf("%d", &num);
if (num <= 0) {
printf("Invalid number, skipping...\n");
i--; // Don't count this iteration
continue;
}
sum += num;
}
printf("Sum of positive numbers: %d\n", sum);
Nested Loops with Control Statements
// break only affects innermost loop
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
if (i * j > 4) {
printf("Breaking inner loop\n");
break; // Only breaks inner loop
}
printf("%d * %d = %d\n", i, j, i * j);
}
}
// Using labels with break (rarely needed)
outer: for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
if (i * j > 4) {
printf("Breaking both loops\n");
break outer; // Break both loops
}
printf("%d * %d = %d\n", i, j, i * j);
}
}
goto Statement (Use With Caution)
// goto can make code hard to read - use sparingly!
int attempts = 0;
retry:
if (attempts >= 3) {
printf("Too many attempts!\n");
return 1;
}
int number;
printf("Enter a positive number: ");
scanf("%d", &number);
if (number <= 0) {
printf("Invalid input. ");
attempts++;
goto retry;
}
printf("Valid number: %d\n", number);
Common Pitfalls
- Using
breakwhencontinueis more appropriate - Overusing
gotocreates "spaghetti code" breakonly exits the innermost loop- Placing code after
continuethat will never execute
Nested Loops in C
Nested loops are loops within loops. They are essential for working with multi-dimensional data, matrices, and complex patterns.
Basic Nested Loops
#include <stdio.h>
int main() {
// Simple nested loop
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
printf("(%d,%d) ", i, j);
}
printf("\n");
}
/* Output:
(1,1) (1,2) (1,3)
(2,1) (2,2) (2,3)
(3,1) (3,2) (3,3) */
// Multiplication table
printf("\nMultiplication Table:\n");
for (int i = 1; i <= 5; i++) {
for (int j = 1; j <= 5; j++) {
printf("%d\t", i * j);
}
printf("\n");
}
return 0;
}
Pattern Printing with Nested Loops
// Right triangle
int rows = 5;
for (int i = 1; i <= rows; i++) {
for (int j = 1; j <= i; j++) {
printf("*");
}
printf("\n");
}
// Inverted triangle
for (int i = rows; i >= 1; i--) {
for (int j = 1; j <= i; j++) {
printf("*");
}
printf("\n");
}
// Pyramid
for (int i = 1; i <= rows; i++) {
// Print spaces
for (int j = 1; j <= rows - i; j++) {
printf(" ");
}
// Print stars
for (int j = 1; j <= 2 * i - 1; j++) {
printf("*");
}
printf("\n");
}
Working with 2D Arrays
// Matrix operations
#define ROWS 3
#define COLS 3
int matrix[ROWS][COLS] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
// Print matrix
printf("Matrix:\n");
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
printf("%d ", matrix[i][j]);
}
printf("\n");
}
// Sum of all elements
int sum = 0;
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
sum += matrix[i][j];
}
}
printf("Sum of all elements: %d\n", sum);
// Transpose matrix
printf("Transpose:\n");
for (int i = 0; i < COLS; i++) {
for (int j = 0; j < ROWS; j++) {
printf("%d ", matrix[j][i]);
}
printf("\n");
}
Mixed Loop Types
// while inside for
for (int i = 1; i <= 3; i++) {
int j = 1;
while (j <= 3) {
printf("i=%d, j=%d\n", i, j);
j++;
}
}
// do-while inside for
for (int i = 1; i <= 2; i++) {
int j = 1;
do {
printf("i=%d, j=%d\n", i, j);
j++;
} while (j <= 2);
}
Common Pitfalls
- O(n²) time complexity can be inefficient for large data
- Using wrong loop variables in inner vs outer loops
- Forgetting to reset inner loop variables
- Excessive nesting reduces readability (try to keep to 2-3 levels)
Functions in C
Functions are reusable blocks of code that perform specific tasks. They help in organizing code, reducing duplication, and making programs more modular.
Function Definition and Declaration
#include <stdio.h>
#include <math.h>
// Function declaration (prototype)
double calculateArea(double radius);
// Function definition
double calculateArea(double radius) {
return M_PI * radius * radius;
}
// Function with multiple parameters
int addNumbers(int a, int b) {
return a + b;
}
// Function without parameters
void greet() {
printf("Hello, welcome to the program!\n");
}
// Function without return value (void)
void printMessage(char message[]) {
printf("Message: %s\n", message);
}
int main() {
greet();
double radius = 5.0;
double area = calculateArea(radius);
printf("Area of circle with radius %.1f is %.2f\n", radius, area);
int sum = addNumbers(10, 20);
printf("Sum: %d\n", sum);
printMessage("Functions make code organized!");
return 0;
}
Function Parameters
// Pass by value (default)
void incrementByValue(int x) {
x++; // Only modifies local copy
printf("Inside function: %d\n", x);
}
// Pass by pointer
void incrementByPointer(int *x) {
(*x)++; // Modifies original variable
printf("Inside function: %d\n", *x);
}
int main() {
int num = 5;
incrementByValue(num);
printf("After pass by value: %d\n", num); // Still 5
incrementByPointer(&num);
printf("After pass by pointer: %d\n", num); // Now 6
return 0;
}
Recursive Functions
// Factorial using recursion
unsigned long long factorial(int n) {
if (n <= 1) return 1; // Base case
return n * factorial(n - 1); // Recursive case
}
// Fibonacci sequence
int fibonacci(int n) {
if (n <= 1) return n; // Base cases
return fibonacci(n - 1) + fibonacci(n - 2);
}
int main() {
printf("Factorial of 5: %llu\n", factorial(5));
printf("Fibonacci sequence: ");
for (int i = 0; i < 10; i++) {
printf("%d ", fibonacci(i));
}
printf("\n");
return 0;
}
Function Pointers
// Function that takes a function pointer as parameter
void applyOperation(int a, int b, int (*operation)(int, int)) {
int result = operation(a, b);
printf("Result: %d\n", result);
}
// Operation functions
int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }
int main() {
int x = 10, y = 5;
// Using function pointers
applyOperation(x, y, add);
applyOperation(x, y, multiply);
// Array of function pointers
int (*operations[])(int, int) = {add, multiply};
printf("Using array: %d\n", operations[0](x, y));
return 0;
}
Common Pitfalls
- Forgetting return statement in non-void functions
- Function declaration mismatch with definition
- Infinite recursion without proper base case
- Not checking if function pointers are NULL before calling
Include Directives and Headers in C
Header files contain declarations that can be shared across multiple source files. Include directives tell the preprocessor to include the contents of header files.
Standard Library Headers
// Input/output operations
#include <stdio.h>
// String manipulation
#include <string.h>
// Mathematical functions
#include <math.h>
// Character classification
#include <ctype.h>
// Standard library functions
#include <stdlib.h>
// Time and date
#include <time.h>
// Error handling
#include <errno.h>
// Limits of basic types
#include <limits.h>
int main() {
// Examples using different headers
char text[] = "Hello World";
printf("String length: %zu\n", strlen(text));
printf("Square root of 16: %.2f\n", sqrt(16));
printf("Is 'A' uppercase? %d\n", isupper('A'));
return 0;
}
Creating and Using Custom Headers
// math_utils.h (header file)
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
// Function declarations
int add(int a, int b);
double calculateCircleArea(double radius);
int isPrime(int number);
#endif
// math_utils.c (implementation)
#include "math_utils.h"
#include <math.h>
int add(int a, int b) {
return a + b;
}
double calculateCircleArea(double radius) {
return M_PI * radius * radius;
}
int isPrime(int number) {
if (number <= 1) return 0;
for (int i = 2; i * i <= number; i++) {
if (number % i == 0) return 0;
}
return 1;
}
// main.c
#include <stdio.h>
#include "math_utils.h"
int main() {
printf("5 + 3 = %d\n", add(5, 3));
printf("Area of circle with radius 2: %.2f\n", calculateCircleArea(2));
printf("Is 17 prime? %s\n", isPrime(17) ? "Yes" : "No");
return 0;
}
Include Guards and Pragma Once
// Traditional include guards
#ifndef MY_HEADER_H
#define MY_HEADER_H
// Header content here
#endif
// Modern alternative (compiler-specific but widely supported)
#pragma once
// Header content here
/* Why use include guards?
Prevents multiple inclusion of the same header
Avoids redefinition errors
Improves compilation speed */
Common Header Organization
// Typical header structure:
// 1. Include guards
#ifndef MY_HEADER_H
#define MY_HEADER_H
// 2. System includes (angle brackets)
#include <stdio.h>
#include <stdlib.h>
// 3. Constant definitions
#define MAX_SIZE 100
#define PI 3.14159
// 4. Type definitions
typedef struct {
int x, y;
} Point;
// 5. Function declarations
void initialize(void);
int processData(int data);
void cleanup(void);
// 6. End include guard
#endif
Common Pitfalls
- Missing include guards causing redefinition errors
- Circular includes (A includes B, B includes A)
- Including implementation files (.c) instead of headers
- Forgetting to include necessary headers for used functionality
File Input/Output in C
C provides file stream functions for reading from and writing to files. The main functions use the FILE structure and are declared in stdio.h.
Basic File Operations
#include <stdio.h>
int main() {
// Writing to a file
FILE *outFile = fopen("example.txt", "w");
if (outFile != NULL) {
fprintf(outFile, "Hello, File!\n");
fprintf(outFile, "This is line 2\n");
fprintf(outFile, "Number: %d\n", 42);
fclose(outFile);
printf("File written successfully\n");
} else {
printf("Error opening file for writing\n");
}
// Reading from a file
FILE *inFile = fopen("example.txt", "r");
if (inFile != NULL) {
char line[100];
while (fgets(line, sizeof(line), inFile) != NULL) {
printf("%s", line);
}
fclose(inFile);
} else {
printf("Error opening file for reading\n");
}
return 0;
}
Different File Opening Modes
// Various file opening modes
FILE *file1 = fopen("output.txt", "w"); // Write (truncate)
FILE *file2 = fopen("output.txt", "a"); // Append
FILE *file3 = fopen("output.txt", "r"); // Read
FILE *file4 = fopen("data.bin", "wb"); // Write binary
FILE *file5 = fopen("data.txt", "r+"); // Read and write
// Example: Append to file
FILE *logFile = fopen("log.txt", "a");
if (logFile != NULL) {
fprintf(logFile, "New log entry\n");
fclose(logFile);
}
Reading Different Data Types
// Writing mixed data types
FILE *dataFile = fopen("data.txt", "w");
if (dataFile != NULL) {
fprintf(dataFile, "John Doe 25 85.5\n");
fprintf(dataFile, "Jane Smith 30 92.0\n");
fclose(dataFile);
}
// Reading mixed data types
FILE *inputFile = fopen("data.txt", "r");
if (inputFile != NULL) {
char firstName[20], lastName[20];
int age;
double score;
while (fscanf(inputFile, "%s %s %d %lf",
firstName, lastName, &age, &score) == 4) {
printf("Name: %s %s, Age: %d, Score: %.1f\n",
firstName, lastName, age, score);
}
fclose(inputFile);
}
// Reading character by character
FILE *charFile = fopen("example.txt", "r");
if (charFile != NULL) {
int ch;
while ((ch = fgetc(charFile)) != EOF) {
putchar(ch);
}
fclose(charFile);
}
Binary File Operations
#include <stdio.h>
struct Person {
char name[50];
int age;
double salary;
};
int main() {
// Writing binary data
struct Person p1 = {"Alice", 30, 50000.0};
struct Person p2 = {"Bob", 25, 45000.0};
FILE *binOut = fopen("people.dat", "wb");
if (binOut != NULL) {
fwrite(&p1, sizeof(struct Person), 1, binOut);
fwrite(&p2, sizeof(struct Person), 1, binOut);
fclose(binOut);
}
// Reading binary data
struct Person p;
FILE *binIn = fopen("people.dat", "rb");
if (binIn != NULL) {
while (fread(&p, sizeof(struct Person), 1, binIn) == 1) {
printf("Name: %s, Age: %d, Salary: %.2f\n",
p.name, p.age, p.salary);
}
fclose(binIn);
}
return 0;
}
File Position Operations
// Working with file positions
FILE *file = fopen("data.txt", "r+");
if (file != NULL) {
// Get current position
long pos = ftell(file);
printf("Current position: %ld\n", pos);
// Seek to beginning
fseek(file, 0, SEEK_SET);
// Seek to end
fseek(file, 0, SEEK_END);
long size = ftell(file);
printf("File size: %ld bytes\n", size);
// Seek to specific position
fseek(file, 10, SEEK_SET); // 10 bytes from beginning
fclose(file);
}
Common Pitfalls
- Forgetting to check if file opened successfully
- Not closing files (can cause resource leaks)
- Mixing text and binary modes
- Assuming file operations always succeed - always check for errors
Memory Management in C
C provides manual memory management through functions like malloc, calloc, realloc, and free. Proper memory management is crucial for avoiding leaks and crashes.
Dynamic Memory Allocation
#include <stdio.h>
#include <stdlib.h>
int main() {
// Allocate memory for an integer
int *ptr = (int*)malloc(sizeof(int));
if (ptr == NULL) {
printf("Memory allocation failed\n");
return 1;
}
*ptr = 42;
printf("Value: %d\n", *ptr);
// Free the memory
free(ptr);
ptr = NULL; // Good practice to avoid dangling pointers
return 0;
}
Array Allocation
#include <stdlib.h>
// Allocate array of integers
int size = 5;
int *arr = (int*)malloc(size * sizeof(int));
if (arr == NULL) {
printf("Memory allocation failed\n");
return 1;
}
// Initialize array
for (int i = 0; i < size; i++) {
arr[i] = i * 10;
}
// Use array
for (int i = 0; i < size; i++) {
printf("arr[%d] = %d\n", i, arr[i]);
}
// Free memory
free(arr);
arr = NULL;
calloc and realloc
// calloc - allocates and initializes to zero
int *zeros = (int*)calloc(5, sizeof(int));
if (zeros != NULL) {
for (int i = 0; i < 5; i++) {
printf("zeros[%d] = %d\n", i, zeros[i]); // All zeros
}
free(zeros);
}
// realloc - resize allocated memory
int *dynamicArr = (int*)malloc(3 * sizeof(int));
if (dynamicArr != NULL) {
dynamicArr[0] = 1;
dynamicArr[1] = 2;
dynamicArr[2] = 3;
// Resize to hold 5 elements
int *temp = (int*)realloc(dynamicArr, 5 * sizeof(int));
if (temp != NULL) {
dynamicArr = temp;
dynamicArr[3] = 4;
dynamicArr[4] = 5;
for (int i = 0; i < 5; i++) {
printf("%d ", dynamicArr[i]);
}
printf("\n");
}
free(dynamicArr);
}
Common Patterns
// Dynamic string
char *createString(const char *text) {
char *str = (char*)malloc(strlen(text) + 1); // +1 for null terminator
if (str != NULL) {
strcpy(str, text);
}
return str;
}
// Dynamic array of strings
char **createStringArray(int count, int maxLength) {
char **arr = (char**)malloc(count * sizeof(char*));
if (arr != NULL) {
for (int i = 0; i < count; i++) {
arr[i] = (char*)malloc(maxLength * sizeof(char));
if (arr[i] == NULL) {
// Cleanup on failure
for (int j = 0; j < i; j++) {
free(arr[j]);
}
free(arr);
return NULL;
}
}
}
return arr;
}
void freeStringArray(char **arr, int count) {
for (int i = 0; i < count; i++) {
free(arr[i]);
}
free(arr);
}
Common Pitfalls
- Memory leaks (forgetting to free allocated memory)
- Dangling pointers (using memory after it's freed)
- Buffer overflow (writing beyond allocated memory)
- Double free (freeing the same memory twice)
- Not checking if allocation succeeded
C Preprocessor
The C preprocessor processes source code before compilation. It handles directives like #include, #define, and conditional compilation.
Macro Definitions
#include <stdio.h>
// Simple macro
#define PI 3.14159
#define MAX(a, b) ((a) > (b) ? (a) : (b))
// Multi-line macro
#define SWAP(a, b) do { \
typeof(a) temp = a; \
a = b; \
b = temp; \
} while(0)
int main() {
double radius = 5.0;
double area = PI * radius * radius;
printf("Area: %.2f\n", area);
int x = 10, y = 20;
printf("Max: %d\n", MAX(x, y));
printf("Before swap: x=%d, y=%d\n", x, y);
SWAP(x, y);
printf("After swap: x=%d, y=%d\n", x, y);
return 0;
}
Conditional Compilation
#include <stdio.h>
#define DEBUG 1
#define VERSION 2
int main() {
// Conditional compilation based on macro value
#if DEBUG
printf("Debug mode enabled\n");
#endif
// Multiple conditions
#if VERSION == 1
printf("Running version 1\n");
#elif VERSION == 2
printf("Running version 2\n");
#else
printf("Unknown version\n");
#endif
// Check if macro is defined
#ifdef DEBUG
printf("DEBUG is defined\n");
#endif
#ifndef RELEASE
printf("RELEASE is not defined\n");
#endif
return 0;
}
Predefined Macros
#include <stdio.h>
int main() {
printf("File: %s\n", __FILE__);
printf("Line: %d\n", __LINE__);
printf("Date: %s\n", __DATE__);
printf("Time: %s\n", __TIME__);
#if defined(__STDC__)
printf("ANSI C compliant\n");
#endif
#if defined(__cplusplus)
printf("C++ compiler\n");
#else
printf("C compiler\n");
#endif
return 0;
}
Stringification and Token Pasting
#include <stdio.h>
// Stringification - convert parameter to string
#define STRINGIFY(x) #x
// Token pasting - combine tokens
#define PASTE(a, b) a##b
int main() {
int xy = 100;
printf("Stringified: %s\n", STRINGIFY(Hello World));
printf("Pasted variable: %d\n", PASTE(x, y)); // xy
// Practical example - creating function names
#define MAKE_FUNCTION(name) void name##_function() { \
printf("Called " #name "\n"); \
}
MAKE_FUNCTION(test)
test_function();
return 0;
}
Common Pitfalls
- Missing parentheses in macro definitions causing operator precedence issues
- Multiple evaluation of macro parameters
- Overusing macros when functions would be better
- Not protecting multi-statement macros with
do { ... } while(0)
Advanced C Concepts
Advanced C features provide powerful tools for writing efficient, system-level code. These include function pointers, variable arguments, and low-level operations.
Variable Arguments
#include <stdio.h>
#include <stdarg.h>
// Function with variable arguments
int sum(int count, ...) {
va_list args;
va_start(args, count);
int total = 0;
for (int i = 0; i < count; i++) {
total += va_arg(args, int);
}
va_end(args);
return total;
}
// Variable arguments with different types
void printValues(const char *types, ...) {
va_list args;
va_start(args, types);
while (*types) {
switch (*types) {
case 'i':
printf("int: %d\n", va_arg(args, int));
break;
case 'd':
printf("double: %.2f\n", va_arg(args, double));
break;
case 's':
printf("string: %s\n", va_arg(args, char*));
break;
}
types++;
}
va_end(args);
}
int main() {
printf("Sum: %d\n", sum(3, 10, 20, 30));
printf("Sum: %d\n", sum(5, 1, 2, 3, 4, 5));
printValues("ids", 42, 3.14, "hello");
return 0;
}
Function Pointers and Callbacks
#include <stdio.h>
#include <stdlib.h>
// Typedef for function pointer
typedef int (*CompareFunc)(int, int);
// Comparison functions
int ascending(int a, int b) { return a - b; }
int descending(int a, int b) { return b -; }
// Function that takes a callback
void sortArray(int arr[], int size, CompareFunc compare) {
for (int i = 0; i < size-1; i++) {
for (int j = 0; j < size-i-1; j++) {
if (compare(arr[j], arr[j+1]) > 0) {
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
int main() {
int numbers[] = {5, 2, 8, 1, 9};
int size = 5;
printf("Original: ");
for (int i = 0; i < size; i++) printf("%d ", numbers[i]);
printf("\n");
sortArray(numbers, size, ascending);
printf("Ascending: ");
for (int i = 0; i < size; i++) printf("%d ", numbers[i]);
printf("\n");
sortArray(numbers, size, descending);
printf("Descending: ");
for (int i = 0; i < size; i++) printf("%d ", numbers[i]);
printf("\n");
return 0;
}
Command Line Arguments
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
printf("Number of arguments: %d\n", argc);
for (int i = 0; i < argc; i++) {
printf("argv[%d]: %s\n", i, argv[i]);
}
// Example: simple calculator
if (argc == 4) {
double a = atof(argv[1]);
double b = atof(argv[3]);
char op = argv[2][0];
double result;
switch (op) {
case '+': result = a + b; break;
case '-': result = a - b; break;
case '*': result = a * b; break;
case '/': result = a / b; break;
default:
printf("Unknown operator: %c\n", op);
return 1;
}
printf("Result: %.2f\n", result);
}
return 0;
}
Bit Fields and Low-Level Operations
#include <stdio.h>
// Structure with bit fields
struct Settings {
unsigned int enabled : 1;
unsigned int mode : 2;
unsigned int reserved : 5;
};
// Low-level bit manipulation
void printBinary(unsigned int num) {
for (int i = 31; i >= 0; i--) {
printf("%d", (num >> i) & 1);
if (i % 8 == 0) printf(" ");
}
printf("\n");
}
int main() {
// Bit fields
struct Settings settings = {1, 2, 0};
printf("Size of settings: %zu bytes\n", sizeof(settings));
printf("Enabled: %u, Mode: %u\n", settings.enabled, settings.mode);
// Bit manipulation
unsigned int flags = 0;
flags |= (1 << 3); // Set bit 3
flags |= (1 << 5); // Set bit 5
printf("Flags: ");
printBinary(flags);
// Check if bit is set
if (flags & (1 << 3)) {
printf("Bit 3 is set\n");
}
// Clear bit
flags &= ~(1 << 3);
printf("After clearing bit 3: ");
printBinary(flags);
return 0;
}
Error Handling
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
void demonstrateErrorHandling() {
FILE *file = fopen("nonexistent.txt", "r");
if (file == NULL) {
printf("Error opening file: %s\n", strerror(errno));
perror("fopen failed");
}
// Using errno
errno = 0;
double result = sqrt(-1);
if (errno != 0) {
printf("Math error: %s\n", strerror(errno));
}
// Custom error handling
#define CHECK(condition, message) \
do { \
if (!(condition)) { \
fprintf(stderr, "Error: %s (%s:%d)\n", \
message, __FILE__, __LINE__); \
exit(EXIT_FAILURE); \
} \
} while(0)
int *ptr = malloc(100 * sizeof(int));
CHECK(ptr != NULL, "Memory allocation failed");
free(ptr);
}
int main() {
demonstrateErrorHandling();
return 0;
}
Common Pitfalls
- Not checking return values of functions
- Buffer overflows in string operations
- Memory leaks from not freeing allocated memory
- Undefined behavior from accessing invalid memory
- Integer overflow and underflow