Skip to content

Instantly share code, notes, and snippets.

@ramntry
Created October 28, 2011 18:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ramntry/1322950 to your computer and use it in GitHub Desktop.
Save ramntry/1322950 to your computer and use it in GitHub Desktop.
OOP start
// Реализация не-inline методов класса. Для обращения к внутреннему пространству имен класса необходимо
// использовать оператор разрешения области видимости имен :: с предваряющим его именем
// класса.
#include "intarray.h"
IntArray::IntArray(int n) :
m_size(n), // Инициализация полей класса методом прямого
m_array(new int[n]) // ... вызова конструкторов этих полей (встроенные
{ // ... типы в C++ тоже имеют свои конструкторы)
for (int i = 0; i < n; i++) // Более сложные действия по инициализации экземпляра осуществляют
m_array[i] = 0; // ... в теле конструктора.
// (new в случае неудачи выбрасывает исключение std::bad_alloc, потому
// ... нет необходимости сверять m_array на равенство нулю, что пришлось
// ... бы делать при работе с malloc)
}
int& IntArray::operator [](int index) // Возвращать этот метод должен именно ссылку на индексируемый элемент
{ // ... нашего массива, поскольку ее можно использовать и для чтения
if (index < 0) // ... значения - справа от оператора присваивания, и для записи в
index += m_size; // ... элемент массива - слева от оператора присваивания. Иначе говоря,
// ... a[2] = 5; развернется в a.operator [](2) = 5; что будет работать как
if (index < m_size && index > -1) // ... a.m_array[2] = 5; только если поток выполнения дойдет
return m_array[index]; // ... до этой (<-) строки
// Мы реализуем индексацию по отрицательному индексу, где -1-ый элемент - последний
throw OutOfBoundsException(); // ... с конца. Если после корректировки индекса он все равно вне допустимого диапазона
} // ... значений, бросаем исключение.
std::ostream& operator <<(std::ostream& out, const IntArray& self) // Реализация дружественной функции (обратите внимание, без IntArray::)
{ // ... со стандартной для оператора << в библиотеке вывода iostream
for (int i = 0; i < self.m_size - 1; i++) // ... сигнатурой и поведением. Друг хорошо знает, как вы устроены и не
out << self.m_array[i] << ' '; // ... попытается вам навредить. Потому ему можно разрешить напрямую работать
out << self.m_array[self.m_size - 1]; // ... с полем m_array не тратя усилий на проверку выхода за границы
// ... массива или поддержку не использующихся здесь отрицательных индексов
return out;
}
// В заголовочный файл следует поместить интерфейс класса - все то, что непосредственно
// понадобиться пользователю класса, а также реализовать все подстановочные (inline) методы,
// поскольку после выполнения директив include их тела окажутся в одном файле с использованиями,
// что для inline-функций критично и, конечно, здесь нужно задать структуру класса - все его поля.
#pragma once
#include <iostream>
class OutOfBoundsException {}; // Пустой класс достаточно функционален в роли исключения:
// ... он имеет конструктор по умолчанию и можно создавать
// ... его экземляры, также он обладает вполне говорящим именем.
// ... (Очень полезно обратиться сюда:
// ... http://www.cplusplus.com/reference/std/exception/exception/)
class IntArray
{ // Функции-друзья не являются методами класса, потому неплохо
friend std::ostream& operator <<(std::ostream& out, const IntArray& self); // ... указать их вне секций класса (хотя на самом деле "вне
// ... секции" под ключевым словом class то же, что и в секции
// Со спецификатором "друг" указывают функции, которым разрешен доступ // ... private - но для друзей нет никакой разницы, где они
// ... к приватным членам класса, но эти члены не внедряются в локальную // ... объявлены. Другое дело struct - там секция по
// ... область видимости таких функций - потому им нужно явно передвать // ... умолчанию - public) На практике друзей помещают
// ... экземляр класса для работы с его членами. // ... в конец объявления класса - подальше от глаз.
public:
IntArray(int n); // (впрочем, будет не лишним указать конструктор как explicit. См. сноску)
~IntArray() // Методы, тело которых объявлено в теле класса могут быть только
{ delete[] m_array; } // ... inline - прямое указание этого спецификатора ничего не изменит.
// ... Не стоит делать inline'овыми функции длиной более, чем в 1-2 строки.
int& operator [](int index); // Конструкция some[i] эквивалентна some.operator [](i). Подробнее см. *.cpp
int size() const // Метод size не меняет объект - неплохо это явно отметить спецификатором const
{ return m_size; }
private:
int const m_size; // Префикс m_ - всего лишь неплохое решение двух проблем - обхода совпадения имен методов
int* const m_array; // ... и полей класса, а также выделение полей класса среди других переменных (параметров
// ... и локальных переменных) в телах реализаций методов. Оба поля приватны и недоступны
// ... пользователю, он не может их прочитать или изменить. Более того, оба поля объявлены
// ... константными, что позволяет установить их значение лишь раз за все время жизни объекта,
// ... что здесь оправдано - наш массив не умеет менять свой размер. (Обратите внимание, что
// ... константен указатель на массив - его не настроить на другой массив. Но сами элементы
// ... массива легко могут быть изменены) Важно, что инициализировать константные поля вы сможете
// ... только из конструктора.
};
/*
По поводу поведения ключевого слова explicit и конвертирующего конструктора все очень хорошо сказано
тут: http://alexeivasin.com/post/explicit-cpp-keyword.aspx
*/
HEADERS += \
intarray.h
SOURCES += \
main.cpp \
intarray.cpp
#include <iostream>
#include "intarray.h"
using namespace std;
int main(void)
{
IntArray a(10); // Явный вызов конструктора
cout << "After declaration: " << a << endl; // Здесь вызывается независимая от класса, но дружественная
// ... ему функция operator <<
for (int i = 1; i <= a.size(); i++) // Протестируем доступ по отрицательным индексам
a[-i] = i;
cout << "After initialization: " << a << endl;
// Здесь осуществляется операция доступа за пределы
// try // ... массива, что приводит к выбросу объектом соответствующего
// { // ... исключения. Обратите внимание на сообщение об исключении в
cout << a[a.size()] << endl; // ... окне консоли и на то, что программа была немедленно завершена.
// }
// catch (OutOfBoundsException) // Эта ветка try-блока (похожего на switch, где catch - аналог case'а,
// { // ... в данном случае фиксирующего тип исключения) обрабатывает
// cerr << "Out of bounds exception" << endl; // ... исключение выводом сообщения в поток ошибок. Раскомментируйте
// } // ... try-блок и посмотрите, как это работает. Обратите внимание на то,
// ... что программа нормально продолжает работу и прощается с вами
cout << "Bye" << endl;
return 0;
}
/* С выходом из блока, в котором был создан экземпляр класса (внимание, не указатель на экземпляр, размещенный
в куче, а сам экземпляр - в автоматической области памяти (на стеке) - а именно так и был создан a), он, как
и все остальные локальные переменные, уничтожается - но стираются не только поля класса (здесь - m_size и m_array),
(это как раз поведение деструктора по умолчанию) но и вызывается пользовательский деструктор, который в данном
случае освобождает память в куче вызовом delete[] для указателя, ранее инициализированного операцией new в
конструкторе нашего класса. Если бы и сам экземпляр создавался в куче (например, так:
IntArray* pa = new IntArray(10);
), то для него нужно было бы вызвать delete (delete pa;), тогда память, отведенная под поля m_size и m_array,
на этот раз в куче, была бы освобождена - а вот уже в связи со смертью этих полей, как и в прошлом случае,
вызвался бы деструктор. То есть здесь вручную вызывается только деструктор по умолчанию, а пользовательский
им (!) опять-таки вызывается автоматически)
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment