C Ripped Apart :: Section 7
Author: Mike Ware
Website: [warebiz] :: "The Programmer's Domain" - http://warebiz.tripod.com
Email: warebiz@yahoo.com
Copyright © 2003 Michael Shawn Ware, All Rights Reserved.


You are here --> [Section 7 :: Text Files / Structures]

"Jump To Articles"
    --> File Manipulation
    --> Structures


File Manipulation

Working with files is not difficult and adds power to the language. The file streams give you access to read and write data to and from your hard drive. After all, that is the reason for working with files. Your program will either need to read data from a file on your hard drive, or you will need to save data to a file on your hard drive. C associates a file as a sequence of bytes each of which can be read individually. Files can be text or binary. Text files are first converted into ASCII internally (by the computer) before any manipulation can be done. You can create and view text files using a text editor such as Notepad on a Windows pc. Your compiler should also be able to view and create text files. If your compiler does have this option, you should use it when viewing text files created from your program because it will give you the most accurate representation of what your program actually produced. Binary files are the multimedia files on your computer; they may have extensions such as: obj, exe, gif, jpg, avi (etc). I will only brush through the topics of text files in this section. I may discuss binary files in future articles (depending on visitor feedback). Please note that all information in this tutorial is based on the ANSI C Standard Input/Output.

File Declaration
Declaration must be performed before all other manipulations when working with files. This associates a variable as being of a file type. In C, declaration is simply performed using the FILE data type. Consider the following examples:


     FILE *inFile, *outFile;

This associates inFile and outFile as being FILE types, and now inFile and outFile may be used as file pointers in the rest of the program. The file pointers will be used when any manipulation needs to be done to a particular file.

File Opening
With declaration complete, we can open the declared file and set its mode. Once opened, the file can then be explicitly accessed for processing or storing purposes. Opening a file is done by using the fopen( ) member function defined in <stdio.h>. The fopen( ) function has the following form:


	fopen( char* fileName, "mode" );

where char* fileName is a string containing the path on disk of the file attempting to be opened and mode is how the file will be opened. The mode options are as follows:


	"r"	: for when data will be read from the file
	"w"	: for when data will be written or sent to the file (truncation could occur)
	"a"	: for when data will be appended to the end of a file (creation of new file could occur)
	"r+"	: for when data will be read from or written to a file
	"w+"	: fow when data will be read from or written to a file (creation or truncation could occur)
	"a+"	: for when data will be read from or written to a file in appending mode (creation could occur)

The fopen( ) function will return a file identifier or file pointer if successful or will return a value of NULL if the file is error-filled. Because it will return one of these values, a previously declared file pointer should be assigned the value of the fopen( ) attempt. For example, consider:


	FILE *inFile; // declaration of file handler

	// open file and perform validation
	if ( (inFile = fopen(fileName, "r")) == NULL )
	{
		printf("%s is error-filled.\n\n", fileName);
		exit(1);
	}
	else
	{
		// process file
	}

Writing to a File
In order to write data to a file, the file must be opened in a mode set for writing. Writing to a file is performed similar to writing to the normal console output streams except that you must explicitly specify which file will be manipulated using the file pointer. The putc( ) function is used similar to the putchar( ) function except that you must specify a file to be used as a second argument. putc( ) has the following form:


	putc( char charValue, FILE *filePointer );

where char charValue is the value being sent and FILE *filePointer is the file handler defined for the particular file being manipulated. It is also possible to write to a file using the fprintf( ) function, which is used very similar to the printf( ) function. The fprintf( ) has the following form:


	fprint( filePointer, controlString, [argument1, argument2, ...] );

where filePointer is the declared file being manipulated, controlString is the "normal" string containing format specifiers and other text, and [argument1, argument2, ...] is the listing of arguments which correspond to the format specifiers in controlString.

Reading From a File
To read from a file, the file must be opened for reading purposes. Reading data from a file is much like reading data from the "normal" input console streams except yet again you must explicitly specify which file will be read from by passing a file pointer argument to the function. The getc( ) is performed similar to getchar( ) except that getc( ) has the following form:


	getc( filePointer );

where filePointer is the declared file that is being read from. Since the getc( ) will extract data from the file, the value it grabs should be returned and stored in a variable. For example, consider:


	char score;
	FILE *inFile;
	.
	.
	// open inFile for input
	.
	.

	score = getc( inFile );

As with printf( ) and its corresponding fprintf( ) function, it is also possible to read from a file using the fscanf( ) function, which is used very similar to the scanf( ) function. The fscanf( ) has the following form:


	fscanf( filePointer, controlString, (argument list) );

where filePointer is the declared file being manipulated, controlString is a string containing the conversion specifiers used to format the data to be converted, and argument list is a listing of variables matching their corresponding conversion specifiers in controlString that will be assigned the data values supplied by the file stream.

Closing a File
Closing a file is done to ensure that the file stream is "cut off" or "destroyed" after the program doesn't need access to the file. This is particularly important when writing to text files. If you do not close a file after writing to it, garbage data that the programmer is not aware of can possibly be sent and stored in the file by mistake. Because of this, be sure to close any file after you are done using it. Closing a file is performed by using the fclose( ) function which has the following form:


	fclose( filePointer );

where filePointer is the declared file being closed.

FEOF (End of File) Function
The feof( ) function is used to determine if end-of-file has been reached during processing. If end-of-file has been reached, it will return a nonzero value; it will return zero otherwise.

FERROR Function
The ferror( ) function is used to determine if an error has occurred during file processing. It will return a nonzero value if an error occurred.

There are many more file manipulation functions defined in the ANSI standard library. Too many for me to elaborate on in a tutorial of this scope. I have provided the necessary functions for you to at least grasp some file manipulation concepts. The following are two programs demonstrating the use of most of the file functions previously covered in this tutorial. Study the examples and try to write a few of your own before reading on in the tutorial.


////////////////////////////////// COMPILED USING DEV C/C++ V.4 COMPILER ///////////////////////

// PURPOSE: Uses simple file processing techniques to retrieve data from a
//          data file on disk (specified by user) and display results to
//          screen accordingly. The data file used for this program should
//          reflect data contained in an employee database with names,
//          identification numbers, and hourly rate values for each employee.
//
//         INPUT: DATA FILE using the following format:
//                1- all data must be entered in consecutive order
//                2- first values must be at least 21 character values
//                      representing employee name
//                3- next value should be integer representing employee id number
//                4- last value should be float representing employee hourly rate
//
//          NOTE: A SAMPLE DATA FILE HAS BEEN PROVIDED.
//                IT IS STRONGLY RECOMMENDED TO DOWNLOAD THE DATA FILE FOR
//                REFERENCE AND PROGRAM EXECUTION.
//
//
// PROGRAMMER: Mike Ware
// DATE LAST UPDATED: 7-21-03

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

#define FILENAMELEN 81
#define MAXSTULEN 21

int main()
{
      FILE *inFile;  // file handle pointer
      char fileName[FILENAMELEN]; // actual file path
      char empName[MAXSTULEN]; // temp storage for file name processing
      int empId; // temp storage for file id processing
      float empRate; // temp storage for hourly rate processing
      int count = 0; // keeps track of number of employees processed

      // retrieve path of data file from user
      printf("Enter the disk path of the data file: ");
      scanf("%s", fileName);

      // open file and perform validation
      if ( (inFile = fopen(fileName, "r")) == NULL )
      {
            printf("%s is error-filled.\n\n", fileName);
            system("PAUSE");
            exit(EXIT_FAILURE);
      }

      // begin file processing / display results
      while ( feof(inFile) == 0 )
      {
            // retrieve file data for each employee
            fgets(empName, MAXSTULEN, inFile);
            fscanf(inFile, "%d %f", &empId, &empRate);

            // display temporary values retrieved from file
            printf("\n\nID:   %d\n", empId);
            printf("NAME: %s\n", empName);
            printf("RATE: %.2f", empRate);
            count++;
      }

      // display total number of employees processed
      printf("\n\n[%d employee records were successfully processed.]", count);

      // close input file
      fclose(inFile);

      printf("\n\n\n");
      system("PAUSE");
      return EXIT_SUCCESS;
}

////////////////////////////////// COMPILED USING DEV C/C++ V.4 COMPILER ///////////////////////

To download a sample data file that will work for the above program, right click CDATA.DAT and select save link or target as...


////////////////////////////////// COMPILED USING DEV C/C++ V.4 COMPILER ///////////////////////

// PURPOSE: Demonstrates how to write data contained in an integer array to a
//          file on disk (specified by user). The integer array will be filled
//          with 50 randomly chosen integers in the range 0 to 100.
//
//         OUTPUT: DATA FILE using the following format:
//                1- each array element will be stored on a single line
//
// PROGRAMMER: Mike Ware
// DATE LAST UPDATED: 7-21-03

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

#define FILENAMELEN 81
#define NUMOFINTS 50
#define RANDOMRANGE 101

int main()
{
      FILE *outFile;  // file handle pointer
      char fileName[FILENAMELEN]; // actual file path
      int integers[NUMOFINTS]; // array containing random integers
      int index; // used for looping purposes

      // store random numbers in array structure
      for (index = 0; index < NUMOFINTS; index++)
            integers[index] = rand() % RANDOMRANGE; // random numbers from 0 to 100

      // retrieve path of report data file from user
      printf("The integer array of 50 values has been successfully filled.\n");
      printf("Enter the disk path of the data report file: ");
      scanf("%s", fileName);

      // open file and perform validation
      if ( (outFile = fopen(fileName, "w")) == NULL )
      {
            printf("file %s is error-filled.\n\n", fileName);
            system("PAUSE");
            exit(EXIT_FAILURE);
      }

      // send random values to report data file
      for (index = 0; index < NUMOFINTS; index++)
            fprintf(outFile, "%d\n", integers[index]);

      // display successful processing
      printf("[%s] has been successfully processed and saved at your request.", fileName);

      // close output file /// IMPORTANT //
      fclose(outFile);

      printf("\n\n\n");
      system("PAUSE");
      return EXIT_SUCCESS;
}

////////////////////////////////// COMPILED USING DEV C/C++ V.4 COMPILER ///////////////////////

In the next section, we explore the wonderful world of structures. A structure is a user-defined type similar to an array but with many more capabilities. Read on for more about structures...

Structures

A structure in C is similar to a record in Ada, Pascal, QBasic, and some other programming languages. A structure, like an array, is a collection of values but structures are different from arrays in that the values stored in an structure may be of different types, and a structure's stored values, called members, are individually named and typed, just like "ordinary" variables. In other words, a structure is a collection of related elements, possibly of different types, having a single referencing name. Each element (member) in a structure is called a field of the structure. All elements or data specified in a structure should be related to one object. Each element in a structure can be individually accessed by using the "dot" ( . ) operator.

The form for declaring a structure type is:


    struct type-id
    {
        type1 member1-name;
        type2 member2-name;
        type3 member3-name;
        .
        .
        typeN memberN-name;
    };


NOTE: The semi-colon placed after the closing bracket is required. The proper placement of a structure type is at the beginning of a program in the global area before the main ( ) function or in a user-defined header file.

The above structure type would create a data type, not a variable. To declare of variable of this type, simply declare it as follows:


    struct type-id var-id;

Suppose we need to store the following data for a student:


    id #
    name
    grade
    gpa

We could create a structure type to handle this situation as follows:


    #define NAMELEN 25

    struct tStudent
    {
    	int idNumber;
    	char name[NAMELEN];
    	char grade;
    	float gpa;
    };

To declare two variables of this type, simply use:


    struct tStudent stu1, stu2;

We can then give values to each individual member as follows ( string.h required for strncpy ):


    stu1.idNumber = 111;
    strncpy(stu1.name, "studentname", NAMELEN);
    stu1.grade = 'A';
    stu1.gpa = 4.0;

You are allowed, but not advised, to combine a structure specifier with the declaration of a variable of that structure type. For example:


    #define NAMELEN 25

    struct tStudent
    {
    	int idNumber;
    	char name[NAMELEN];
    	char grade;
    	float gpa;
    } thisStudent;

This can be used instead of creating a structure type and then declaring a variable; in this case, thisStudent would be immediately associated with the structure type. Obviously, this is not recommended because there would be no way to declare multiple variables of that particular structure type. You can also initialize structures at compile time just like you can arrays. For example, with the earlier tStudent structure type, we could use:


    struct tStudent stu1 = { 111, "studentname", 'a', 4.0 };

Unlike arrays, you can directly assign one structure variable the value of another structure that is of the same type. For example, if we had another variable declared struct tStudent thisStu, we could simply use the assignment operator to assign thisStu the value of stu1:


    thisStu = stu1;

The above assignment statement would perform a data member by data member copy of stu1 to thisStu. The following program illustrates this concept.


//////////////////////////////////////// USING DEV C/C++ V.4 COMPILER /////////////////////////////////////////

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

#define NAMELEN 25

    struct tStudent
    {
    	int idNumber;
    	char name[NAMELEN];
    	char grade;
    	float gpa;
    };


int main()
{
      struct tStudent stu1 = { 111, "studentname", 'a', 4.0 };
      struct tStudent stu2;

      stu2 = stu1;

      printf("%s\n", stu1.name);
      printf("%s", stu2.name);

      printf("\n\n\n");

      system("PAUSE");
      return EXIT_SUCCESS;
}

//////////////////////////////////////// USING DEV C/C++ V.4 COMPILER /////////////////////////////////////////

You can also declare an array of structure types and are allowed to give structure types arrays as individual members (we actually have already done this with strings). For example:


    struct STUDENT
    {
        int idNum;
        int testScores[10];
        int finalExam;
    };

    struct STUDENT classStu[50];

In the above structure type, testScores[] is an array that holds ten values. classStu[] is an array that holds 50 STUDENT structure types. We can access and manipulate the contents of each element in the classStu[] array much like we do ordinary arrays. For example:


    classStu[5].testScores[1] = 88;

The above statement would assign the second test score of the sixth element in classStu[] a value of 88.

Structure Example:
The following program acts as a simplified computerized ordering system for an automobile part store. Study the code before reading further in the tutorial.


//////////////////////////////////////// USING DEV C/C++ V.4 COMPILER /////////////////////////////////////////

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

#define MAXORDERSIZE 2

struct tAutoPart
{
      int partNumber;
      int idNumber;
      float retailPrice;
      float wholesalePrice;
};

int main()
{
      int j, k;

      struct tAutoPart partOrder[MAXORDERSIZE];

      printf("MAXIMUM ORDER SIZE IS 2\n\n");

      for (k = 0; k < MAXORDERSIZE; k++)
      {
            printf("Enter the part number for order %d : ", k+1);
            scanf("%d", &partOrder[k].partNumber);
            printf("Enter the identification number for order %d : ", k+1);
            scanf("%d", &partOrder[k].idNumber);
            printf("Enter the retail price for order %d : ", k+1);
            scanf("%f", &partOrder[k].retailPrice);
            printf("Enter the wholesale price for order %d : ", k+1);
            scanf("%f", &partOrder[k].wholesalePrice);
            printf("\n");
      }

      printf("\n\n");
      printf("COMPLETE ORDER\n");
      printf("---------------------------------------------------\n\n");
      for (j = 0; j < MAXORDERSIZE; j++)
      {
            printf("Order %d -->\n", j+1);
            printf("Part Number: %d\n", partOrder[j].partNumber);
            printf("ID Number: %d\n", partOrder[j].idNumber);
            printf("Retail Price: $%.2f\n", partOrder[j].retailPrice);
            printf("Wholesale Price: $%.2f\n", partOrder[j].wholesalePrice);
            printf("\n---------------------------------------------------\n\n");
      }
      printf("\n\n\n");

      system("PAUSE");
      return EXIT_SUCCESS;
}

//////////////////////////////////////// USING DEV C/C++ V.4 COMPILER /////////////////////////////////////////

You have reached the end of the C Ripped Apart tutorial. Currently, I am not writing any articles on C. I guess my motivation for continuing to explore and lecture on C will stem from visitor and reader feedback. I'm always open for suggestions. If you have encountered any problems in the tutorial or have a question, simply contact me at the address provided above. Until then, happy coding...

Back to Top