Добавлен: 03.04.2023
Просмотров: 393
Скачиваний: 5
2. Классы и объекты в языке C++
Классы представляют собой расширенную концепцию структур данных: подобно структурам данных, они могут содержать элементы данных, но они также могут содержать функции в качестве членов [a.3.].
Объект – это экземпляр класса. В терминах переменных класс будет типом, а объект – переменной.
Классы определяются с использованием класса ключевых слов или структуры ключевых слов со следующим синтаксисом (листинг 1).
Листинг 1. Синтаксис определения класса
class имя_класса {
спецификатор_доступа_1:
член1;
спецификатор_доступа_2:
член2;
...
} имя_объекта;
Здесь имя_класса является допустимым идентификатором для класса, имя_объекта является необязательным списком имен для объектов этого класса. Тело объявления может содержать члены, которые могут быть объявлениями данных или функций, и иметь различные спецификаторы доступа [a.3.].
Классы имеют тот же формат, что и простые структуры данных, за исключением того, что они могут также включать функции и иметь спецификаторы доступа. Спецификатор доступа – это одно из следующих трех ключевых слов: private (личные элементы класса), public (общие элементы класса) или protected (защищенные элементы класса). Эти спецификаторы определяют права доступа – под личными элементами понимаются используемые только функциями своего класса. Доступ к общим функциям возможен из любой точки программы, а доступ к защищенным разрешен только функциям данного класса и его наследникам. По умолчанию все члены класса, объявленные с ключевым словом class, имеют private-доступ ко всем его членам. Таким образом, любой член, который объявлен перед любым другим спецификатором доступа, имеет private-доступ автоматически. Пример класса, имеющего члены с различными спецификаторами доступа, продемонстрирован в листинге 2.
Листинг 2. Пример класса, имеющего члены с различными спецификаторами доступа
class Rectangle {
int width, height;
public:
void set_values (int,int);
int area (void);
} rect;
Здесь объявлен класс (то есть тип) с именем Rectangle и объект (то есть переменная) этого класса, называемая rect. Этот класс содержит четыре члена: два члена данных типа int (ширина и высота прямоугольника) с закрытым доступом (потому что private является уровнем доступа по умолчанию) и две функции-члена с открытым доступом: функции set_values и area – в класс включено только объявление этих функций, но не их определение.
После объявления Rectangle и rect можно получить доступ к любому из открытых членов объекта rect, как если бы они были обычными функциями или обычными переменными, просто вставив точку (.) между именем объекта и именем члена. Это соответствует тому же самому синтаксису, что и доступ к членам простых структур данных [a.4.].
Единственные члены rect, к которым нельзя получить доступ вне класса, – это ширина и высота, поскольку они имеют закрытый доступ и на них можно ссылаться только из других членов этого же класса.
В листинге 3 приведен полный пример класса Rectangle.
Листинг 3. Пример использования объекта класса
#include <iostream>
#include <conio.h>
#include <locale.h>
using namespace std;
class Rectangle {
int width, height;
public:
void set_values (int,int);
int area() {return width*height;}
};
void Rectangle::set_values (int x, int y) {
width = x;
height = y;
}
int main () {
setlocale(LC_ALL, "rus");
Rectangle rect;
rect.set_values (3, 4);
cout << "Площадь прямоугольника: " << rect.area();
_getch();
return 0;
}
В этом примере вводится оператор области видимости (::, два двоеточия), который используется также в отношении пространств имен. Здесь он рассматривается в определении функции set_values для определения члена класса вне самого класса. Определение функции-члена было включено непосредственно в определение класса Rectangle, учитывая ее простоту. И наоборот, функция set_values просто объявлена внутри класса, но ее определение находится за пределами класса. В этом внешнем определении оператор :: используется для указания того, что определяемая функция является членом класса Rectangle, а не обычной функцией, не являющейся членом данного класса.
Оператор :: указывает класс, к которому принадлежит определяемый член, предоставляя точно такие же свойства области видимости, как если бы это определение функции было непосредственно включено в определение класса. Например, функция set_values в предыдущем примере имеет доступ к переменным width и height, которые являются закрытыми членами класса Rectangle и, таким образом, доступны только из других членов класса.
Единственная разница между определением функции-члена полностью внутри класса или просто включением ее объявления и последующим ее определением вне класса заключается в том, что в первом случае функция автоматически считается встроенной функцией-членом компилятором, тогда как во втором это обычная (не встроенная) функция-член класса. Это не вызывает различий в поведении, но только при возможных оптимизациях компилятора [a.4.].
Члены класса width и height имеют private-доступ. Объявление их закрытыми означает, что доступ вне класса запрещен. Это имеет смысл, поскольку функция-член для установки значений этих элементов в объекте уже определена; это set_values. Поэтому остальная часть программы не должна иметь прямой доступ к ним. Возможно, в таком простом примере, как этот, трудно понять, насколько полезным может быть ограничение доступа к этим переменным, но в больших проектах может быть очень важно, чтобы значения не могли быть изменены неожиданным образом (неожиданно с точки зрения объекта).
Наиболее важным свойством класса является то, что он является типом, и поэтому можно объявить несколько объектов этого класса. Например, следуя предыдущему примеру класса Rectangle, можно было бы объявить объект rect2 в дополнение к объекту rect (листинг 4).
Листинг 4. Пример использования нескольких объектов класса
#include <iostream>
#include <conio.h>
#include <locale.h>
using namespace std;
class Rectangle {
int width, height;
public:
void set_values (int,int);
int area () {return width*height;}
};
void Rectangle::set_values (int x, int y) {
width = x;
height = y;
}
int main () {
setlocale(LC_ALL, "rus");
Rectangle rect, rect2;
rect.set_values (3, 4);
rect2.set_values (5, 6);
cout << "Площадь прямоугольника 1: " << rect.area() << endl;
cout << "Площадь прямоугольника 2: " << rect2.area() << endl;
_getch();
return 0;
}
В этом конкретном случае классом (типом объектов) является Rectangle, у которого имеется два экземпляра (т.е. объекта): rect и rect2. Каждый из них имеет свои собственные переменные-члены и функции-члены.
Вызов rect.area () не дает тот же результат, что и вызов rect2.area (). Это потому, что каждый объект класса Rectangle имеет свои собственные переменные width и height, так как они – в некотором роде – имеют также свои собственные члены-функции set_value и area, которые работают с собственными переменными-членами объекта.
Классы позволяют программировать с использованием объектно-ориентированных парадигм: данные и функции являются членами объекта, что уменьшает необходимость передавать и переносить обработчики или другие переменные состояния в качестве аргументов функций, поскольку они являются частью объекта, член которого вызывается [a.5.]. При вызовах rect.area и rect2.area не было передано никаких аргументов. Эти функции-члены напрямую использовали элементы данных своих соответствующих объектов rect и rect2.
Конструктор класса – это специальная функция-член класса, которая выполняется всякий раз, когда создаются новые объекты этого класса.
Имя конструктора будет точно таким же, как и у класса, причем конструктор не имеет никакого возвращаемого типа, даже void. Конструкторы могут быть очень полезны для установки начальных значений для определенных переменных-членов [a.5.].
Следующий пример (листинг 5) объясняет концепцию конструктора.
Листинг 5. Пример использования конструктора
#include <iostream>
#include <conio.h>
using namespace std;
class Line {
public:
void setLength(double len);
double getLength(void);
Line(){} // Конструктор класса
private:
double length;
};
void Line::setLength(double len) {
length = len;
}
double Line::getLength( void ) {
return length;
}
int main() {
Line line; // создан объект класса для описания линии
line.setLength(6.0); // задана длина линии
cout << line.getLength() << endl; // длина линии выведена на экран
_getch();
return 0;
}
Конструктор по умолчанию не имеет никаких параметров, но, если это необходимо, конструктор может иметь параметры. Это позволит установить начальные значения полей объекта во время его создания.
Деструктор – это специальная функция-член класса, которая выполняется всякий раз, когда объект класса выходит из области видимости или когда выражение delete применяется к указателю на объект этого класса.
Деструктор будет иметь точно такое же имя, что и класс с префиксом тильды (~), и он не может ни возвращать значение, ни принимать какие-либо параметры. Деструктор может быть очень полезен для освобождения ресурсов перед выходом из программы.
Классы и объекты этих классов, а также поля и права доступа к ним являются ключевыми понятиями объектно-ориентированного программирования. С появлением объектно-ориентированных технологий классы позволили объединить в одну сущность модули и типы, являясь как структурными единицами, составляющими программу, так и статистическими описаниями определенных типов данных, обрабатываемых в процессе ее выполнения.
3. Работа с файлами в языке C++
Файл данных – это компьютерный файл (именованная область внешней памяти, выделенная для хранения), в котором хранятся данные, используемые компьютерным приложением или системой, включая входные и выходные данные [a.6.]. Файл данных обычно не содержит инструкций или кода для выполнения (то есть компьютерной программы). Большинство компьютерных программ работают с файлами данных.
Файлы данных могут храниться двумя способами:
- Текстовые файлы;
- Бинарные файлы.
Текстовые файлы (часто называемые ASCII-файлами) хранят информацию в символах кодировки ASCII. Текстовый файл содержит удобочитаемые символы. Пользователь может читать содержимое текстового файла или редактировать его с помощью текстового редактора. В текстовых файлах каждая строка текста заканчивается (разделяется) специальным символов, известным от EOL (от англ. End Of Line – конец строки). Примером текстового файла может служить текстовый документ – файл с расширением .txt.
Бинарные (двоичные) файлы содержат информацию в том же формате, в котором она хранится в памяти, то есть в двоичной форме. В двоичном файле нет разделителей строк и символов перевода. Это позволяет проще и быстрее осуществлять операции чтения и записи, чем при работе с текстовыми файлами [a.6.]. До тех пор, пока файл не нужно переносить в другую операционную систему либо читать при помощи текстового редактора, двоичные файлы являются лучшим способом хранения информации. Примером бинарного файла может служить изображение в формате JPEG.
C++ предоставляет следующие классы для организации операций ввода (вывода) символов из файла (в файл) [a.4.]:
- ofstream: потоковый класс для записи в файл;
- ifstream: потоковый класс для чтения файла;
- fstream: потоковый класс для чтения (записи) из файла (в файл).
Эти классы прямо или косвенно получены из классов istream и ostream. Операции ввода-вывода на экран (в консоль) используют объекты этих классов: cin – это объект класса istream, cout – объект класса ostream. Таким образом, файловые потоки используются точно так же, как cin и cout, с той разницей, что эти потоки связываются с физическими файлами на диске.
Связывание с файлом является первой операцией, выполняемой над объектом одного из этих классов. Эта процедура называется открытием файла. Таким образом, любая операция ввода или вывода, выполненная с объектом класса – файловым потоком – будет применена к физическому файлу.
Чтобы открыть файл, используя объект потока, необходимо использовать его функцию-член open: open (filename, mode), где filename – это строка, представляющая имя открываемого файла, mode – необязательный параметр, который представлен одним из следующих флагов либо их комбинацией:
ios :: in – открыть для операций ввода;
ios :: out – открыть для операций вывода;
ios :: binary – открыть в двоичном режиме;
ios :: ate – установить начальную позицию в конец файла;
Если этот флаг не установлен, начальная позиция – начало файла.
ios :: app – все операции вывода выполняются в конец файла, добавляя содержимое к текущему содержимому файла;
ios :: trunc – если файл открыт для операций вывода и он уже существует, его предыдущее содержимое удаляется и заменяется новым.
Все эти флаги могут быть объединены с помощью побитового оператора ИЛИ (|). Например, код открытия файла example.bin в двоичном режиме для добавления данных представлен в листинге 6.
Листинг 6. Открытие файла в бинарном режиме для добавления данных
ofstream myfile;
myfile.open ("example.bin", ios :: out | ios :: app | ios :: binary);
Каждая функция-член open для классов ofstream, ifstream и fstream имеет режим по умолчанию, который используется, если файл открывается без второго аргумента – ios::out для ofstream, ios::in для ifstream и ios::in | ios::out для fstream.
Для классов ifstream и ofstream режимы ios::in и ios::out соответственно автоматически предполагаются, даже если необязательный параметр, который не включает их, передается в качестве второго аргумента в функцию-член open (флаги комбинируются).
Для fstream значение по умолчанию применяется, только если функция вызывается без указания какого-либо значения для параметра mode. Если функция вызывается с любым значением в этом параметре, режим по умолчанию переопределяется, а не комбинируется.