Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Advanced C 1992

.pdf
Скачиваний:
92
Добавлен:
17.08.2013
Размер:
4.28 Mб
Скачать

Special Pointers and Their Use

C C C

 

C4C

 

C C C

 

C

if (argc <= 2)

{

GiveHelp(argc, NULL); return(16);

}

_splitpath(argv[0], szDrive,

szDir,

szFname,

szExt);

strncpy(szProgram, szFname, sizeof(szProgram) - 1);

for (i = 1; argv[i]; i++)

{

if (argv[i][0] == '/' || argv[i][0] == '-')

{/* You have an argument, convert to lowercase, and test. */ pszTemp = strlwr(argv[i]);

for (j = 1; j < strlen(pszTemp); j++)

{

switch(pszTemp[j])

{

case ARG_LEFT: nJustification = LEFT; break;

case ARG_RIGHT: nJustification = RIGHT; break;

case ARG_JUSTIFY: nJustification = JUSTIFY; break;

case ARG_HELP: GiveHelp(NOINNAME, NULL); exit(4);

break;

continues

105

Part I • Honing Your C Skills

Listing 4.2. continued

case ARG_SLASH: case ARG_DASH: break;

default:

GiveHelp(BAD_OPTION, &pszTemp[j]); break;

}

}

}

else

{/* Either a filename or width. */ switch(nCurrentParameter)

{

case INNAME:

strcpy(szInputFile, argv[i]); nCurrentParameter = OUTNAME; break;

case OUTNAME:

strcpy(szOutputFile, argv[i]); nCurrentParameter = WIDTH; break;

case WIDTH:

sscanf(argv[i], "%d", &nTempWidth);

if (nTempWidth < 20 || nTempWidth > 128)

{

GiveHelp(BAD_WIDTH, NULL);

}

else

{

nLineWidth = nTempWidth;

}

nCurrentParameter = LAST_THING;

break;

106

Special Pointers and Their Use

C C C

 

C4C

 

C C C

 

C

default:

GiveHelp(BAD_PARM, NULL); break;

}

}

}

if (nCurrentParameter < WIDTH)

{/* Didn't get two filenames! */ GiveHelp(NAME_MISSING, NULL); return(16);

}

printf("\n");

printf(

"%s would read the file '%s' and write the file '%s'\n\n", szProgram,

szInputFile,

szOutputFile);

switch(nJustification)

{

case LEFT:

printf("The lines would be %d characters long, left \ aligned\n",

nLineWidth);

break;

case RIGHT:

printf("The lines would be %d characters long, right \ aligned\n",

nLineWidth);

break;

case JUSTIFY:

printf("The lines would be %d characters long, justified\n", nLineWidth);

break;

}

continues

107

Part I • Honing Your C Skills

Listing 4.2. continued

/* In the final version of this program, the files would

*be opened next and the input file would be read into a buffer,

*formatted according to the wishes of the user, and written

*to the output file. At the end, the files would be closed,

*and perhaps some statistical information could be

*presented to the user.

*/

return (0);

}

 

void

GiveHelp(

int

nLevel,

char

*psItem)

{

printf("\n");

switch(nLevel)

{

case NOINNAME:

case NOOUTNAME: // Not enough parameters! printf(

"FORMAT [-r|-l|-j] inputfile outputfile width\n"

"where \n"

"Options - -r (or /r) to right align \n"

"-l (or /l) to left align \n"

"-j (or /j) to justify\n"

"\n"

"inputfile - is the input file name \n"

"outputfile - is the output file name \n" "\n"

"width is the desired output width (20 to 128)\n"

"(default is 80 characters).\n"

"\n"

"Note: lines are concatenated, paragraph breaks are\n"

"signaled with a blank line\n\n");

break;

108

Special Pointers and Their Use

case BAD_WIDTH: printf(

"The width parameter must be between 20 and 128!\n" "the width is ignored\n");

break;

case BAD_PARM:

printf("Excessive parameters have been entered\n");

/* Force a display of full help! */

GiveHelp(NOINNAME, NULL); break;

C C C

C4C C

C C C

case BAD_OPTION:

printf("'%c' is an invalid option! (Use only -l, -r or -j)\n", *psItem);

break;

case NAME_MISSING:

printf("One or both of the required file names is missing!\n");

/* Force a display of full help! */

GiveHelp(NOINNAME, NULL); break;

default:

printf(

"An unspecified error occurred! FORMAT has ended!\n" );

exit(16);

break;

}

}

109

Part I • Honing Your C Skills

This isn’t so hard, is it? You have three possible options in JUSTIFY. You don’t check to see whether one of these options has been entered—you just accept the last one entered. You can set a flag, and if too many options are entered or there are conflicting options, warn the user. The syntax of JUSTIFY shows the following:

1.The filenames (input and then output) and the width must be entered in that order.

2.The options must be preceded by either a slash (/) or a dash (-) option flag. One or more options can follow the option flag.

3.The options can be entered anywhere in the command line, before, after, or interspersed with other parameters.

4.The filenames must be entered; the width, however, is optional.

5.The GiveHelp() function is recursive—it calls itself to give the command syntax for some errors.

You can use JUSTIFY as a shell to create almost any simple utility program by changing what it expects for files, parameters, and options.

So that you have a better understanding of what JUSTIFY does with the command line arguments, let’s look at several parts of the program in detail. First, you check to see that there are at least two command line arguments. Because both an input and an output filename are required, if there are not two arguments, one (or both) of these is missing. This test isn’t exhaustive—you must test again later to make sure that you have received two filenames and not just a lot of options. The test for the number of arguments is simply:

if (argc <= 2)

{

GiveHelp(argc, NULL); return(16);

}

The return’s value is passed back to the operating system, and if this program was run under DOS, the value can be tested using the DOS BATCH command if

errorlevel command.

After you have checked to see that you have the minimum number of command line arguments, rather than hard-code the name of the program, you then extract the program’s name. You do this extraction so that, if the user has renamed your program, you present the user with the correct program name. It’s confusing to rename a

110

Special Pointers and Their Use

C C C

 

C4C

 

C C C

 

C

command and have the error messages continue to give the old name. You then loop through the list of command line arguments, using a simple for() loop:

for (i = 1; argv[i]; i++)

{

You test for the terminating NULL command line argument pointer that signifies the end of the list. For each parameter, you look at the first character. If it is either a slash or a dash, the command line argument is an option:

if (argv[i][0] == '/' || argv[i][0] == '-')

{/* You have an argument, convert to lowercase, and test. */ pszTemp = strlwr(argv[i]);

You convert options to lowercase (to minimize the testing) because you don’t have case-sensitive options in this program. Because in some programs the options -p and -P have different meanings, it’s unlikely that users will remember the difference between the two. Make your program user-friendly by ignoring case if possible.

After changing the case, you simply loop through the option’s string. Start with the second character because you know that the first character is the slash or dash. Using a switch, you check each valid letter option, and when there is a match, you set that option as required. Some programs have used the slash as the option prefix flag, and a dash to turn the option off; however, I suggest that you turn an option on with one letter, and off with another. Two-letter options (common with compilers and other complex programs) can be processed by looking at the first letter, and then the second, simply by adding a second nested switch() statement where needed:

for (j = 1; j < strlen(pszTemp); j++)

{

switch(pszTemp[j])

{

case ARG_LEFT: nJustification = LEFT; break;

case ARG_RIGHT: nJustification = RIGHT; break;

case ARG_JUSTIFY: nJustification = JUSTIFY; break;

111

Part I • Honing Your C Skills

case ARG_HELP: GiveHelp(NOINNAME, NULL); exit(4);

break;

case ARG_SLASH: case ARG_DASH: break;

default:

GiveHelp(BAD_OPTION, &pszTemp[j]); break;

}

}

}

In the preceding switch() block, you ignore imbedded slashes and dashes. Users commonly enter a set of options with a slash or dash before each option and with no intervening spaces.

You also process correctly the /? option, the relatively standard syntax for help. You can also process /h for help.

Notice the default: in this block: If the user has entered an unrecognized option letter, you provide a message that indicates the invalid option letter.

In the following block, you have either one of the filenames or the width specifier.

else

{/* Either a filename or width. */

These three items are positional; that is, the input filename is always the first of the three, the output filename is the second, and the width (which is optional) is always the third. There are numerous reasons for this order: One reason is that a filename can be a number (and therefore, width cannot be first if it is to be optional); another reason is that the two filenames must be provided in a known order because they are indistinguishable to the program.

You keep track of which of these three items you are processing by using the variable nCurrentParameter. This variable works as a state machine (see the “State Machines” section, later in this chapter), and changes its state every time a parameter is encountered:

112

Special Pointers and Their Use

switch(nCurrentParameter)

{

case INNAME:

strcpy(szInputFile, argv[i]); nCurrentParameter = OUTNAME; break;

case OUTNAME:

strcpy(szOutputFile, argv[i]); nCurrentParameter = WIDTH; break;

case WIDTH:

sscanf(argv[i], "%d", &nTempWidth);

if (nTempWidth < 20 || nTempWidth > 128)

{

GiveHelp(BAD_WIDTH, NULL);

}

else

{

nLineWidth = nTempWidth;

}

C C C

C4C C

C C C

nCurrentParameter = LAST_THING;

break;

default:

GiveHelp(BAD_PARM, NULL); break;

}

}

}

The width parameter is tested, and the variable nLineWidth is updated only if the width is within the program’s bounds of 20 to 128. (I’m not saying that these bounds are realistic—just that they can be.) You know that the user entered at least two parameters, but you don’t know whether two of them were filenames. A confused user

113

Part I • Honing Your C Skills

might have entered the command name, with no filenames, and with all three option letters as separate command line parameters:

JUSTIFY /j /l -r

This command line syntax has the minimum number of command line parameters; however, you don’t have any filenames. You test for this with the following code:

if (nCurrentParameter < WIDTH)

{/* Didn't get two filenames! */ GiveHelp(NAME_MISSING, NULL); return(16);

}

Again, the state machine variable, nCurrentParameter, lets you know how many filenames the user entered. If the state machine variable isn’t at least to the WIDTH state, you didn’t get the required filenames.

The remainder of the program is simple because I didn’t write the textformatting part of this program. It has minimal error checking and a simple errormessage function that receives an error message code and an optional character pointer. The character pointer can point to either a single character or a string. You must make sure that the message’s printf() statements know what the pointer is pointing to.

One of the interesting things about the error message processor is that it is recursive: It calls itself when the user needs to have the full command syntax. The following syntax is accepted: /lr. That is, more than one option can follow the slash or dash option flag. If the user has entered a number of options following the slash or dash, and one of the options is invalid, do you stop processing this command line parameter? Probably not, but you must consider all possible situations and program accordingly.

Function Pointers

Function pointers can be used to do almost anything you can do with a function name. You can still pass parameters to a function that is being called with a pointer.

Sometimes, using pointers to functions is the only way to do something. A classic example is the library function qsort(), which takes a pointer to a function. This pointer often is coded as a constant; you might, however, want to code it as a function pointer variable that gets assigned the correct function to do qsort()’s compare.

114