Компьютерный форум OSzone.net  

Компьютерный форум OSzone.net (http://forum.oszone.net/index.php)
-   Программирование и базы данных (http://forum.oszone.net/forumdisplay.php?f=21)
-   -   [решено] Помогите дорешать задачу с созданием класса Book (http://forum.oszone.net/showthread.php?t=123814)

Sky-er 23-11-2008 04:11 960668

Помогите дорешать задачу с созданием класса Book
 
Здравствуйте помогите пожалуйста довести задачу до ума

Вот задание:Создать класс типа — книга. Поля — название, автор, год выпуска, вид литературы (худож., методич., справочн., ...). Класс имеет конструктор, деструктор. Методы: установки и возвращения значения полей, вычисления «возраста» книги, метод печати. Методы установки полей класса должны проверять корректность задаваемых параметров. Проверить работу этого класса.

Вот исходник:
Код:

#include "stdafx.h"
#include "stdio.h"
#include "conio.h"
#include "math.h"
#define _CRT_NONSTDC_NO_DEPRECATE
class book{
    char* avtor,*nazv,*vid;
    int year;
public: book (int y, char* v, char* a,char* n){
            avtor=a;
            year=y;
            vid=v;
            nazv=n;
    }
        {
        char *avtor = new char[strlen(a)+1];    //Выделяем память
        strcpy(avtor.a);
        char *vid = new char[strlen(v)+1];
        strcpy(vid.v);
        char *nazv = new char[strlen(n)+1];
        strcpy(nazv.n);
        }
        char *get_avtor(){return avtor;}
        char *get_vid(){return vid;}
        int get_year(){return year;}
        char *get_nazv(){return nazv;}
 
 
~book(){delete [avtor,vid,nazv] book;}  //деструктор
 
 
};   
void main()
{
    return ;
}


Drongo 23-11-2008 15:15 960958

В общем немного решил, но я сам запутался, знаний нехватает. Самому интересно.
У меня вопрос:
1. Как организовать ввод данных с клавиатуры?
2. И как правильно записать деструктор? :dont-know Я запутался...

По первому вопросу придумал так:
В определении класса объявляю функцию
Код:

void UserFromKeyboard();
В реализации этой функции считываю через дополнительные переменные, данные, и передаю в функции установки "set", каждое значение в свою функцию, но выпадает ошибка о том что приложение будет закрыто. Причём если только одно значение считывается и передаётся, то программа работает, а если четыре, то никак. Ошибки выскакивают при вводе символьных значений задачи. Подскажите как правильно ввести с клавиатуры значения и сохранить\присвоить полям объекта?
Решение
Код:

// Создать класс типа — книга.
// Поля — название, автор, год выпуска, вид литературы (худож., методич., справочн., ...).
// Класс имеет конструктор, деструктор.
// Методы: установки и возвращения значения полей, вычисления «возраста» книги,
// Метод печати.
// Методы установки полей класса должны проверять корректность задаваемых параметров.
// Проверить работу этого класса

//#include "stdafx.h"
#include <iostream.h>
using std::cout;
using std::cin;
using std::endl;

#include <cstring.h>
#include <cassert>
//#include "math.h"
//#include <stdarg.h>
#include <stdio.h>
#include "conio.h"
               
//#define _CRT_NONSTDC_NO_DEPRECATE
class Book{
public:
  Book(char *au, char *nb, char *jr, int yr); // Конструктор
//  void UserFromKeyboard();
  //void setBook(char *au, char *nb, char *jr, int yr);
  // Функции "set", установки значений полей книги
  void setAuthorBook(char *au); // Установка имени автора книги
  void setNameBook(char *nb);  // Установка названия книги
  void setJanreBook(char *jr);  // Установка жанра книги
  void setYearBook(int yr);  // Установка года выпуска книги

  // Функции "get", возвращение значений полей книги
  char *getAuthorBook() { return author; } ;// Возвращение имени автора книги
  char *getNameBook()  { return nameBook; };  // Возвращение названия книги
  char *getJanreBook()  { return janr; };  // Возвращение жанра книги
  int getYearBook()  { return year; };// Возвращение года выпуска книги

  void printBook();  // Печать значений книги
  void CalculateYearBook();
  ~Book(); // Деструктор
private:
    char *author;
    char *nameBook;
    char *janr;
    int year;
};
// Конструктор----------------------------------------------------------------
Book::Book(char *au, char *nb, char *jr, int yr)
{
  setAuthorBook(au);
  setNameBook(nb);
  setJanreBook(jr);
  setYearBook(yr);
}
// Функция ввода данных значений книги с клавиатуры
//void Book::UserFromKeyboard()
//{

//}
// Функция установки Автора Книги
void Book::setAuthorBook(char *au)
{
  // Проверка и выделение памяти для поля - Автор
  int len = strlen(au);
  if( 0 <= len && len < 256){ // Проверка диапазона, должен быть меньше 255
    author = new char[strlen(au) + 1]; // Выделение памяти в 255 байт плюс завершающий символ
    assert(author != 0);  // Проверка выделения памяти
    strcpy(author, au);  // Копирование...
  }
  else  // Вывод ошибки если диапазон массива меньше нуля и больше 256 байт...
    cout<<" Error! Lenght > 256 bytes!";
}
// Функция установки Названия книги
void Book::setNameBook(char *nb)
{
  // Проверка и выделение памяти для поля - Имя Книги
  int len = strlen(nb);
  if(len >= 0 && len < 256){ // Проверка диапазона, должен быть меньше 255
    nameBook = new char[strlen(nb) + 1]; // Выделение памяти в 255 байт плюс завершающий символ
    assert(nameBook != 0);  // Проверка выделения памяти
    strcpy(nameBook, nb);  // Копирование...
  }
  else  // Вывод ошибки если диапазон массива меньше нуля и больше 256 байт...
    cout<<" Error! Lenght > 256 bytes!";
}
// Функция установки Жанра Книги
void Book::setJanreBook(char *jr)
{
  // Проверка и выделение памяти для поля - Жанр
  int len = strlen(jr);
  if(len >= 0 && len < 256){ // Проверка диапазона, должен быть меньше 255
    janr = new char[strlen(jr) + 1]; // Выделение памяти в 255 байт плюс завершающий символ
    assert(janr != 0);  // Проверка выделения памяти
    strcpy(janr, jr);  // Копирование...
  }
  else  // Вывод ошибки если диапазон массива меньше нуля и больше 256 байт...
    cout<<" Error! Lenght > 256 bytes!";
}
// Функция установки Года Книги
void Book::setYearBook(int yr)
{
  // Проверка значений поля - Год Книги
  if(yr < 2008) // Проверка диапазона, должен быть меньше 255
    year = yr;
  else  // Вывод ошибки если диапазон массива меньше нуля и больше 256 байт...
    cout<<" Error! Year > 2008 !";
}
// Расчёт возраста Книги
void Book::CalculateYearBook()
{
  int MyYear = 2008,  // Текущий год
      resultYear;  // Результат расчёта возраста книги

  resultYear = MyYear - year;

  cout<<"\n This Book "<<resultYear<<" Years! \n"<<endl;
}
// Печать объекта-------------------------------------------------------------
void Book::printBook()
{
  cout<<" Author Book: "<<author<<endl;
  cout<<" Name Book: "<<nameBook<<endl;
  cout<<" Janre Book: "<<janr<<endl;
  cout<<" Year Book: "<<year<<endl;
}
// Деструктор-----------------------------------------------------------------
Book::~Book()
{
}
//---------------------------------------------------------------------------
void main()
{
  int z;
  Book b("Servantes", "Don Kixot", "Romantic", 1550);

  b.printBook();
  b.CalculateYearBook();

  b.setAuthorBook("Pushkin");
  b.setNameBook("Eugeny Onegin");
  b.setJanreBook("Roman");
  b.setYearBook(1834);
  b.printBook();
  b.CalculateYearBook();

  cin>>z;
}
//---------------------------------------------------------------------------


pva 23-11-2008 17:04 961044

Деструктор:
Код:

Book::~Book()
{
  delete [] author;
  delete [] nameBook;
  delete [] janr;
}

Вопрос меня смущает: ежели используешь стандартную библиотеку, почему бы не использовать стандартные контейнеры? Например класс string легко превращается в char* при необходимости. С памятью они очень нежно обращаются.
Код:

#include <iostream>
#include <string>
#include <stdexcept>
using namespace std;

class Book
{
public:
    Book(const string& au, const string& nb, const string& jr, unsigned int yr);
    Book(istream& input);// для ввода из потока

    void setAuthorBook(const string& au) {author = checkLength(au);}
    void setNameBook(const string& nb)  {nameBook = checkLength(nb);}
    void setJanreBook(const string& jr)  {janr = checkLength(jr);}
    void setYearBook(int yr)            {year = checkYear(jr);}

    const string& getAuthorBook() { return author; }
    const string& getNameBook()  { return nameBook; }
    const string& getJanreBook()  { return janr; }
    unsigned int  getYearBook()  { return year; }

    void printBook();
    void calculateYearBook();

private:

    string author;
    string nameBook;
    string janr;
    unsigned int year;

    static void checkLength(const string& str);
    static const unsigned& checkYear(const unsigned&);
};

const Book::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& yr)
{
    if (2008 <= yr)
    {
        throw out_of_range(
                "Book::checkYear "
                    "year>=2008 is not permitted");
    }

    return str;
}

Book::Book(const string& au, const string& nb, const string& jr, unsigned int yr) :
    author  (checkLength(au)),
    nameBook(checkLength(nb)),
    janr    (checkLength(jr)),
    year    (yr)
{
}

Book::Book(std::stream& input) :
    author  (),
    nameBook(),
    janr    (),
    year    ()
{
    // использование копирования позволяет сохранить объект в целости и сохранности
    // в случае исключения. Например если при чтении любого поля возникнет ошибка,
    // то следующий код оставит книгу b1 без изменений:

    // Book b1(...);
    // b1.setAuthorBook(...);
    // ...
    // try
    // {
    //  b1 = Book(cin); // произошла ошибка при чтении года!!!
    // }
    // catch(runtime_error& err)
    // {
    //    clog << "error: " << err.what() << endl;
    // }

    clog << "reading data from stream\n";

    if (((clog << "autor [enter]: "),      getline(input, author)) &&
        ((clog << "nameBook [enter]: "),    getline(input, nameBook)) &&
        ((clog << "janr [enter]: "),        getline(input, janr)) &&
        ((clog << "year [any space character]: "), (input >> jear)))
    {
        clog << "checking data\n";

        checkLength(author);
        checkLength(nameBook);
        checkLength(janr);
        checkYear(yr);

        clog << "all ok\n";
    }
    else
    {
        throw runtime_error("error reading from stream");
    }
}

// Печать объекта-------------------------------------------------------------
void Book::printBook()
{
  cout<<" Author Book: "<<author<<endl;
  cout<<" Name Book: "<<nameBook<<endl;
  cout<<" Janre Book: "<<janr<<endl;
  cout<<" Year Book: "<<year<<endl;
}
//---------------------------------------------------------------------------
void main()
{
  Book b("Servantes", "Don Kixot", "Romantic", 1550);

  b.printBook();
  b.CalculateYearBook();

  b.setAuthorBook("Pushkin");
  b.setNameBook("Eugeny Onegin");
  b.setJanreBook("Roman");
  b.setYearBook(1834);
  b.printBook();
  b.CalculateYearBook();

    { // вариант 1
        Book book_from_stream(cin);
        book_from_stream.printBook();
    }

    { // вариант 2
        b = Book(cin);
        b.printBook();
    }
}


Drongo 23-11-2008 17:31 961063

pva, Спасибо за пояснения!!!
Цитата:

Цитата pva
Вопрос меня смущает: ежели используешь стандартную библиотеку, почему бы не использовать стандартные контейнеры? »

Это потому, что опыта нехватает.

pva, У меня при компиляции вашего варианта, выскакивает несколько ошибок... Что я не так делаю?


pva 23-11-2008 22:30 961341

Цитата:

Цитата 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()();
}

проверил - работает

Drongo 24-11-2008 11:38 961797

Цитата:

Цитата pva
Сейчас напишу самый "доведённый до ума" вариант »

Ого, класс, :up Только у меня опять ошибки, 26 штук... Компилирую Borland C++ Builder 6.0.

pva 24-11-2008 12:50 961873

Вот это обкатал на 6-м билдере. К сожалению C++ Builder - это не компилятор C++ :(
Код:

#pragma hdrstop
//------------------------------

#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(); // для стандартных контейнеров книг

    // установка свойств
    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 Title"),
    _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  ))
{
    // один из подходов последовательной проверки при построении класса.
    // неправильного класса не получится.
}

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)
{
    // немного подумав обрал коснтруктор из потока.
    // потому что при чтении итератором istream_iterator<Book> будет всё равно
    // использоваться копия Book, которая находятся в самом итераторе.

    // использование копирования позволяет сохранить объект в целости и сохранности
    // в случае исключения. Например если при чтении любого поля возникнет ошибка,
    // то следующий код оставит книгу b1 без изменений:

    // Book b1(...);
    // b1.setAuthorBook(...);
    // ...
    // try
    // {
    //  b1 = Book(cin); // произошла ошибка при чтении года!!!
    // }
    // catch(runtime_error& err)
    // {
    //    clog << "error: " << err.what() << endl;
    // }

    if (getline(ws(in), book._author, Book::_io_field_delimiter) &&
            getline(ws(in), book._title,  Book::_io_field_delimiter) &&
            getline(ws(in), book._genre,  Book::_io_field_delimiter) &&
            (in >> book._year))
    {
            // проверяем только в случае, если считалось полностью.
            // если не полностью (поток кончился), то in.good(),
            // он же `operator istream::bool()` выдаст false
            Book::_checkLength(book._author);
            Book::_checkLength(book._title );
            Book::_checkLength(book._genre );
            Book::_checkYear  (book._year  );
    }

    // чтобы отловить случаи неполного чтения, нужно установить флаг:
    // in.exceptions(ios::badbit|ios::failbit);
    // но это мешает чтению из потока до его конца (в конце выйдет исключение)
    return in;
}

/*-----------------23.11.2008 22:14-----------------
 * Демонстрация возможностей
 * --------------------------------------------------*/

#include <fstream>
#include <iterator>
#include <list>
#include <map>

// пример использования функционального класса
// title_predicate(book) возвращает true если введённое название книги совпадает
// иначе false
// используется в Job::cmd_erase(), но сюда вынесено, потому что борландовский
// компилятор умеет подставлять в шаблоны только классы, видимые снаружи
// (а не в текущем блоке). Дебильная урезка по-моему.

struct title_predicate
{
    string _title1;

    title_predicate()
    {
        cout << "Enter title to erase: ";
        getline(ws(cin), _title1);
    }

    bool operator()(const Book& book)
    {
        return book.title()==_title1;
    }
};

void main()
{
    // прикольный способ создавать функции в теле функции (как в паскале) - если кто не знал ;-)
    // но, к сожалению, длинный код может запутать, пожтому при возможности лучше выносить наружу.

    class Job
    {
    private:
        string      _cmd;
        list<Book>  _books;
        // да, в C++ писанины много...
        // void (Job::*)() - так выглядит тип "указатель на функцию void Job::function()"
        // борланд такую конструкцию не воспринимает :( ему обязательно надо typedef
        // и вообще у него из-за ввода __closure началась путаница с указателями на методы
        // я привёл к стандартному сишному вызову cdecl
        typedef void (cdecl Job::*handler_type)();
        // без этого тоже не хотел собирать почему-то.
        // а всё глюкавый сборщик шаблонов виноват...
        typedef map<string,pair<handler_type, const char*> > handler_map_type;
        handler_map_type _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(static_cast<handler_type>(&Job::cmd_help),  "brief about instructions");
            _handlers["add"  ] = make_pair(static_cast<handler_type>(&Job::cmd_add),  "add book to storage");
            _handlers["clear" ] = make_pair(static_cast<handler_type>(&Job::cmd_clear), "clear storage");
            _handlers["save"  ] = make_pair(static_cast<handler_type>(&Job::cmd_save),  "save storage to file \"storage.txt\"");
            _handlers["load"  ] = make_pair(static_cast<handler_type>(&Job::cmd_load),  "load storage from file \"storage.txt\"");
            _handlers["erase" ] = make_pair(static_cast<handler_type>(&Job::cmd_erase), "erase one book from storage");
            _handlers["list"  ] = make_pair(static_cast<handler_type>(&Job::cmd_list),  "display storage content");
        }

        // так класс превращается в функциональный объект.

        // Job job1;
        // ...
        // job1();

        void cdecl operator()()
        {
            // полезный приём для избежания break внутри цикла. Облегчает чтение кода
            // описание функции в теле класса даёт указание раскрывать её как inline
            while (enter_cmd())
            {
                handler_map_type::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 cdecl cmd_help()
        {
            // прикольно выглядит:
    //        cout.fill('.');
            cout.flags(ios::left);

            // map<> держит ключи в сортированном виде, поэтому вывод будет в
            // алфавитном порядке

            for (handler_map_type::iterator
                    first=_handlers.begin(), last=_handlers.end(); first!=last; ++first)
            {
                cout.width(10);
                cout << first->first << first->second.second << "\n";
            }
        }

        void cdecl cmd_list()
        {
            // вот так просто теперь выглядит вывод
            // ostream_iterator<> использует оператор << для вывода в поток
            copy(_books.begin(), _books.end(), ostream_iterator<Book,char>(cout));
        }

        void cdecl cmd_add()
        {
            // небезопасно читаем новую книгу
            // и безопасно добавляем в контейнер
            Book new_book;
            new_book.human_read_unsafe();
            _books.push_back(new_book);
        }

        void cdecl cmd_clear()
        {
            _books.clear();
        }

        void cmd_save()
        {
            // вот так просто теперь выглядит сохранение
            ofstream out("storage.txt");
            copy(_books.begin(), _books.end(), ostream_iterator<Book,char>(out));
        }

        void cdecl cmd_load()
        {
            // загружаем в отдельный список - безопасно при исключениях
            list<Book> new_list;
            ifstream in("storage.txt");
            copy(istream_iterator<Book,char>(in), istream_iterator<Book,char>(), back_inserter(new_list));

            if (!in.eof())
            {
                in.clear(ios::goodbit);
                string error_word;
                getline(in, error_word);

                clog << "Job::cmd_load parser error before: " << error_word << endl;
                throw runtime_error("Job::cmd_load parser error");
            }

            // меняем местами содержимое списков. При чистке стека старый список удалится.
            new_list.swap(_books);
        }

        void cdecl cmd_erase()
        {
            // remove_if перемещает в конец списка все элементы, которые удовлетворяют условию.
            // если до выполнения erase произойдёт исключение, книги не потеряются. Но правда
            // изменится порядок.
            _books.remove_if<title_predicate>(/*это вызов конструктора*/ title_predicate());
        }
    };


    // вот теперь настал тот момент, когда непонятно, что кончилось и что началось,
    // если помещать описание класса внутрь функции.
    Job()();
}
//------------------------------


Drongo 24-11-2008 14:21 961978

pva, Спасибо большое!!! :up Хотя задача и вопрос не мой, но мне он интересен тоже! Всё компилируется и работает без нареканий. :yes:

P.S. Вот только автор темы куда-то пропал...

Sky-er 25-11-2008 00:33 962625

Извиняюсь, что не мог ответить на ПК попал какой то странный червь(dr web его не обнаружил) пришлось сносить систему.
Огромное спасибо! В принципе мне бы хватило и маленького дописания кода без пабликов и приватов, очень благодаре, так как они как раз у меня в ледующих лабораторках будут, а теперь можно будет посмотреть принцип их работы.

Sky-er 25-11-2008 19:40 963451

Еще вопрос у меня не кампилируется какой проект нужно создавать? просто нас учили создавать проект С++ win32 console aplication а в нем файл по умолчанию stdafx.h и он ругатся из - за его отсутствия

http://forum.oszone.net/attachment.p...1&d=1227631655

pva 26-11-2008 08:21 963796

микрософтовский компилятор это не поднимет из-за просто отсутсвия стандартной библиотеки в наборе. В том, которыя я видел не было по крайней мере. Собери борландовским, Gcc, mingcc или Метроверковским. Либо перепиши к микрософту в инклюды её откуда-нибудь.

Sky-er 26-11-2008 17:16 964296

Вложений: 1
Добавил функцию работает, только выдает ошибку все равно вот ошибки http://forum.oszone.net/attachment.p...1&d=1227708956
ругается на cmd

pva 27-11-2008 20:57 965588

Убери амперсанд перед именем функции, скорее всего в нём проблема. У меня нету вижуалстадио под руками, проверить нечем :( Попробуй собрать первый вариант, который у Drongo билдером не собрался. Я его собирал на Metrowerks CodeWarrior 8.0

Sky-er 27-11-2008 21:20 965613

Я вроде и пробовал тот вариант именно в нем выскакивают эти ошибки с cmd, а если убрать вообще ошибки просто оставить саму программу?

pva 28-11-2008 08:27 965931

а, подожди, он же кричит что надо заменить Job::cmd_xxx на &main::Job::cmd_xxx
Код:

            _handlers["help"  ] = make_pair(&main::Job::cmd_help,  "brief about instructions");
            _handlers["add"  ] = make_pair(&main::Job::cmd_add,  "add book to storage");
            _handlers["clear" ] = make_pair(&main::Job::cmd_clear, "clear storage");
            _handlers["save"  ] = make_pair(&main::Job::cmd_save,  "save storage to file \"storage.txt\"");
            _handlers["load"  ] = make_pair(&main::Job::cmd_load,  "load storage from file \"storage.txt\"");
            _handlers["erase" ] = make_pair(&main::Job::cmd_erase, "erase one book from storage");
            _handlers["type"  ] = make_pair(&main::Job::cmd_type,  "display storage content");


Sky-er 29-11-2008 03:40 966989

Да не не помогает может вообще удалить эту справку как можно безболезненно для программы попробовать снести половину лишнего? Чтобы осталось, только то, что написано в задании и не строчкой больше?

pva 29-11-2008 19:20 967475

Sky-er, Я принципиально против тупого (и бесплатного) делания чужих лаб. Если не интересно разобраться, больше тему не продолжаю. Если интересно лабу доделать - пиши в личку, договоримся


Время: 13:51.

Время: 13:51.
© OSzone.net 2001-