tags:

views:

127

answers:

2

I'm trying to input a form of data validation in which when a user enter's a book's ISBN number, if it has already been stored then it will output an error message. However, I'm having trouble doing this. I'm not sure if I'm overloading the == operator correctly, and I'm not sure how to compare the vector values in the store_ISBN() function.

Here is the code:

#include "std_lib_facilities.h"

// Classes ---------------------------------------------------------------------

class Book{
public:
       vector<Book> books; // stores book information
       Book() {}; // constructor
       friend ostream& operator<<(ostream& out, const Book& b);
       bool operator==(const Book& d);
       string what_title();
       string what_author();
       int what_copyright();
       void store_ISBN();
       void is_checkout();
private:
        char check;
        int ISBNfirst, ISBNsecond, ISBNthird;
        char ISBNlast;
        string title;
        string author;
        int copyright;
};

// Class Functions -------------------------------------------------------------

string Book::what_title()
{
       cout << "Title: ";
       getline(cin,title);
       cout << endl;
       return title;
}

string Book::what_author()
{
       cout << "Author: ";
       getline(cin,author);
       cout << endl;
       return author;
}

int Book::what_copyright()
{
    cout << "Copyright Year: ";
    cin >> copyright;
    cout << endl;
    return copyright;
}

void Book::store_ISBN()
{
     bool test = false;
     cout << "Enter ISBN number separated by spaces: ";
     while(!test){
     cin >> ISBNfirst >> ISBNsecond >> ISBNthird >> ISBNlast;
     for(int i = 0; i < books.size(); ++i)
             if(ISBNfirst == books[i]) cout << "test"; // no idea how to implement this line            
     if((ISBNfirst<0 || ISBNfirst>9) || (ISBNsecond<0 || ISBNsecond>9) || (ISBNthird<0 || ISBNthird>9))
                   error("Invalid entry.");
     else if(!isdigit(ISBNlast) && !isalpha(ISBNlast))
          error("Invalid entry.");
     else test = true;}     
     cout << endl;
}

void Book::is_checkout()
{
     bool test = false;
     cout << "Checked out?(Y or N): ";
     while(!test){
     cin >> check;
     if(check == 'Y') test = true;
     else if(check == 'N') test = true;                                
     else error("Invalid value.");}
     cout << endl;
}

// Operator Overloading --------------------------------------------------------

bool Book::operator==(const Book& d){ // is this right???
     if((ISBNfirst == d.ISBNfirst) && (ISBNsecond == d.ISBNsecond) 
             && (ISBNthird == d.ISBNthird) && (ISBNlast == d.ISBNlast)) return true;
     else return false;
}

ostream& operator<<(ostream& out, const Book& b){
         out << "Title: " << b.title << endl;
         out << "Author: " << b.author << endl;
         out << "ISBN: " << b.ISBNfirst << "-" << b.ISBNsecond << "-" << b.ISBNthird << "-" << b.ISBNlast << endl;
         out << endl;
         return out;
}

// Main ------------------------------------------------------------------------     

int main()
{
    Book store;
    string question;
    while(true){
        store.what_title();
        store.what_author();
        store.what_copyright();
        store.store_ISBN();
        store.is_checkout();
        store.books.push_back(store);
        cout << "Are you finished?(Y or N): ";
        cin >> question;
        if(question == "Y") break;
        else if(question == "N"){
              cout << endl;
              cin.ignore();}
        else error("Invalid value.");
        }
    cout << endl;
    cout << "Books stored -\n" << endl;
    for(int i = 0; i < store.books.size(); ++i)
            cout << store.books[i];
    keep_window_open();
}

Note that in the store_ISBN function I've only included testing for one variable since I don't want to type out the whole thing before I figure out how to do it.

As you can see each time a book passes through the loop in main, the data for that book is stored. I'm then able to output all the data input after the loop by overloading the << operator to print Title, Author, and ISBN. So I think I should be able to access that individual data in the vector to compare to the user input ISBN, but I don't know how. The parts that I am confused about have been commented as such.

+1  A: 

books refers to a vector of Book class. You are comparing Book to an integer, which is undefined behavior. You need to dereference the Book object before you can access its data members.

First, don't access vectors using subscript [] notation. It is inefficient and makes life difficult. Use an iterator (something like, not sure on how you would want to implement):

for (std::vector::iterator it = books.begin(); it != books.end(); ++it)
{
}

That isn't your problem, however. You use the -> operator to dereference objects to get to their members. You made your members private, however, so you either need a get function like

ISBNf() { return ISBNfirst; }

Or make your members public, but that is a bad idea (people can fool with your data). However, for simplicity, assume they are public, this is what you want:

for (std::vector::iterator it = books.begin(); it != books.end(); ++it)
{
    if (*this == *it) cout << "test"; 
}

There is no good solution, here, because I have no idea what you are trying to achieve. I think you are trying to compare the number of digits on the integer, but this is not how to achieve that. If you are just trying to make sure you are assigning ISBNfirst properly, let me put your mind to rest: you are. However, you aren't accessing them correctly, which is where the -> operator comes in.

Next, this code is overkill:

else if(!isdigit(ISBNlast) && !isalpha(ISBNlast)

instead, use the isalphnum() function:

else if (!isalphnum(ISBNlast));

Posted; I will edit my post to point out all the flaws in your code.

Hooked
"don't access vector's using subscript [] notation. It is inefficient". Myth. Accessing through indices is unlikely to be measurably slower in a situation like this, and is considerably more concise than the iterator code.
Steve Jessop
Could you link me to something about that? I'd like to have some proof before I stop using iterators. Besides, I think the dereference operator is much clearer and cleaner.
Hooked
Also, did you notice that store_ISBN is a member function of Book? So there's no need for accessors, or public members, or ->, to access member variables. Not that I approve of the design, but I think you're claiming syntax problems which don't exist. Note though that `(*it)->ISBNFirst` won't work, because it's a vector of Book, not of `Book*`. So `(*it)` is a Book, and `(*it).ISBNFirst is` the correct way to access its fields.
Steve Jessop
Oh, thanks. I got mixed up with that (I've been working with Node's too much for this kind of thing).
Hooked
Using iterators because you consider them clearer is fine. I'm not trying to persuade you against that, and its essential for generic code. Check the disassembly of the two different loops if you want to see what difference it makes to the actual code - typically operator[] will result in an extra load per access, to read the address of the vector's buffer, and a multiply. In a performance-critical loop, sure, iterators are likely to be a little faster. But IMO we shouldn't train beginners to fuss over tiny performance differences in a small vector. Don't optimize what you can't measure.
Steve Jessop
Just looking at the code (nice formatting, overloading, etc.) I assumed it was production code. Btw +1 at your answer, it was much clearer than mine.
Hooked
+4  A: 

I'm not sure quite what the user is expected to type for an ISBN.

Reading from a stream into an int will read digits up to a space, and convert the result to int (if all goes well, anyway). Reading into a char will store the char value. So at the moment you're validating than an ISBN looks like three single digits (0-9), and then the next char. That's not what I think an ISBN looks like.

Your operator== looks OK, although note that for a bool return value,

if (X) return true;
else return false;

can be replaced with

return X;

because conditionals are already of type bool.

After setting your ISBN values (and any other fields you plan to use in operator==, if it's not finished yet), the way to look for a matching book in the store is:

for(int i = 0; i < books.size(); ++i)
    if(*this == books[i]) cout << "test";

In other words, look for a book equal to this book. Or you could use std::find from <algorithms>, although in this case it wouldn't really be any more concise.

By the way, it is unusual to use the same class (Book) to represent both a single book, and the whole store. Unpicking that is a fairly complex set of changes and decisions, though, so I'll just say that a class should represent a single kind of thing, and an object of the class represent an example of that kind. So normally Book and Bookstore are different kinds of thing. The vector in Book is an instance variable, meaning that every Book has its own vector of Books. That doesn't really make sense.

Steve Jessop
Well all of the data for each book is stored for each vector element. I thought that would be a better implementation than creating separate book objects for every book. But I can see how it would be confusing to the person who didn't write the code.
trikker
Wait, are you trying to compare Books or ints? You can compare a Book to a Book and an int to an int, or a Book->int to an int.
Hooked
This is just an exercise not an actual program so I'm not going for a realistic ISBN, I just designed it as the book asked me to (3 single digits then a letter or digit). I'm going to try your solution right now.
trikker
I'm comparing the input ISBN number during that loop to all of the other ISBN numbers stored in the vector.
trikker
This worked! But I'm not really sure how it worked since I've never seen *this before (I'm a beginner). Does *this refer back to the operator shown next or something?
trikker
this is a reference to the current object. For instance, Book book; this == book in any member function. You dereference it to look directly at the members and use your ==operator to compare them to the Books in the vector directly.
Hooked
"I thought that would be a better implementation than creating separate book objects for every book". Actually, the vector is creating a different Book object for each book on your behalf. That's what a vector does - when you call push_back, it copies the parameter into its internal buffer. I see that your code works, so for an exercise, it does the job. But in the long run I'd say work towards objects always being the thing that their class name says they are, no more and no less.
Steve Jessop
Too clarify, is he creating a god object with this code? I am also a beginner (although not as much so as Mr. Trikker, I think) and still bad at identifying antipatterns.
Hooked
On `*this*`: First, "this" is a pointer to the object that you called the member function on. Don't ask why it's a pointer not a reference: it just is, the C++ standard says so. `*` is the dereference operator - it takes a pointer and evaluates to the object it points to. So `*this` is (a reference to) the same book object that `this` points to. You can then compare this Book with another Book using ==.
Steve Jessop
On God objects: sort of. A God object is one which can do everything. This Book class does two things - it represents a Book, but it also represents the whole collection of books in a bookstore. Personally I think "God object" is a bit pejorative for any class that has too much to do, if only because it's always a bit subjective anyway. But certainly I think this class does too much considering that "book" and "bookstore" clearly are distinct concepts. Since currently all the program does is create a bookstore, yeah, I guess it's kinda God-like...
Steve Jessop
Alright thanks a lot. Got it.
trikker