Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
lr2.ООП.2015.pdf
Скачиваний:
12
Добавлен:
16.03.2016
Размер:
240 Кб
Скачать

Реализация практических классов

Обыкновенно для реализации класса используют два файла: заголовочный файл и файл с исходным кодом. В заголовочном файле класс описывается, а в файле исходного кода реализуются методы. Такой способ организации позволяет повысить производительность при сборке программы. Операторы #ifndef MYSTRING_H и #define MYSTRING_H нужны для ограничения включения файла один раз в программу. Множественное включение приводит к ошибкам множественного определения и объявления. #endif указывает на окончание блока включения.

Приведем пример полной реализации класса строки.

#ifndef MYSTRING_H #define MYSTRING_H

#include <iostream>

class MyString { public:

MyString(); MyString(const char* s);

MyString(const MyString& s); ~MyString();

MyString& operator =(const MyString& v); MyString operator +(const MyString& v) const; bool operator ==(const MyString& v) const; char operator [] (unsigned int i) const;

int length() const; private:

friend std::istream& operator >>(std::istream& i, MyString& s); friend std::ostream& operator <<(std::ostream& o, MyString& s); char *ptr;

};

std::istream& operator >>(std::istream& i, MyString& s); std::ostream& operator <<(std::ostream& o, MyString& s);

#endif // MYSTRING_H

Обратите внимание на const и &. В конце объявления метода const означает то, что метод может использоваться для константного экземпляра. При указании аргументов const гарантирует, что данные аргумента не будут изменены. А & позволяет передавать не все данные класса, а только адрес их расположения. Тип MyString& похож на тип const MyString*. Но только похож! Для прояснения различий рекомендую обратиться к [1][2].

Файл с кодом будет выглядеть так:

#include <cstring> #include "mystring.h"

using namespace std;

MyString::MyString() {

*(ptr = new char[1])='\0';

}

MyString::MyString(const char* s){ ptr = new char[1]; strcpy(ptr, s);

}

MyString::MyString(const MyString& s) { ptr = new char[1];

strcpy(ptr, s.ptr);

}

MyString::~MyString() { delete[] ptr;

}

MyString &MyString::operator =(const MyString &v) { strcpy(ptr, v.ptr);

return *this;

}

MyString MyString::operator +(const MyString &v) const { MyString t(*this);

strcat(t.ptr, v.ptr); return t;

}

bool MyString::operator ==(const MyString &v) const { return !strcmp(ptr, v.ptr);

}

char MyString::operator [](unsigned int i) const { return ptr[i];

}

int MyString::length() const { return strlen(ptr);

}

istream& operator >>(std::istream& i, MyString &s) { return i >> s.ptr;

}

ostream& operator <<(std::ostream& o, MyString &s){ return o << s.ptr;

}

Теперь мы имеем класс строки, который имеет возможность для практического применения в программе.

Расширение и использование

Теперь рассмотрим вопрос расширения возможностей класса. Первый способ, который активно применяют начинающие – это дописать класс. В результате получается новый класс с нужными функциями. Иногда такой способ применяют на практике. Например, когда передаётся объект по сети или между программами. Так работают технологии OLE, ActiveX, CORBA. Но в большинстве случаев, такой подход больше походит на применение КамАЗов в качестве такси.1

В случае обмена объектами между программами, мы сталкиваемся с необходимостью передачи дополнительных данных об используемых объектах. Собственно, вполне вероятна ситуация, когда нам понадобиться передать описание более половины объектов, применяющихся в программе. Поэтому, проще включить всю нужную информацию в объект. Такой подход к построению объектов называется компетентно-ориентированным.

1В действительности, использование этого подхода в ООП нарушает правило единственности абстракции.

В нашем случае более разумными и эффективным будут наследование и агрегирование. Сначала рассмотрим наследование.

Суть подхода заключается в создании производного класса от нашей строки. Для этого надо данные строки переместить в секцию protected. А в производном классе мы будем их ис-

пользовать напрямую. Например:

class AnotherString: public MyString { AnotherString();

AnotherString(const AnotherString &v); ~AnotherString();

void foo(char x){

for(int i=0;ptr[i]!='\0';++i) ptr[i]^=x;

}

};

Также, требуется создать конструкторы и деструкторы в новом классе.

Этот способ часто используется, когда требуется расширить абстракцию. В принципе, наследование позволяет значительно сэкономить ресурсы и повысить возможности расширения программы. В целом, при написании программы, важно построить правильную иерархию классов.

При использовании агрегирования, мы создадим новый класс и включим в него строку как поле. То есть используем переменную типа строка в новом классе. Это весьма удобно, поскольку вся работа с памятью возложена на класс строки.

Такой способ более подходит для последнего варианта класса, так как он имеет достаточный набор методов для эффективного использования.

class NewString { NewString();

NewString(const NewString& v); ~NewString();

void foo(char x){

for(int i=0;i<String.length();++i) String[i]^=x;

}

private:

MyString String;

};

Преимущество агрегирования в рассматриваемом нами случае состоит в том, что можно смело изменить внутреннюю структуру строки, сохранив внешний интерфейс. И это никак не скажется на внутренней структуре новой строки. В тоже время, при наследовании нам придётся изменить класс так, чтобы он соответствовал новой структуре класса строки.

Библиография

1:Р. Лафоре, Объектно-ориентированное программирование в С++

2:Дж. Коплиен, Программирование на C++

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]