Welcome to Software Development on Codidact!
Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.
Problems with data structures and filestreams.
So I just started learning how to use file-streams in C & decided to attempt a question which is to do with library management in C, however I am currently encountering some problems and feel like I need some help/advice. Basically, my file name is libraryy.txt, I want to delete a book that I have inserted into my libraryy.txt file. When I enter the book's title into my compiler's output screen, I am expecting the compiler to read from my file (libraryy.txt) & delete that particular book's title, author & subject. However when I run my program, it doesn't seem to delete anything from my libraryy.txt file.
/*Data structures that the question wants me to use:*/
struct book{
char *title;
char *author;
char *subject;
};
struct library {
struct book collection;
int num_books;
struct library *next;
};
struct library* head;
struct book collection;
struct book *aPtr;
struct library aCollection;
struct library *aaPtr;
/*Below is my deleteBook function which I'm having a problem with*/
void deleteBook(struct library* thislib){
//How to define library here?
FILE *cfPtr, *cfPtr1;
int j, found=0;
char stitle[MAX];
cfPtr = fopen("libraryy.txt", "r");
cfPtr1 = fopen("temp.txt", "w");
printf("Enter book title to delete:\n");
scanf("%s", stitle);
/*I'm confused on what I should be adding beside the '&' operator in the brackets of my 'while' loop*/
while(fread(&thislib->collection, sizeof(struct library),1,cfPtr)){
if(strcmp(stitle, thislib->collection.title)==0){
found = 1;
}
else
fwrite(&thislib->collection, sizeof(library), 1, cfPtr);
}
fclose(cfPtr);
fclose(cfPtr1);
if (found){
cfPtr1 = fopen("temp.txt", "r");
cfPtr = fopen("libraryy.txt", "w");
while(fread(&thislib->collection, sizeof(library),1,cfPtr1)){
fwrite(&thislib->collection, sizeof(library),1,cfPtr);
}
fclose(cfPtr);
fclose(cfPtr1);
}
}
/*Other irrelevant functions which I did not include here like insertBook().....*/
/*Main function*/
int main (void){
FILE *cfPtr;
/*Didn't include the whole coding to avoid the lengthiness of this question*/
Regarding the data structures provided that we have to use as it is a pre-requisite of the question as shown and defined in the code, I want to add that it seems that the 'library' data structure is supposed to access the 'book' data structure & the elements in it, so that it will allow me to delete the book (& its author, title as well as subject) from the file (libraryy.txt) when I enter the title of the book in my compiler screen.) Due to the question providing the data structure to me in that way, I am practically confused on how to access one data structure from another data structure, hence I'm not so sure on what to include beside the '&' operator in the while loop brackets as shown below for fread... is this how you do it, as shown in my code above? Also how do I define my library in the deleteBook() function?
2 answers
Notice
You'll need to backpedal some! Read the answer by @r~~ above, but I expect you need some hints.
This reads as either a selfstudy or schooling exercise task, so I'll stick to review + hints and leave you some discovery of your own; I know lots of peeps have trouble grokking pointers and the code shows it, so we'll focus on those. Then there's organization, i.e. a mental picture of what you're doing and how stuff is organized on disk and in memory (RAM) and how we might go about editing that organization.
Analysis of struct book
and commentary
struct book{
char *title;
char *author;
char *subject;
};
This tells us a book
type consists of 3 pointers (the *
in each char *
type is a clear mark of that).
Pointers point somewhere ('referencing') and generally take up separate space themselves. char *
is often also referred as a string but that's just what it's used for most often: a string in C is a sequence of char
characters (terminated by a NUL character a.k.a. NUL sentinel to mark the end of the string).
Here those 3 pointers imply a struct book
only carries the 3 references, while the actual strings are stored elsewhere. (Usually, those are 'allocated on the heap' if you want to keep them alive for a while; I don't know if you've already exercised with malloc()
and free()
(and calloc()
might be handy!) -- if you haven't, do that first in a separate exercise.
Pointers (and a sub-par analogy towards understanding them)
Pointers are references, a bit like phone numbers: when you've got a phone number, you can call that phone (and thus reach somebody), but it's definitely not the phone itself. As with phone numbers, which you need to write down or otherwise memorize in order not to loose them (or you won't be able to call that person ever again), pointers need a little space of their own to be remembered: a pointer variable. struct book
has 3 of those, so clearly it intends to remember where 3 char
(character) type values are stored.
The slightly wicked bit in C, which I've found throws off a lot of folks learning the language, is that C can do 'pointer arithmetic': that's a bit like adding or subtracting 1 (or more) from a phone number, so you get the next one. Here the analogy goes horribly bad as that won't be the phone number of their neighbour, but in C and computers, it generally is.
Very Important Nitpick
Hence we usually read char *
as pointer to a string while technically it's more properly interpreted as (almost always!) a pointer to the first character (char
!) of that string value.
I find that many textbooks and educators gloss over this precise reading of type declarations, causing a lot of confusion about pointers at the very start.
*
must be read as "points at <what came before in the type declaration>" thuschar *
reads as:
char
: "(space for) 1 character".*
: "points at..." what?char
! Hence: "*points at a single character". We may call this particular type a 'string' colloquially, but that's only true in most circumstances, because C can do pointer arithmetic and thus reach the next character, and the one after that, and so on, when it only has a pointer at the first one!const char *p = "Hello world\n"; while (*p) { printf("%c", *p); p++; }
p
now will point at the NUL sentinelchar
at the end of that"Hello world\n"
string. You have 'walked the pointer down the array of characters that is a string'.BTW: the
while (*p)
loop terminates because*p
is read and when it has become NUL (0), this evaluates towhile(0)
hencewhile(false)
and thus the loop is stopped. OTOH,while(*p)
at first evaluates towhile('H')
:'H'
is a non-zerochar
value and thus in thewhile
evaluates towhile(true)
. I expect this to be obvious already, but your question and code strongly indicate a missing comprehension of pointers at large, so I address these here to make sure you can pick up from a place where you're probably already comfortable.You can also use indexing, which is just another way of using pointers, without walking them:
const char *p = "Hello world\n"; int i = 0; while (p[i]) { printf("%c", p[i]); i++; }
i
will now be 12 andp[i]
will be NUL(0). Meanwhile,p
has not been changed and can thus be re-used to access that "Hello world" string again if we feel like it.Pointer arithmetic also means that this next code is identical to the previous one:
const char *p = "Hello world\n"; int i = 0; while (*(p + i)) { printf("%c", *(p + i)); i++; }
p + i
reads as: "add an offset to the pointerp
; the offset isi
times the size of a single element of type*p
(in this casechar
)" --> the effective pointer resulting from this pointer arithmetic will point at thei
-thchar
in the string pointed at byp
. And anywhere I usestring
, you can also use the word 'array'.
fread
analysis
This means that your fread(&thislib->collection, ...)
might compile but is reading like an oddity, because to a programmer it reads as: thislib->collection
is of type struct book
, so this fread()
is told to load a struct book
instance (and I ignore your sizeof(struct library),1,
bit there on purpose now, to keep things simple) and thus is supposed to load three pointers from disk! That, to a programmer, feels like nonsense (or somebody is being horribly clever and knows exactly which machine this is going to run on and how -- somebody like Ken Thompson (look him up) might be able to pull this off, but for all us mere mortals this yells "bug!!1!"
Why? Because, unlike those phone numbers from the analogy, pointers (pointer values) are ephemeral: they are expected to be different on every run of your application, thus storing and loading those from disk will lead to disaster: the data we want isn't there this time around, but stored someplace else in RAM.
Organization: what is stored where?
So we come at your first real hurdle, which is encoded as part of the task list in @f~~ answer: you need to sit down and come up with some organization: how are those 3 strings that make a book
entry stored on disk?
One of many answers to that is to store all 3 strings consecutively and decree that your 'library' (series of book
s) can then be read as reading the entire series of strings from front to back: (1,2,3),(4,5,6),... -- which is relatively easy, but has the significant drawback that you cannot jump to a book in the middle as those titles, authors and subject descriptions will have varying lengths.
Plus you won't be able to detect any tiny mistake the program might have made the previous time it ran: if a string is missing or corrupted (e.g. you haven't delimited/terminated it properly last time you wrote the file) you won't be able to tell as each of the strings you encounter along the way can become a tile, author or subject: you won't know where the book record boundary is in that library file.
More food for thought: using text files
Since you mention a .txt
file as the library file, an obvious organizational choice might be to store each book as three lines of text, thus using '\n'
(NewLine) as a string delimiter on disk. Of course, it is then strictly prohibited to have it anywhere in the subject
, title
or author
strings or you'ld have a problem.
To delineate a record, one could add an extra line then as part of each record, e.g. BOOK RECORD
, so your application could then check that each book record starts with such a line or report an error if it doesn't. The possibilities here abound. (Of course, this assumes we're not worried about, say, the title
of the book reading "BOOK RECORD"
itself: think about it and see if you can visualize the file organization in your mind (or on paper) and observe that this would not pose a real problem. Only, possibly, a very minor one when the library file has been damaged ('corrupted') on disk.
Given the .txt
file you mention and my mentioning of \n
as a delimiter, this doesn't sit well with C, which expects its strings to be NUL terminated. Meanwhile, the human convention is to not write NUL characters to .txt
files, so we can open them without hassle in editors like Notepad or vi.
So loading the library from file into memory is a bit of a challenge then. Of course, one can use fread()
for this (a apply a few bits of cleverness afterwards), but fread()
and fwrite()
are generally reserved for use with 'binary file formats'; at least that's what a programmer reading thee code would initially expect. Check fscanf()
, fgets()
and friends: that's a hint.
Which still leaves the struct book
layout: once you have those title, author and subject strings read into memory using any fstream approach, you still need to make sure those strings have a proper 'lifetime' and are pointed to by the pointers in your struct book
instance variable before you can add it to your library in memory (RAM).
May I suggest you write a small program attempting to add a few books in memory to your library first? You're expected to use
malloc()
andfree()
as you'll be needing those for a lot of this stuff: anything that needs to survive beyond the function call where it was created is usually allocated on the heap, i.e. storage space is requested for these strings usingmalloc()
orcalloc()
.I expect this will already be tough enough to get right initially, before you add file I/O to the mix.
Analysis of struct library
and commentary
Assuming you have been able to write a few (small) programs to create a single book structure in memory and load or store one to disk, we have a look at your struct library
because there's something quite odd with it (I guess it's a typo):
struct library {
struct book collection;
int num_books;
struct library *next;
};
reads to a human programmer as: this struct library
apparently is intended to contain a collection of books (num_books
entries in total) and apparently also points at the next library, so we're looking at the basics for a chain of libraries? Would that be a linked list of libraries?
But the very odd part is this: when struct library
would contain (or reference?) a collection of books, wouldn't we need something similar to that 'trick' C uses to reference strings, i.e. reference the initial element using a pointer and then use pointer arithmetic to get at the subsequent entries? But struct library
is specified as having a
struct book collection;
which reads as: contains one instance of a struct book
! Of course I can use C to get a pointer to that internal instance too, if I want (which is what &
is good for, as in your &thislib->collection
, but I (and the machine) won't know where the other num_books-1
book entries are stored!
How about this?
struct book *collection;
That's more like it: when char *title
is read as pointer to char
and thus can be interpreted as pointer to first char
in an array of char
s (a.k.a. a string), then the same would be valid for struct book *collection;
: a pointer to thee first struct book
instance in an array of struct book
instances.
With C strings, we have that NUL sentinel to mark the end, rather than keeping a string length as a value somewhere (check out strlen()
if you haven't already). Here we have a length: num_books
, so you're supposed to be using that to keep track of the total number of struct book
records in this particular library instance.
Hence I expected the struct library
definition to look like this:
struct library {
struct book *collection;
int num_books;
struct library *next;
};
because then it would read as something sensible to use as a part of memory organization for your library. (And apparently the exercise is hinting there may be more libraries loaded into memory at some time due to the next
pointer.)
Memory organization once again
Again, we need to think about how that struct library
will sit in memory and how it will be able to get at the individual struct book
-referenced strings specifying author, title and subject: we must be able to print those using printf()
before we can assume we'll be successful comparing any book title against a given title in order to delete that book.
Also, you then need to reconsider how you remove a book from the array of books in memory. Do you need to adjust anything else? What if you delete multiple books, before you write the library back to disk?
Spinning down...
Plenty questions left, but time is running out here and I think you're best served with slowly descending onto your original posted problem: write several small application codes for the parts as suggested in @f~~'s answer. If that doesn't go smoothly and swiftly, backpedal and try to write a small test program for each of the bits discussed above. When you do that, always first try to write a piece of code that does '1 struct instance', then follow up with another copy that does work for 2..N (arbitrary number of items): you'll only encounter your pointer problems (and learn from them) once you do that as well and think through the various ways of doing this. At this stage there many ways to skin the cat, from trivial to smart & convoluted, and each will hopefully add to your understanding of C pointers: they're fundamental to the language (and hiding underneath many constructs in other languages).
I hope there's no due date on the delivery of that exercise: there's plenty to learn and when it doesn't immediately *click* (which is the way it goes with most folks), persevere and keep the "Very Important Nitpick" section in mind: that part can be improved (it would probably fill a session back-to-back), but at least it's a start to prevent some horrible confusions I've met along the way.
0 comment threads
My advice to you is to break this down into smaller problems and try to solve them one at a time, making sure you have a program that can compile and run at each step.
First: figure out how to successfully read and write a single book. Write a function that reads book information from stdin and returns a struct book
. Write another function that accepts a struct book
as an argument and writes all that information to a file somehow. Write a third function that reads from that file and returns a struct book
. Write a fourth function that accepts a struct book
and prints its information to stdout. You should be able to write a program that uses these four functions and prints the same information, in the same order, that you give it. (Based on the state of your code and the questions you've asked so far, I suspect this is the hard part for you—it has nothing to do with the deleting operation.)
Second: now figure out how to successfully read and write an entire library. Adapt the above functions to handle struct library *
pointers instead of struct book
values, and test your program again to make sure that it prints what you give it.
Third: figure out how to write a function that edits a struct library *
in the way that you want. You can do this either by accepting and returning struct library *
pointers, or just by accepting a struct library **
double pointer. This function should do nothing with files or with scanf
(you can use printf
if it helps you understand what's going on). Test this function by incorporating it into the program you wrote in the second part. If it doesn't work the way you expect, revisit the function.
1 comment thread