§ 01 — Computer Programming 2
C has no separate "string" type. A string is simply a char array whose last used slot holds the null character '\0' (the character with value 0). Every string function, and printf with %s, reads characters until it reaches that '\0'. It is the only way C knows where the text stops.
char name[6] = "Maria"; /* 5 letters + '\0' = 6 slots */
char name2[] = "Maria"; /* the compiler counts: also 6 */
char city[20] = "Cebu"; /* 4 letters, '\0', then 15 more '\0' */
When you write a string literal in double quotes, the compiler adds the '\0' for you. Your job is to leave room for it: a string of n characters needs an array of at least n + 1.
How "Maria" sits in a 6-slot array:
'A' is not "A"'A' (single quotes) is one character, a small integer (65 in ASCII)."A" (double quotes) is a string: two characters, 'A' and '\0'.Use single quotes with char variables and comparisons like s[i] == 'a'. Use double quotes for text.
#include <stdio.h>
#include <string.h>
int main(void) {
char name[] = "Maria";
char city[20] = "Cebu";
int i;
printf("%s from %s\n", name, city);
printf("strlen(name) = %d, sizeof name = %d\n", (int)strlen(name), (int)sizeof name);
printf("strlen(city) = %d, sizeof city = %d\n", (int)strlen(city), (int)sizeof city);
for (i = 0; name[i] != '\0'; i++)
printf("[%c]", name[i]);
printf("\n");
return 0;
}
Output:
Maria from Cebu
strlen(name) = 5, sizeof name = 6
strlen(city) = 4, sizeof city = 20
[M][a][r][i][a]
strlen counts the characters before the '\0'. It answers "how long is the text?"sizeof gives the size of the whole array in bytes, '\0' and unused slots included. It answers "how much room is there?"for (i = 0; name[i] != '\0'; i++) is the standard way to visit every character. It works for any length, so you never need to know the size in advance.The (int) casts let you print both values with %d on every compiler. (C99 also has %zu for these values, but some older Windows compilers do not support it.)
A string is still an array, so you can change one character by its index:
#include <stdio.h>
int main(void) {
char section[] = "BSIT-1A";
section[6] = 'B';
printf("%s\n", section);
return 0;
}
Output:
BSIT-1B
printf("%s", name) prints the characters up to the '\0'.puts(name) does the same and adds a newline at the end.%-10s prints the text left-aligned in a 10-character column, which is handy for tables.scanf#include <stdio.h>
int main(void) {
char first[20];
printf("First name: ");
scanf("%19s", first); /* no & : first is already an address */
printf("Hi, %s!\n", first);
return 0;
}
Sample run (you type Juan dela Cruz):
First name: Juan dela Cruz
Hi, Juan!
Three things to notice:
& before the array name. An array name already gives the address of its first element, which is exactly what scanf needs.%s stops at the first space. Only Juan was stored. The rest of the line stays waiting in the input.%19s is a limit. It reads at most 19 characters, leaving room for the '\0' in a 20-slot array. A plain %s keeps writing past the end of the array if the user types a long word.fgetsTo read text with spaces, such as a full name or an address, use fgets:
#include <stdio.h>
#include <string.h>
int main(void) {
char full[40];
printf("Full name: ");
if (fgets(full, sizeof full, stdin) != NULL) {
full[strcspn(full, "\n")] = '\0'; /* remove the Enter key */
printf("Hello, %s! (%d characters)\n", full, (int)strlen(full));
}
return 0;
}
Sample run (you type Juan dela Cruz):
Full name: Juan dela Cruz
Hello, Juan dela Cruz! (14 characters)
fgets(full, sizeof full, stdin) reads one line, spaces included, but never more than sizeof full - 1 characters, so it cannot overflow the array.strcspn(full, "\n") finds the position of that newline (or of the '\0' if there is none), and storing '\0' there removes it. Without this step the newline would end up in the middle of your output.NULL when there is nothing left to read, which is why the call sits inside an if.Never use gets. It has no size limit at all, so any long input overflows the array. It was removed from the C language in 2011, and modern compilers warn when you use it.
If you read a number with scanf("%d", ...) and then a line with fgets, the fgets seems to be skipped. scanf leaves the Enter key's newline in the input, and fgets reads that empty line immediately. Clear the rest of the line before calling fgets:
#include <stdio.h>
#include <string.h>
int main(void) {
int age, ch;
char name[40];
printf("Age: ");
scanf("%d", &age);
while ((ch = getchar()) != '\n' && ch != EOF)
; /* throw away the rest of the line */
printf("Name: ");
if (fgets(name, sizeof name, stdin) != NULL) {
name[strcspn(name, "\n")] = '\0';
printf("%s is %d years old\n", name, age);
}
return 0;
}
Sample run (you type 19, then Ana Reyes):
Age: 19
Name: Ana Reyes
Ana Reyes is 19 years old
Because a string is an array, you cannot copy, join or compare strings with =, + or ==. The standard header <string.h> gives you functions for these jobs.
| Function | What it does | Watch out for |
|---|---|---|
strlen(s) | Number of characters before the '\0' | Does not count the '\0' |
strcpy(dest, src) | Copies src into dest, '\0' included | dest must be big enough |
strcat(dest, src) | Adds src to the end of dest | dest needs room for both strings plus '\0' |
strcmp(a, b) | Compares alphabetically: negative, 0, or positive | 0 means equal |
strncpy(dest, src, n) | Copies at most n characters | May leave dest without a '\0' |
strncat(dest, src, n) | Adds at most n characters, then a '\0' | n counts only the added characters |
strchr(s, c) | Finds the first c in s | Returns a pointer, or NULL if not found |
#include <stdio.h>
#include <string.h>
int main(void) {
char first[20] = "Jose";
char last[] = "Rizal";
char full[40];
strcpy(full, first); /* full = "Jose" */
strcat(full, " "); /* full = "Jose " */
strcat(full, last); /* full = "Jose Rizal" */
printf("%s (%d)\n", full, (int)strlen(full));
printf("%d\n", strcmp("apple", "apple"));
printf("%d\n", strcmp("apple", "banana") < 0);
printf("%d\n", strcmp("Zebra", "apple") < 0);
return 0;
}
Output:
Jose Rizal (10)
0
1
1
strcmp decidesstrcmp compares the two strings character by character, using their character codes, until it finds a difference or reaches the end:
strcmp(a, b) == 0, not if (strcmp(a, b)), which is true when they are different.-1 and another -15, so always compare the result with 0."Zebra" comes before "apple".strncpy trapstrncpy(dest, src, n) copies at most n characters. If src is longer than that, it does not add the '\0'. Add it yourself:
#include <stdio.h>
#include <string.h>
int main(void) {
char code[4];
strncpy(code, "BSIT-1A", sizeof code - 1);
code[sizeof code - 1] = '\0'; /* strncpy did not add it */
printf("%s\n", code);
return 0;
}
Output:
BSI
snprintfsnprintf works like printf, but writes into an array and never writes more than the size you give it, terminator included:
#include <stdio.h>
int main(void) {
char full[12];
snprintf(full, sizeof full, "%s %s", "Andres", "Bonifacio");
printf("%s\n", full);
return 0;
}
Output:
Andres Boni
The full name needs 16 characters plus '\0', but full has only 12 slots, so snprintf stored the first 11 characters and the '\0'. The text was cut short, but nothing outside the array was touched. With strcpy and strcat, the same mistake would overwrite memory that is not yours.
A char is a small integer: the code of a character. In ASCII, 'A' is 65, 'a' is 97, and the digit '0' is 48. That means you can do arithmetic with characters:
'A' + 2 is 'C', and lowercase is always 32 more than uppercase ('a' - 'A' is 32).'7' - '0' is the number 7. This is how you turn a typed digit into a value.<ctype.h> functionsInstead of memorizing codes, use the standard character functions. The tests return non-zero (true) or 0 (false). They do not promise exactly 1, so use them in an if, never compare them with 1.
| Function | True when the character is... |
|---|---|
isalpha(c) | a letter, A to Z or a to z |
isdigit(c) | a digit, 0 to 9 |
isalnum(c) | a letter or a digit |
isspace(c) | a space, tab, or newline |
isupper(c) / islower(c) | an uppercase / lowercase letter |
ispunct(c) | punctuation, such as , - ! |
toupper(c) and tolower(c) return the converted character. Anything that is not a letter comes back unchanged, so toupper('5') is still '5'.
#include <stdio.h>
#include <ctype.h>
int main(void) {
char s[] = "BSIT 1-A, Room 204";
int i, letters = 0, digits = 0, spaces = 0, others = 0;
for (i = 0; s[i] != '\0'; i++) {
if (isalpha(s[i]))
letters++;
else if (isdigit(s[i]))
digits++;
else if (isspace(s[i]))
spaces++;
else
others++;
}
printf("letters %d, digits %d, spaces %d, others %d\n",
letters, digits, spaces, others);
for (i = 0; s[i] != '\0'; i++)
s[i] = toupper(s[i]); /* change the string in place */
printf("%s\n", s);
return 0;
}
Output:
letters 9, digits 4, spaces 3, others 2
BSIT 1-A, ROOM 204
The else if chain puts every character in exactly one group, so the four counts add up to strlen(s), which is 18.
#include <stdio.h>
#include <ctype.h>
int main(void) {
char text[] = "2025";
int i, value = 0;
for (i = 0; text[i] != '\0'; i++)
if (isdigit(text[i]))
value = value * 10 + (text[i] - '0');
printf("%d\n", value + 1);
return 0;
}
Output:
2026
Each step shifts the number one place left (* 10) and adds the new digit: 2, then 20, 202, 2025. Subtracting '0' is what turns the character '5' into the number 5. Forget it and you add 53 instead (the code of '5').
The standard library can do this for you: atoi("2025") from <stdlib.h> returns 2025. Professors still ask for the loop, because it shows you understand character codes.
Exams often say "without using <string.h>". That is a test of whether you understand the '\0'. Every function below follows the same plan: walk the string with an index until you reach '\0'.
#include <stdio.h>
#include <ctype.h>
int my_strlen(const char s[]) {
int n = 0;
while (s[n] != '\0')
n++;
return n;
}
void my_strcpy(char dest[], const char src[]) {
int i = 0;
while (src[i] != '\0') {
dest[i] = src[i];
i++;
}
dest[i] = '\0'; /* copy the terminator too */
}
void reverse(char s[]) {
int i = 0, j = my_strlen(s) - 1;
char temp;
while (i < j) { /* swap the ends, move inward */
temp = s[i];
s[i] = s[j];
s[j] = temp;
i++;
j--;
}
}
int count_words(const char s[]) {
int i, words = 0, in_word = 0;
for (i = 0; s[i] != '\0'; i++) {
if (isspace(s[i])) {
in_word = 0; /* a space ends the current word */
} else if (!in_word) {
in_word = 1; /* first letter of a new word */
words++;
}
}
return words;
}
int main(void) {
char word[20];
my_strcpy(word, "Mabuhay");
printf("%s has %d letters\n", word, my_strlen(word));
reverse(word);
printf("Reversed: %s\n", word);
printf("%d words\n", count_words(" Tara na, kain tayo "));
return 0;
}
Output:
Mabuhay has 7 letters
Reversed: yahubaM
4 words
my_strlen counts until it sees '\0'. The count is also the index where the '\0' sits.my_strcpy copies character by character, then adds the terminator. The loop stops at the '\0' without copying it, so the last line is required. Leave it out and dest is not a string, and printing it runs on into whatever memory comes next.reverse is the reverse-in-place pattern from Lesson 1, applied to characters. The right end starts at my_strlen(s) - 1, the last real character, not at the '\0'. If you swapped the '\0' to the front, the string would become empty.count_words cannot just count spaces: there can be several spaces in a row, and spaces at the start and the end. Instead it counts the moments a word starts, which is a non-space character that comes right after a space (or at the very beginning). The in_word flag remembers whether we are inside a word.const on string parametersmy_strlen and count_words only read their string, so their parameter is const char s[]. That lets you pass string literals like "Mabuhay" safely, and the compiler stops you if the function tries to change the text. my_strcpy writes to dest, so only src is const.
Almost every string bug comes down to one of these. Some are caught by the compiler. The dangerous ones compile fine and fail at run time, or only on someone else's computer.
'\0'char code[4] = "BSIT"; /* compiles, but there is no '\0' */
char ok[5] = "BSIT"; /* right: 4 letters + '\0' */
C allows the first line: when the letters exactly fill the array, the terminator is simply left out. Newer compilers warn about it, but older ones say nothing. code is then not a string, and printf("%s", code) or strlen(code) keeps reading past the end of the array. That is undefined behavior: you may see garbage after BSIT, or nothing wrong at all until it matters. The easy fix is to let the compiler count: char code[] = "BSIT";.
=char name[20];
name = "Ana"; /* compile error: an array cannot be assigned */
strcpy(name, "Ana"); /* right */
= with a string literal only works in the declaration (char name[20] = "Ana";). After that, use strcpy, or snprintf when the size might not fit.
==#include <stdio.h>
#include <string.h>
int main(void) {
char pass[20] = "admin123";
char typed[20] = "admin123";
printf("== says %d\n", pass == typed);
printf("strcmp says %d\n", strcmp(pass, typed) == 0);
return 0;
}
Output:
== says 0
strcmp says 1
pass == typed compares the two arrays' addresses, and two different arrays never share an address, so it is always false even when the text matches. Compilers usually warn about it. Compare text with strcmp(a, b) == 0.
strcpy, strcat, and scanf("%s") copy until the source ends, no matter how small the destination is. Before you copy, ask: does the destination have room for every character plus the '\0'?
scanf("%19s", name) for char name[20].fgets(name, sizeof name, stdin).snprintf(dest, sizeof dest, ...).'\0' when you build a string yourselfWhen you fill a char array one character at a time, the loop never writes a terminator unless you do:
#include <stdio.h>
int main(void) {
char digits[11];
int i;
for (i = 0; i < 10; i++)
digits[i] = '0' + i;
digits[10] = '\0'; /* without this, digits is not a string */
printf("%s\n", digits);
return 0;
}
Output:
0123456789
char *p = "hello"; /* p points to a literal, which is read-only */
char s[] = "hello"; /* s is your own copy, which you may change */
s[0] = 'H'; /* fine */
Writing p[0] = 'H'; compiles, but changing a string literal is undefined behavior, and on most computers the program crashes. If you need to change the text, declare a char array. (Lesson 3 explains the difference between char *p and char s[].)
scanf("%d") followed by fgetsfgets returns an empty line because scanf left the Enter key in the input. Clear the rest of the line first, as shown in the Reading and Printing Strings section.
char array that ends with '\0'. A string of n characters needs at least n + 1 slots, and 'A' (one character) is not "A" (two: 'A' and '\0').strlen counts the characters before the '\0'; sizeof gives the whole array's size. Walk any string with for (i = 0; s[i] != '\0'; i++).scanf("%19s", name) reads one word with no &; fgets(name, sizeof name, stdin) reads a whole line and keeps the newline, which name[strcspn(name, "\n")] = '\0'; removes. Never use gets.strcpy, join with strcat (or snprintf), and compare with strcmp(a, b) == 0. Never = or ==, and always check that the destination has room for the text plus '\0'.<ctype.h> tests them (isalpha, isdigit, isspace) and converts them (toupper, tolower), and c - '0' turns a digit character into its value.Put your own name in first and last, then predict the length, the uppercase version and the reversed name before you press Run.
#include <stdio.h>
#include <string.h>
#include <ctype.h>
int main(void) {
char first[20] = "Juan";
char last[20] = "dela Cruz";
char full[40];
char upper[40];
int i, n;
snprintf(full, sizeof full, "%s %s", first, last);
n = (int)strlen(full);
printf("Full name: %s (%d characters)\n", full, n);
for (i = 0; full[i] != '\0'; i++)
upper[i] = toupper(full[i]);
upper[i] = '\0';
printf("Uppercase: %s\n", upper);
printf("Reversed: ");
for (i = n - 1; i >= 0; i--)
printf("%c", full[i]);
printf("\n");
if (strcmp(first, last) < 0)
printf("\"%s\" comes before \"%s\"\n", first, last);
else
printf("\"%s\" comes after \"%s\"\n", first, last);
return 0;
}Pro3 reviewers locked
Part of this subject's paid reviewers. Not included in this printout.
Done with this module? Track it — your progress shows on the subject list.
Studying with blockmates? Send them this lesson.
Up next
Lesson 3: Pointers→←Previous: Lesson 1: Arrays in Depth