One String Is Never Enough

Every program so far held a single string in a single array. Real programs hold lists: the names in a class, the cities on a route, the words in a menu. C's answer reuses something you already have. A string is a char array; a list of strings is an array of char arrays, which is the two-dimensional array of chapter 7 with char as its element type. Exam papers call it a table of strings, and it is where the null terminator and the row-major layout meet.

The Declaration

char city[5][10];

Read it the way chapter 7 taught: five rows, ten columns, 5 * 10 = 50 contiguous bytes. What is new is the interpretation of a row. Each of the five rows is a ten-byte char array, and each holds one string with its own terminator. The second dimension is therefore not "the longest name" but the longest name plus one, the same capacity arithmetic as always, now paid five times over.

Initializing from string literals looks exactly like the list you would write on paper:

row 0: Chennai    length 7
row 1: Madras     length 6
row 2: Bombay     length 6
row 3: Delhi      length 5
row 4: Kolkata    length 7
city[2][0] is the character B
whole table: 50 bytes

Every row is padded: "Delhi" uses six of its ten bytes and the remaining four are set to '\0', because a partially initialized array has the rest zero-filled. Fifty bytes are spent regardless of what the names cost, which is the trade this structure makes.

One Subscript Names a String

This is the whole lesson in one line: city[i] is a string, city[i][j] is a character. Apply one subscript and you have named a row, which is a char array terminated by '\0', which is exactly what %s and every <string.h> function want. So printf("%s", city[i]) prints a name, strlen(city[i]) measures one, strcmp(city[i], city[j]) compares two, and none of them know or care that the row lives inside a larger table. Apply both subscripts and you have named one char, which is why city[2][0] printed with %c above.

The most common beginner error is printf("%s", city) for the whole table, or printf("%c", city[i]) for a row. Match the number of subscripts to what the conversion wants: %s takes one, %c takes two.

Reading a List

Reading a list is last lesson's bounded scanf inside a counting loop, with the row as the destination:

Type three words and they come back bracketed. The width is 9 because WIDTH is 10: capacity minus one, per row, exactly as for a single buffer. One honest wart, worth knowing before an exam asks: the width has to be written into the format string as a literal, so it cannot be spelled WIDTH - 1 and must be kept in step with the macro by hand. Guarding the read still matters, and now it matters per name, because a loop that stops early leaves the remaining rows uninitialized, and printing one of those with %s is undefined behaviour.

For lines containing spaces, fgets(word[i], WIDTH, stdin) takes a row just as happily, with the same newline to trim.

Sorting the List

The staple exam question is to put a list of names in alphabetical order, and it is chapter 7's bubble sort with two substitutions. The comparison becomes strcmp(...) > 0, because > on two rows would compare addresses. The swap becomes three strcpy calls through a temporary row, because arrays cannot be assigned with =:

Delhi
London
Manchester
Moscow
Paris

temp is declared char temp[WIDTH] so it can hold any row, terminator included, and the three copies are the same swap-through-a-temporary you wrote for integers, spelled in the only way arrays allow. Because strcmp orders by character code, the sort is lexicographic rather than dictionary order: in ASCII every uppercase letter sorts before every lowercase one, so "Zebra" lands before "apple". Examiners like that detail precisely because it looks wrong.

What This Structure Costs

Every row is WIDTH bytes whether it holds "Delhi" or "Manchester", so a table sized for its longest name wastes the difference on all the others, and a name one character too long has nowhere to go. That is the price of storing strings in a rectangle. The fix is an array of pointers, char *name[5], where each element points at a string of exactly its own length; it needs pointers, so it waits for chapter 11. Until then the rectangle is the tool, and sizing it is your job: longest expected name plus one.

Key Takeaways

  • A table of strings is a two-dimensional char array: char city[5][10] is five rows of ten bytes, and the second dimension must be the longest name plus one for its terminator.
  • Initializing from a list of string literals zero-fills the unused bytes of every row.
  • One subscript names a whole string, so city[i] goes straight to %s, strlen, and strcmp; two subscripts name one character for %c.
  • Read a list with the bounded scanf in a loop, scanf("%9s", word[i]) for ten-byte rows, guarding every read because an unfilled row printed with %s is undefined behaviour.
  • Sort with chapter 7's bubble sort, comparing with strcmp(a, b) > 0 and swapping with three strcpy calls through a temporary row, because arrays support neither > nor =.
  • strcmp orders by character code, so all uppercase sorts before all lowercase in ASCII.
  • Fixed-width rows waste the difference between the longest name and the rest; the array-of-pointers alternative arrives with pointers in chapter 11.