Войти

Показать полную графическую версию : [решено] Помогите дорешать задачу с созданием класса Book


Sky-er
23-11-2008, 04:11
Здравствуйте помогите пожалуйста довести задачу до ума

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

Вот исходник:
#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
В общем немного решил, но я сам запутался, знаний нехватает. Самому интересно.
У меня вопрос:
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
Деструктор:

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
pva, Спасибо за пояснения!!!
Вопрос меня смущает: ежели используешь стандартную библиотеку, почему бы не использовать стандартные контейнеры? »Это потому, что опыта нехватает.

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

http://s44.radikal.ru/i105/0811/ca/d36b9c86d54d.png

pva
23-11-2008, 22:30
У меня при компиляции вашего варианта, выскакивает несколько ошибок... Что я не так делаю? »
э.. это сорри, это я не так делаю. Я сначала сделал без 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
Сейчас напишу самый "доведённый до ума" вариант »Ого, класс, :up Только у меня опять ошибки, 26 штук... Компилирую Borland C++ Builder 6.0. http://s50.radikal.ru/i129/0811/59/621250b49713.jpg

pva
24-11-2008, 12:50
Вот это обкатал на 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
pva, Спасибо большое!!! :up Хотя задача и вопрос не мой, но мне он интересен тоже! Всё компилируется и работает без нареканий. :yes:

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

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

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

http://forum.oszone.net/attachment.php?attachmentid=18071&stc=1&d=1227631655

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

Sky-er
26-11-2008, 17:16
Добавил функцию работает, только выдает ошибку все равно вот ошибки http://forum.oszone.net/attachment.php?attachmentid=18112&stc=1&d=1227708956
ругается на cmd

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

Sky-er
27-11-2008, 21:20
Я вроде и пробовал тот вариант именно в нем выскакивают эти ошибки с cmd, а если убрать вообще ошибки просто оставить саму программу?

pva
28-11-2008, 08:27
а, подожди, он же кричит что надо заменить 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
Да не не помогает может вообще удалить эту справку как можно безболезненно для программы попробовать снести половину лишнего? Чтобы осталось, только то, что написано в задании и не строчкой больше?

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




© OSzone.net 2001-2012