Copyright (C) 1989 by Victor A. Wagner, Jr. 5. Functions & Program Stucture Functions allow us to break up our programs into small re-useable pieces. If we do a good job of selecting the functions, we can use them in future programs, making our job easier. We can also use functions written by other people. This way we don't need to know how to solve everything. We can create libraries of functions for all to share. Another use of functions is to 'hide information'. That is, appropriate selection of functions (and appropriate selection of names for these functions) can hide unnecessary details of the implementation from places where the details would obscure the meaning of the program. As a general rule, this makes a program easier to understand, and _much_ easier to maintain. Changes to the program should be much more localized. C was designed with ease of creating functions in mind. Historically, C programs have many small functions instead of few big ones. Programs can be split up into many files (each compiled separately) and then linked together to create our programs. Thusfar, we have not created any separately compiled functions, although we use them (e.g. printf( ond, getchar()). The programs we have been playing with and using as examples are really quite small programs and although non-trivial, not particularly difficult. I will not go into the details of separate compilation and linking for two (2) reasons: first, the details vary from compiler to compiler; and second, because you all have already learned how to do that with the compiler you are using. We have all written at least one function already (logon()), so what follows should be at least familiar. I know that many of you have been playing around with your compilers and have already written many functions on your own. What follows will formalize a LOT of what you have already learned by trial and error, and perhaps give some general rules of which you are unaware. 5.1 Basics (might as well start at the beginning) To begin, let's design and write a program to find and print all lines of its input which contain a 'pattern' or string of characters. This program will be an extreme subset of any of the 'grep' utilities running around. To show an example, looking for 'the' in the input: Now is the time for all good men to come to the aid of their country. should produce the output: Now is the time to come to the aid of their country. This program falls sort of naturally into three (3) pieces: while if print Even though we could write all of this admittedly small program in one function, there are several advantages to splitting things up as shown. For one, we have already written a routine to get a line (HOMEWORK #19), and we can use printf() to print it, so we only have to write the 'pattern is in line' function to do this exercise. The main program can easily be written now, and should look something like this: #define MAX 500 /* 500 characters per line seems a reasonable maximum */ void main() { char myline[MAX]; while (getline(myline, MAX) >= 0) if (where("the", myline) >= 0) printf ("%s", myline); } Here is my version of getline. You might want to compare it to your homework #19. int getline(line, size) char line[]; int size; { int c, index; index = 0; while (--size > 0 && ((c = getchar()) != EOF) && (c != '\n')) line[index++] = c; if (c == '\n') line[index++] = c; /* put in the end-of-line if we found one */ line[index] = '\0'; /* make sure we have a 'string terminator' */ return(index); } Now for a function called where which will return the starting location of a pattern in a string. We might as well return the starting position as TRUE and FALSE, or YES and NO because, as you will see, it costs no more, and it may even be more useful this way. int where(pattern, string) /* find starting postion of pattern in string */ char pattern[], string[]; { int si, pi; for (si = 0; string[si]; si++) { for(pi =0; pattern[pi] && pattern[pi] == string[si+pi]; pi++) ; /* do nothing, we just want to wait for the test to fail */ if (pattern[pi]) return(sI); } return(-1) /* flag that the pattern is NOT present */ } Each function has the following template: name() { } As you probably inferred, parts may be left out. A minimum function would look like: DoNothing() {} Not a particularly useful function, but legal. You probably noticed the 'type, if any' in the declaration. Well, the 'type' can be absent also if the function returns an int. I do NOT reccommend leaving the type off, this is strictly for compatability with earlier compilers (which only allowed one type to be returned from functions). It is important to note, that the compiler will 'assume' that a function returns an int if you call it before you declare it. Therefore it behooves us all to declare functions before we reference them. A program is just a collection of functions definitions. Communication between functions can either be by arguments (as it is in the preceeding case) or by variables declared external to the functions. Functions which pass information via external variables are said to be 'tightly coupled' and there is a large body of literature which shows that 'tightly coupled' systems are harder to maintain. In fairness, I should point out that there are those who disagree with this. It is my opinion that 'tightly coupled' functions should be avoided generally. It is worth noting that functions can reside in different files, can be compiled separately, and then linked into a coherent program. The return statement is the mechanism for a function to return its 'answer' to the calling function. The format of the return is: return () The calling function is free to ignore the return if it wishes. Modern C compilers will publish a warning if you do. In order to intentionally ignore the return from a function (and eliminate all those ugly warnings from your compiler), simply cast the function to 'void' 'a la: (void) getline(myline, MAX); You know, the more I see that syntax, the less I like it. Well, I guess we live with it (not being on any C reform committees). The mechanics of splitting up a program into many files, then compiling and linking them together into a cohesive whole, varies across the three (3) compilers we are using in the class, but it's now time to find out how YOUR system works. HOMEWORK #20 Take the above program and put it into 3 files. getline.c and where.c should be two of them (I don't really care what you call the main file), and compile and link them together. I guess it's time to compile logon.ci separately too. Anyway, put it all together, and include in the .ARC file a script file which will successfully compile and link together all of the files. 5.2 Functions returning non-integers (well, at least they return) Thusfar none of the functions we have written needed any functions which returned anything other than an int. I know we haven't gotten into enumeration types yet, and I don't wish to explain them in detail here, but this is the simplest function I could find to show off this feature: enum TurnKind {Left, Right, Around}; enum Direction {Undefined, West, North, East, South}; enum Direction Turn (how, from) enum TurnKind how; enum Direction from; { /* if (debugging) printf(" turn %s", Dirspell[how]); */ switch (how) { case Left: switch(from) { case South: return(East); case East: return(North); case North: return(West); case West: return(South); } case Right: return(Turn(Around, Turn(Left, from))); case Around: return(Turn(Left, Turn(Left, from))); } return(from); } This function (and its associated 'enum' declarations) is from a small game I'm writing. The meaning should be clear, but just in case it's not, I'll explain. There are variables which hold the direction something is moving, and these variables can hold ONLY the 'values' Undefined, West, North, East, and South. In writing the rest of the code, it was easier in places to determine whether this object needed to change its direction....and to keep track of the motion, in terms of the current motion, rather than in absolute terms. Thus was born the function 'turn' which you see above. It accepts two arguments (the current dirrection being travelled, and a 'turnkind' (Left, Right, and Around)). It returns a direction.