Lesson Progress
0%
Lesson  ยท  Strings, Characters & Arrays

Strings Are Just Char Arrays in Disguise

C has no real "string" type. What you call a string is secretly a char array with one hidden rule: it ends with a silent terminator. Once you see that terminator, gets(), puts(), toupper(), and tolower() all stop being separate things to memorize.

char vs string
gets() & puts()
toupper() & tolower()
Manual traversal
Case-insensitive check
๐Ÿ“–

char vs String โ€” What's Actually Different?

A char holds exactly one character โ€” one letter, one digit, one symbol. It's a single mailbox, one slot, nothing more:

A single char โ€” one box, one letter
A
char c

A string is what happens when you line up several char boxes in a row โ€” an array of characters. But there's a catch: C needs some way to know where the string ends, since the array itself might be bigger than the word stored in it. The solution is a hidden, invisible character called the null terminator, written '\0', silently placed right after the last real letter.

The string "CODE" stored in a char array of size 6 โ€” note the hidden '\0' after the last letter
C
[0]
O
[1]
D
[2]
E
[3]
\0
[4]
?
[5]
๐Ÿ’ก That's the whole secret. Every string function you'll ever use โ€” puts(), strlen(), strcpy() โ€” works by walking forward through the char array one box at a time and stopping the instant it sees '\0'. Box [5] still has garbage in it, and nothing cares, because nothing reads past the terminator.
example 1
1

Reading & Printing with gets() and puts()

Whole-line string I/O

scanf("%s", ...) stops reading at the first space โ€” useless for names like "John Smith." gets() reads an entire line, spaces and all, until Enter is pressed. puts() is its printing partner โ€” it prints a string and automatically adds a newline, so you don't need \n yourself.

Example 1 ยท gets_puts.c
gets_puts.c
C
#include <stdio.h>

int main() {
    char name[50];

    puts("Enter your full name:");
    gets(name);        // reads the WHOLE line, including spaces

    puts("Hello, welcome!");
    puts(name);        // prints the string + a newline automatically

    return 0;
}
terminal
output
Enter your full name:
Ravi Kumar Sharma
Hello, welcome!
Ravi Kumar Sharma
FunctionStops reading/printing atAdds newline?
scanf("%s", ...)First space or EnterNo
gets(name)Enter only (spaces are kept)No (but stores '\0' at the end)
puts(name)The string's '\0'Yes โ€” automatically
printf("%s", name)The string's '\0'No โ€” you must add \n
example 2
2

Why gets() Is Dangerous โ€” And the Safer Fix

fgets() as the modern replacement

Here's the problem hiding inside Example 1: gets() never checks how big the char array actually is. If the user types more characters than the array can hold, gets() keeps writing anyway โ€” straight past the end of the array, into memory that belongs to something else. This is called a buffer overflow, and it's exactly why modern C compilers refuse to even compile gets() without a warning (or at all).

char name[6] can only safely hold 5 letters + '\0' โ€” gets() doesn't check this
R
[0]
A
[1]
V
[2]
I
[3]
\0
[4]
?
[5]
โš ๏ธ Typing "RAVIKUMAR" into a 6-byte array writes 4 extra letters into memory that isn't part of the array at all โ€” silently corrupting whatever data happens to sit right after it. This is one of the most common real-world security bugs in C's history.
Example 2 ยท fgets_safe.c
fgets_safe.c
C
#include <stdio.h>
#include <string.h>

int main() {
    char name[6];   // deliberately small, to see the fix in action

    puts("Enter your name:");
    fgets(name, sizeof(name), stdin);   // NEVER writes past 6 bytes

    // fgets keeps the newline character โ€” strip it off
    name[strcspn(name, "\n")] = '\0';

    printf("Stored safely as: %s\n", name);

    return 0;
}
terminal โ€” typed "RAVIKUMAR"
output
Enter your name:
RAVIKUMAR
Stored safely as: RAVIK
๐Ÿ’ก fgets(name, sizeof(name), stdin) tells C the exact array size up front, so it simply stops reading once the array is full โ€” cutting the input short instead of overflowing. It's the same idea as passing a length parameter to array-processing functions, applied to string input.
example 3
3

toupper() and tolower() โ€” One Char at a Time

ctype.h functions

Here's something that surprises beginners: toupper() and tolower() don't work on strings at all โ€” they only work on a single char. To change a whole word's case, you loop through the char array box by box, applying the function to one letter at a time.

Example 3 ยท case_convert.c
case_convert.c
C
#include <stdio.h>
#include <ctype.h>
#include <string.h>

int main() {
    char word[50];

    puts("Enter a word:");
    fgets(word, sizeof(word), stdin);
    word[strcspn(word, "\n")] = '\0';

    printf("Single char: toupper('a') = %c\n", toupper('a'));
    printf("Single char: tolower('Z') = %c\n\n", tolower('Z'));

    // To convert a WHOLE word, loop over every char
    for (int i = 0; word[i] != '\0'; i++) {
        word[i] = toupper(word[i]);   // one box at a time
    }
    printf("UPPERCASE: %s\n", word);

    for (int i = 0; word[i] != '\0'; i++) {
        word[i] = tolower(word[i]);
    }
    printf("lowercase: %s\n", word);

    return 0;
}
terminal โ€” typed "Hello"
output
Enter a word:
Hello
Single char: toupper('a') = A
Single char: tolower('Z') = z

UPPERCASE: HELLO
lowercase: hello
โš ๏ธ for (int i = 0; word[i] != '\0'; i++) โ€” no fixed number here on purpose. The loop doesn't know or care how long the word is; it just keeps going until it hits the null terminator. This is the standard way every string-processing loop in C is written.
example 4
4

Proving It: Build Your Own strlen() and Reverse

Strings as plain char arrays

If a string really is just a char array, you should be able to write your own length-finder and reverser using nothing but array indexing โ€” no string library required. This is the clearest possible proof that "string functions" are just ordinary array loops with a special stopping condition.

Example 4 ยท manual_string_ops.c
manual_string_ops.c
C
#include <stdio.h>

int myStrlen(char str[]) {
    int count = 0;
    while (str[count] != '\0') {   // keep counting until the terminator
        count++;
    }
    return count;
}

void myReverse(char str[]) {
    int len = myStrlen(str);
    for (int i = 0; i < len / 2; i++) {
        char temp        = str[i];
        str[i]            = str[len - 1 - i];
        str[len - 1 - i] = temp;
    }
}

int main() {
    char text[] = "CODE";

    printf("Original : %s\n", text);
    printf("Length   : %d (built with myStrlen, not strlen)\n", myStrlen(text));

    myReverse(text);
    printf("Reversed : %s\n", text);

    return 0;
}
terminal
output
Original : CODE
Length   : 4 (built with myStrlen, not strlen)
Reversed : EDOC
๐Ÿ’ก The real strlen() from string.h does exactly this โ€” walk the array, count until '\0'. There's no hidden magic; the library function is just a well-tested version of the loop you just wrote yourself.
example 5
5

Bonus: Case-Insensitive Vowel Counter

Combining everything

One last program that uses everything from this lesson together: read a full line safely with fgets(), walk it char by char, normalize each character with tolower() so "A" and "a" count the same way, and stop naturally at the null terminator.

Example 5 ยท vowel_counter.c
vowel_counter.c
C
#include <stdio.h>
#include <ctype.h>
#include <string.h>

int main() {
    char sentence[100];
    int vowels = 0, consonants = 0;

    puts("Enter a sentence:");
    fgets(sentence, sizeof(sentence), stdin);
    sentence[strcspn(sentence, "\n")] = '\0';

    for (int i = 0; sentence[i] != '\0'; i++) {
        char c = tolower(sentence[i]);   // normalize case first

        if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') {
            vowels++;
        } else if (c >= 'a' && c <= 'z') {
            consonants++;
        }
        // spaces and punctuation are simply skipped
    }

    printf("Vowels     : %d\n", vowels);
    printf("Consonants : %d\n", consonants);

    return 0;
}
terminal โ€” typed "Hello World"
output
Enter a sentence:
Hello World
Vowels     : 3
Consonants : 7
FunctionHeaderWorks onPurpose
gets()stdio.hWhole stringUnsafe line input โ€” avoid
fgets()stdio.hWhole stringSafe, size-limited line input
puts()stdio.hWhole stringPrint + auto newline
toupper()ctype.hOne charConvert to uppercase
tolower()ctype.hOne charConvert to lowercase
quiz
Q

Quick Quiz

Question 1 of 5

What marks the end of a string stored in a char array?

Question 2 of 5

Why is scanf("%s", name) a poor choice for reading a full name like "John Smith"?

Question 3 of 5

Why is gets() considered dangerous?

Question 4 of 5

Why can't toupper() convert a whole string in one call?

Question 5 of 5

In a custom loop like while (str[count] != '\0'), what determines when the loop stops?

โœ“

Lesson Checklist

  • I understand a string is a char array ending in '\0'
  • I can use gets() and puts() and explain what each does
  • I understand why gets() can overflow a buffer
  • I can use fgets() as a safer alternative
  • I know toupper()/tolower() work on a single char, not a whole string
  • I can loop through a string to convert its whole case
  • I can write my own strlen() using array indexing
  • I can reverse a string manually using a char array
  • I completed the quiz