Thus, the following program will create a new file called "data.txt" and open it for reading and writing; #include void main() { FILE *fp; fp = fopen("data.txt","w+"); } To close a stream C provides the function fclose() which accepts the stream's file pointer as a parameter; fclose(fp); If an error occurs in closing the file stream, fclose() returns non zero. There are four basic functions for receiving and sending data to and from streams; fgetc(), fputc(), fgets() and fputs(). fgetc() simply reads a single character from the specified input stream; char fgetc(FILE *fp); Its opposite number is fputc() which simply writes a single character to the specified input stream; char fputc(char c, FILE *fp); fgets() reads a string from the input stream; char *fgets(char s, int numbytes, FILE *fp); It stops reading when either numbytes - 1 bytes have been read, or a newline character is read in. A null terminating byte is appended to the read string, s. If an error occurs, fgets() returns NULL. fputs() writes a null terminated string to a stream; int fputs(char *s, FILE *fp); With the exception of fgets() which returns a NULL pointer if an error occurs, all the other functions described above return EOF (defined in stdio.h) if an error occurs during the operation. The following program creates a copy of the file "data.dat" as "data.old" and illustrates the use of fopen(), fgetc(), fputc() and fclose(); #include int main() { FILE *in; FILE *out; in = fopen("data.dat","r"); if (in == NULL){ printf("\nUnable to open file data.dat for reading\n"); return(0); } out = fopen("data.old","w+"); if (out == NULL){ printf("\nUnable to create file data.old\n"); return(0); } /* Loop reading and writing one byte at a time untill end-of-file */ while(!feof(in)) fputc(fgetc(in),out); /* Close the file streams */ fclose(in); fclose(out); return(0); } Example program using fputs() to copy text from stream stdin (usually typed in at the keyboard) to a new file called "data.txt". #include int main() { FILE *fp; char text[100]; fp = fopen("data.txt","w+"); do{ gets(text); fputs(text,fp); }while(*text); fclose(fp); } Random access using streams: Random file access for streams is provided for by the fseek() function which has the following prototype; int fseek(FILE *fp, long numbytes, int fromwhere); fseek() repositions a file pointer associated with a stream previously opened by a call to fopen(). The file pointer is positioned 'numbytes' from the location 'fromwhere', which may be the file beginning, the current file pointer position, or the end of the file, symbolised by the constants SEEK_SET, SEEK_CUR and SEEK_END respectively. If a call to fseek() succeeds, a value of zero is returned. Associated with fseek() is ftell() which reports the current file pointer position of a stream, and has the following function prototype; long int ftell(FILE *fp); ftell() returns either the position of the file pointer, measured in bytes from the start of the file, or -1 in the event of an error occuring. Handles: File handles are opened with the open() function which has the prototype; int open(char *filename,int access[,unsigned mode]); If open() is successful, the number of the file handle is returned. Otherwise open() returns -1. The access integer is comprised from bitwise oring togther of the symbolic constants declared in fcntl.h. These vary from compiler to compiler but may be; O_APPEND If set, the file pointer will be set to the end of the file prior to each write. O_CREAT If the file does not exist it is created. O_TRUNC Truncates the existing file to a length of zero bytes. O_EXCL Used with O_CREAT O_BINARY Opens the file in binary mode O_TEXT Opens file in text mode The optional mode parameter is comprised by bitwise oring of the symbolic constants defined in stat.h. These vary from C compiler to C compiler but may be; S_IWRITE Permission to write S_IREAD Permission to read Once a file handle has been assigned with open(), the file may be accessed with read() and write(). Read() has the function prototype; int read(int handle, void *buf, unsigned num_bytes); It attempts to read 'num_bytes' and returns the number of bytes actually read from the file handle 'handle', and stores these bytes in the memory block pointed to by 'buf'. Write() is very similar to read() and has the same function prototype and return values, but writes 'num_bytes' from the memory block pointed to by 'buf'. Files opened with open() are closed using close() which has the function prototype; int close(int handle); close() returns zero on success, and -1 if an error occurs trying to close the handle. Random access is provided by lseek() which is very similar to fseek(), except that it accepts an integer file handle as the first parameter rather than a stream FILE pointer. This example uses file handles to read data from stdin (usually the keyboard) and copy the text to a new file called "data.txt". #include #include #include int main() { int handle; char text[100]; handle = open("data.txt",O_RDWR|O_CREAT|O_TRUNC,S_IWRITE); do{ gets(text); write(handle,&text,strlen(text)); }while(*text); close(handle); } THE C PREPROCESSOR C allows for commands to the compiler to be included in the source code. These commands are then refered to as preprocessor commands and are defined by the ANSI standard to be; #if #ifdef #ifndef #else #elif #include #define #undef #line #error #pragma All preprocessor commands start with a hash symbol, "#", and must be on a line on their own (although comments may follow). #define: The #define command specifies an identifier and a string which the compiler will substitute every time it comes accross the identifier within that source code module. For example; #define FALSE 0 #define TRUE !FALSE The compiler will replace any subsequent occurence of 'FALSE' with '0' and any subsequent occurence of 'TRUE' with '!0'. The substitution does NOT take place if the compiler finds that the identifier is enclosed by quote marks, so printf("TRUE"); would NOT be replaced, but printf("%d",FALSE); would be. The #define command can also be used to define macros which may include parameters. The parameters are best enclosed in parenthesis to ensure that correct substitution occurs. This example declares a macro 'larger()' which accepts two parameters and returns the larger of the two; #include #define larger(a,b) (a > b) ? (a) : (b) int main() { printf("\n%d is largest",larger(5,7)); } #error: The #error command causes the compiler to stop compilation and to display the text following the #error command. For example; #error REACHED MODULE B will cause the compiler to stop compilation and display; REACHED MODULE B #include: The #include command tells the compiler to read the contents of another source file. The name of the source file must be enclosed either by quotes or by angular brackets thus; #include "module2.c" #include Generally, if the file name is enclosed in angular brackets, then the compiler will search for the file in a directory defined in the compiler's setup. Whereas if the file name is enclosed in quotes then the compiler will look for the file in the current directory. #if, #else, #elif, #endif The #if set of commands provide conditional compilation around the general form; #if constant_expression statements #else statements #endif #elif stands for '#else if' and follows the form; #if expression statements #elif expression statements #endif #ifdef, #ifndef: These two commands stand for '#if defined' and '#if not defined' respectively and follow the general form; #ifdef macro_name statements #else statements #endif #ifndef macro_name statements #else statements #endif where 'macro_name' is an identifier declared by a #define statement. #undef: Undefines a macro previously defined by #define. #line: Changes the compiler declared global variables __LINE__ and __FILE__. The general form of #line is; #line number "filename" where number is inserted into the variable '__LINE__' and 'filename' is assigned to '__FILE__'. #pragma: This command is used to give compiler specific commands to the compiler. The compiler's manual should give you full details of any valid options to go with the particular implementation of #pragma that it supports. STRINGS The C language has one of the most powerful string handling capabilities of any computer language. A string is a single dimension array of characters terminated by a zero byte. Strings may be initialised in two ways. Either in the source code where they may be assigned a constant value, as in; int main() { char *p = "System 5"; char name[] = "Test Program" ; } or at run time by the function strcpy() which has the function prototype; char *strcpy(char *destination, char *source); strcpy() copies the string pointed to by source into the location pointed to by destination as in the following example; #include int main() { char name[50]; strcpy(name,"Servile Software"); printf("\nName equals %s",name); } C also allows direct access to each individual byte of the string, so the following is quite permissable; #include int main() { char name[50]; strcpy(name,"Servile Software"); printf("\nName equals %s",name); /* Replace first byte with lower case 's' */ name[0] = 's'; printf("\nName equals %s",name); } The ANSI standard on the C programming language defines the following functions for use with strings; char *strcat(char *dest, char *source) Appends string source to the end of string destination. char *strchr(char *s, int c) Returns a pointer to the first occurence of character 'c' within s. int strcmp(char *s1, char *s2) Compares strings s1 and s2 returning < 0 if s1 is less than s2 == 0 if s1 and s2 are the same > 0 if s1 is greater than s2 int strcoll(char *s1, char *s2) Compares strings s1 and s2 according to the collating sequence set by setlocale() returning < 0 if s1 is less than s2 == 0 if s1 and s2 are the same > 0 if s1 is greater than s2 char *strcpy(char *dest, char *src) Copies string src into string dest. unsigned strcspn(char *s1, char *s2) Returns the length of string s1 which consists entirely of characters not in string s2. unsigned strlen(char *s) Returns the length of string s. char *strncat(char *dest, char *src, Copies at most 'len' characters from unsigned len) string src into string dest. int strncmp(char *s1, char *s2, Compares at most 'len' characters from unsigned len) string s1 with string s2 returning < 0 if s1 is less than s2 == 0 if s1 and s2 are the same > 0 if s1 is greater than s2 char *strncpy(char *dest, char *src, Copies 'len' characters from string unsigned len) src into string dest, truncating or padding with zero bytes as required. char *strpbrk(char *s1, char *s2) Returns a pointer to the first character in string s1 which occurs in string s2. char *strrchr(char *s, int c) Returns a pointer to the last occurence of 'c' within string s. unsigned strspn(char *s1, char *s2) Returns the length of the initial segment of string s1 which consists entirely of characters in string s2. char *strstr(char *s1, char *s2) Returns a pointer to the first occurence of string s2 within string s1, or NULL if string s2 is not found in string s1. char *strtok(char *s1, char *s2) Returns a pointer to the token found in string s1 which is defined by delimiters in string s2. Returns NULL if no tokens are found. The ANSI standard also defines various functions for converting strings into numbers and numbers into strings. Some C compilers include functions to convert strings to upper and lower case, but these functions are not defined in the ANSI standard. However, the ANSI standard does define the functions; toupper() and tolower() which return an integer parameter converted to upper and lowercase respectively. By using these functions we can create our own ANSI compatible versions; #include void strupr(char *source) { char *p; p = source; while(*p){ *p = toupper(*p); p++; } } void strlwr(char *source) { char *p; p = source; while(*p){ *p = tolower(*p); p++; } } int main() { char name[50]; strcpy(name,"Servile Software"); printf("\nName equals %s",name); strupr(name); printf("\nName equals %s",name); strlwr(name); printf("\nName equals %s",name); } C does not impose a maximum length which a string may be, unlike other computer languages. However, some CPUs impose restrictions on the maximum size a block of memory can be. For example, the 8088 family of CPUs impose a limit of 64K bytes on a segment of memory. An example program to reverse the order of characters in a string. #include #include char *strrev(char *s) { /* Reverses the order of all characters in a string except the null */ /* terminating byte */ char *start; char *end; char tmp; /* Set pointer 'end' to last character in string */ end = s + strlen(s) - 1; /* Preserve pointer to start of string */ start = s; /* Swop characters */ while(end >= s){ tmp = *end; *end = *s; *s = tmp; end--; s++; } return(start); } main() { char text[100]; char *p; strcpy(text,"This is a string of data"); p = strrev(text); printf("\n%s",p); } TIME C provides a function, time(), which reads the computer's system clock to return the system time as a number of seconds since midnight on January the first, 1970. However, this value can be converted to a useful string by the function ctime() as illustrated in the following example; #include #include int main() { /* Structure to hold time, as defined in time.h */ time_t t; /* Get system date and time from computer */ t = time(NULL); printf("Today's date and time: %s\n",ctime(&t)); } The string returned by ctime() is comprised of seven fields; Day of the week, Month of the year, Date of the day of the month, hour, minutes, seconds, century of the year terminated by a newline character and null terminating byte. Since the fields always occupy the same width, slicing operations can be carried out on the string with ease. The following program defines a structure 'time' and a function gettime() which extracts the hours, minutes and seconds of the current time and places them in the structure; #include #include struct time{ int ti_min; /* Minutes */ int ti_hour; /* Hours */ int ti_sec; /* Seconds */ }; void gettime(struct time *now) { time_t t; char temp[26]; char *ts; /* Get system date and time from computer */ t = time(NULL); /* Translate dat and time into a string */ strcpy(temp,ctime(&t)); /* Copy out just time part of string */ temp[19] = 0; ts = &temp[11]; /* Scan time string and copy into time structure */ sscanf(ts,"%2d:%2d:%2d",&now->ti_hour,&now->ti_min,&now->ti_sec); } int main() { struct time now; gettime(&now); printf("\nThe time is %02d:%02d:%02d",now.ti_hour,now.ti_min,now.ti_sec); } The ANSI standard on C does actually provide a function ready made to convert the value returned by time() into a structure; #include #include int main() { time_t t; struct tm *tb; /* Get time into t */ t = time(NULL); /* Convert time value t into structure pointed to by tb */ tb = localtime(&t); printf("\nTime is %02d:%02d:%02d",tb->tm_hour,tb->tm_min,tb->tm_sec); } The structure 'tm' is defined in time.h as; struct tm{ int tm_sec; int tm_min; int tm_hour; int tm_mday; int tm_mon; int tm_year; int tm_wday; int tm_yday; int tm_isdst; }; HEADER FILES Function prototypes for library functions supplied with the C compiler, and also standard macros are declared in header files. The ANSI standard on the C programming language lists the following header files; Header file Description assert.h Defines the assert debugging macro ctype.h Character classification and conversion macros errno.h Constant mnemonics for error codes float.h Defines implementation specific macros for dealing with floating point mathematics limits.h Defines implementation specific limits on type values locale.h Country specific parameters math.h Prototypes for mathematics functions setjmp.h Defines typedef and functions for setjmp/longjmp signal.h Constants and declerations for use by signal() and raise() stdarg.h Macros for dealing with argument lists stddef.h Common data types and macros stdio.h Types and macros required for standard I/O stdlib.h Prototypes of commonly used functions and miscellany string.h String manipulation function prototypes time.h Structures for time conversion routines