C Programming Basics
Tutorial on C Programming
Your First Steps in C Programming: A Beginner’s Tutorial
Welcome to the world of C programming! C is a powerful, versatile, and foundational programming language that has influenced countless other languages. Developed in the early 1970s by Dennis Ritchie at Bell Labs, it’s known for its efficiency, low-level memory access, and widespread use in operating systems (like Linux and Windows), embedded systems, game development, and high-performance computing.
If you’re new to programming or coming from a higher-level language, C might seem a bit different, but mastering its basics provides a solid understanding of how computers actually work. Let’s dive in!
Who is this tutorial for? Absolute beginners with little to no programming experience, or those familiar with other languages looking to understand C fundamentals.
What will you learn?
- How to set up your environment (briefly).
- The structure of a basic C program.
- Variables and fundamental data types.
- Basic input and output operations.
- Essential operators.
- Control flow (making decisions and looping).
- The basics of functions.
- A glimpse into arrays and pointers.
1. Setting Up Your C Environment
Before you can write and run C code, you need a C compiler. A compiler translates your human-readable C code into machine code that your computer can execute.
- GCC (GNU Compiler Collection): The most common C compiler, available for free on Linux, macOS, and Windows (often via MinGW or Cygwin).
- Linux: Usually pre-installed or installable via package manager (e.g.,
sudo apt update && sudo apt install build-essential
on Debian/Ubuntu). - macOS: Install Xcode Command Line Tools (
xcode-select --install
in Terminal). - Windows: Install MinGW-w64 (Minimalist GNU for Windows) or use WSL (Windows Subsystem for Linux).
- Linux: Usually pre-installed or installable via package manager (e.g.,
- IDE (Integrated Development Environment): Software that bundles a text editor, compiler, debugger, and other tools. Popular choices include:
- Visual Studio Code (VS Code): Free, highly extensible, works on all platforms (needs C/C++ extension and a compiler like GCC).
- Code::Blocks: Free, open-source, cross-platform IDE specifically for C/C++.
- Visual Studio (Windows): Powerful IDE from Microsoft (Community edition is free).
- Online Compilers: Websites like Replit, OnlineGDB, or Programiz Online C Compiler let you write and run C code directly in your browser – great for starting quickly!
For this tutorial, we’ll assume you have access to a compiler like GCC.
2. Your First C Program: “Hello, World!”
It’s a tradition! Let’s write a program that prints “Hello, World!” to the screen.
1
2
3
4
5
6
7
8
9
10
11
// 1. Include Header File
#include <stdio.h>
// 2. The main Function - Program execution begins here
int main() {
// 3. Use printf to display output
printf("Hello, World!\n");
// 4. Return 0 to indicate successful execution
return 0;
}
Let’s break it down:
#include <stdio.h>
: This is a preprocessor directive. It tells the compiler to include the contents of the standard input/output header file (stdio.h
). This file contains declarations for functions likeprintf
(for printing output) andscanf
(for reading input).int main() { ... }
: This is the main function. Every C program must have amain
function, as it’s the starting point of execution.int
: Specifies that themain
function will return an integer value to the operating system (by convention,0
means success).()
: Parentheses indicate it’s a function. Sometimes you might seeint main(void)
which explicitly states it takes no arguments.{ ... }
: Curly braces define the beginning and end of the function’s code block.
printf("Hello, World!\n");
: This is a statement calling theprintf
function.printf
: The function used to print formatted output to the console."Hello, World!\n"
: The string literal (text) to be printed.\n
: An escape sequence representing a newline character. It moves the cursor to the next line after printing.;
: The semicolon marks the end of the statement. Almost every statement in C ends with a semicolon.
return 0;
: This statement exits themain
function and returns the value0
to the operating system, indicating that the program ran successfully.
Compiling and Running:
- Save the code above in a file named
hello.c
. - Open your terminal or command prompt.
- Navigate to the directory where you saved the file.
- Compile the code using GCC:
1
gcc hello.c -o hello
gcc
: Invokes the compiler.hello.c
: The input source file.-o hello
: Specifies the name of the output executable file (you can name it anything).
- Run the compiled program:
- On Linux/macOS:
./hello
- On Windows:
hello.exe
or.\hello.exe
- On Linux/macOS:
You should see Hello, World!
printed on your screen. Congratulations!
3. Variables and Data Types
Variables are named containers used to store data. In C, you must declare a variable before using it, specifying its data type.
Common Data Types:
int
: Stores whole numbers (integers). Example:10
,-5
,0
.float
: Stores single-precision floating-point numbers (numbers with decimal points). Example:3.14
,-0.5
.double
: Stores double-precision floating-point numbers (more precision thanfloat
). Example:3.14159265
.char
: Stores a single character. Example:'A'
,'a'
,'!'
,' '
. Note the single quotes.
Declaration and Initialization:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>
int main() {
// Declaration
int age;
float price;
char initial;
// Initialization (assigning an initial value)
age = 30;
price = 99.95;
initial = 'J';
// Declaration and Initialization together
double pi = 3.14159;
int count = 0;
// Using printf to display variable values
// Format Specifiers: %d (int), %f (float/double), %c (char)
printf("Age: %d\n", age);
printf("Price: %.2f\n", price); // %.2f prints float/double with 2 decimal places
printf("Initial: %c\n", initial);
printf("Count: %d\n", count);
printf("Pi: %f\n", pi); // %f can be used for double too
return 0;
}
Format Specifiers: Notice printf
uses placeholders like %d
, %f
, %c
. These are format specifiers that tell printf
what type of data to expect and how to format it.
4. Basic Input/Output (I/O)
We’ve seen printf
for output. To get input from the user, we use the scanf
function.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
int main() {
int userAge;
float userPrice;
char userInitial;
// Prompt the user for input
printf("Enter your age: ");
// Read an integer input from the user
scanf("%d", &userAge); // IMPORTANT: Note the '&' symbol!
printf("Enter a price: ");
scanf("%f", &userPrice);
// Be careful when mixing scanf for numbers and characters
// It's often better to read characters separately or handle whitespace
printf("Enter your first initial: ");
// Notice the space before %c to consume any leftover newline from previous scanf
scanf(" %c", &userInitial);
// Display the values entered by the user
printf("\nYou entered:\n");
printf("Age: %d\n", userAge);
printf("Price: %.2f\n", userPrice);
printf("Initial: %c\n", userInitial);
return 0;
}
Crucial Point about scanf
: Notice the &
symbol before the variable names (&userAge
, &userPrice
, &userInitial
). This is the address-of operator. scanf
needs to know the memory address where it should store the input value. We’ll touch on addresses again when we mention pointers. Forgetting the &
with scanf
(except when reading strings into character arrays) is a very common beginner mistake!
5. Operators
C provides various operators to perform operations:
- Arithmetic:
+
(addition),-
(subtraction),*
(multiplication),/
(division),%
(modulo - remainder of division).1 2 3 4 5 6 7
int a = 10, b = 3; int sum = a + b; // 13 int diff = a - b; // 7 int product = a * b; // 30 int quotient = a / b; // 3 (integer division truncates) int remainder = a % b; // 1 float preciseQuotient = (float)a / b; // 3.333... (cast 'a' to float for float division)
- Relational: Used for comparison, result in
1
(true) or0
(false).==
(Equal to)!=
(Not equal to)>
(Greater than)<
(Less than)>=
(Greater than or equal to)<=
(Less than or equal to)1 2 3
int x = 5, y = 10; printf("x == y? %d\n", x == y); // 0 (false) printf("x < y? %d\n", x < y); // 1 (true)
- Logical: Combine or negate boolean conditions.
&&
(Logical AND - true if both sides are true)||
(Logical OR - true if at least one side is true)!
(Logical NOT - inverts true to false, false to true)1 2 3
int isRaining = 1; // Treat 1 as true int haveUmbrella = 0; // Treat 0 as false printf("Go out? %d\n", !isRaining || haveUmbrella); // 0 (false, because it's raining AND no umbrella)
- Assignment:
=
(Assign value),+=
,-=
,*=
,/=
,%=
(Shorthand operations, e.g.,x += 5
isx = x + 5
).
6. Control Flow
Control flow statements allow you to alter the execution path based on conditions or repeat blocks of code.
a) if
, else if
, else
(Conditional Execution)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
int main() {
int score = 75;
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");
}
return 0;
}
b) Loops (Repetition)
for
loop: Used when you know the number of iterations in advance.1 2 3 4 5 6 7 8 9 10
#include <stdio.h> int main() { // Print numbers from 1 to 5 // for (initialization; condition; increment/decrement) for (int i = 1; i <= 5; i++) { printf("%d ", i); // Output: 1 2 3 4 5 } printf("\n"); return 0; }
while
loop: Used when the loop continues as long as a condition is true. The condition is checked before each iteration.1 2 3 4 5 6 7 8 9 10
#include <stdio.h> int main() { int count = 0; while (count < 3) { printf("Looping... "); count++; // Important: update the counter to avoid infinite loop } printf("\nDone.\n"); // Output: Looping... Looping... Looping... Done. return 0; }
do-while
loop: Similar towhile
, but the condition is checked after the loop body executes. Guarantees the loop runs at least once.1 2 3 4 5 6 7 8 9 10 11
#include <stdio.h> int main() { int input; do { printf("Enter a positive number: "); scanf("%d", &input); } while (input <= 0); // Keep asking until positive number is entered printf("You entered: %d\n", input); return 0; }
7. Functions
Functions allow you to break down your code into smaller, reusable, and manageable blocks. We’ve already used main
and printf
. Let’s define our own.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
// 1. Function Declaration (Prototype) - Tells the compiler about the function
int add(int num1, int num2); // Takes two ints, returns an int
// The main function
int main() {
int a = 10;
int b = 20;
int sum;
// 3. Function Call
sum = add(a, b); // Pass 'a' and 'b' as arguments
printf("The sum of %d and %d is %d\n", a, b, sum); // Output: The sum of 10 and 20 is 30
return 0;
}
// 2. Function Definition - Contains the actual code
int add(int num1, int num2) { // 'num1' and 'num2' are parameters
int result = num1 + num2;
return result; // Return the calculated value
}
Key Parts:
- Declaration (Prototype): Placed before
main
(or in a header file). Tells the compiler the function’s name, return type, and parameter types. - Definition: The actual implementation of the function’s logic.
- Call: Invoking the function from another part of the program (like
main
), passing arguments (values likea
andb
) that correspond to the function’s parameters (num1
andnum2
).
8. A Glimpse into Arrays and Pointers
Arrays: Store multiple values of the same data type in a contiguous block of memory. Accessed using an index (starting from 0).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
int main() {
// Declare an integer array of size 5
int numbers[5];
// Initialize elements
numbers[0] = 10;
numbers[1] = 20;
numbers[2] = 30;
numbers[3] = 40;
numbers[4] = 50;
// numbers[5] = 60; // ERROR: Index out of bounds! (Valid indices are 0 to 4)
// Access and print an element
printf("The element at index 2 is: %d\n", numbers[2]); // Output: 30
// Initialize during declaration
float temps[] = {25.5, 26.1, 24.9}; // Size inferred from initializers
// Loop through an array
printf("Temperatures: ");
for (int i = 0; i < 3; i++) { // Need to know the size (3 here)
printf("%.1f ", temps[i]);
}
printf("\n");
return 0;
}
Pointers (Brief Intro): A pointer is a variable that stores the memory address of another variable. They are fundamental in C for tasks like dynamic memory allocation, efficient function arguments, and working with complex data structures.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
int main() {
int var = 10;
int *ptr; // Declare a pointer variable 'ptr' that can point to an int
ptr = &var; // Assign the address of 'var' to 'ptr' using '&' (address-of operator)
printf("Value of var: %d\n", var);
printf("Address of var: %p\n", &var); // %p is format specifier for addresses
printf("Value stored in ptr (address of var): %p\n", ptr);
printf("Value pointed to by ptr (*ptr): %d\n", *ptr); // Use '*' (dereference operator) to get the value at the address
// Change value using the pointer
*ptr = 25;
printf("New value of var (changed via pointer): %d\n", var); // Output: 25
return 0;
}
Pointers are a deeper topic, but understanding the &
(address-of) and *
(dereference) operators is the first step. Remember how scanf
needed the &
? It needed the address to store the value!
9. Comments
Use comments to explain your code. The compiler ignores them.
1
2
3
4
5
6
7
// This is a single-line comment
/*
This is a
multi-line
comment.
*/
Strings (In Depth)
In many programming languages, strings are a built-in fundamental data type. In C, however, a string is essentially treated as an array of characters that follows a specific convention: it must end with a special character called the null terminator (\0
). This null character signals the end of the string.
Working with strings in C often involves direct memory manipulation (via arrays and pointers) and using standard library functions designed for these null-terminated character arrays.
1. Representing Strings: Null-Terminated Character Arrays
The most common way to handle strings is using an array of char
.
1
2
char message[20]; // Declares a character array that can hold up to 19 characters + the null terminator.
char greeting[] = "Hello"; // Declares and initializes an array. Size is automatically determined (6: H, e, l, l, o, \0).
Let’s visualize greeting
:
Index | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
Value | ‘H’ | ‘e’ | ‘l’ | ‘l’ | ‘o’ | \0 |
"Hello"
: This string literal automatically includes the null terminator (\0
) when used to initialize an array.- Size: The
greeting
array needs 6 elements to store the 5 characters of “Hello” plus the final\0
. If you declarechar greeting[5] = "Hello";
, you won’t have space for the null terminator, which will cause problems with standard string functions.
2. Declaring and Initializing Strings
There are several ways to create strings:
a) Character Array Initialization:
1
2
3
4
5
6
7
8
// Size automatically determined (including \0)
char str1[] = "Welcome";
// Explicit size (must be large enough for content + \0)
char str2[50] = "Learning C is fun!";
// Initializing character by character (MANUALLY add \0)
char str3[] = {'C', ' ', 'i', 's', ' ', 'f', 'u', 'n', '!', '\0'}; // Don't forget \0!
b) Using Pointers (String Literals):
1
char *str4 = "This is a string literal.";
- Important Distinction: When you use
char *str = "..."
,str
is a pointer that points to the memory location where the string literal"..."
is stored. This memory area is often read-only. Attempting to modify the string through this pointer (e.g.,str4[0] = 'X';
) leads to undefined behavior (often a crash). - In contrast, strings declared as arrays (
char str1[] = "..."
) are stored in memory that can usually be modified (e.g.,str1[0] = 'W';
is typically okay).
Rule of Thumb: If you need to modify the string later, declare it as an array (char name[size];
or char name[] = "...";
). If it’s a constant string you only need to read, a char *
pointer is acceptable (and sometimes more efficient).
3. The Importance of the Null Terminator (\0
)
Standard C string functions (like those for copying, concatenating, or finding the length) rely entirely on the null terminator (\0
) to know where the string data ends. If a character array that you intend to use as a string lacks a \0
, these functions will read past the end of your intended data, accessing invalid memory, which leads to crashes or incorrect results.
1
2
3
char bad_string[3] = {'B', 'A', 'D'}; // NO null terminator!
// printf("%s\n", bad_string); // DANGEROUS! printf will read past the array bounds.
// strlen(bad_string); // DANGEROUS! strlen will keep counting until it finds a \0 somewhere in memory.
Always ensure your character arrays have enough space for and contain the \0
if you plan to treat them as strings.
4. String Manipulation Functions (<string.h>
)
C provides a standard library string.h
containing many useful functions for working with null-terminated strings. You need to include it: #include <string.h>
a) strlen()
- Finding String Length
Calculates the length of a string excluding the null terminator.
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <string.h>
int main() {
char myString[] = "Example"; // Length is 7
size_t len = strlen(myString); // size_t is an unsigned integer type suitable for sizes
printf("String: %s\n", myString);
printf("Length: %zu\n", len); // Use %zu format specifier for size_t
return 0;
}
Output:
1
2
String: Example
Length: 7
b) strcpy()
and strncpy()
- Copying Strings
Used to copy one string into another character array.
strcpy(destination, source);
- Copies the string from
source
(including\0
) intodestination
. - DANGER:
strcpy
performs no bounds checking. Ifsource
is longer thandestination
can hold (including\0
), it will write past the end of thedestination
buffer, causing a buffer overflow – a major security risk and common cause of crashes. Avoidstrcpy
unless you are absolutely certain the destination is large enough.
- Copies the string from
strncpy(destination, source, n);
- Copies at most
n
characters fromsource
todestination
. - Safer, but tricky: If
source
hasn
or more characters (without\0
),strncpy
will not null-terminatedestination
. Ifsource
has fewer thann
characters, it copies the\0
and pads the rest ofdestination
(up ton
characters) with\0
. - Best Practice: Always manually ensure null termination after using
strncpy
if there’s a chance the source was truncated.
- Copies at most
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <string.h>
int main() {
char src[] = "Copy this!";
char dest1[50];
char dest2[10]; // Too small for src!
char dest3[10];
// Using strcpy (safe here because dest1 is large enough)
strcpy(dest1, src);
printf("dest1 after strcpy: %s\n", dest1);
// Using strncpy (safer)
strncpy(dest3, src, sizeof(dest3) - 1); // Copy at most 9 chars (leave space for \0)
dest3[sizeof(dest3) - 1] = '\0'; // MANUALLY ensure null termination
printf("dest3 after strncpy: %s\n", dest3); // Output: Copy this
// --- DANGER ZONE ---
// strcpy(dest2, src); // <-- !! BUFFER OVERFLOW !! Don't do this! Crashes likely.
// printf("dest2 after unsafe strcpy: %s\n", dest2);
return 0;
}
c) strcat()
and strncat()
- Concatenating Strings
Appends one string to the end of another.
strcat(destination, source);
- Appends a copy of
source
to the end ofdestination
. The first character ofsource
overwrites the\0
at the end ofdestination
. - DANGER: Like
strcpy
, performs no bounds checking. Ifdestination
doesn’t have enough space for its original content plus the content ofsource
plus the final\0
, it will cause a buffer overflow. Avoidstrcat
unless absolutely certain the destination is large enough.
- Appends a copy of
strncat(destination, source, n);
- Appends at most
n
characters fromsource
to the end ofdestination
. - It always null-terminates the result (provided
destination
had space for its original content plus at least one appended character plus the final\0
). - Safer, but you need to calculate
n
carefully to avoid truncation or overflow.n
should be less thansizeof(destination) - strlen(destination) - 1
.
- Appends at most
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <string.h>
int main() {
char greeting[50] = "Hello, "; // Ensure enough space initially
char name[] = "Alice";
char place[20] = "Welcome "; // Less space
char city[] = "Wonderland";
// Using strcat (safe here)
strcat(greeting, name); // Appends "Alice"
printf("strcat result: %s\n", greeting); // Output: Hello, Alice
// Using strncat (safer)
// Calculate remaining space: sizeof(place) - strlen(place) - 1
size_t remaining_space = sizeof(place) - strlen(place) - 1;
strncat(place, city, remaining_space);
// place might be "Welcome Wonderland" if space permits, or truncated like "Welcome Wonderlan"
printf("strncat result: %s\n", place);
return 0;
}
d) strcmp()
and strncmp()
- Comparing Strings
Compares two strings lexicographically (like in a dictionary).
strcmp(str1, str2);
strncmp(str1, str2, n);
(Compares at mostn
characters)
Return Value:
0
ifstr1
is equal tostr2
.< 0
(negative value) ifstr1
comes beforestr2
alphabetically.> 0
(positive value) ifstr1
comes afterstr2
alphabetically.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>
#include <string.h>
int main() {
char pass1[] = "secret123";
char pass2[] = "secret123";
char pass3[] = "Secret123"; // Different case
char pass4[] = "secretABC";
if (strcmp(pass1, pass2) == 0) {
printf("'%s' and '%s' are identical.\n", pass1, pass2);
}
if (strcmp(pass1, pass3) != 0) {
printf("'%s' and '%s' are different (case matters).\n", pass1, pass3);
}
if (strcmp(pass1, pass4) > 0) { // 's' > 'A'
printf("'%s' comes after '%s'.\n", pass1, pass4);
}
// Using strncmp - compare first 6 characters
if (strncmp(pass1, pass4, 6) == 0) {
printf("The first 6 characters of '%s' and '%s' match.\n", pass1, pass4);
}
return 0;
}
5. String Input/Output
a) Printing Strings: Use printf
with the %s
format specifier.
1
2
char message[] = "Printing strings is easy.";
printf("%s\n", message);
b) Reading Strings:
scanf("%s", buffer);
- Reads a sequence of non-whitespace characters from input and stores it in the
buffer
array. - It automatically adds the
\0
terminator. - Stops reading at the first whitespace character (space, tab, newline).
- DANGER: Like
strcpy
,scanf("%s", ...)
performs no bounds checking. If the user types more characters than the buffer can hold (before hitting whitespace), it causes a buffer overflow. Avoidscanf("%s", buffer)
without width limits. - Slightly Safer
scanf
:scanf("%19s", buffer);
- Reads at most 19 characters intobuffer
(assumingbuffer
has size 20), leaving space for\0
. Better, but still stops at whitespace.
- Reads a sequence of non-whitespace characters from input and stores it in the
fgets(buffer, size, stdin);
- The recommended safe way to read string input.
- Reads characters from the specified input stream (
stdin
for standard input/keyboard) intobuffer
. - Reads at most
size - 1
characters, ensuring space is left for the\0
. - Stops reading when it encounters a newline (
\n
) or reaches the size limit. - Includes the newline character (
\n
) in the buffer if it was read before hitting the size limit. This often needs to be handled (removed). - Returns
NULL
on error or end-of-file.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <string.h> // For strcspn
int main() {
char name[50]; // Buffer to hold the name
printf("Enter your full name: ");
// Read using fgets (safer)
if (fgets(name, sizeof(name), stdin) != NULL) {
// Remove trailing newline, if present
name[strcspn(name, "\n")] = '\0'; // Finds the first newline and replaces it with null
printf("Hello, %s!\n", name);
} else {
printf("Error reading input.\n");
}
return 0;
}
(Note: strcspn(name, "\n")
returns the index of the first newline character in name
. Replacing that character with \0
effectively removes the newline read by fgets
.)
Key Takeaways for C Strings
- Strings are null-terminated (
\0
) character arrays. - Be mindful of buffer sizes to prevent overflows. Always allocate enough space for your string data plus the
\0
. - Prefer safer functions like
strncpy
,strncat
, andfgets
over their unsafe counterparts (strcpy
,strcat
,scanf("%s", ...)
). - Manually ensure null termination when using functions like
strncpy
that might not add it automatically. - Remember the difference between
char array[] = "..."
(modifiable) andchar *ptr = "..."
(points to potentially read-only literal). - Include
<string.h>
for string manipulation functions. - Handle the trailing newline character often included by
fgets
.
We’ve covered the fundamentals, including variables, control flow, basic functions, arrays, and strings. Now, let’s delve into more powerful features of C that allow for greater flexibility and control: Structures, File Input/Output, a deeper look at Pointers, and Dynamic Memory Allocation.
Structs: Creating Custom Data Types
Often, you need to group related data items together, even if they are of different types. For example, to represent a student, you might need their ID (integer), name (string/char array), and GPA (float). C’s struct
(structure) allows you to create your own custom data types to bundle these items.
a) Defining a Structure:
You define a structure using the struct
keyword, followed by a tag name (like Student
), and then curly braces {}
containing the member variables (fields) with their data types.
1
2
3
4
5
6
// Define a structure named 'Student'
struct Student {
int studentID;
char name[100]; // Character array for the name
float gpa;
}; // Don't forget the semicolon here!
This definition creates a blueprint or template for a Student
type. It doesn’t allocate any memory yet.
b) Declaring Structure Variables:
Once defined, you can declare variables of this structure type, just like you declare int
or float
variables.
1
2
struct Student student1; // Declares a variable 'student1' of type 'struct Student'
struct Student student2, student3; // Declare multiple variables
c) Accessing Structure Members:
You access the individual members (fields) within a structure variable using the dot (.
) operator.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <stdio.h>
#include <string.h> // For strcpy
// Define the structure
struct Student {
int studentID;
char name[100];
float gpa;
};
int main() {
// Declare a structure variable
struct Student student1;
// Assign values to members using the dot operator
student1.studentID = 101;
strcpy(student1.name, "Alice Wonderland"); // Use strcpy for strings
student1.gpa = 3.85;
// Access and print members
printf("Student ID: %d\n", student1.studentID);
printf("Name: %s\n", student1.name);
printf("GPA: %.2f\n", student1.gpa);
// You can also initialize during declaration (C99+)
struct Student student2 = {102, "Bob The Builder", 3.5};
printf("\nStudent ID: %d\n", student2.studentID);
printf("Name: %s\n", student2.name);
printf("GPA: %.2f\n", student2.gpa);
return 0;
}
d) Pointers to Structures:
You can have pointers that point to structure variables. Accessing members through a pointer uses the arrow (->
) operator instead of the dot (.
).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <string.h>
struct Student { /* ... definition as above ... */ };
int main() {
struct Student student1 = {101, "Alice Wonderland", 3.85};
struct Student *ptrStudent; // Declare a pointer to a Student struct
ptrStudent = &student1; // Assign the address of student1 to the pointer
// Access members using the arrow operator (->) with the pointer
printf("ID (via pointer): %d\n", ptrStudent->studentID);
printf("Name (via pointer): %s\n", ptrStudent->name);
printf("GPA (via pointer): %.2f\n", ptrStudent->gpa);
// The arrow operator is shorthand for (*ptrStudent).member
printf("GPA (alternative pointer syntax): %.2f\n", (*ptrStudent).gpa);
return 0;
}
e) typedef
with Structs:
Using typedef
can create an alias for struct YourStructName
, making declarations cleaner.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Define the struct and create an alias 'Point' using typedef
typedef struct {
int x;
int y;
} Point; // 'Point' is now an alias for the struct type
int main() {
Point p1 = {10, 20}; // Use the alias directly
Point p2;
p2.x = 5;
p2.y = -3;
printf("P1: (%d, %d)\n", p1.x, p1.y);
printf("P2: (%d, %d)\n", p2.x, p2.y);
return 0;
}
Structs are essential for organizing complex data and are the building blocks for more advanced data structures like linked lists and trees.
File I/O: Reading From and Writing To Files
Your C programs often need to interact with files to read data or store results persistently. C provides a set of functions (mostly in stdio.h
) for file input/output (I/O).
a) The FILE
Pointer:
File operations revolve around a special pointer type: FILE *
. This pointer holds information about the file being accessed (like its location, current position, etc.).
1
FILE *filePointer; // Declare a FILE pointer
b) Opening a File (fopen
)
Before you can read or write, you must open the file using fopen()
.
FILE *fopen(const char *filename, const char *mode);
filename
: A string containing the name (and optionally path) of the file.mode
: A string specifying how you want to open the file. Common modes:"r"
: Open for reading (file must exist)."w"
: Open for writing (creates file if it doesn’t exist, truncates to zero length/overwrites if it does)."a"
: Open for appending (creates file if it doesn’t exist, writes are added to the end)."rb"
,"wb"
,"ab"
: Liker
,w
,a
, but in binary mode (important for non-text files)."r+"
,"w+"
,"a+"
: Open for both reading and writing.
Crucially: fopen
returns a FILE
pointer if successful, or NULL
if an error occurs (e.g., file not found in "r"
mode, no permissions). Always check the return value!
1
2
3
4
5
6
FILE *outfile = fopen("output.txt", "w");
if (outfile == NULL) {
perror("Error opening file for writing"); // perror prints a system error message
return 1; // Indicate an error occurred
}
// ... proceed to write to the file ...
c) Writing to a File:
fprintf(filePointer, format_string, ...);
: Writes formatted output to the specified file, similar toprintf
.fputc(char, filePointer);
: Writes a single character.fputs(string, filePointer);
: Writes a string (stops at\0
, doesn’t add a newline).
1
2
3
4
5
fprintf(outfile, "Hello from C!\n");
int value = 123;
fprintf(outfile, "The value is: %d\n", value);
fputs("This is another line.\n", outfile);
fputc('X', outfile);
d) Reading from a File:
fscanf(filePointer, format_string, ...);
: Reads formatted input from the file, similar toscanf
. Needs addresses (&
) for variables. Returns the number of items successfully read, orEOF
(End Of File).fgetc(filePointer);
: Reads a single character (returnsint
, orEOF
at end-of-file/error).fgets(buffer, size, filePointer);
: Reads a line of text (or up tosize-1
characters) intobuffer
. Includes newline (\n
) if read. Safer thanfscanf
for reading strings. ReturnsNULL
at end-of-file/error.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
FILE *infile = fopen("input.txt", "r");
if (infile == NULL) {
perror("Error opening file for reading");
return 1;
}
char lineBuffer[256];
int readValue;
// Read using fscanf
if (fscanf(infile, "%d", &readValue) == 1) { // Check if one item was read
printf("Read value using fscanf: %d\n", readValue);
}
// Read lines using fgets
printf("Reading lines with fgets:\n");
while (fgets(lineBuffer, sizeof(lineBuffer), infile) != NULL) {
printf("Read line: %s", lineBuffer); // fgets includes \n, so no extra \n needed here
}
e) Closing a File (fclose
)
When you’re finished with a file, you must close it using fclose()
.
int fclose(FILE *filePointer);
- Flushes any buffered output to the disk.
- Releases system resources associated with the file.
- Returns
0
on success,EOF
on error.
1
2
fclose(outfile);
fclose(infile);
Forgetting to close files can lead to data loss (output might stay in buffers) and resource leaks.
Example: Write and Read
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <stdio.h>
#include <stdlib.h> // For exit()
int main() {
FILE *fp;
char textToWrite[] = "This is line 1.\nThis is line 2.";
char buffer[100];
// --- Writing to file ---
fp = fopen("mydata.txt", "w");
if (fp == NULL) {
perror("Error opening file for writing");
exit(1);
}
printf("Writing to file...\n");
fprintf(fp, "%s\n", textToWrite);
fprintf(fp, "Adding a number: %d\n", 42);
fclose(fp); // Close after writing
printf("Finished writing.\n\n");
// --- Reading from file ---
fp = fopen("mydata.txt", "r");
if (fp == NULL) {
perror("Error opening file for reading");
exit(1);
}
printf("Reading from file using fgets:\n");
while(fgets(buffer, sizeof(buffer), fp) != NULL) {
printf("%s", buffer); // Print line read (includes newline)
}
fclose(fp); // Close after reading
printf("\nFinished reading.\n");
return 0;
}
Pointers (In Depth)
We introduced pointers as variables storing memory addresses. Let’s explore their capabilities further.
a) Pointer Arithmetic:
You can perform limited arithmetic operations on pointers. When you add an integer n
to a pointer ptr
, the pointer is advanced by n * sizeof(*ptr)
bytes in memory. This means it points n
elements forward, based on the size of the data type it points to. Similarly for subtraction.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
int main() {
int numbers[] = {10, 20, 30, 40, 50};
int *ptr = numbers; // Pointer points to the first element (numbers[0])
printf("ptr points to address: %p, value: %d\n", ptr, *ptr); // Address of numbers[0], value 10
// Advance the pointer by 1 element (adds sizeof(int) bytes to the address)
ptr++;
printf("ptr+1 points to address: %p, value: %d\n", ptr, *ptr); // Address of numbers[1], value 20
// Advance by 2 more elements
ptr = ptr + 2; // equivalent to ptr += 2;
printf("ptr+3 points to address: %p, value: %d\n", ptr, *ptr); // Address of numbers[3], value 40
// Subtracting pointers (gives the number of elements between them)
int *ptr_start = numbers;
int *ptr_end = &numbers[4]; // Pointer to the last element
ptrdiff_t diff = ptr_end - ptr_start; // ptrdiff_t is a type for pointer differences
printf("Elements between ptr_end and ptr_start: %td\n", diff); // Output: 4
return 0;
}
Valid Operations: Addition/subtraction of an integer, subtracting two pointers (pointing within the same array), comparison (<
, >
, ==
, etc.). Invalid Operations: Adding two pointers, multiplication, division.
b) Pointers and Arrays:
There’s a very strong connection:
- An array name, when used in many contexts (like being passed to a function or assigned to a pointer), decays into a pointer to its first element.
array_name
is equivalent to&array_name[0]
.array_name[i]
is equivalent to*(array_name + i)
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
void printArray(int *arr, int size) { // Function accepting a pointer to int
printf("Array elements: ");
for (int i = 0; i < size; i++) {
// Both notations work:
// printf("%d ", arr[i]);
printf("%d ", *(arr + i)); // Access using pointer arithmetic + dereference
}
printf("\n");
}
int main() {
int data[] = {5, 10, 15, 20};
int size = sizeof(data) / sizeof(data[0]);
printf("Value at data[1]: %d\n", data[1]); // Array notation
printf("Value at *(data + 1): %d\n", *(data + 1)); // Pointer notation
printArray(data, size); // Pass the array name (decays to pointer)
return 0;
}
c) Pointers and Functions:
Pass by Reference: As seen with
scanf
and ourswap
example (if we wrote one), passing a pointer allows a function to modify the original variable whose address was passed. This simulates “pass by reference”.1 2 3 4 5 6 7 8 9 10 11 12 13
#include <stdio.h> void increment(int *value_ptr) { // Accepts a pointer to an integer (*value_ptr)++; // Increment the value AT the address pointed to } int main() { int counter = 10; printf("Counter before: %d\n", counter); increment(&counter); // Pass the ADDRESS of counter printf("Counter after: %d\n", counter); // Output: 11 return 0; }
Returning Pointers: Functions can return pointers. However, NEVER return a pointer to a local variable of that function. Local variables cease to exist when the function returns, making the returned pointer invalid (a dangling pointer). Pointers returned should typically point to dynamically allocated memory (see next section) or static/global variables, or be pointers passed into the function.
1 2 3 4 5 6 7
// DANGEROUS - DO NOT DO THIS: /* int* createLocalVar() { int localVar = 100; return &localVar; // Returning address of variable that will be destroyed! } */
Dynamic Memory Allocation (<stdlib.h>
)
Sometimes, you don’t know how much memory you need at compile time, or you need data to persist beyond the scope of a single function. C allows you to manually allocate and deallocate memory from a pool called the heap using functions from <stdlib.h>
.
a) malloc
(Memory Allocation):
void *malloc(size_t size);
- Allocates a block of
size
bytes on the heap. - Returns a
void*
(generic pointer) to the beginning of the allocated block. - Returns
NULL
if allocation fails (e.g., out of memory). Always check forNULL
! - The allocated memory block is uninitialized (contains garbage values).
- You need to cast the
void*
to the appropriate pointer type.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int *dynamicArray;
int numElements = 10;
// Allocate memory for 10 integers on the heap
dynamicArray = (int *)malloc(numElements * sizeof(int)); // Calculate total bytes needed
if (dynamicArray == NULL) {
fprintf(stderr, "Memory allocation failed!\n");
exit(1);
}
// Now you can use dynamicArray like a regular array (up to numElements)
dynamicArray[0] = 100;
*(dynamicArray + 1) = 200; // Pointer arithmetic works too
b) calloc
(Contiguous Allocation):
void *calloc(size_t num, size_t size);
- Allocates memory for an array of
num
elements, each ofsize
bytes. - Initializes the allocated memory block to all zeros.
- Returns
void*
orNULL
(check return value).
1
2
3
4
5
6
float *floatArray;
int count = 5;
floatArray = (float *)calloc(count, sizeof(float)); // Allocate 5 floats, initialized to 0.0
if (floatArray == NULL) { /* ... handle error ... */ }
c) realloc
(Re-allocation):
void *realloc(void *ptr, size_t newSize);
- Changes the size of a previously allocated memory block (pointed to by
ptr
). ptr
must beNULL
or a pointer returned by a previousmalloc
,calloc
, orrealloc
call.- Tries to resize the existing block. If not possible, allocates a new block, copies the contents from the old block (up to the minimum of the old and new sizes), frees the old block, and returns a pointer to the new block.
- Returns
NULL
if reallocation fails (the original block remains allocated). Always use a temporary pointer to store the result ofrealloc
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int *arr = (int *)malloc(5 * sizeof(int));
if (!arr) { /* error */ }
// ... use arr ...
// Need more space, try to resize to hold 10 ints
int *temp = (int *)realloc(arr, 10 * sizeof(int));
if (temp == NULL) {
// Reallocation failed! arr still points to the original 5 ints.
fprintf(stderr, "Reallocation failed\n");
// Decide how to handle this - maybe free(arr) and exit?
free(arr); // Don't leak the original memory
exit(1);
} else {
// Reallocation successful, update the main pointer
arr = temp;
// Now arr can hold 10 ints (first 5 have old values, next 5 are uninitialized)
arr[9] = 99;
}
d) free
(Deallocation):
void free(void *ptr);
- Releases a block of memory previously allocated with
malloc
,calloc
, orrealloc
, returning it to the heap. ptr
must beNULL
or a pointer returned by one of the allocation functions that hasn’t already been freed.- Crucial: For every
malloc
/calloc
/realloc
, there should eventually be a correspondingfree
when the memory is no longer needed. Failure tofree
memory leads to memory leaks.
1
2
3
4
5
6
7
8
free(dynamicArray); // Release the memory allocated earlier
dynamicArray = NULL; // Good practice: set pointer to NULL after freeing
free(floatArray);
floatArray = NULL;
free(arr); // Release the reallocated block
arr = NULL;
Common Dynamic Memory Errors:
- Memory Leak: Forgetting to
free
allocated memory. The program consumes more and more memory over time. - Dangling Pointer: Using a pointer after the memory it pointed to has been
free
d. Leads to crashes or unpredictable behavior. Setting pointers toNULL
afterfree
helps prevent this. - Double Free: Calling
free
more than once on the same memory block. Leads to corruption. - Invalid Free: Calling
free
on a pointer that was not obtained frommalloc
/calloc
/realloc
(e.g., address of a local variable).
Dynamic memory allocation gives you great power but requires careful management to avoid errors.
Happy Coding!!