Scrigroup - Documente si articole

     

HomeDocumenteUploadResurseAlte limbi doc
AccessAdobe photoshopAlgoritmiAutocadBaze de dateCC sharp
CalculatoareCorel drawDot netExcelFox proFrontpageHardware
HtmlInternetJavaLinuxMatlabMs dosPascal
PhpPower pointRetele calculatoareSqlTutorialsWebdesignWindows
WordXml

AspAutocadCDot netExcelFox proHtmlJava
LinuxMathcadPhotoshopPhpSqlVisual studioWindowsXml

Complete Programs in C

c



+ Font mai mare | - Font mai mic



Complete Programs in C

  • 1. Putting it all together
  • 2. Arguments to main
  • 3. Interpreting program arguments
  • 4. A pattern matching program
  • 5. A more ambitious example
  • 6. Afterword


1. Putting it all together

Having considered the language and the libraries defined by the Standard, all that now remains is to demonstrate what complete programs look like. This chapter contains some example programs which illustrate how to combine these elements to build programs.

However, just before these examples are presented there is one more aspect of the C language to discuss.

2. Arguments to main

For those writing programs which will run in a hosted environment, arguments to main provide a useful opportunity to give parameters to programs. Typically, this facility is used to direct the way the program goes about its task. It's particularly common to provide file names to a program through its arguments.

The declaration of main looks like this:

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

This indicates that main is a function returning an integer. In hosted environments such as DOS or UNIX, this value or exit status is passed back to the command line interpreter. Under UNIX, for example, the exit status is used to indicate that a program completed successfully (a zero value) or some error occurred (a non-zero value). The Standard has adopted this convention; exit( is used to return 'success' to its host environment, any other value is used to indicate failure. If the host environment itself uses a different numbering convention, exit will do the necessary translation. Since the translation is implementation-defined, it is now considered better practice to use the values defined in <stdlib.h>: EXIT_SUCCESS and EXIT_FAILURE.

There are at least two arguments to main: argc and argv. The first of these is a count of the arguments supplied to the program and the second is an array of pointers to the strings which are those arguments-its type is (almost) 'array of pointer to char'. These arguments are passed to the program by the host system's command line interpreter or job control language.

The declaration of the argv argument is often a novice programmer's first encounter with pointers to arrays of pointers and can prove intimidating. However, it is really quite simple to understand. Since argv is used to refer to an array of strings, its declaration will look like this:

char *argv[]

Remember too that when it is passed to a function, the name of an array is converted to the address of its first element. This means that we can also declare argv as char **argv; the two declarations are equivalent in this context.

Indeed, you will often see the declaration of main expressed in these terms. This declaration is exactly equivalent to that shown above:

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

When a program starts, the arguments to main will have been initialized to meet the following conditions:

  • argc is greater than zero.
  • argv[argc] is a null pointer.
  • argv[ through to argv[argc-1] are pointers to strings whose meaning will be determined by the program.
  • argv[ will be a string containing the program's name or a null string if that is not available. Remaining elements of argv represent the arguments supplied to the program. In cases where there is only support for single-case characters, the contents of these strings will be supplied to the program in lower-case.

To illustrate these points, here is a simple program which writes the arguments supplied to main on the program's standard output.

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv)

Example 1

If the program name is show_args and it has arguments abcde, text, and hello when it is run, the state of the arguments and the value of argv can be illustrated like this:


Figure 1. Arguments to a program

Each time that argv is incremented, it is stepped one item further along the array of arguments. Thus after the first iteration of the loop, argv will point to the pointer which in turn points to the abcde argument. This is shown in Figure 2.


Figure 2. Arguments to a program after incrementing argv

On the system where this program was tested, a program is run by typing its name and then the arguments, separated by spaces. This is what happened (the is a prompt):

$ show_args abcde text hello
show_args
abcde
text
hello
$

3. Interpreting program arguments

The loop used to examine the program arguments in the example above is a common C idiom which you will see in many other programs. An additional common idiom is to use 'options' to control the behaviour of the program (these are also sometimes called switches or flags). Arguments which start with a ' ' are taken to introduce one or more single-letter option indicators, which can be run together or provided separately:

progname -abxu file1 file2
progname -a -b -x -u file1 file2

The idea is that each of the options selects a particular aspect from the program's repertoire of features. An extension to that idea is to allow options to take arguments; if the -x option is specified to take an argument, then this is how it might be used:

progname -x arg file1

so that the arg argument is associated with the option. The options function below automates the processing of this style of use, with the additional (common but preferably considered obsolescent) support for the provision of option arguments immediately following the option letter, as in:

progname -xarg file1

In either of the above cases, the options routine returns the character 'x' and sets a global pointer, OptArg, to point to the value arg.

To use this routine, a program must supply a list of valid option letters in the form of a string; when a letter in this string is followed by a ' ' this indicates that the option letter is to be followed by an argument. When the program is run, it is then simply a question of repeatedly calling the options routine until no more option letters remain to be found.

It seems to be a fact of life that functions which scan text strings looking for various combinations or patterns within them end up being hard to read; if it's any consolation they aren't all that easy to write either. The code that implements the options is definitely one of the breed, although by no means one of the worst:

/*
* options() parses option letters and option arguments from the argv list.
* Succesive calls return succesive option letters which match one of
* those in the legal list. Option letters may require option arguments
* as indicated by a ':' following the letter in the legal list.
* for example, a legal list of 'ab:c' implies that a, b and c are
* all valid options and that b takes an option argument. The option
* argument is passed back to the calling function in the value
* of the global OptArg pointer. The OptIndex gives the next string
* in the argv[] array that has not already been processed by options().
*
* options() returns -1 if there are no more option letters or if
* double SwitchChar is found. Double SwitchChar forces options()
* to finish processing options.
*
* options() returns '?' if an option not in the legal set is
* encountered or an option needing an argument is found without an
* argument following it.
*
*/

#include <stdio.h>
#include <string.h>

static const char SwitchChar = '-';
static const char Unknown = '?';

int OptIndex = 1; /* first option should be argv[1] */
char *OptArg = NULL; /* global option argument pointer */

int options(int argc, char *argv[], const char *legal)

}
letter = *posn++;
if(!(legal_index = strchr(legal, letter)))
if(*++legal_index != ':') else else
OptArg = argv[OptIndex];
posn = '';
OptIndex++;
}
return letter;
}

Example 2

4. A pattern matching program

This section presents a complete program which makes use of option letters as program arguments to control the way it performs its job.

The program first processes any arguments that resemble options; the first argument which is not an option is remembered for use as a 'search string'. Any remaining arguments are used to specify file names which are to be read as input to the program; if no file names are provided, the program reads from its standard input instead. If a match for the search string is found in a line of input text, that whole line is printed on the standard output.

The options function is used to process all option letters supplied to the program. This program recognises five options: -c, -i, -l, -n, and -v. None of these options is required to be followed by an option argument. When the program is run with one or more of these options its behaviour is modified as follows:

-c

the program prints a count of the total number of matching lines it found in the input file(s). No lines of text are printed.

-i

when searching for a match, the case of letters in both the input lines and string is ignored.

-l

each line of text printed on the output is prefixed with the line number being examined in the current input file.

-n

each line of text printed on the output is prefixed with the name of the file that contained the line.

-v

the program prints only lines which do not match the string supplied.

When the program finishes, it returns an exit status to indicate one of the following situations:

EXIT_SUCCESS

at least one match was found.

EXIT_FAILURE

no match was found, or some error occurred.

The program makes extensive use of standard library functions to do all of the hard work. For example, all of the file handling is performed by calls to stdio functions. Notice too that the real heart of the program, the string matching, is simply handled by a call to the strstr library function.

Here is the code for the whole program. Of course, to get this to work you would need to compile it together with the code for the options routine presented above.

/*
* Simple program to print lines from a text file which contain
* the 'word' supplied on the command line.
*
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

/*
* Declarations for the pattern program
*
*/

#define CFLAG 0x001 /* only count the number of matching lines */
#define IFLAG 0x002 /* ignore case of letters */
#define LFLAG 0x004 /* show line numbers */
#define NFLAG 0x008 /* show input file names */
#define VFLAG 0x010 /* show lines which do NOT match */

extern int OptIndex; /* current index into argv[] */
extern char *OptArg; /* global option argument pointer */

/*
* Fetch command line switches from arguments to main()
*/

int options(int, char **, const char *);

/*
* Record the required options ready to control program beaviour
*/

unsigned set_flags(int, char **, const char *);

/*
* Check each line of the input file for a match
*/

int look_in(const char *, const char *, unsigned);

/*
* Print a line from the input file on the standard output
* in the format specified by the command line switches
*/

void print_line(unsigned mask, const char *fname,
int lnno, const char *text);


static const char
/* Legal options for pattern */
*OptString = 'cilnv',
/* message when options or arguments incorrect */
*errmssg = 'usage: pattern [-cilnv] word [filename]n';

int main(int argc, char *argv[])


flags = set_flags(argc, argv, OptString);

if(argv[OptIndex])
search_string = argv[OptIndex++];
else

if(flags & IFLAG)

if(argv[OptIndex] == NULL) else while(argv[OptIndex] != NULL)
success += look_in(argv[OptIndex++],
search_string, flags);

if(flags & CFLAG)
printf('%dn', success);

exit(success ? EXIT_SUCCESS : EXIT_FAILURE);
}

unsigned set_flags(int argc, char **argv, const char *opts)

}
return flags;
}


int look_in(const char *infile, const char *pat, unsigned flgs)

} else
in = stdin;

while(fgets(line[0], BUFSIZ, in))

if(strstr(line_to_use, pat)) else if(flgs & VFLAG)
print_line(flgs, infile, lineno, line[0]);
}
fclose(in);
return matches;
}

void print_line(unsigned mask, const char *fname,
int lnno, const char *text)

Example 3

5. A more ambitious example

Finally here is a set of programs designed to cooperate and manipulate a single data file in a coherent, robust fashion.

The programs are intended to help keep track of a ladder of players who compete against each other at some game, squash or chess perhaps.

Each player has a rank from one to n, where n is the number of players who play, one being the highest rank on the ladder. Players lower down the ladder may challenge players above them and, if the lower ranked player wins, he or she moves up taking the rank of the player who loses. The loser in such a situation, and any other players between challenger and loser, are then moved down one rank. If a challenger does not win, the rankings on the ladder remain unchanged.

To provide some measure of equilibrium in the rankings, a player may challenge any higher ranked player, but only wins over players ranked three (or less) higher will allow the challenger to move up the rankings. This ensures that new players added to the bottom of the ladder are forced to play more than one game to reach the top of the ladder!

There are three basic tasks which are required to record all the information needed to keep such a ladder going:

  • Printing the ladder.
  • Addition of new players.
  • Recording of results.

The design to be used here provides a separate program to perform each of these tasks. Having made this decision it is clear that a number of operations needed by each program will be common to all three. For example, all three will need to read player records from the data file, at least two will need to write player records into the data file.

This suggests that a good approach would be to design a 'library' of functions which manipulate player records and the data file which may in turn be combined to make up the programs which maintain the ladder.

Before this can be done it will be necessary to define the data structure which represents player records. The minimum information necessary to record for each player consists of player name and rank. However, to allow for more interesting statistics to be compiled about the ladder let us chose to also keep a record of games won, games lost and the time when the last game was played. Clearly this disparate set of information is best collected together in a structure.

The player structure declaration together with the declarations of the player library functions are combined together in the player.h header file. The data file is maintained as lines of text, each line corresponding to a record; this requires input and output conversions to be performed but is a useful technique if the conversions don't cost too much in performance terms.

/*
*
* Declarations and definitions for functions which manipulate player
* records which form the basis of the ladder
*
*/

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define NAMELEN 12 /* max. for player name length */

#define LENBUF 256 /* max. for input buffer length */

#define CHALLENGE_RANGE 3 /* number of higher ranked players who may
* be challenged to move up in rank
*/

extern char *OptArg;

typedef struct player;

#define NULLPLAYER (player *)0

extern const char *LadderFile;

extern const char *WrFmt; /* used when writing records */
extern const char *RdFmt; /* used when reading records */

/*
* Declarations for routines used to manipulate the player records
* and the ladder file which are defined in player.c
*
*/

int valid_records(FILE *);
int read_records(FILE *, int, player *);
int write_records(FILE *, player *, int);
player *find_by_name(char *, player *, int);
player *find_by_rank(int, player *, int);
void push_down(player *, int, int, int);
int print_records(player *, int);
void copy_player(player *, player *);
int compare_name(player *, player *);
int compare_rank(player *, player *);
void sort_players(player *, int);

Example 4

Here is the code for the player.c file implementing the generic functions which manipulate player records and the data file. These functions can be combined with more specific routines to make up the three programs required to maintain the ladder.

Notice that to manipulate the player records, each program is required to read the entire data file into a dynamically allocated array. Before this array is written back to the data file, it is assumed that the records it contains will have been sorted into rank order. If the records do not remain sorted, the push_down function will produce some 'interesting' results!

/*
* Generic functions to manipulate the ladder data file and
* player records.
*
*/

#include 'player.h'

const char *LadderFile = 'ladder';

const char *WrFmt = '%s %d %d %d %ldn';
const char *RdFmt = '%s %d %d %d %ld';


/* note use of string-joining */
const char *HeaderLine =
'Player Rank Won Lost Last Gamen'
'===============================================n';

const char *PrtFmt = '%-12s%4d %4d %4d %sn';

/* return the number of records in the data file */

int valid_records(FILE *fp)


/* read num player records from fp into the array them */

int read_records(FILE *fp, int num, player *them)


fseek(fp, tmp, SEEK_SET);
return i;
}

/* write num player records to the file fp from the array them */

int write_records(FILE *fp, player *them, int num)


return i;
}

/*
* return a pointer to the player in array them
* whose name matches name
*/

player *find_by_name(char * name, player *them, int num)


/*
* return a pointer to the player in array them
* whose rank matches rank
*/

player *find_by_rank(int rank, player *them, int num)


/*
* reduce by one the ranking of all players in array them
* whose ranks are now between start and end
*/

void push_down(player *them, int number, int start, int end)
else
(pp->rank)++;
}
}

/* pretty print num player records from the array them */

int print_records(player *them, int num)


return i;
}

/* copy the values from player from to player to */

void copy_player(player *to, player *from)


/* compare the names of player first and player second */

int compare_name(player *first, player *second)


/* compare the ranks of player first and player second */

int compare_rank(player *first, player *second)


/* sort num player records in the array them */

void sort_players(player *them, int num)

Example 5

This code, when tested, was compiled into an object file which was then linked (together with an object file containing the code for the options function) with one of the following three programs to for the ladder maintenance utilities.

Here is the code for the simplest of those utilities, showlddr which is contained in the file showlddr.c.

This program takes a single option, -f, which you will notice takes an option argument. The purpose of this argument is to allow you to print a ladder data file with a name other than the default file name, ladder.

The player records in the data file should be stored pre-sorted but, just to be safe, showlddr sorts them before it prints them out.

/*
* Program to print the current ladder status.
*
*/

#include 'player.h'

const char *ValidOpts = 'f:';

const char *Usage = 'usage: showlddr [-f ladder_file]n';

char *OtherFile;

int main(int argc, char *argv[])

}
} else if(argc > 1)

fname = (OtherFile == 0)? LadderFile : OtherFile;
fp = fopen(fname, 'r+');

if(fp == NULL)

number = valid_records (fp);

them = (player *)malloc((sizeof(player) * number));

if(them == NULL)

if(read_records(fp, number, them) != number)

fclose(fp);

sort_players(them, number);

if(print_records(them, number) != number)

free(them);
exit(EXIT_SUCCESS);
}

Example 6

Of course the showlddr program works only if there is an existing data file containing player records in the correct format. The program newplyr creates such a file if one does not already exist and then adds a new player record, in the correct format to that file.

Typically, new players are added at the bottom of the rankings but for the odd occasion where this really may not make sense, newplyr also allows a player to be inserted into the middle of the rankings.

A player may only appear once on the ladder (unless a pseudonym is used!) and there can only be one player at any one rank. Thus the program checks for duplicate entries and if the new player is to be inserted into a middling rank, moves other players already on the ladder out of the way.

As with the showlddr program, newplyr recognises a -f option as a request to add the new player to a file named by the option argument rather than the default file, ladder. In addition, newplyr requires two options, -n and -r, each with option arguments to specify both the new player's name and initial ranking respectively.

/*
* Program to add a new player to the ladder.
* You are expected to assign a realistic
* ranking value to the player.
*
*/

#include 'player.h'

const char *ValidOpts = 'n:r:f:';

char *OtherFile;

static const char *Usage = 'usage: newplyr -r rank -n name [-f file]n';

/* Forward declaration of function defined in this file */

void record(player *extra);

int main(int argc, char *argv[])
{
char ch;
player dummy, *new = &dummy;

if(argc < 5)

while((ch = options(argc, argv, ValidOpts)) != -1)
break;
case '?':
fprintf(stderr, Usage);
break;
}
}

if((new->rank == 0))

if(strlen(new->name) == 0)

new->wins = new->losses = 0;
time(& new->last_game); /* make now the time of the 'last game' */

record(new);

exit(EXIT_SUCCESS);
}

void record(player *extra)

}

number = valid_records (fp);
new_number = number + 1;

if((extra->rank <= 0) || (extra->rank > new_number))

them = (player *)malloc((sizeof(player) * new_number));

if(them == NULL)

if(read_records(fp, number, them) != number)

if(find_by_name(extra->name, them, number) != NULLPLAYER)

copy_player(&them[number], extra);

if(extra->rank != new_number)
push_down(them, number, extra->rank, number);

sort_players(them, new_number);

if((fp = freopen(fname, 'w+', fp)) == NULL)

if(write_records(fp, them, new_number) != new_number)
fclose(fp);
free(them);
}

Example 7

The only remaining utility required is one for recording the results of games played. The result program performs this task.

As with the previous two utilities, result will accept a -f option together with a file name to specify an alternative to the default player record file.

Unlike the newplyr utility, result interactively prompts the user for the names of the winning and losing players. The program insists that the names supplied should be those of existing players.

Given a valid pair of names, a check is then made to see if the loser is higher ranked than winner and whether or not the winner is ranked close enough for the victory to alter the rankings.

If a change in the standings is in order, the victor takes the loser's rank and the loser (as well as any other player on an intervening rank) is demoted one rank.

Here is the code for the result utility.

/*
* Program to record a result in the ladder
*
*/

#include 'player.h'

/* Forward declarations for functions defined in this file */

char *read_name(char *, char *);
void move_winner(player *, player *, player *, int);

const char *ValidOpts = 'f:';

const char *Usage = 'usage: result [-f file]n';

char *OtherFile;

int main(int argc, char *argv[])

}
} else if(argc > 1)

fname = (OtherFile == 0)? LadderFile : OtherFile;
fp = fopen(fname, 'r+');

if(fp == NULL)

number = valid_records (fp);

them = (player *)malloc((sizeof(player) * number));

if(them == NULL)

if(read_records(fp, number, them) != number)

fclose(fp);

if((winner = find_by_name(read_name(buf, 'winner'), them, number))
== NULLPLAYER)

if((loser = find_by_name(read_name(buf, 'loser'), them, number))
== NULLPLAYER)

winner->wins++;
loser->losses++;

winner->last_game = loser->last_game = time(0);

if(loser->rank < winner->rank)
if((winner->rank - loser->rank) <= CHALLENGE_RANGE)
move_winner(winner, loser, them, number);

if((fp = freopen(fname, 'w+', fp)) == NULL)

if(write_records(fp, them, number) != number)
fclose(fp);
free(them);
exit(EXIT_SUCCESS);
}

void move_winner(player *ww, player *ll, player *them, int number)


char *read_name(char *buf, char *whom)

}

Example 8

6. Afterword

The programs shown in this chapter should help to to get a feel for what middle-of-the-road C programs look like, using the language and libraries defined in the Standard.

What do we mean by 'middle-of-the-road'? Simply this: they have been designed, implemented, tested and documented in a way appropriate for small, self-contained programs that have no real need to show high levels of robustness and reliability. Many programs don't need to meet demanding criteria; to do more to them would be over-engineering. Clearly, it is entirely dependent on the eventual purpose for which the program is intended.

There are situations which place very high demands on the software that is in use; programs to meet these requirements are very carefully engineered and have much higher amounts of effort put into reviewing, testing and the control of access to the source code than would be appropriate for simple illustrative example programs. C is also used in these application areas. The source code of programs that meet such high requirements tends to look distinctively different; the language is the same, but the amount of error checking and correction is typically much higher. We have not tried to illustrate that type of program.

Whichever environment you work in, we hope that this book has helped you in your understanding of C. Good luck!



Politica de confidentialitate | Termeni si conditii de utilizare



DISTRIBUIE DOCUMENTUL

Comentarii


Vizualizari: 1030
Importanta: rank

Comenteaza documentul:

Te rugam sa te autentifici sau sa iti faci cont pentru a putea comenta

Creaza cont nou

Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved