📄 chapter4.html
字号:
int atoi(char s[]) { double atof(char s[]); return (int) atof(s); }</pre>Notice the structure of the declarations and the return statement. The valueof the expression in<pre> return <em>expression</em>;</pre>is converted to the type of the function before the return is taken.Therefore, the value of <tt>atof</tt>, a <tt>double</tt>, is convertedautomatically to <tt>int</tt> when it appears in this <tt>return</tt>, sincethe function <tt>atoi</tt> returns an <tt>int</tt>. This operation doespotentionally discard information, however, so some compilers warn of it. Thecast states explicitly that the operation is intended, and suppresses anywarning.<p><strong>Exercise 4-2.</strong> Extend <tt>atof</tt> to handle scientificnotation of the form<pre> 123.45e-6</pre>where a floating-point number may be followed by <tt>e</tt> or <tt>E</tt> andan optionally signed exponent.<h2><a name="s4.3">4.3 External Variables</a></h2>A C program consists of a set of external objects, which are either variablesor functions. The adjective ``external'' is used in contrast to ``internal'',which describes the arguments and variables defined inside functions. Externalvariables are defined outside of any function, and are thus potentionallyavailable to many functions. Functions themselves are always external, becauseC does not allow functions to be defined inside other functions. By default,external variables and functions have the property that all references to themby the same name, even from functions compiled separately, are references tothe same thing. (The standard calls this property <em>external linkage</em>.)In this sense, external variables are analogous to Fortran COMMON blocks orvariables in the outermost block in Pascal. We will see later how to defineexternal variables and functions that are visible only within a single sourcefile.Because external variables are globally accessible, they provide an alternativeto function arguments and return values for communicating data betweenfunctions. Any function may access an external variable by referring to it byname, if the name has been declared somehow.<p>If a large number of variables must be shared among functions, externalvariables are more convenient and efficient than long argument lists. Aspointed out in <a href="chapter1.html">Chapter 1</a>, however, this reasoning should beapplied with some caution, for it can have a bad effect on program structure,and lead to programs with too many data connections between functions.<p>External variables are also useful because of their greater scope and lifetime.Automatic variables are internal to a function; they come into existence whenthe function is entered, and disappear when it is left. External variables, onthe other hand, are permanent, so they can retain values from one functioninvocation to the next. Thus if two functions must share some data, yetneither calls the other, it is often most convenient if the shared data iskept in external variables rather than being passed in and out via arguments.<p>Let us examine this issue with a larger example. The problem is to write acalculator program that provides the operators <tt>+</tt>, <tt>-</tt>, <tt>*</tt>and <tt>/</tt>. Because it is easier to implement, the calculator will use reversePolish notation instead of infix. (Reverse Polish notation is used by somepocket calculators, and in languages like Forth and Postscript.)<p>In reverse Polish notation, each operator follows its operands; an infixexpression like<pre> (1 - 2) * (4 + 5)</pre>is entered as<pre> 1 2 - 4 5 + *</pre>Parentheses are not needed; the notation is unambiguous as long as we knowhow many operands each operator expects.<p>The implementation is simple. Each operand is pushed onto a stack; when anoperator arrives, the proper number of operands (two for binary operators)is popped, the operator is applied to them, and the result is pushed backonto the stack. In the example above, for instance, 1 and 2 are pushed, thenreplaced by their difference, -1. Next, 4 and 5 are pushed and then replacedby their sum, 9. The product of -1 and 9, which is -9, replaces them onthe stack. The value on the top of the stack is popped and printed when theend of the input line is encountered.<p>The structure of the program is thus a loop that performs the proper operationon each operator and operand as it appears:<pre> while (<em>next operator or operand is not end-of-file indicator</em>) if (<em>number</em>) <em>push it</em> else if (<em>operator</em>) <em>pop operands</em> <em>do operation</em> <em>push result</em> else if (<em>newline</em>) <em>pop and print top of stack</em> else <em>error</em></pre>The operation of pushing and popping a stack are trivial, but by the time errordetection and recovery are added, they are long enough that it is better toput each in a separate function than to repeat the code throughout the wholeprogram. And there should be a separate function for fetching the next inputoperator or operand.<p>The main design decision that has not yet been discussed is where the stack is,that is, which routines access it directly. On possibility is to keep it in<tt>main</tt>, and pass the stack and the current stack position to the routinesthat push and pop it. But <tt>main</tt> doesn't need to know about the variablesthat control the stack; it only does push and pop operations. So we havedecided to store the stack and its associated information in external variablesaccessible to the <tt>push</tt> and <tt>pop</tt> functions but not to <tt>main</tt>.<p>Translating this outline into code is easy enough. If for now we think of theprogram as existing in one source file, it will look like this:<p> <tt>#include</tt><em>s</em><br> <tt>#define</tt><em>s</em><p> <em>function declarations for</em> <tt>main</tt><p> <tt>main() { ... }</tt><p> <em>external variables for</em> <tt>push</tt> <em>and</em> <tt>pop</tt><pre> void push( double f) { ... } double pop(void) { ... } int getop(char s[]) { ... }</pre> <em>routines called by</em> <tt>getop</tt><p>Later we will discuss how this might be split into two or more source files.<p>The function <tt>main</tt> is a loop containing a big <tt>switch</tt> on the type ofoperator or operand; this is a more typical use of <tt>switch</tt> than the oneshown in <a href="chapter3.html#s3.4">Section 3.4</a>.<pre> #include <stdio.h> #include <stdlib.h> /* for atof() */ #define MAXOP 100 /* max size of operand or operator */ #define NUMBER '0' /* signal that a number was found */ int getop(char []); void push(double); double pop(void); /* reverse Polish calculator */ main() { int type; double op2; char s[MAXOP]; while ((type = getop(s)) != EOF) { switch (type) { case NUMBER: push(atof(s)); break; case '+': push(pop() + pop()); break; case '*': push(pop() * pop()); break; case '-': op2 = pop(); push(pop() - op2); break; case '/': op2 = pop(); if (op2 != 0.0) push(pop() / op2); else printf("error: zero divisor\n"); break; case '\n': printf("\t%.8g\n", pop()); break; default: printf("error: unknown command %s\n", s); break; } } return 0; }</pre>Because <tt>+</tt> and <tt>*</tt> are commutative operators, the order in whichthe popped operands are combined is irrelevant, but for <tt>-</tt> and <tt>/</tt>the left and right operand must be distinguished. In<pre> push(pop() - pop()); /* WRONG */</pre>the order in which the two calls of <tt>pop</tt> are evaluated is not defined.To guarantee the right order, it is necessary to pop the first value into atemporary variable as we did in <tt>main</tt>.<pre> #define MAXVAL 100 /* maximum depth of val stack */ int sp = 0; /* next free stack position */ double val[MAXVAL]; /* value stack */ /* push: push f onto value stack */ void push(double f) { if (sp < MAXVAL) val[sp++] = f; else printf("error: stack full, can't push %g\n", f); } /* pop: pop and return top value from stack */ double pop(void) { if (sp > 0) return val[--sp]; else { printf("error: stack empty\n"); return 0.0; } }</pre>A variable is external if it is defined outside of any function. Thus thestack and stack index that must be shared by <tt>push</tt> and <tt>pop</tt>are defined outside these functions. But <tt>main</tt> itself does not referto the stack or stack position - the representation can be hidden.<p>Let us now turn to the implementation of <tt>getop</tt>, the function thatfetches the next operator or operand. The task is easy. Skip blanks and tabs.If the next character is not a digit or a hexadecimal point, return it.Otherwise, collect a string of digits (which might include a decimal point),and return <tt>NUMBER</tt>, the signal that a number has been collected.<pre> #include <ctype.h> int getch(void); void ungetch(int); /* getop: get next character or numeric operand */ int getop(char s[]) { int i, c; while ((s[0] = c = getch()) == ' ' || c == '\t') ; s[1] = '\0'; if (!isdigit(c) && c != '.') return c; /* not a number */ i = 0; if (isdigit(c)) /* collect integer part */ while (isdigit(s[++i] = c = getch())) ; if (c == '.') /* collect fraction part */ while (isdigit(s[++i] = c = getch())) ; s[i] = '\0'; if (c != EOF) ungetch(c); return NUMBER; }</pre>What are <tt>getch</tt> and <tt>ungetch</tt>? It is often the case that a programcannot determine that it has read enough input until it has read too much. Oneinstance is collecting characters that make up a number: until the firstnon-digit is seen, the number is not complete. But then the program has readone character too far, a character that it is not prepared for.<p>The problem would be solved if it were possible to ``un-read'' the unwantedcharacter. Then, every time the program reads one character too many, it couldpush it back on the input, so the rest of the code could behave as if it hadnever been read. Fortunately, it's easy to simulate un-getting a character, bywriting a pair of cooperating functions. <tt>getch</tt> delivers the next inputcharacter to be considered; <tt>ungetch</tt> will return them before reading newinput.<p>How they work together is simple. <tt>ungetch</tt> puts the pushed-backcharacters into a shared buffer -- a character array. <tt>getch</tt> readsfrom the buffer if there is anything else, and calls <tt>getchar</tt> if thebuffer is empty. There must also be an index variable that records theposition of the current character in the buffer.<p>Since the buffer and the index are shared by <tt>getch</tt> and <tt>ungetch</tt>and must retain their values between calls, they must be external to bothroutines. Thus we can write <tt>getch</tt>, <tt>ungetch</tt>, and their sharedvariables as:<pre> #define BUFSIZE 100 char buf[BUFSIZE]; /* buffer for ungetch */ int bufp = 0; /* next free position in buf */ int getch(void) /* get a (possibly pushed-back) character */ { return (bufp > 0) ? buf[--bufp] : getchar(); } void ungetch(int c) /* push character back on input */ { if (bufp >= BUFSIZE) printf("ungetch: too many characters\n"); else buf[bufp++] = c; }</pre>The standard library includes a function <tt>ungetch</tt> that provides onecharacter of pushback; we will discuss it in <a href="chapter7.html">Chapter 7</a>.We have used an array for the pushback, rather than a single character, toillustrate a more general approach.<p><strong>Exercise 4-3.</strong> Given the basic framework, it'sstraightforward to extend the calculator. Add the modulus (%) operator andprovisions for negative numbers.<p><strong>Exercise 4-4.</strong> Add the commands to print the top elements ofthe stack without popping, to duplicate it, and to swap the top two elements.Add a command to clear the stack.<p>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -