Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs

Dashboard
Notifications
Mark all as read
Q&A

Problems with data structures and filestreams.

+2
−4

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?

Why does this post require moderator attention?
You might want to add some details to your flag.
Why should this post be closed?

0 comment threads

2 answers

+6
−0

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>" thus char * 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 sentinel char 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 to while(0) hence while(false) and thus the loop is stopped. OTOH, while(*p) at first evaluates to while('H'): 'H' is a non-zero char value and thus in the while evaluates to while(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 and p[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 pointer p; the offset is i times the size of a single element of type *p (in this case char)" --> the effective pointer resulting from this pointer arithmetic will point at the i-th char in the string pointed at by p. And anywhere I use string, 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 books) 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() and free() 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 using malloc() or calloc().

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 chars (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.

Why does this post require moderator attention?
You might want to add some details to your flag.

0 comment threads

+2
−1

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.

Why does this post require moderator attention?
You might want to add some details to your flag.

0 comment threads

Sign up to answer this question »