Имя пользователя:
Пароль:  
Помощь | Регистрация | Забыли пароль?  

Показать сообщение отдельно
pva pva вне форума

Аватара для pva

Ветеран


Сообщения: 1180
Благодарности: 279

Профиль | Отправить PM | Цитировать


Цитата Drongo:
У меня при компиляции вашего варианта, выскакивает несколько ошибок... Что я не так делаю? »
э.. это сорри, это я не так делаю. Я сначала сделал без using namespace std, а вместо этого внутри Book вставил typedef std::string string. А переделать Book::string в string забыл Если переделать, то ошибки пропадут Сейчас напишу самый "доведённый до ума" вариант:
Код: Выделить весь код
#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()();
}
проверил - работает

Последний раз редактировалось pva, 23-11-2008 в 22:44. Причина: у списка есть оптимизированный remove_if

Это сообщение посчитали полезным следующие участники:

Отправлено: 22:30, 23-11-2008 | #5