
There is no string type in C. There is char, and there are arrays, and a string is an array of char with a 0 after the last character so that anyone reading it knows where it stops. Everything about strings follows from that one convention - what the library functions do, why they need to be told nothing about length, why comparing two with == compiles and is wrong, and why S15's overrun crashed the machine.
char name[] = "Einstein";
Nine bytes, not eight: E i n s t e i n and then a 0. Print them as numbers and you see 69 105 110 115 116 101 105 110 0. The 0 is not the character '0' (which is 48); it is the byte value zero, written '\0' or just 0, and the compiler puts it there for you when you write a string in quotes. strlen counts up to it and returns 8. Every library function that takes a string finds the end the same way, which is why none of them needs a length.
The array must have room for the 0. A string of eight letters needs char[9]. Forget the 0 and every function that reads the string runs on past the end into whatever is next.
name above is an ordinary array, and you may change it: name[0] = 'e' makes it einstein. Being global, it may be given its value in the declaration; a local char array may not (S15), and gets its string from strcpy or gets instead.
All in STDIO.LIB, all ending at the 0:
| Call | Does |
|---|---|
strlen(s) | the number of characters before the 0 |
strcpy(to, from) | copies from into to, 0 included |
strcat(to, from) | copies from onto the end of to |
strcmp(a, b) | 0 if they are the same; otherwise the difference between the first characters that differ |
strchr(s, c) | the address of the first c in s, or 0 if there is none |
toupper(c) | the capital of a lower-case letter; other characters unchanged |
strcpy(buf, "Tatung ") then strcat(buf, "Einstein") leaves Tatung Einstein in buf, fifteen characters and the 0. buf has to be big enough: char buf[40] here, and nothing checks.
strcmp is the one to read carefully. It returns 0 when the strings match, which is false in an if, so the test for equality is if (strcmp(a, b) == 0). When they differ it returns the difference of the first pair of characters that do: strcmp("apple", "banana") is -1 (a is one less than b), strcmp("pear", "apple") is 15 (p minus a). Negative means the first sorts earlier; that is all the number is for.
strchr returns a pointer (S16) to the character inside the string, so printing it with %s prints from there to the end: strchr(buf, 'E') on Tatung Einstein gives Einstein.
== Does Not Worka == b on two char * compares the addresses, not the characters. Two strings in different places are never equal to ==, however alike their letters. And they are in different places even when you might expect otherwise:
a = "same";
b = "same";
a == b is 0. The compiler made two copies of "same", and changing one - a[0] = 'X' - leaves the other alone: Xame same. So compare strings with strcmp, and never assume two quotations of the same text share storage.
The library is short, and anything it lacks is a loop. toupper works on one character, so upper-casing a string is:
for (i = 0; buf[i]; i++)
buf[i] = toupper(buf[i]);
buf[i] as the loop's test is true until it reaches the 0 - the idiom S16's while (*s) used. Counting a character, replacing one, finding the last word: the same loop with a different body.
S15 showed the crash: strcpy of "Einstein" into char small[4]. The copy went where it was told, eight letters and a 0 into four bytes, and the five that did not fit went into whatever came next. The program printed everything correctly and then fell into the monitor on its way out, because among the things that came next was the address it needed to return to.
So for every char array, ask what the longest string will be and add one. For gets, that is the width of the screen plus one, and the course uses 40 for a line. For a name the player types, the same. For a string you build with strcat, the sum of the parts plus one. There is no message when you get this wrong, and the crash will not be at the line that did it.
#include STDIO.H
char name[] = "Einstein";
main()
{
char buf[40];
int i;
printf("%s has %d letters\n", name, strlen(name));
for (i = 0; i <= 8; i++)
printf("%d ", name[i]);
printf("\n");
strcpy(buf, "Tatung ");
strcat(buf, name);
printf("%s, %d letters\n", buf, strlen(buf));
printf("%d ", strcmp("apple", "banana"));
printf("%d ", strcmp("pear", "pear"));
printf("%d\n", strcmp("pear", "apple"));
printf("%s\n", strchr(buf, 'E'));
for (i = 0; buf[i]; i++)
buf[i] = toupper(buf[i]);
printf("%s\n", buf);
name[0] = 'e';
printf("%s\n", name);
}
#include ?STDIO.LIB?
Starting from
Typed as STR.C; compiled and run.
What you should see
Einstein has 8 letters
69 105 110 115 116 101 105 110 0
Tatung Einstein, 15 letters
-1 0 15
Einstein
TATUNG EINSTEIN
einstein
char buf[40] to char buf[8]. Compile and run. What is printed, and what happens after? Restart the Einstein.i <= 8 to i <= 12. What are the extra numbers?strchr(buf, 'E') to strchr(buf, 'Z'), and predict what %s will do with the result before you run it.17.1 Write count(s, c) that returns how many times the character c occurs in the string s.
17.2 Ask for a line with gets and print it with every space replaced by _.
17.3 Write ends(s, c) that returns 1 if the string s ends with the character c and 0 if not, using strlen.
Worked solutions are in Appendix II.
| Symptom | Cause |
|---|---|
| Two strings that look the same are never equal | == compares addresses. Use strcmp(a, b) == 0. |
if (strcmp(a, b)) takes the branch when they are different | strcmp returns 0 for a match, and 0 is false. |
%s prints the string and then rubbish | No 0 at the end. The array is one too short, or you filled it yourself and forgot. |
| A crash on the way out of a function, after correct output | A string was copied into an array too small for it. |
RESTRICTION: use assignment or blt() to initialise automatics | char s[] = "..." inside a function. Make it global, or strcpy into it. |
%s given a pointer that strchr returned as 0 | The character was not there. Test for 0 first. |
A string is chars ending in 0, one byte longer than its letters. strlen, strcpy, strcat, strcmp (0 means equal), strchr (a pointer, or 0) and toupper; a loop for anything else. == compares addresses, and identical quoted strings are separate. Nothing checks that a copy fits.
S18, Structures - several values of different types under one name: a ball with a position and a direction, a bat with a position and a width, and what this compiler will and will not do with them.