CS50 - Review Section - Week 10


Command Line Arguments

Command line arguments are important to ``real'' programs. At last, we can begin to write programs that have the UNIX look and feel- they get their arguments from the commandline (instead of incessantly prompting the user) and therefore can be easily hooked together with other programs or scripts. Note that there is somewhat exhaustive coverage of this subject in the lecture notes and the textbook (especially K+R) so I'll just touch upon the high points in this handout.

The True Type of main

At last, we've revealed to you the real type of the main function, which you've been using all semester but which we never fully explained. Here it is, in all its glory:

int	main (int argc, char **argv);

(Note that on some older systems, main has a slightly different type, which you might encounter if you look at older code or go sightseeing in odd places. I won't say any more about this now, to save the surprise for later.)

The getopt Function

Note the use of the getopt function (which is shown in the lecture notes). getopt makes handling command line arguments relatively easy, and may be useful for your final projects. It will certainly be useful for assignment 8! The typical use of getopt is to call it in a loop, repeatedly calling getopt until it reports that there are no more options. getopt starts at the beginning of the command line and "remembers" where it has been, and so each time you call it, it gives you the "next" option (or tells you that it has processed all of them). This is not a particularly elegant interface (you can confuse getopt completely in a few ways, such as noodling around with argv in between calls, thus foiling its attempts to keep track of what it has and hasn't seen yet), but as long as you behave yourself, getopt works well.

Here's the typical sort of usage:

	...

	int		opt;
	char		*options	= "x:yz:w";
	extern	char	*optarg;
	extern	int	optind, opterr;

	/*
	 * Loop through the commandline (using getopt to do all the
	 * real work) until getopt returns -1 to indicate that it has
	 * consumed all the optional arguments.  On each iteration,
	 * the variable opt is set to
	 */

	while ((opt = getopt (argc, argv, options)) != -1) {
		switch (opt) {
			case 'x'	:

				/*
				 * optarg is the value of the x param
				 */

				break;
			case 'y'	:
				...
		}
	}

	...

Note that opt must be an int, because it has to be able to be any valid character or -1. In order to be able to represent this "extra" sentinel value, we need more space than just a char could provide.

In this case, our program has four optional arguments: -x and -z (which take parameters, as specified by the : that follows their name in the options string), and -w and -y (which do not take parameters).

This means that the following are some of the possible commandlines that this getopt will accept (assuming that this is in a program named "hello"):

	hello
	hello -x dan
	hello -xdan		# equal to the previous
	hello -y -x dan
	hello -yxdan		# equal to the previous
	hello -xdan -y		# equal to the previous
	hello -yw
	hello -wy		# equal to the previous
	hello -y -w		# equal to the previous

getopt uses three externally defined global variables to communicate its finding with the caller:

optarg

The string that represents the value of the optional argument (for those arguments that require values).

optind

The index into the argv of the element that getopt will consider next. Note that since more than one optional argument can be specified by a single string in argv, this index doesn't necessarily change every time you call getopt. For example, in the previous code snippet:

	hello -wyxdan
	

This would take three calls to getopt to get through the string "-wyxdan", since this represents three options!

Similarly, a single option might consume more than one element of argv:

	hello -x dan
	

Here, because the "-x" flag takes an additional argument, optind will be incremented by two.

The moral is that you should let getopt tell you what element of argv it is chewing on, rather than trying to figure this out on your own just by counting how many times you've called getopt and/or what options you've seen- it's more complicated than you want to know.

opterr

By default, if the user specifies an illegal commandline, then getopt will bark at the user. If you don't want this behavior, but instead want to handle the error in your own code, with or use a less haughty error message, then set opterr to zero.


File I/O: stdio

Like command line arguments, file I/O is very important to ``real'' programs; even though we haven't done all that much with file I/O in this course, if you're planning on doing more programming, you're going to see this stuff again.

This subject is covered somewhat in the Roberts book, but much more throughly in K+R and the online manual pages.

FILE Pointers

The abstraction of files provided by the standard I/O library (stdio) is implemented in a structure called FILE. Almost all of the stdio functions take a pointer to one of these structures as one of their parameters (either explicitly or implicitly). The main exception is fopen, which is used to get one of these pointers in the first place.

UNIX gives your programs three default FILE pointers, just for showing up:

Here is list of some of the more popular functions:

fopen and fclose

Open or close a file.

fgetc and fputc

Read or write a single character.

An important note about fgetc (that pops up in a few other places): although fgetc reads a char, it returns an int. This is esential for the following reason- if fgetc just returned a char, then what value could it return to indicate to you that something had gone wrong? Every possible return value (any member of the set of possible chars) would correspond to something that might actually be in a file. Therefore, in order to make it possible to also return error codes, fgetc returns an int. If the value is 0..255 (for 8-bit chars) then you know that fgetc succeeded, but if it is outside this range, then something else happened.

For most implementations of fgetc that I am aware of, there's only one error code, which is EOF (which is returned when you attempt to read past the end of the file, or some other error occurs), other functions use a similar philosophy and return different things.

Note that there are several related functions and macros: getchar, getc, putchar, and putc. These can be useful, and you'll see them a lot (particularly in older code) but fgetc and fputc are all you really need.

fgets and fputs

Read or write a line of text. Assumes that the file is text. (Note- fputs does not add a newline, so if you want there to be one, you must add it yourself)

There is another function named gets which is similar to fgets, but notoriously dangerous to use. (It doesn't check its arguments, nor does it check that what the user types is valid, and so can permit the user to scribble all over memory.) I suggest that you never use it.

fread and fwrite

Read or write "blocks" of data. Useful for reading or writing an entire array or structure, or any arbitrary (but specific) amount of data.

fscanf and fprintf

Read or write data according to given format. fprintf is almost exactly like printf, but prints to a file instead of the screen.

fscanf is sort of like fprintf, only backwards. It can be tricky to use; one simplification to help do formatted reading is to use fgets to read a line of text and then use sscanf (a relative of fscanf that scans strings instead of files) instead of using fscanf.

fseek and ftell

Move around in a file, or find out where you are in a file.

The rewind function is a special case of the fseek that can be used to rewind to the beginning of a file. (Note that some files can't be rewound- you'd need a time machine to rewind stdin, for example!) This can be handy on assignment 8.

fflush

Flush changes (make them happen immediately).

feof and ferror

Determine if the End Of File has been reached, or some error encountered.


Assignment 8

For this assignment, you must complete two programs: labyrinth and 800.

labyrinth

labyrinth is a maze-solving program. To finish this program, you only need to write two functions (although you may or may not find it useful to write additional functions that you use as helper functions for the functions that you must write):
parse_args

In labyrinth.c you must complete the parse_args function in order to allow your labyrinth to parse commandline arguments- and since this program has several important options, it's pretty helpless until you write this function.

See labyrinth.c for more info; the options that you need to recognize are documented there. When in doubt as to what your program should do with regards to commandline options, use sol_labyrinth as a model.

laby_solve

In laby_solve.c you must complete the laby_solve function in order for your labyrinth program to actually do what it's supposed to do- solve mazes.

This function returns SOLVABLE if the labyrinth can be solved (there is a path from the start position to the finish position) or UNSOLVABLE if no such path exists. In addition, if there is a path, this function specifies one possible path.

I will not give you any hints about what algorithm to use to accomplish this goal, beyond the following:

One amusement that you can add to your program is an "animation" of the process of your program attempting to solve each maze. Descriptions for how to do this are given in labyrinth.txt. It makes your program a lot of fun to watch. Remember to use the sleep_milli function to pause after each step, however, or else the whole thing will flash by too quickly to enjoy.

Also note that a bunch of the routines that implement the "graphics" mode are provided for you to browse through and have (hopefully) been written in a manner so that they should be relatively easy to snip out of this code and reuse as a model for many different sorts of text-base games or displays. See laby_win.c and curses_utils.c for more information. Some of this info could be quite useful on final projects.

800

800 is a program that reads in a sorted database of phone numbers and then allows the user to look for entries that have "interesting" telephone numbers (for example, telephone numbers that spell things or consist of amusing sequences of numbers, if you are amused by such things).

I think this program is well-described in the assignment, but here are some details that have come up since then:


Please bring any errors or inconsistencies in this document to the attention of the author.

Dan Ellard - (ellard@deas.harvard.edu)