C
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Arrays and Strings
-
Section 5: Pointers
-
Section 6: Memory Management
-
Section 7: Structures and Unions
-
Section 8: The Preprocessor and Build Process
-
Section 9: Standard Library: stdio.h
-
Section 10: Standard Library: stdlib.h
-
Section 11: Standard Library: string.h
-
Section 12: Standard Library: ctype.h and wctype.h
-
Section 13: Standard Library: math.h, complex.h, fenv.h, tgmath.h
-
Section 14: Standard Library: Type and Limit Headers
-
Section 15: Standard Library: Error Handling and Debugging
-
Section 16: Standard Library: Localization and Encoding
-
Section 17: Standard Library: time.h
-
Section 18: Standard Library: Concurrency (C11)
-
Section 19: POSIX and System Programming (unistd.h)
-
Section 20: More Data Structures
-
Section 21: Algorithms in C
-
Section 22: Bitwise Operations
-
Section 23: Command-Line Programs
-
Section 24: Debugging and Best Practices
-
Section 25: Compiler and Language Internals
-
Section 26: Embedded and Cross-Platform Considerations
-
Section 27: Networking Basics
-
Section 28: Practical Projects
-
Section 29: Interview Practice
-
Section 30: C23 Modern Features
-
Section 31: More Practice and Review
80: Character I/O: getchar, putchar, fgetc, fputc
Imagine you're standing at a conveyor belt in a factory. Instead of whole boxes arriving at once, the belt delivers a single, tiny bead every second. You have one job: pick up a bead, look at its color, and if it's the right color, drop it into a collection jar. You aren't dealing with the whole shipment; you're dealing with the smallest possible unit of the product, one by one, in a loop.
In C, character I/O works exactly like that conveyor belt. Instead of reading an entire line of text or a whole file into a massive buffer, you process a single char, decide what to do with it, and then move to the next. Here is how that maps to the code:
- The Conveyor Belt: This is your input stream (either
stdinfor the keyboard or aFILEpointer for a file). - Picking up a bead: This is
getchar()orfgetc(). - Dropping it in the jar: This is
putchar()orfputc(). - The belt stopping: This is the
EOF(End Of File) marker.
The Basics: getchar and putchar
When you're just interacting with the terminal, getchar() and putchar() are your best friends. They are lean and fast. Let's say you want to write a program that "censors" the letter 'e' from anything the user types. You wouldn't need a huge array to store the string; you just process the characters as they fly by.
#include <stdio.h>
int main() {
int c; // Note: I'm using int, not char. I'll explain why in a second.
printf("Type something (Ctrl+D to stop):\n");
while ((c = getchar()) != EOF) {
if (c == 'e' || c == 'E') {
putchar('*');
} else {
putchar(c);
}
}
return 0;
}
The EOF Trap and why we use int
You might have noticed I declared c as an int, even though we're dealing with characters. This is a classic spot where new C programmers trip up. I've seen it a hundred times.
Here is the deal: getchar() needs to return every possible character value (0 to 255), but it also needs a way to tell you, "Hey, the stream is empty!" To do this, it returns a special value called EOF, which is typically defined as -1. If you store the result in a char, you might not be able to distinguish between the actual character with the value 255 and the EOF signal. Using an int gives us enough room to hold all character values plus the distinct EOF value.
Moving to Files with fgetc and fputc
Now, getchar() and putchar() are actually just "shortcuts." getchar() is essentially fgetc(stdin), and putchar(c) is just fputc(c, stdout). When you want to move away from the keyboard and start reading from an actual file on your hard drive, you use the f versions.
The f stands for "file" (or more accurately, "stream"). These functions require a FILE * pointer so the program knows which conveyor belt to watch.
#include <stdio.h>
int main() {
FILE *inputFile = fopen("input.txt", "r");
FILE *outputFile = fopen("output.txt", "w");
if (inputFile == NULL || outputFile == NULL) {
perror("File error");
return 1;
}
int c;
while ((c = fgetc(inputFile)) != EOF) {
// Convert everything to uppercase on the fly
if (c >= 'a' && c <= 'z') {
c = c - 32;
}
fputc(c, outputFile);
}
fclose(inputFile);
fclose(outputFile);
return 0;
}
I personally prefer this character-by-character approach when I'm writing parsers or simple filters. It keeps the memory footprint tiny because you only ever have one character in RAM at a time, regardless of whether the input file is ten bytes or ten gigabytes.
📋 Practical Task
Build a "Whitespace-to-Tab" Converter
Your task is to create a utility that reads from a file named raw_text.txt and writes to a file named cleaned_text.txt. The program should process the file character by character. Every time it encounters a space character ' ', it should instead write a tab character '\t' to the output file. All other characters should be copied exactly as they are.
Requirements:
- Use
fgetcto read andfputcto write. - Ensure you handle the
EOFcondition correctly using anintvariable. - Properly close both file pointers before the program exits.
There are no comments for now.