Table Driven Programming
CS 301 Lecture, Dr. Lawlor
The C++ "Array Initializer"
So C++ allows this handy "array initializer syntax" for setting up a 1D array:
const int arr[]={7,9,23};
(Try this in NetRun now!)
This makes an array, "arr", containing three ints: seven, nine, and twenty-three.
It's the same as declaring:
int arr[3];
arr[0]=7;
arr[1]=9;
arr[2]=23;
But clearly the initializer is shorter and simpler!
Whitespace inside an initializer doesn't matter, and you can even add comments and stuff in there:
const int arr[]={
7, /* dwarves, in decimal */
0x9, /* cube of three, expressed in hexadecimal */
027, /* number of angels that can dance on the head of a pin, in octal */
};
It's still the same three ints as before.
If you want to save memory, you can make a 1D
array of unsigned char (which are like int, but only 8 bits wide):
const unsigned char arr[]={7,9,23};
Table-Driven Programming
A little array that tells the program what to do is called a "table",
and programs written this way are called "table-driven". Here's a
simple table-driven program that prints out a certain number of "#"
signs on each line, with the exact number determined from a little
table:
const unsigned char table[]={
20,
20,
2,
2,
2,
20,
20,
0 /* end of the table */
};
int foo(void) {
int i=0; /* location in the table */
while (table[i]!=0) { /* print one entry in the table */
int n=table[i++]; /* number of times to print */
char c='#'; /* character to print */
for (int repeat=0;repeat<n;repeat++) /* print it n times */
std::cout<<c<<" ";
std::cout<<std::endl;
}
return 0;
}
(Try this in NetRun now!)
Here's a similar table-driven program that reads two entries in the
table each time around the loop. The first table entry is treated
as a repetition count, and the second table entry is the letter to
repeat. Again, the program stops when it hits a repetition count
of zero:
const unsigned char table[]={
/* --- n, character to print n times ---- */
3,'q',
2,'@',
4,'~',
1,'z',
0 /* end of the table */
};
int foo(void) {
int i=0; /* location in the table */
while (table[i]!=0) { /* print one entry in the table */
int n=table[i++]; /* number of times to print */
char c=table[i++]; /* character to print */
for (int repeat=0;repeat<n;repeat++) /* print it n times */
std::cout<<c<<" ";
}
return 0;
}
(Try this in NetRun now!)
Note that the above is just a nine-entry table; the relationship between 3 and 'q' is purely conceptual.
The big advantage of table-driven programming is "separation of concerns".
The table, and the code that walks through the table (the function
"foo" above), are completely separate pieces. They can be written
by different people. They can be updated independently.
They can be read from different sources. For example:
- In a game, the code used to make a game character do stuff (like
attack the player, or drool, or buy and sell stuff) is part of the
executable, written by the game engine company. The particular
stuff the character should do, and when they should do it, comes from
some sort of table, usually part of that level's map, and is read from
disk or downloaded from the network.
- The table can contain user interface strings from the program,
like "Please enter a number". To translate the program into
another human language, like German or Greek, you just have to change
the table to contain "Bitte geben Sie eine Zahl" or "Παρακαλώ εισάγετε
έναν αριθμό". This "look up user interface text from a table" idea is a key part of internationalization (i18n), as implemented in, e.g., GNU gettext.
- A short program can look up "the answer" in a table. The
answer can be the result of a much longer (slower, more
resource-intensive) program, or even human effort. This only
works if there are only a small possible number of questions.
I've used this method for problems like designing playoff tournaments;
big tournaments are regular (a big tree) and hence the general case is
easy, but small tournaments are tricky to write code for, so I just
encoded all the good small tournaments in a table!
I posted several other examples of table-driven code in the NetRun 301 Examples section.
The C++ "Switch" statement
I'm using "switch"
in some of my examples above, so I thought I'd explain this
syntax. In C++, if you have several values to test, you can use a
series of "if" statements:
int x=0; std::cin>>x; /* read what to do */
if (x==3) std::cout<<"Lucky three!\n";
else if (x==7) std::cout<<"Seven! Yes!\n";
else if (x==13) std::cout<<"NooooOOOO!!!!\n";
else std::cout<<"meh\n";
(Try this in NetRun now!)
If all the "if" statements are testing the same integral value, a "switch" does the same thing:
int x=0; std::cin>>x; /* read what to do */
switch (x) {
case 3: std::cout<<"Lucky three!\n"; break;
case 7: std::cout<<"Seven! Yes!\n"; break;
case 13: std::cout<<"NooooOOOO!!!!\n"; break;
default: std::cout<<"meh\n"; break;
}
(Try this in NetRun now!)
Often "switch" is faster than a nested block of "if" statements,
especially if there are many possibilities. But don't forget about the "break"!