--------------------------------------------------------------------------- Some of the code here will need mods to enable it to work on amiga's. All the code works fine apart from one bug, i will let you find that one he he...L8z pCm......................................................... ->iNDUSTRIAl sTRENGTh<- ---------------------------------------------------------------------------- 'C' Programming Course - Workbook 1. ------------------------------------- PART ONE --====-- Introduction. ------------- C is a small language and an elegant language. It has imperfections which are easier to live with than the restrictions of other languages such as standard PASCAL. Most of these other languages have non-standard extensions to make them useable. Extensions to C are achieved through macros in the pre-processor or additional libraries of functions. Many aspects of the language challenge current practice in other "standard" programming languages and it is arguable which is the better approach. Features that other languages regard as transparent, for example passing parameters by reference, C places under the responsibility of the programmer. Many of these features are implemented using the pointer which is a far more powerful and flexible construction in C than in other languages. As a language C contains no input/output statements. These are defined within standard libraries of functions. This ensures the compiler is small and easily portable. However, differences in the implementation of these "standard" functions does occur. C has a powerful set of operators as standard but some, the increment /decrement operators in particular, are directly analogous to machine instructions or addressing modes on the PDP-11. This was the machine on which the first full C compiler was implemeted. This gives rise to some inconsistencies across various implementations. C is a modular language but does not exhibit the modularity of modern languages. Limited privacy of data and functions within files is achieved using the keyword static. There is no inter-module checking of imported/exported functions and data. Amongst the criticisms aimed at C the most serious are the implementation dependent nature of the increment/decrement operator and the fact that the compiler can reorder evaluation within expressions and parameter lists. C uses multiple symbols for relational and equality operators and makes multiple use of some of these symbols for logical and arithmetic operators. Relational operators such as != and >= cannot be specified out of order as they then have a different meaning. ============================================================================== THE HELLO WORLD PROGRAM. (Kernigan and Ritchie Chapter 1 Pg 8) ------------------------ #include /* header for standard i/o library */ /* (pre-processor directive) */ main() /* start point for program execution */ { /* start of main() statements */ printf("hello, world\n"); /* print output to stdout device */ } /* end of main() statements */ The #include Statement. ----------------------- #include is a request to the pre-processor to include the header file (.h) for the standard i/o library. This makes available to your program certain i/o functions and constants, in this case the printf statement. This line will normally be found in every program. The right angle brackets <> tell the pre-processor to find the header file in the standard library directory. This is usually pre-defined. Using TurboC (and many other compilers), the program will compile correctly if this statement is omitted, but the compiler will not be able to perform error checking for valid use. The Keyword main(). ------------------- The keyword main() signifies the start of the main statement block. Statements "belonging to" main() are enclosed in braces {}. Program execution always begins at main(). The printf() Statement. ----------------------- The printf statement writes output to the screen. As indicated above, printf() is actually a function found in the standard i/o library. The output sent to the screen is enclosed between double quotes "" and is known as the control string. The \n is a special character meaning print a new line. NOTE: the output in printf MUST be entirely on one line. If the output is too long then two or more printf statements may be specified. printf("hello,"); printf("world\n"); Statements in General. ---------------------- NOTE 1: all executable statements end in a semi-colon. Pre-processor directives such as #include do not end in semi colon. NOTE 2: that #include, main() and printf() are all in lower case. ** THIS IS IMPORTANT ** All C language key words are in lower case and in C lower case and upper case are different (the language is case sensitive). Thus main, Main and MAIN are ALL different and only main is a valid C keyword. ============================================================================== VARIABLES. ---------- A variable definition specifies a type and the variable name. More than one variable may be defined on one line, each variable name separated by a comma. E.g. int counter; int x,y,z; char ch; float realnum; The variable may also be initialized. E.g. int counter = 100; char ch = '*'; int x=0, y=1, z=2; Variable Names or Identifiers. ------------------------------ Variables and functions must both be given a name (identifier). ³ Valid characters are letters, digits and underscores. The first character MUST be a letter. Upper and lower case letters are different. Identifiers can be any length but ANSI standards recommend that the first 31 characters are unique. (This may depend on the particular implementation). Beware of using predefined identifiers (keywords) which are always lower case. E.g. if, then, else, int, long, etc. Data Types. ----------- All variables MUST be defined with their assigned types 4 basic data types are defined in C. char - a character int - an integer float - single precision floating point double - double precision floating point Additional qualifiers can be used with integers. (int is optional). E.g short int, long int, unsigned int The range of values represented by the various data types is implementation dependent. Also valid are: long float, long unsigned, signed char. Typical ranges for the PC (a 16 bit machine). type size range ------------------------------------ char 8 bits 0 to 255 short 16 bits -32768 to +32767 int 16 bits -32768 to +32767 unsigned 16 bits 0 to 65,535 long 32 bits -2147483648 to +2147483647 float 32 bits 1E-36 to 1E+36 7 bits precision double 64 bits 1E-303 to 1E+303 13 bits precision ============================================================================== NUMERIC CONSTANTS. ------------------ Numeric constants may be assigned to integer or floating point variables to initialize them to a known state. Integer constants are decimal by default. Octal constants are preceded by a O. Hexadecimal constants are preceded by Ox. Long integers are followed by L. E.g. x = 10; (decimal) x = O12; (octal) x = OxA; (hexadecimal) long_x = 10L; Floating constants can be specified with a decimal point and fraction or in scientific notation. E.g. 6.7, 1.345E7, 0.1345E8. ============================================================================== THE OUTPUT STATEMENT Printf(). ------------------------------ Consider the following program: ------------------------------- #include main() { int x; char c; float f; c = 'a'; printf("The value of the character variable is - %c",c); x = 100; printf("The value of the integer variable is - %d",x); f = 3.1412; printf("The value of the floating pt. variable is - %f",f); printf("The contents of the three variables are - %c %d %f",c,x,f); } This program defines three variables of different types: a character, an integer and a floating point number (or real). Each variable is assigned an appropriate value (a constant). The printf() function is then used to print out the contents of each variable. The fourth printf() shows the use of multiple arguments. Printf(). --------- The full form of the printf statement consists of a control string enclosed in double qoutes "" and a list of variables or expressions to be output. Format: printf("control string",arg1,arg2,..argn); Each variable or expression must be accompanied by an appropriate format specifier in the control string. Format specifiers are % signs followed by single letter to denote the format required. Again the "control string" is the actual output. It includes exactly one format specifier for each argument in the statement. Too few or too many format specifiers is an error. The full list of format specifiers is: %d - decimal integer %o - unsigned octal %x - unsigned hexadecimal %u - unsigned decimal integer %c - single character %s - string (null terminated) %f - floating point number %e - floating pt. in scientific notation %g - %f or %e whichever is shorter The character 'l' may precede specifiers d,o,x or u to specify long integers. The following may also precede specifiers: positive number - this is the minimum number of characters to be printed (field width). negative number - as for positive numbers but with chars. left justified. '.' and number - the number of decimal places to print for a floating point number. E.g. printf("%4.2f",real); printf("%-5d",number); printf("%ld",long_number); ============================================================================== THE INPUT FUNCTION Scanf(). --------------------------- Consider the following program: ------------------------------- #include main() { int num; printf("Enter a number - "); scanf("%d",&num); printf("\nThe number you entered was: %d\n",num); } Format: scanf("control string",arg1,arg2,...argn); The control string has almost the same format as for the printf function. However, this does not imply that scanf actually outputs anything !. Scanf is used solely for input. E.g. scanf("%5d",&num); Specifiers of the form %5.2f and %-5d are invalid in scanf !. The & character indicates that the address of the variable rather than the value of the variable itself is passed to the scanf function. Scanf() is not used often as it requires accurate input to function correctly. Characters in the input string must comply exactly with those specified in the control string. Characters in the control string which are not format specifiers must match with identical characters typed in the input string. ============================================================================== FAHRENHEIT TO CELSIUS CONVERSION (1). ------------------------------------- Summary ------- The following program combines the material from the previous exercises with the while statement to control the flow of the program. This in turn introduces relational operators and the concept of simple and compound statements. The results of complex expressions and the effects of type casting are examined and the effect this has on subsequent assignments. Consider the following program: ------------------------------- /* print Fahrenheit to Celsius table */ #include main() { int fahr,celsius; - define integer variables int lower,upper,step; lower = 0; - initialize variables upper = 300; - by assignment. step = 20; fahr = lower; while (fahr <= upper) - loop while true { - begin statements celsius = 5 * (fahr - 32) / 9; - arithmetic expression printf("%d\t%d\n",fahr,celsius); - print variables fahr = fahr + step; } - end while statements } - end of main block Five integer variables have been defined by this program. The variables lower, upper ansd step are all given initial values in the first three lines of the main() code (i.e. initialized). The initialization of variables may be combined with the definition of the variables. E.g. int lower = 0, upper = 300, step = 20; The effect of this is IDENTICAL to the previous code but is shorthand notation. Run the program. Are the results as you expect ?. The value for celsius is only approximate as it has been defined as an integer and as we have seen, an integer has limited numeric range and no fractional part. Convert the variable celsius to a float and change the printf statement accordingly. float celsius; int fahr; . . printf("%d\t%f\n",fahr,celsius); Recompile and check the results of each of the following arithmetic expressions: celsius = 5 * (fahr - 32) / 9; celsius = 5 / 9 * (fahr - 32); celsius = 5.0 / 9.0 * (fahr - 32); celsius = 5.0 / 9 * (fahr - 32); Explain the results. The expression on the right hand side evaluates to a temporary result which is subsequently assigned to the left hand side. The size or type of the temporary result is governed by the largest type in the right hand side expression. This is not strictly what happens but the effect is identical and it is easier to understand this way. This is demonstrated by the floating point constants in the last two examples. Only one floating point constant is required for correct operation. The second example will always generate an incorrect result because the value resulting from the division of the two integer constants 5/9 is zero. All of this could have been avoided in the original code by making the variable fahr of type float. Type casts may also be used to control the type of the temporary result on the right hand side. float b; int a = 30000, c = 20000; b = (float) a + c; Enclosing the type name in parenthesis before the expression will control the type of the temporary result. Type are automatically converted (promoted) across assignments in C. For example an integer expression can be assigned to a result of type float. Consider the previous example without the type cast. float b; int a = 30000, c = 20000; b = a + c; What is the result ?. The result is undefined as the numeric range of an integer is only 32768 (16 bit machine) and as both variables in the right hand side are of type integer, the temporary result must be of type integer. The true result 50000 cannot be represented by an integer and so the result is an overflow condition. While Loops. ------------ Format: while (expression) statement; The statement is repeated while 'expression' is TRUE. An expression is TRUE if it has a non-zero value and FALSE if it is zero. NOTE: TRUE and FALSE are not part of the C language. They are not keywords nor are they defined automatically. while (fahr <= upper) { celsius = 5 * (fahr - 32) / 9; printf("%d\t%d\n",fahr,celsius); fahr = fahr + step; } In the example the loop continues to execute until fahr is greater than upper. The expression fahr <= upper is evaluated for each iteration of the loop and returns either TRUE or FALSE. When fahr is greater than upper the result is FALSE and the loop terminates. The operator <= is a relational operator used to conduct tests in loops. Relational Operators. --------------------- The following relational operators are available: a == b a is equal to b a != b a is not equal to b a < b a is less than b a > b a is greater than b a <= b a is less than or equal to b a >= b a is greater than or equal to b They all evaluate to 1 when TRUE and zero when FALSE. Compound Statements. -------------------- Normally only the next statement (terminated by a semicolon) will be executed as part of the while loop. This is often refered to as a simple statement. Where more than one statement is to be executed as part of a (while) loop, the statements are collected into a compound statement. A compound statement is a collection of statements enclosed in braces '{' and '}'. E.g. while (expression) { statement1; statement2; . statementN; } After the loop terminates, the next statement folowing either the simple statement or the compound statement is executed. In the example this is the end of main() and the end of the program execution. Further exercises. ------------------ Modify the program to print a heading above the table. Experiment with the format specifiers in the printf statement to control the field width of both results. Use scanf() to obtain values for lower, upper and step. E.g. scanf("%d",&lower); ============================================================================== FAHRENHEIT TO CELSIUS CONVERSION (2). ------------------------------------- Summary ------- This example introduces the 'C' for loop and illustrates that it is a specialized form of the more general while loop. The use of another pre-processor directive #define is illustrated for defining constant values. Consider the modified version of the previous program: ------------------------------------------------------ #include #define LOWER 0 #define UPPER 300 #define STEP 20 /* print Fahrenheit to Celsius table */ main() { int fahr; for (fahr = LOWER; fahr <= UPPER; fahr = fahr + STEP) printf("%8d\t%8.2f\n",fahr,5*(fahr-32)/9); } The Pre-Processor Directive #define. ------------------------------------ #define is used to set up constant identifiers. Constant identifiers can improve the style, legibility and portability of a program. More importantly, they make the program easier to maintain. Constant identifiers are usually specified in upper case letters. E.g. #define BIGINT 10000 Note: there is no semi-colon at the end of the line. These directives are normally placed at the start of the program prior to the keyword 'main'. The effect of the directive is to replace all occurrences of the identifier (e.g. BIGINT) with the specified value (e.g. 10000) before the program is compiled. Hence the term pre-processor directive. The For Loop. ------------- Format: for (exp1; exp2; exp3) statement; The expressions exp1, exp2 and exp3 need not all be specified depending on use but the two semicolons MUST always be present. exp1 - is executed ONCE before the for loop begins. exp2 - is an expression which is evaluated before each iteration and the statement is then only executed if the result is TRUE. exp3 - is executed at the end of each iteration (after statement or a compound statement has been executed). The statement may be compound, simple or null. The for loop is directly equivalent to: exp1; while (exp2) { statement; exp3; } ============================================================================== PRIME NUMBERS. -------------- Summary ------- This example uses both a for loop and a while loop and introduces the if statement. Two new operators are also introduced, the binary modulus (or remainder) operator and the auto increment operator. A Program To Find Prime Numbers. -------------------------------- A prime number is an integer greater than or equal to 2 which can only be divided by 1 and itself. E.g. 2,3,5,7,11,13,17,19,23,29,..... Write a program to find prime numbers between 2 and some given upper limit. For each number (k) between 2 and LIMIT, check wether k is divisible by any number between 2 and k-1. How do you determine when a number is divisible by another ? #define LIMIT 1000 main() { int cnt = 0, j, k; for (k=2; k < LIMIT; ++k) { j = 2; while (k % j != 0) ++j; if (j == k) { ++cnt; if (cnt % 6 == 1) printf("\n"); printf("%12d",k); } } printf("\n\nThere are %d prime numbers less than %d\n\n",cnt,LIMIT); } Try the above program. There are two instances of the modulus operator in this program, explain their use in both cases. Exercises. ---------- Is the number of primes less than 10000 itself a prime number ?. If p and q are both prime numbers, then if q = p + 2 the pair p,q are called twin primes. E.g. 3 and 5 are twin primes. Find all twin primes less than LIMIT. Binary Operators. ----------------- The following are the five most commonly used binary operators. + - addition - - subtraction * - multiplication / - division % - modulus They are termed binary operators as they take two operators. Operators have a precedence... * and / have precedence over + and -. The order of precedence can be changed by the use of parenthesis. 6 + 2 * 3 = 12 (6 + 2) * 3 = 24 Increment And Decrement Operators. ---------------------------------- Increment and decrement operators add or subtract one from the operand. Increment operator is ++ Decrement operator is -- If the operator is prefixed: ++operand; --operand; The increment/decrement takes place before the variable is dealt with in an expression. If the operator is postfixed: operand++; operand--; The increment/decrement is postponed until after the variable is dealt with. Note: num1 = num2 + 1; and num1 = num2++; are not identical as num2 is only altered in the second case. If Statement. ------------- Format: if (expression) statement; or: if (expression) statement1; else statement2; If the expression is TRUE then statement or statement1 is executed. If the expression is FALSE then in the first case nothing is executed, but in the second statement2 is executed. Compound statements may be substituted for any of the statements above. ============================================================================== FAHRENHEIT TO CELSIUS CONVERSION (3). ------------------------------------- Summary ------- The purpose of this example is to introduce functions in an elementary way and to show the effects of automatic (local) variables and external variables. It also introduces the concepts of definition and declaration illustrating this with the keyword extern. ----------- Using the Fahrenheit to Celsius conversion program, move the while loop from main() into a seperate function called table. (Use the initial while loop version corrected to print out floating point values). #include main() { float fahr,celsius; int lower=0,upper=300,step=20; fahr = lower; table(); } table() /* the function table */ { while (fahr <= upper) { celsius = 5 * (fahr - 32) / 9; printf("%f\t%f\n",fahr,celsius); fahr = fahr + step; } } The function table is called from main() just as you would call the standard i/o function printf(). However, unlike printf(), table() has no arguments. Attempting to compile this program results in a number of compiler errors. This is because the code in table() cannot "see" the variables defined in main(). Variables defined inside main or inside a function are known as AUTOMATIC variables. They can only be referenced by the function in which they are defined. Variables may also be declared outside main or other functions and are known as EXTERNAL variables. External variables can be "seen" by main and other functions. There are two ways to rid this program of compilation errors. - make the variables external i.e visible to both main and table. - pass the variables as arguments to the function table as you would if calling printf. (this is the preferable solution as the function then becomes re-usable in other programs with no modifications required). Dealing with external variables first: Move all the variables above main() leaving them unchanged. #include float fahr,celsius; int lower=0,upper=300,step=20; main() { fahr = lower; table(); } table() /* the function table */ { while (fahr <= upper) { celsius = 5 * (fahr - 32) / 9; printf("%f\t%f\n",fahr,celsius); fahr = fahr + step; } } This now compiles and runs correctly. Check... What happens if the variables are placed between main() and table(). Try this with fahr only. #include float celsius; int lower=0,upper=300,step=20; main() { fahr = lower; table(); } float fahr; table() /* the function table */ { while (fahr <= upper) { celsius = 5 * (fahr - 32) / 9; printf("%f\t%f\n",fahr,celsius); fahr = fahr + step; } } Again this results in a compiler error as the reference to fahr in main() is before the definition of the variable. Now modify the code in main() to read as follows: main() { extern float fahr; fahr = lower; table(); } float fahr; etc.. This now compiles and runs succesfully. External Variables. ------------------- Defining variables outside main() and functions gives external variables. Syntactically they are the same as automatic/local variables. Before main() or a function can use an external variable, it must be made known through the process of declaration. The keyword extern is added before the type to indicate an external declaration. int ext_var; some_function() { extern int ext_var; . . } This would be repeated in all functions that use the external variable ext_var. However, extern declarations can be omitted. The usual practice is to place all external DEFINITIONS at the start of the program. The rule is if the DEFINITION occurs before the use of an external variable, then the DECLARATION may be omitted. (There are other implications for the use of the keyword extern) Definition And Declaration. --------------------------- An external variable must be defined ONCE outside any function. - Definition implies storage is allocated. The variable must also be declared in each function that wants to access it. - No storage is allocated. Simple Comparison Of Auto And External Variables. ------------------------------------------------- Auto Variables -------------- - Created automatically on the stack each time a function is entered. - Their contents are lost when a function exits. - They are not implicitly initialzed (hence the common compiler warning variable xxx used in function xxx before assignment). External Variables. ------------------- - "Seen" by main and all functions. - Can be initialized (more applicable to structured/complex types such as arrays. - Are allocated in "static" memory and retain their contents from one function call to the next (assuming they are not overwritten). - Implicitly initialized to zero. Does an external or automatic variable have to be declared/defined at the top of a function ?. The rule is the declaration/definition must follow an opening curly brace. This has other implications but for the moment we will follow rule that variables must be declared/defined at the top of a function. ============================================================================== 'C' FUNCTIONS. -------------- A function in C is equivalent to subroutines or functions in Fortran and procedures or functions in Pascal. C does not distinguish between procedures which do not return a value and functions which do. In C they are both classed as functions. The function is used to decompose a computing task into a number of smaller sub-tasks. These sub-tasks are arranged to perform specific operations which may then be called several times within the same program. Economic programming demands that functions should be written to be of general use wherever possible. This allows functions to be re-used in other programs. (E.g. the libraries of functions supplied with most C compilers). Good use of functions hide specific details from the main body or flow of a program. C supports modular programming by allowing programs (functions) to be written in several source files and subsequently linked together. NOTE: All functions are automatically exported to other modules (i.e. made global/public). The format of a function is: type function_name(arguments) argument_declarations; { declarations; executable statements; } We have already used some functions: printf(), scanf(). These were supplied in the standard i/o library. ============================================================================== RAISING A NUMBER TO A POSITIVE POWER. ------------------------------------- Summary. -------- This example introduces passing of arguments to functions and returning a value from a function - the function result. Consider the following example: ------------------------------- This program is used to raise a number to a given positive power. #include main() { int i; for (i=0; i<10; i++) printf("%d %d %d\n",i,power(2,i),power(-3,i)); return 0; } int power(int base, int n) { int i,p; p=1; for (i=1; i<=n; ++i) p = p * base; return p; } Study how the function power is called and how it returns the function result. The keyword return must be specified to return a SPECIFIC value. If return is not specified, the flow of the program will still return to the calling point at the end of the function. Run the program and experiment with it. Passing Arguments To Functions. ------------------------------- In C all arguments are passed 'by value'. That is the variables defined in the argument list actually create storage and become variables in their own right. The values defined in the argument list of the caller are assigned to the variables defined in the argument list of the function for use by that function. Passing arguments 'by reference' can be achieved using pointers and passing the address of the variable. More later... The argument list may be empty, e.g. int somefunction() if there are no arguments. Returning Values From Functions. -------------------------------- Return is used to return a specific value from a function The flow of code will automatically return even if no value is passed back. Return can also return control to the calling point before the end of a function. NOTE: When defining a function, the type of the return value will default to int. Thus if the return type is an integer, int need not be specified. E.g. max(int x, int y) { if (x > y) return(x); else return(y); } Other types may be returned by functions but these must be defined explicitly and they will require a function prototype. Modern versus Classic Style --------------------------- This is the 'classic style'. E.g. int somefunction(x,y) int x; int y; { int i; i = x + y; return(i); } There is a 'modern style' which is now more commonly used. You should be aware of both definitions as you will encounter both. E.g. int somefunction(int x, int y) { int i; i = x + y; return(i); } NOTE: in the classic style, only the argument variables are defined outside the braces (curly brackets). ============================================================================== PSUEDO RANDOM NUMBER GENERATOR. ------------------------------- Summary. -------- Both automatic and external variables have now been covered and the use of the keyword extern. This example introduces another keyword, static, which effects the action of automatic variables. Example. -------- Psuedo-random sequences of numbers can be generated using linear congruential methods. The following is an example: #define MULTIPLIER 25173 #define MODULUS 65536 #define INCREMENT 13849 #define INITIAL_SEED 17 random() { static int seed = INITIAL_SEED; seed = (MULTIPLIER * seed + INCREMENT) % MODULUS); return (seed); } By assigning the result of random divided by 65536.0 to a variable of type double, a random number in the range 0.0 to 1.0 is generated. Write a main() routine to call the function random and generate a random number in this way. Generate a 1000 such numbers and calculate the mean value. How close this is to 0.5 is an elementary check of the randomness of the generator. The Keyword Static. ------------------- Static variables are declared in a function and are private to that function. They are declared with the keyword 'static' and are allocated storage for the life of the program. Static variables are implicitly initialized to zero. The value within the variable is not lost when leaving the function. In essence, static variables behave in the same way as external variables except that they can only be "seen" by the function in which they are declared. E.g. int somefunction() { static int variable_name = 1; ... }  The 'C' Programming Course - WorkBook 2. ---------------------------------------- CHARACTER INPUT AND OUTPUT: getchar() and putchar(). ---------------------------------------------------- Input and output facilities are not part of the C language itself, but are provided through standard library routines. The standard library provides a set of functions (not all) for input and output. In C, input and output takes place through streams. A stream is a source or destination of data that may be associated with a disk or other peripheral. Both text and binary streams are supported through the functions in . A text stream is a sequence of characters, divided into lines. Each line consists of zero or more characters followed by the newline ('\n') character. The function getchar() reads a single character from the text stream known as the standard input device (stdin). This is normally associated with the keyboard. The function putchar() writes one character to the standard output text stream (stdout). This is normally associated with the screen. The standard input and output devices may be redirected to other input and output devices under certain operating systems, particularly DOS and UNIX. E.g. DOS> dir >outfile.txt In this example the output of the directory command is redirected to the file outfile.txt. Both getchar and putchar work with buffered i/o to avoid the excessive overhead of dealing with single characters. Buffered i/o means that lines of input or output are stored by the operating system and only passed to or from the application when the end of the line is reached. Getchar(). ---------- Format: ch = getchar(); This assigns the input character to the variable ch. To correctly handle the value EOF, ch is normally defined as a variable of type int. The value EOF is defined in the header file Thus: int ch; ch = getchar(); Putchar(). ---------- Format: putchar(ch); This sends ch to the standard output device. There is no restriction on the type of the variable ch when using putchar. ============================================================================== COPYING INPUT TO OUTPUT USING Getchar() AND Putchar(). ------------------------------------------------------ Consider the following example: ------------------------------- #include main() { int c; c = getchar(); while (c != EOF) { putchar(c); c = getchar(); } } The variable ch is of type int NOT char. This is because in C a character is treated as an unsigned 8 bit integer having a numeric range of 1 to 255. When getchar() reaches the end of the input, the value EOF will be returned. On most systems the value of EOF is defined as -1. This cannot be represented by a variable of type char thus ch must be a variable of type integer. Chars and integers may be freely intermixed in C programs. The relational operator != means "is not equal to". The above example can be simplified to: --------------------------------------- #include main() { int c; while ((c = getchar()) != EOF) putchar(c); } An assignment such as c = getchar() is an expression and has a value (the value of the l.h.s. after the assignment). This allows an assignment to appear as part of a larger expression. The precedence of the relational test (!=) is higher than the assignment operator (=) hence c=getchar() is enclosed in parenthesis to alter the order of evaluation. (c=getchar() != EOF) would assign either 0 or 1 (FALSE or TRUE) to c. That is the result of the relational test would be assigned to c NOT the character returned by getchar(). Run the program using the keyboard and screen for input and output. The EOF is returned from the keyboard by pressing CTRL- Z. Beware of the effects of buffered i/o. Run the program from the command line redirecting the input and output devices. Add a line to the program to verify the value of EOF. [ printf("%d\n",EOF); or printf("%d\n",c) ] ============================================================================== COUNTING CHARACTERS AND LINES IN INPUT. --------------------------------------- Write a program to count the number of characters and lines in an input text stream. This is a possible solution: #include main() { long nc; int c,nl; nc = 0; nl = 0; while ((c = getchar()) != EOF) { if (c == '\n') nl++; nc++; } printf("%ld\t%d\n",nc,nl); } The variable nc has been made a long integer as the number of characters in a given text file could easily exceed 32767. NOTE: the use of the format specifier %ld in the printf statement to cause the long to be printed correctly. The if statement uses the relational operator (==) is equal to. Do not confuse this with the assignment operator (=), one the most common mistakes in C as an assignment is perfectly valid in most cases. ============================================================================== ARRAYS OF CHARACTERS (STRINGS). ------------------------------- An array is a collection of variables of the same type organised consecutively and grouped for convenient handling. E.g. type variable[constant_expression]; char mikes_string[20]; The constant expression gives the length of the array. Storage is allocated for 20 characters in the example. NOTE in C, elements are always numbered from 0 to length-1. E.g. mikes_string[0]......mikes_string[19] The number enclosed in the square brackets is known as the subscript. The standard string terminator is the NULL character ('\0'). This convention is assumed when using many of the standard functions supplied with an implementation of the language. You are responsible for ensuring the NULL character is in place otherwise many of the standard string functions will continue accessing memory beyond the end of string until a suitable NULL byte is found. Individual elements of the array can be assigned and manipulated. E.g. mikes_string[4] = 'c'; The subscript can also be a variable. E.g. mikes_string[i] = 'c'; There is no runtime checking for invalid subscripts in C (as in other languages). Therefore mikes_string[20] will access something but it will not be a valid component of the example string. NOTE: if using an array of characters with scanf(), the name of an array is the address of the array. Therefore the address operator (&) is not required. scanf("%20s", mikes_string); A string may be initialized at compilation time: E.g. static char flower_string[] = "tulip"; "Classic" or older 'C' compilers required that the string variable be declared static or made external. Modern C compilers do not have this restriction and automatic string variables can be initialized. When a string is initialized, the size is automatically calculated together with the null terminator if the size of the array is left blank. ============================================================================== THE FUNCTIONS getline() AND putline(). -------------------------------------- Summary. -------- We will write a function getline() employing getchar() to read whole lines of text from the standard input device. A line of text is zero or more ASCII characters ending with the newline character '\n'. To read a line of characters: ----------------------------- int getline(char buf[]) { int c, i=0; while ((c = getchar()) != EOF && c != '\n') buf[i++] = c; } NOTE that the function argument char buf[] appears to be a character array except that there is no size specified. In this instance, the array is being passed "by reference" rather than by value. Structured types such as arrays are rarely passed by value in any language that uses the stack for parameter passing. This is because the amount of stack space is restricted and stack overflows are fatal errors and difficult to predict. It should also be noted that arrays can be passed by value. When a variable is passed by reference, the argument is the address of the variable rather than the variable itself. In C, the name of an array (no subscript) is the address of the start of the array. The relational test != means does not equal. A number of relational tests may be connected, in this case by a logical and (&&). Remember automatic variables are not implicitly initialized hence int i = 0;. The variable i is initialized each time the function is entered. i++ is the increment operator. It is losely equivalent to saying i = i + 1. ------------------- The getline() function developed above is incomplete for a number of reasons. First, if buf is an argument passed by reference it must refer to a character array outside the function getline. How does getline() know what size this array is to avoid overflowing the array. It doesn't !. The size must be passed as another argument. This size can then be tested in the while loop against the array index i. The second problem with getline() has to do with the way C handles character arrays. All C functions that deal with character arrays expect the array to be terminated with a NULL character. A special character constant has been set aside for this '\0'. This is literally a byte of zero value. NOTE that the length of a string in a fixed size array can be any length upto the size of the array and hence the NULL character can appear anywhere in the array after the string rather than at the end of the array. A NULL character must be appended to the array after the while loop in getline(). int getline(char buf[], int lim) { int c,i=0; while ((c=getchar()) != EOF && c != '\n' && i < (lim - 1)) buf[i++] = c; buf[i] = '\0'; } This version of getline() incorporates these changes. Note that there are two arguments: buf is passed by reference and lim by value. The test against the size of the array is i < (lim -1) why ? Remember if an array is declared as char string[30] then the first element is string[0] and the last string[29]. A test i < lim would account for this. The test is against lim -1 as space must be allowed for the NULL character. This version of getline() contains one important fault. The character is read into c before the test against lim - 1. That is we have read the character before checking if there is room to store it. This can be corrected by moving the test i < (lim-1) before (c=getchar()) != EOF. The reason this works is because each relational test is connected by the logical and operator. For the whole expression to evaluate to TRUE, each relational test must be true. If one relational test is FALSE there is no point in checking the remaining tests as the result of the whole expression must be FALSE. C "short circuits" the remaining tests and hence the assignment c=getchar() is not made. The final point about getline() is that whatever is calling the function must know when getline() has found EOF to stop any subsequent calls. If the getline reads zero characters it has reached the EOF therefore zero can be returned as a function result. The easiest way to do this is to return the value in the variable i. This has the added benefit that it returns the nuber of characters read or zero if EOF. However, at present a blank line ('\n') would have the same result as EOF. A subsequent test is required to check if the terminating character was a newline and if it was to append it to buf. int getline(char buf[], int lim) { int c,i=0; while (i < (lim - 1) && (c=getchar()) != EOF && c != '\n') buf[i++] = c; if (c == '\n') buf[i++] = c; buf[i] = '\0'; return(i); } Rewrite the program to count characters and lines in the input stream so that it makes use of the function getline(). A companion function putline() can also be written. This again takes an argument which is a character array passed by reference. Each character in the array is output using putchar() until the end of string is reached. int putline(char buf[]) { int i; for(i=0; buf[i] != '\0'; i++) putchar(buf[i]); } Increment/Decrement Operators. ------------------------------ An increment (or decrement) operator can either be prefixed (++i) or postfixed (i++), the increment operation taking place before or after the variable is used in an expression. So expressions like x = n++; and x = ++n; or buf[i++]; and buf[++i]; are not the same. Increment and decrement operators can only be applied to variables, so the expression (i+j)++ is illegal. ============================================================================== THE FUNCTION getword(). ----------------------- Summary. -------- Develop a function getword() which will read each successive "word" from a text string. A word in this instance is any group of characters seperated by whitespace (blank, tab or newline) characters. The function should provide notification when the end of the input string is reached. END OF PART ONE ---=========---