I'm trying to write a program where you can insert and display some books (without using a database).
For doing this, I use three classes:
Book
- is the base class.TehnicBook
andLiterature
- that will inherit some properties fromBook
.
#include <iostream>
#include <string>
#include <stdlib.h>
using namespace std;
class Book {
public:
string author, title;
bool rented;
Book(string &author, string &title, bool rented) {
this -> author = author;
this -> title = title;
this -> rented = rented;
};
void display(void);
};
class TehnicBook:public Book {
int amount, RlYear;
string language;
TehnicBook *head, *next;
public:
TehnicBook(string &author, string &title, bool rented, int amount, string &language, int RlYear):Book(author, title, rented) {
head = NULL;
this -> language = language;
this -> amount = amount;
this -> RlYear = RlYear;
};
~TehnicBook(void) {
delete head;
};
void display(void);
void add(void);
void dellete(string&);
};
class Literature:public Book {
string bookType;
Literature *head, *next;
public:
Literature(string &author, string &title, bool rented, string &bookType):Book(author, title, rented) {
head = NULL;
this -> bookType = bookType;
};
~Literature(void) {
delete head;
};
void display(void);
void add(void);
};
void TehnicBook::add(void) {
string author, title, language;
int year, amount;
bool rented;
cout << endl << "Author: ", cin >> author;
cout << "Title: ", cin >> title;
cout << "Rented? (0/1): ", cin >> rented;
cout << "The amount of books: ", cin >> amount;
cout << "Language: ", cin >> language;
cout << "Release year: ", cin >> year;
TehnicBook *p = new TehnicBook(author, title, rented, amount, language, year);
p -> next = head;
head = p;
}
void TehnicBook::display(void) {
TehnicBook *p = head;
while(p) {
cout << "-----------------------------\n";
cout << "Author: " << p -> author << endl;
cout << "Title: " << p -> title << endl;
cout << "Is " << ((p -> rented) ? "" : "not ") << "rented" << endl;
cout << "Amount of books: " << p -> amount << endl;
cout << "Language: " << p -> language << endl;
cout << "Release year: " << p -> RlYear << endl;
cout << endl;
p = p -> next;
}
}
void Literature::add(void) {
string author, title, bookType;
bool rented;
cout << "\nAuthor: ", cin >> author;
cout << "Title: ", cin >> title;
cout << "Is rented? ", cin >> rented;
cout << "Book type (hardcover/...: ", cin >> bookType;
Literature *p = new Literature(author, title, rented, bookType);
p -> next = head;
head = p;
}
void Literature::display(void) {
Literature *p = head;
while(p) {
cout << "\n-----------------------------\n";
cout << "Author: " << p -> author << endl;
cout << "Title: " << p -> title << endl;
cout << "Is rented? " << ((p -> rented) ? "yes" : "no") << endl;
cout << "Book type: " << p -> bookType << endl << endl;
p = p -> next;
}
}
int main(int argc, char const **argv) {
string blank = "";
TehnicBook *tehnicB = new TehnicBook(blank, blank, false, 0, blank, 0);
Literature *litB = new Literature(blank, blank, false, blank);
int opt;
for(;;) {
cout << "\n\n1) Add a tehnic book.\n";
cout << "2) Display all tehnic books.\n";
cout << "3) Add a literature book.\n";
cout << "4) Display all literature books.\n";
cout << "5) Exit.\n\n";
cout << "Your option: ", cin >> opt;
switch(opt) {
case 1:
tehnicB -> add();
break;
case 2:
tehnicB -> display();
break;
case 3:
litB -> add();
break;
case 4:
litB -> display();
break;
case 5:
exit(0);
default:
continue;
}
}
return 0;
}
Am I doing this right? Any better ideas?
-
\$\begingroup\$ It doesn't seem to actually work. When I put in "Marius Marusanici" as the author for the first book, the code simply infinitely repeats the menu. \$\endgroup\$Edward– Edward2014年10月28日 19:48:20 +00:00Commented Oct 28, 2014 at 19:48
-
\$\begingroup\$ @Edward Yep, it only works when you insert something without spaces. I think this is how cin works with strings. \$\endgroup\$mariusmmg2– mariusmmg22014年10月28日 20:00:57 +00:00Commented Oct 28, 2014 at 20:00
2 Answers 2
Major bug:
Book::~Book
is notvirtual
. This means you have a memory leak in the following case:TehnicBook *tb = new TehnicBook(...); tb->add(...); Book *b = tb; delete tb; //<- tb->head is leaked
If you have C++11 I recommend that every class either has a
virtual
destructor or is declaredfinal
such asclass Book final{...}
.Major design issue: You are combining
TehnicBook
with a linked list. A class should have only one responsibility: Either manage a book or manage memory, not both. At least usestd::list
or betterstd::vector
. Better removeadd
fromTehnicBook
andLiterature
and put avector<Book>
intomain
to seperate storage and functionality.Similarly
add
should not usecin
andcout
. You are mixing user interface and class functionality. It should simply take aTehnicBook *
. You can make a free standing function that conveniently does this, but it should not be part ofTehnicBook
.Similarly
display
should not usecout
. Either make it return astring
which you then give tocout
or teachcout
how to print books such as this:ostream &operator <<(ostream &os, TehnicBook &t){ os << os << "-----------------------------\n"; os << "Author: " << p->author << endl; os << "Title: " << p->title << endl; os << "Is " << ((p->rented) ? "" : "not ") << "rented" << endl; os << "Amount of books: " << p->amount << endl; os << "Language: " << p->language << endl; os << "Release year: " << p->RlYear << endl; os << endl; return os; }
Now you can do
TehnicBook book(...); cout << book;
, which is pretty neat, but more importantly you can do things likeofstream file("test.txt"); file << book;
, so we got printing a book to a file and TCP-streams and so on for free.I see
void dellete(string&);
insideTehnicBook
, but no implementation. I guess you meant to add the functionality to remove books from the list. Remove this function, it does not belong to aTehnicBook
.Is
TehnicBook
supposed to beTechnicBook
orTechnicalBook
?This may be a bit over your head, but I want to at least mention it: Do not use
new
anddelete
. Ever. Instead usemake_unique
.TehnicBook *tehnicB = new TehnicBook(blank, blank, false, 0, blank, 0);
becomes
auto tehnicB = make_unique<TehnicBook>(blank, blank, false, 0, blank, 0);
The point is that now you do not need to manage memory. Managing memory is very difficult and error prone and unnecessary. For example you forgot to clean up
tehnicB
andlitB
insidemain
. This automatically gets fixed without thinking about it with modern C++.
-
1\$\begingroup\$ A book collection could be
std::vector<std::unique_ptr<Book>> library;
and then books could be added aslibrary.emplace_back(std::make_unique<Literature>("Tolstoy", "War and Peace", false, "hardcover"));
\$\endgroup\$Edward– Edward2014年10月28日 21:46:19 +00:00Commented Oct 28, 2014 at 21:46 -
\$\begingroup\$ You might also want to mention how to actually implement polymorphism. For instance, define
virtual std::ostream &display(std::ostream &out)
inBook
and then implementdisplay
in derived classes, callingBook::display
first. Then use it asstd::ostream &operator<<(std::ostream &out, const Book& b) { return b.display(out); }
\$\endgroup\$Edward– Edward2014年10月28日 21:53:07 +00:00Commented Oct 28, 2014 at 21:53 -
1
-
1\$\begingroup\$
A class should have only one responsibility
: Pattern called"Separation of Concerns"
\$\endgroup\$Loki Astari– Loki Astari2014年10月28日 23:04:48 +00:00Commented Oct 28, 2014 at 23:04 -
1\$\begingroup\$
The point is that now you do not need to manage memory.
Correct. So don't even dynamically allocate whenautomatic
variables will do. \$\endgroup\$Loki Astari– Loki Astari2014年10月28日 23:07:34 +00:00Commented Oct 28, 2014 at 23:07
Bug: you cannot enter multiple words for any of the string arguments, for example author and title. It will make more sense to read full lines there, for example:
cout << endl << "Author: ";
getline(cin, author);
Please don't use using namespace std
(read this).
This is ok though:
using std::cin;
using std::cout;
using std::endl;
using std::string;
But the best is to not do this at all, but use explicitly std::cout
when you need,
especially for common names like string
, which might easily conflict with other implementations. See also @Loki's comment.
Instead of #include <stdlib.h>
, write like this: #include <cstdlib>
.
But actually, as @Edward pointed out,
it would be better to remove this import,
and change the single exit(0)
statement to return 0
.
It's unusual to put spaces around ->
. This is more common:
this->author = author;
RlYear
is not a very good name. Use camelCase
for variable names in general.
For this variable, releaseYear
would be a better name.
You can simplify the constructor of Book
like this:
Book(string &author, string &title, bool rented) :
author(author), title(title), rented(rented) {}
Similarly, TechnicBook
like this:
TehnicBook(string &author, string &title, bool rented, int amount, string &language, int RlYear) :
Book(author, title, rented), head(NULL), language(language), amount(amount), RlYear(RlYear) {}
Lastly, although the title says "c++ polymorphism", there's no example of polymorphism in this code.
-
\$\begingroup\$ @Edward
g++
cannot findexit
without that line \$\endgroup\$janos– janos2014年10月28日 20:58:17 +00:00Commented Oct 28, 2014 at 20:58 -
1\$\begingroup\$ True, but then that line would be better as
return 0;
anyway. \$\endgroup\$Edward– Edward2014年10月28日 20:58:56 +00:00Commented Oct 28, 2014 at 20:58 -
\$\begingroup\$ @Edward good call, updated my answer, thanks for that! \$\endgroup\$janos– janos2014年10月28日 21:01:37 +00:00Commented Oct 28, 2014 at 21:01
-
1\$\begingroup\$
This is ok though:
Depends: In global scope it is better (not ideal (especially string it is way too common an identifier to be put in the global namespace)). Best is to just be specificstd::cout << "Works fine\n";
\$\endgroup\$Loki Astari– Loki Astari2014年10月28日 23:38:11 +00:00Commented Oct 28, 2014 at 23:38 -
\$\begingroup\$ Thanks @LokiAstari, I added a comment on that to clarify. \$\endgroup\$janos– janos2014年10月28日 23:42:29 +00:00Commented Oct 28, 2014 at 23:42
Explore related questions
See similar questions with these tags.