Сделав предварительные замечания о понятиях объектно-ориентированного подхода, перейдем к конкретному рассмотрению классов, как они реализованы в языке C++.
Сначала мы рассмотрим простейший пример определений классов, которые можно было бы разместить в заголовочном файле. Так обычно и делается, если классы должны быть доступны нескольким модулям программы. Следует напомнить, что класс – это расширение понятия типа данных, а точнее, понятия структуры. В C++ принято говорить просто о типах; представитель класса уже нельзя считать просто данными, поскольку ему присуще некоторое поведение.
В известном смысле объект представляет собой сущность. Программа обычно использует переменные для хранения информации о различных реально существующих сущностях, например служащих, книгах и даже файлах. При объектно-ориентированном программировании вы фокусируетесь на предметах образующих систему, и операциях, которые вы должны выполнять над этими предметами. Например, для объекта-файла вы могли бы иметь операции, которые печатают, отображают или изменяют файл. В C++ вы можете создать тип данных - класс для определения своих объектов. Ваша цель состоит в том, чтобы включить в класс столько информации об объекте, сколько требуется. Исходя из этого, можно подобрать класс, созданный вами для одной программы, и использовать его в нескольких разных программах.
Определение класса
Класс позволяет вашим программам группировать данные, определяющие свойства объекта, и функции, которые выполняют операции над этими данными. В литературе по объектно-ориентированному программированию функции класса называют методами или элементами-функциями а данные могут называть полями или элементами данных. Подобно структуре, класс C++ должен иметь уникальное имя, за которым следует открывающая фигурная скобка, один или несколько элементов данных и функций и закрывающая фигурная скобка:
class class_name {
int data_member; // Элемент данных
void show_member(int); // Функция-элемент
};
После определения класса вы можете объявлять переменные типа этого класса (называемые объектами), как показано ниже:
class_name object_one, object_two, object_three;
Следующее определение создает класс employee, который содержит определения данных и метода:
В данном случае класс содержит три переменные и одну функцию-элемент. Обратите внимание на использование метки public внутри определения класса. Как вы узнаете далее, элементы класса могут быть частными (private) или общими (public), от чего зависит, как ваши программы обращаются к элементам класса. В данном случае все элементы являются общими (public), это означает, что программа может обращаться к любому элементу, используя оператор точку. После определения класса внутри вашей программы вы можете объявить объекты (переменные) типа этого класса, как показано ниже:
employee worker, boss, secretary; //Переменные (объекты) класса employee
Следующая программа EMPCLASS.CPP создает два объекта типа employee - worker и boss. Используя оператор точку, программа присваивает значения элементам данных. Затем программа использует метод show_employee для вывода информации о служащих.
могут иметь любой тип, кроме типа этого же класса (но могут быть указателями или ссылками на этот класс);
могут быть описаны с модификатором const, при этом они инициализируются только один раз (с помощью конструктора) и не могут изменяться;
могут быть описаны с модификатором static , но не как auto, extern и register.
Инициализация данных при описании класса не допускается.
Классы могут быть глобальными (объявленными вне любого блока) и локальными (объявленными внутри блока, например, функции или другого класса).
Ниже перечислены некоторые особенности локального класса:
внутри локального класса можно использовать типы, статические (static) и внешние (extern) переменные, внешние функции и элементы перечислений из области, в которой он описан; запрещается использовать автоматические переменные из этой области;
локальный класс не может иметь статических элементов;
методы этого класса могут быть описаны только внутри класса;
если один класс вложен в другой класс, они не имеют каких-либо особых прав доступа к элементам друг друга и могут обращаться к ним только по общим правилам.
Определение методов класса вне класса
В предыдущем классе employee функция была определена внутри самого класса (встроенная (inline) функция). При увеличении функций определение встроенных функций внутри класса может внести беспорядок в описание класса. В качестве альтернативы вы можете поместить прототип функции внутри класса, а затем определить функцию вне класса. Ваше определение класса с прототипом становится следующим:
class employee
{ public:
char name[64];
long employee_id;
float salary;
void show_employee(void); //Прототип функции
};
Так как разные классы могут использовать функции с одинаковыми именами, вы должны предварять имена определяемых вне класса функций именем класса и оператором глобального разрешения (::). В данном случае определение функции становится следующим:
Как видите, приведенный код предваряется определением функции с именем класса (employee) и оператором глобального разрешения (::). Следующая программа CLASSFUN. CPP помещает определение функции show_employee вне класса, используя оператор глобального разрешения для указания имени класса. Это требует выноса функции за пределы функции main. Поэтому выделим две части программы – до и после функции main:
Часть 1:
//Здесь могут вводится функции и классы пользователя
Как вы уже знаете, класс содержит данные и методы (функции). Для использования класса программы просто должны знать информацию, которую хранит класс (его элементы данных) и методы, которые манипулируют данными (функции). Вашим прикладным программам не требуется знать, как работают методы. Более того, программы должны знать только, какую задачу выполняют методы. Например, предположим, что у вас есть класс file. В идеале ваши программы должны знать только то, что этот класс обеспечивает методы flle.print, который печатает отформатированную копию текущего файла, или file.delete, который удаляет файл. Вашей программе не требуется знать, как эти два метода работают. Другими словами, программа должна рассматривать класс как "черный ящик". Программа знает, какие методы необходимо вызывать и какие параметры им передать при выполнении операций с объектами класса, но программа ничего не знает о реальной работе, выполняющейся внутри класса (в "черном ящике"). Сокрытие информации представляет собой процесс, в результате которого программе предоставляется только минимальная информация, необходимая для использования класса. В предыдущих программах каждый из созданных вами классов использовал метку public для объявления всех элементов класса общими, т. е. видимыми для всей программы. Таким образом, программа могла бы непосредственно обратиться к любому элементу класса, используя оператор точку:
…
class employee
{
public:
char name[64];
long employee_id;
float salary;
void show_employee(void);
};
…
employee worker, boss;
…
worker.salary = 25000;
При создании класса вы могли бы иметь элементы, чьи значения используются только внутри класса, и обращаться к которым самой программе нет необходимости. Такие элементы являются частными (private), и их следует срывать от программы. Если вы не используете метку public, то по умолчанию C++ подразумевает, что все элементы класса являются частными. Ваши прикладные программы не могут обращаться к частным элементам класса, используя оператор точку. К частным элементам класса могут обращаться только элементы самого класса. При создании класса вам следует разделить элементы на частные и общие, как показано ниже:
class some_class
{
public: // Общие элементы
int some_variable;
void initialize_private(int, float);
void show_data(void);
private: // Частные элементы
int key_value;
float key_number;
};
В данном случае программа может использовать оператор точку для обращения к общим элементам, как показано ниже:
some_class object; // Создать объект
object.some_variable = 1001;
object.initialize_private(2002, 1.2345);
objeot.show_data();
Если ваша программа попытается обратиться к частным элементам key_value или key_number, используя оператор точку, компилятор сообщит о синтаксических ошибках.
Как правило, Вы будете защищать элементы класса от прямого доступа к ним делая их частными. При этом программы не могут непосредственно присваивать значения таким элементам, используя оператор точку. Для того чтобы присвоить значение этим элементам, программа должна вызвать метод класса. Предотвращая прямой доступ к элементам данных, вы, таким образом, можете гарантировать, что им всегда будут присваиваться допустимые значения. Например, предположим, что объект nuclear_reactor вашей программы использует переменную с именем melt_down, которая всегда должна содержать значение в диапазоне от 1 до 5. Если элемент melt_down является общим, программа может непосредственно обратиться к элементу, изменяя его значение произвольным образом:
nuclear_reactor.melt_down = 101;
Если вместо этого вы делаете переменную частной, то можете использовать метод класса, например assign_meltdown, чтобы присвоить значение этой переменной. Как показано ниже, функция assign_meltdown может проверять присваиваемое значение, чтобы убедиться, что оно является допустимым:
int nuke::assign_meltdown(int value)
{
if ((value > 0) && (value <= 5))
{
melt_down = value;
return(0); // Успешное присваивание
}
else
return(-1); // Недопустимое значение
}
Методы класса, которые управляют доступом к элементам данных, представляют собой интерфейсные функции. При создании классов Вы будете использовать интерфейсные функции для защиты данных своих классов.
Следующая программа INFOHIDE.CPP иллюстрирует использование общих и частных элементов класса. Программа определяет класс employee, как показано ниже:
class employee
{
public:
int assign_values (char *, long, float);
void show_employee ( void) ;
int change_salary ( float ) ;
long get_id(void) ;
private :
char name [64] ;
long employee_id;
float salary;
};
Как видите, класс защищает все свои элементы данных, объявляя их частными. Для доступа к элементам данных программа должна использовать интерфейсные функции. Ниже приведена реализация программы INFOHIDE.CPP:
//Здесь могут вводится функции и классы пользователя
Несмотря на то, что программа достаточно длинна, ее функции на самом деле очень просты. Метод assign_values инициализирует частные данные piacca. Метод использует оператор if, чтобы убедиться, что присваивается допустимый оклад. Метод show_employee в данном случае выводит частные элементы данных. Методы change_salary и get_id представляют собой интерфейсные функции, обеспечивающие программе доступ к частным данным.
Для снижения количества возможных ошибок ограничивайте доступ программ к данным класса, определяя элементы данных класса как частные. Таким образом, программа не сможет обратиться к элементам данных класса, используя оператор точку. Вместо этого класс должен определять интерфейсные функции, с помощью которых программа может присваивать значения частным элементам. Интерфейсные функции в свою очередь, могут исследовать и скорректировать значения, которые программа пытается присвоить.
Частные элементы класса не всегда являются данными. В примере, представленном в этом уроке, частные элементы были всегда элементами данных. По мере того как определение класса становится более сложным, вы, возможно, захотите создать функции, используемые другими методами класса, но для оставшейся части программы доступ к таким функциям должен быть закрыт. В подобных случаях вы просто объявляете такие методы частными элементами. Если функция класса не объявлена как общая, программа не может вызвать такую функцию, используя оператор точку.
Использование оператора глобального разрешения для элементов класса
Если вы рассмотрите функции в программе INFOHIDE.CPP, вы обнаружите, что имена параметров функции часто предваряются символами еmр_, как показано ниже:
int employee::assign_values (char *emp_name, long emp_id, float emp_salary)
Символы етр_ использовались, чтобы избежать конфликта между именами параметров функции и именами элементов класса.
При создании функций-элементов класса возможны ситуации, когда имя локальной переменной, которое вы используете внутри функции, конфликтует с именем элемента класса По умолчанию имя локальной переменной будет переопределять имя элемента класса. Когда происходит подобный конфликт имен, функция может использовать имя класса и оператор глобального разрешения (::)для доступа к элементам класса, как показано ниже:
int employee::assign_values( char *name, long employee_id, float salary)
{
strcpy ( employee :: name , name ) ;
employee :: employee_id = employee_id;
if (salary < 50000.0)
{
employee::salary = salary;
return(0); // Успешно
}
else
return(-1); // Недопустимый оклад
}
При создании функций, работающих с элементами класса, вам следует использовать имя класса и оператор глобального разрешения, чтобы таким образом избежать конфликта имен.
Специальные функции-элементы класса
Специальными функциями-элементами называют функции, которые могут вызываться компилятором неявно. Это может происходить при создании и уничтожении представителей класса, при их копировании и преобразовании в другие типы. К таким функциям относятся:
Конструктор. Инициализирует представители класса.
Конструктор копии. Инициализирует новый представитель класса, используя значения существующего.
Операция присваивания. Присваивает содержимое одного представителя класса другому.
Деструктор. Производит очистку памяти от уничтожаемого объекта.
Операция new. Выделяет память для динамически создаваемого объекта.
Операция delete. Освобождает память, выделенную под динамический объект.
Функции преобразования. Преобразуют представитель класса в другой тип (и наоборот).