Corning Community College
CSCS1320 C/C++ Programming
~~TOC~~
To begin the exploration of structures, and to explore file access functionality.
Some notable references:
This project will deal with two independent concepts:
Please note, the two are not related, although as with many things, may often be used together (file access is extremely useful, and will often find pairings with all our covered topics- variables, selection statements, loops, pointers, arrays, and now structures too).
I've held off on really covering it until we've gotten more of the basics down, so that you can better understand the power it offers to your programming toolkit.
But first things first, the structure.
In some respects, there are 2 classifications of variables:
Both arrays and structures need to be given substance: they are nothing on their own.
The same goes for pointers, of which all arrays are a type of (all arrays are pointers, but NOT all pointers are arrays, unless you consider them an array of 1).
In that respect they are sort of like adjectives (descriptor attributes), they describe a property of a noun (or thing where attributes can be applied). For example:
The adjective “green” DESCRIBES an attribute of the frog. “green” by itself does not make sense in that context:
See what I mean? The green WHAT??
Same goes with arrays and structures. They both enhance things, but cannot be singular entities by themselves:
Same thing with structures, only structures (or the struct keyword) let us pack in any combination of types (including arrays and structs).
Just for clarification, you can also have “arrays of arrays” (argv, the second main() function argument, is one in fact) and “arrays of structs”. But an array specifically focuses on duplicating ONE THING, whatever that thing is… a struct encapsulates a collection of potentially disparate things.
Declaring a struct may appear similar to declaring a function, in that there are braces and things inside those braces.
For instance:
struct stuff { int value; char code; short int range[999]; char *name; };
Here we have a struct (specifically, a “stuff struct”– that is the type, as structs are nothing on their own) that contains 4 entities, of varying types:
Note one important distinguishing syntactical detail of structures: you MUST terminate them with a semi-colon (they are a variable, and variables are terminated with semi-colons).
To declare a struct, we pretty much do the same as when declaring a variable.
In our case, if we have a “stuff struct” as defined above, we'd need to declare an instance of it in order to make use of it. Let's make a “stuff struct” variable by the name of thing:
struct stuff thing;
Bam! Please take note of how precisely like any other variable this declaration is… in our case, thing is of the “type” struct stuff.
In C (and by extension, C++), there are two structure access operators, depending on whether or not the structure is a pointer (yes, we can even have pointers to structs).
If we are dealing with a non-pointer struct, we use the '.' (dot) operator to access the structure member (so, using our thing variable declared above to assign information to its members):
thing.value = 37; thing.code = 'C'; thing.range[59] = 1337;
Especially with arrays and pointers (especially if used as something like a string), we'll probably be routinely combining them with loops, as we cannot access ALL elements in ONE statement.
If the struct we are dealing with has been declared as a pointer (which is the common approach in regular usage, especially as used and interacted with in the C library functions):
struct stuff *something;
There are actually two ways of accessing it, using the pointer dereferencing operator, or using the structure pointer operator. Both arrive at the same ends (the structure pointer operator is a shortcut to make our code look nicer).
(*something).value = 64;
something -> value = 64;
The structure pointer just makes the code look cleaner, so it is the recommended way of accessing elements (when the structured variable has been declared as a pointer, that is).
While information is unique down to bits, and the computer accesses data in units of bytes, information is often packaged and made available to us in containers known as files.
Like a structure, there is no stipulation on what sort of data goes in a generic file; although like an array, a file can be accessed as a sequence of bytes, from a starting index or offset.
C and other production-oriented programming languages provide file I/O functionality, which can greatly increase the usability of the programs we write.
From reading in external data sets to loading/storing pertinent data in specific patterns (ie formats), file access, like pointers and memory access, contribute to the power and influence of C (and its derivatives).
C provides two streamlined ways of accessing a file, and various groups of C library functions to access in conformance with the access method:
FILE pointers are just that: a pointer to a special type of struct, which provides an interface to the file's contents.
File descriptors are perhaps slightly more polished, abstracting away more of the low-level details of file access, instead creating an interface around a unique numeric value (somewhat like a “take a number” approach to service).
For the purposes of this project and this course, we will be focusing on file access via the use of FILE pointers. Just be aware that there are corresponding functions that make use of the file descriptor concept.
To interact with a file, we must do so in accordance with a fixed set of actions (which are woven into the various C library file functions), some common ones of which are:
We'll be specifically focusing on opening, reading, writing, and closing files for this project.
A point of distinction on “write” vs. “append”: when you open a file for writing, you start from its beginning, overwriting and existing content; when you open for appending, you start at its end, adding to (appending) existing content.
To access a file, we must first have an instance of the file access interface to interact with. As indicated above, this comes in the form of a FILE pointer, which for us will take the form of a variable:
FILE *fp = NULL;
There's nothing magical about the name; you may find “fp” being a common variable name used for file pointers (fp = file pointer), but in the case of multiple file access, even seeing names like in, out, inp, outp is not uncommon. Again, the idea is to make your variable names descriptive enough so as to be a form of documentation in and of themselves.
To gain access to a file, we must formally OPEN it. The C library provides us with the fopen() function, which takes 2 arguments:
If no extensive path information is given, the program knows to look in the current working directory. For portability, any program seeing wider usage should be referencing an absolute path to reduce potential access complications.
As for modes, there are 3 main modes we will be focusing on (there are other combinations, but generally are utilized in more advanced usage; just stick with these for now):
Both the location and mode of the parameters are strings (arrays of char, with a terminating NULL terminator).
If we wanted to open the file “output.txt” for writing, we would say:
fopen("output.txt", "w");
fopen() returns a FILE pointer, so in order to make use of it, we need to connect its return value with our FILE pointer, as follows:
fp = fopen("output.txt", "w");
If there was a problem opening the file by the requested mode, fopen() returns NULL (a great thing to check for to ensure if we were successful or not).
At this point, we can write (or output) to this “output.txt” file, via our fp variable.
Output can be done with a familiar function we've been using all along: fprintf(), the first argument of which is a FILE pointer.
This would write “hello, world!\n” to our output file:
fprintf(fp, "hello, world!\n");
This is why I've been having us use fprintf() all along (instead of the printf() shortcut), so that the interface would already be familiar to you, and for you to conceptually see that outputting to the screen and outputting to a file are indistinguishable (because they are the same thing to the operating system: EVERYTHING is a file).
If we had instead opened our file for reading, we could read from it the same way we obtain keyboard input: fscanf()
For example, this will read an unsigned short integer from the file pointed to by our FILE pointer (but ONLY if we opened it for reading):
fscanf(fp, "%hu", &value);
When you are reading from a file and have exhausted its contents, the last character read from the file should be a special EOF symbol, and various other status bits are likely flipped in our FILE pointered struct to indicate that the end of file has been reached.
A good function to use is feof(), which takes a FILE pointer to check, and it will return a nonzero value if the end of file has been reached (great for using in selection statements or loops as a combined check and termination combo!)
Doing any digging you will see that it is entirely within our ability to open files for reading AND writing; I would caution you against this, because there are issues of data corruption and/or data loss at stake if we're not careful. For now, just focus on ONE action, the current intended action. If you need to switch back and forth, close the file and open it in the new desired mode. That will maintain the integrity of your data, especially when first learning (as we are now). In time when the complexity/demands of your programs calls for it, you can start to dabble and experiment with such functionality.
Just as you are the highly responsible and respectful individual when returning rented VHS tapes to the rental store, or loaned cassettes or 8-tracks from the library (WHAT?), you have been kind and put the media in a state where it is no longer connected to your processing environment (you ejected it).
In the case of file access in C, that means remembering to CLOSE the file when we are done with it, and that can be done with the fclose() function:
fclose(fp);
Having a file open allocates additional resources. Forgetting to close the file when done keeps those resources in use. Granted, while they will be deallocated on program exit (and our programs are all quick to execute at this point), it is a good habit to perform proper file management by closing files when we are done with them, so when our programs are more complex, that won't be a bug that needs tracking down.
For this project, mixing together all the skills we've previously learned and just learned is in order.
In /var/public/cprog/sfa0/ is a file called datafile.db, which contains several records of the following format:
NAME # 1S 1T 2S 2T 3S 3T ... #S #T
Where:
It will be your task to write a program that:
Name:#:scoreTally:scoreTotal:avgofAverages:averageofTallies:#s,#t;...;2s,2t;1s,1t
Of particular note:
For example, if the source data was:
KRIS 2 13 17 9 18
The corresponding line written out to sfa0.out would be:
Kris:2:22:35:63.25:62.86:9,18;13,17
Additional constraints:
Just to review the compilation/execution process for working with your source code, if we had a file, hello.c, that we wished to compile to a binary called hello, we'd first want to compile the code, as follows:
lab46:~/src/cprog$ gcc -Wall --std=c99 -o hello hello.c lab46:~/src/cprog$
Assuming there are no syntax errors or warnings, and everything compiled correctly, you should just get your prompt back. In the event of problems, the compiler will be sure to tell you about them.
Conceptually, the arrangement is as follows:
gcc -Wall --std=c99 -o BINARY_FILE SOURCE_FILE
The BINARY_FILE comes immediately after the -o, NOT the SOURCE_FILE (it must never immediately follow a -o). It can precede, and such is perfectly valid (especially if you feel that way more intuitive).
The -Wall (treat all warnings as errors, increase general verbosity about warnings) and –std=c99 (switch compiler to use the C99 standard of the C language) are options given to the compiler.
To execute your binary, we need to specify a path to it, so we use ./, which basically references the current directory:
lab46:~/src/cprog$ ./hello Hello, World! lab46:~/src/cprog$
To successfully complete this project, the following criteria must be met:
To submit this program to me using the submit tool, run the following command at your lab46 prompt:
$ submit cprog sfa0 sfa0.c sfa0.out Submitting cprog project "sfa0": -> sfa0.c(OK) -> sfa0.out(OK) SUCCESSFULLY SUBMITTED
You should get some sort of confirmation indicating successful submission if all went according to plan. If not, check for typos and or locational mismatches.