Цитата:
Цитата Drongo
У меня при компиляции вашего варианта, выскакивает несколько ошибок... Что я не так делаю? »
|
э.. это сорри, это я не так делаю. Я сначала сделал без using namespace std, а вместо этого внутри Book вставил typedef std::string string. А переделать Book::string в string забыл :sorry: Если переделать, то ошибки пропадут ;) Сейчас напишу самый "доведённый до ума" вариант:
Код:
#include <iostream>
#include <string>
#include <stdexcept>
using namespace std;
/*-----------------23.11.2008 22:01-----------------
* Класс Book - доступ к видимым полям, ввод-вывод
* --------------------------------------------------*/
class Book
{
public:
Book(const string& author1,
const string& title1,
const string& genre1,
unsigned int year1);
Book(); // для стандартных контейнеров книг
Book(istream& input);// для ввода из потока
// установка свойств
void setAuthor(const string& author1) {_author = _checkLength(author1);}
void setTitle(const string& title1) {_title = _checkLength(title1); }
void setGenre(const string& genre1) {_genre = _checkLength(genre1); }
void setYear(unsigned int year1) {_year = _checkYear (year1); }
// чтение свойств
const string& author() const throw() { return _author; }
const string& title() const throw() { return _title; }
const string& genre() const throw() { return _genre; }
unsigned int year() const throw() { return _year; }
// ввод-вывод
friend ostream& operator<<(ostream&, const Book&);
friend istream& operator>>(istream&, Book&);
// разделитель полей для ввода-вывода
static const char _io_field_delimiter;
// ввод-вывод для пользователя.
void human_read_unsafe();
void human_write();
private:
string _author;
string _title;
string _genre;
unsigned int _year;
static const string& _checkLength(const string& str);
static const unsigned& _checkYear(const unsigned&);
};
/*-----------------23.11.2008 22:01-----------------
* Функции ввода-вывода не роботу
* --------------------------------------------------*/
void Book::human_write()
{
// просто в красивом виде
cout << "Author:." << _author << "\n"
<< "Title:.." << _title << "\n"
<< "Genre:.." << _genre << "\n"
<< "Year:..." << _year << "\n\n";
}
void Book::human_read_unsafe()
{
// читаем, проверяем на ошибки, но пишем сразу в книгу,
// то есть портим сам объект, поэтому unsafe.
// А вот если потом скопировать результат в хранилище,
// получается вполне безопасно
cout << "Type in book properties:\n";
// ws(istream&) - пропускает пробелы (enter тоже) между словами
// getline(istream&, string&, int symbol='\n') - читает строчку до появления символа symbol
cout << "Author:.";
getline(ws(cin), _author, '\n');
_checkLength(_author);
cout << "Title:..";
getline(ws(cin), _title, '\n');
_checkLength(_title );
cout << "Genre:..";
getline(ws(cin), _genre, '\n');
_checkLength(_genre );
cout << "Year:...";
cin >> _year;
_checkYear (_year );
cout << "thank you!\n\n";
}
/*-----------------23.11.2008 22:02-----------------
* и как это всё делается.
* --------------------------------------------------*/
// прикольно выглядит, если поставить = '|'
//const char Book::_io_field_delimiter = '|';
const char Book::_io_field_delimiter = '\t';
Book::Book() :
_author ("Unknown Author"),
_title ("Unknown Author"),
_genre ("Unknown Genre"),
_year ()
{
// не хочется, но надо...
}
Book::Book(const string& author1, const string& title1,
const string& genre1, unsigned int year1) :
_author (_checkLength(author1)),
_title (_checkLength(title1 )),
_genre (_checkLength(genre1 )),
_year (_checkYear (year1 ))
{
// один из подходов последовательной проверки при построении класса.
// неправильного класса не получится.
}
Book::Book(istream& in) :
_author (),
_title (),
_genre (),
_year ()
{
// использование копирования позволяет сохранить объект в целости и сохранности
// в случае исключения. Например если при чтении любого поля возникнет ошибка,
// то следующий код оставит книгу b1 без изменений:
// Book b1(...);
// b1.setAuthorBook(...);
// ...
// try
// {
// b1 = Book(cin); // произошла ошибка при чтении года!!!
// }
// catch(runtime_error& err)
// {
// clog << "error: " << err.what() << endl;
// }
if (getline(ws(in), _author, _io_field_delimiter) &&
getline(ws(in), _title, _io_field_delimiter) &&
getline(ws(in), _genre, _io_field_delimiter) &&
(in >> _year))
{
// проверяем только в случае, если считалось полностью.
// если не полностью (поток кончился), то in.good(),
// он же `operator istream::bool()` выдаст false
_checkLength(_author);
_checkLength(_title );
_checkLength(_genre );
_checkYear (_year );
}
// чтобы отловить случаи неполного чтения, нужно установить флаг:
// in.exceptions(ios::bad_bit|ios::fail_bit);
}
const string& Book::_checkLength(const string& str)
{
// вынесли отдельно, чтоб съэкономить код
if (str.empty() || 255<=str.size())
{
throw out_of_range(
"Book::checkLength "
"a string which is empty or greater 255 chars is not permitted");
}
return str;
}
const unsigned& Book::_checkYear(const unsigned& year)
{
if (2008 <= year)
{
throw out_of_range(
"Book::checkYear "
"year>=2008 is not permitted");
}
return year;
}
ostream& operator<<(ostream& out, const Book& book)
{
// вывод в таблицу, разделённую табуляторами и переводом строки
// после вывод буфер не сбрасываем.
return
out << book._author << Book::_io_field_delimiter
<< book._title << Book::_io_field_delimiter
<< book._genre << Book::_io_field_delimiter
<< book._year << "\n";
}
istream& operator>>(istream& in, Book& book)
{
// стараемся не запортить book если произойдёт исключение
book = Book(in);
return in;
}
/*-----------------23.11.2008 22:14-----------------
* Демонстрация возможностей
* --------------------------------------------------*/
#include <fstream>
#include <list>
#include <map>
void main()
{
// прикольный способ создавать функции в теле функции (как в паскале) - если кто не знал ;-)
// но, к сожалению, длинный код может запутать, пожтому при возможности лучше выносить наружу.
class Job
{
private:
string _cmd;
list<Book> _books;
// да, в C++ писанины много...
// void (Job::*)() - так выглядит тип "указатель на функцию void Job::function()"
map<string,pair<void (Job::*)(), const char*> > _handlers;
bool enter_cmd()
{
cout << "Enter what to do (\"exit\" to quit or \"help\" for more info): ";
_cmd.clear();
return (cin >> _cmd) && _cmd!="exit";
}
public:
Job()
{
_handlers["help" ] = make_pair(cmd_help, "brief about instructions");
_handlers["add" ] = make_pair(cmd_add, "add book to storage");
_handlers["clear" ] = make_pair(cmd_clear, "clear storage");
_handlers["save" ] = make_pair(cmd_save, "save storage to file \"storage.txt\"");
_handlers["load" ] = make_pair(cmd_load, "load storage from file \"storage.txt\"");
_handlers["erase" ] = make_pair(cmd_erase, "erase one book from storage");
_handlers["type" ] = make_pair(cmd_type, "display storage content");
}
// так класс превращается в функциональный объект.
// Job job1;
// ...
// job1();
void operator()()
{
// полезный приём для избежания break внутри цикла. Облегчает чтение кода
// описание функции в теле класса даёт указание раскрывать её как inline
while (enter_cmd())
{
map<string,pair<void (Job::*)(), const char*> >::iterator iter = _handlers.find(_cmd);
// оператор map<...>::operator[] добавляет элемент, если он не найден в списке,
// а нам этого совсем не надо. Поэтому используем поиск по ключу
if (iter!=_handlers.end())
{
try
{
// програмное исключение exception не приводит к неработоспособности этого кода,
// поэтому останавливаем их обработку здесь с чистым сердцем.
// часто люди злоупотребляют блоками try..catch и оставляют работать
// повреждённую программу.
(this->*iter->second.first)();
}
catch(exception& e)
{
clog << "runtime error: " << e.what() << endl;
}
}
else
{
cout << "Unexpected command! Type \"help\" for list of avaible commands\n";
}
}
cout << "Bye!";
}
// далее пошли наши команды
void cmd_help()
{
// прикольно выглядит:
// cout.fill('.');
cout.flags(ios::left);
// map<> держит ключи в сортированном виде, поэтому вывод будет в
// алфавитном порядке
for (map<string,pair<void (Job::*)(), const char*> >::iterator
first=_handlers.begin(), last=_handlers.end(); first!=last; ++first)
{
cout.width(10);
cout << first->first << first->second.second << "\n";
}
}
void cmd_type()
{
// вот так просто теперь выглядит вывод
// ostream_iterator<> использует оператор << для вывода в поток
copy(_books.begin(), _books.end(), ostream_iterator<Book,char>(cout));
}
void cmd_add()
{
// небезопасно читаем новую книгу
// и безопасно добавляем в контейнер
Book new_book;
new_book.human_read_unsafe();
_books.push_back(new_book);
}
void cmd_clear()
{
_books.clear();
}
void cmd_save()
{
// вот так просто теперь выглядит сохранение
ofstream out("storage.txt");
copy(_books.begin(), _books.end(), ostream_iterator<Book,char>(out));
}
void cmd_load()
{
// загружаем в отдельный список - безопасно при исключениях
list<Book> new_list;
ifstream in("storage.txt");
copy(istream_iterator<Book,char>(in), istream_iterator<Book,char>(), back_inserter(new_list));
// меняем местами содержимое списков. При чистке стека старый список удалится.
new_list.swap(_books);
}
void cmd_erase()
{
// пример использования функционального класса
// title_predicate(book) возвращает true если введённое название книги совпадает
// иначе false
struct title_predicate
{
string _title1;
title_predicate()
{
cout << "Enter title to erase: ";
cin >> _title1;
}
bool operator()(const Book& book)
{
return book.title()==_title1;
}
};
// remove_if перемещает в конец списка все элементы, которые удовлетворяют условию.
// если до выполнения erase произойдёт исключение, книги не потеряются. Но правда
// изменится порядок.
_books.remove_if(/*это вызов конструктора*/ title_predicate());
}
};
// вот теперь настал тот момент, когда непонятно, что кончилось и что началось,
// если помещать описание класса внутрь функции.
Job()();
}
проверил - работает
|