Печатать книгуПечатать книгу

§ 2. Классы

Сайт: Профильное обучение
Курс: Информатика. 11 класс (Повышенный уровень)
Книга: § 2. Классы
Напечатано:: Гость
Дата: Понедельник, 6 Май 2024, 16:00

2.1. Структура класса

 

Напомним основные понятия, связанные с описанием класса. В примере 2.1 используется класс Student.

Имя класса принято писать с заглавной буквы. В общем виде описание класса выглядит следующим образом:

class имя_класса

{

  private:

  /* список свойств и методов для 

  использования внутри класса */

  public:

  /* список методов, доступных другим 

  функциям и объектам программы */

  protected:

  /*список средств, доступных для 

  дружественных классов и наследников*/

};

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

Закрытые данные класса размещаются после модификатора доступа private. Если отсутствует модификатор public, то все функции и переменные по умолчанию являются закрытыми.

Обычно приватными делают все поля класса, а публичными — его методы. Все действия с закрытыми полями класса реализуются через его методы.

Поскольку метод в классе является функцией, то и описывают методы так же, как описывают функции. Объявляют методы внутри класса, а определение методов может быть как внутри класса, так и вне его. Определение методов вне класса позволяет при необходимости легко вынести их в отдельный файл.

Определения методов класса очень похоже на обычные определения функций. Каждый из методов имеет заголовок и тело, может иметь тип возвращаемого значения и параметры. Но есть и отличия:

  1. При определении функции как метода класса используется значок «::»[1] для идентификации класса, которому принадлежит функция.
  2. Методы класса имеют доступ ко всем приватным компонентам своего класса.

Поскольку данные в классе являются приватными, пользователь не может напрямую определить или изменить значения данных. Для этих целей используются специальные методы.

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

На объекты классов, как и на объекты других типов, можно определять указатели. Затем через указатель можно обращаться к членам класса — переменным и методам (пример 2.2). Однако если при обращении через обычную переменную используется символ точка, то для обращения к членам класса через указатель применяется стрелка «–>» (знаки «минус» и «больше»).


[1] Значок обозначает операцию «раскрытие области видимости».

Пример 2.1. Программа для решения следующей задачи: «Прочитать из текстового файла данные о студентах (фамилия, город, год рождения, отметки ЦТ) и вывести в файл в порядке убывания суммарного балла фамилии, город и суммарный балл».

#include <iostream>

#include <fstream>

#include <string>

#include <windows.h>

#include <vector>

#include <algorithm>

 

using namespace std;

 

class Student

{

  private:

  /** поля для хранения фамилии

      и названия города **/

    string fam, gorod;

  /// поле - год рождения

    int god_r;

    vector <int> otm = vector<int>(3);

  public:

  /// метод для суммы отметок

    int summ();

  /// сеттеры для задания значения полей

    void set_fam(string f);

    void set_gorod(string g);

    void set_god_r(int r);

    void set_otm(vector <int> d);

  /** геттер для получения строки

     Фамилия Город Сумма баллов**/

    string get_fam_gor_summ();

};

 

int Student::summ()

{

span lang="EN-US" style="font-size: 10.0pt; font-family: 'Courier New'; color: black; mso-ansi-language: EN-US;">   int s = 0;

   for (auto i:otm)

     s += i;

   return s;

}

 

void Student::set_fam(string f)

{

    fam = f;

}

 

void Student::set_gorod(string g)

{

    gorod = g;

}

 

void Student::set_god_r(int r)

{

    god_r = r;

}

 

void Student::set_otm(vector <int> d)

{

    otm = d;

}

 

string Student::get_fam_gor_summ()

{

   string s;

   s = fam + '\t';

   s += gorod + '\t';

   s += to_string(summ());

   return s;

}

 

void vvod (vector <Student> &d)

{

  ifstream fin ("input.txt");

  int r;

  fin >> r;

  fin.ignore();

  d.resize(r);

  /** Считывание данных из файла и

      определение объектов с

      использованием сеттеров **/

  for (int i = 0; i < r; i++){

    string t_str;

    fin >> t_str;

    d[i].set_fam(t_str);

    fin >> t_str;

    d[i].set_gorod(t_str);

    int t_int;

    fin >> t_int;

    d[i].set_god_r(t_int);

    vector<int> t_vect(3);

    fin >> t_vect[0] >> t_vect[1]

        >> t_vect[2];

    d[i].set_otm(t_vect);

  }

}

 

bool comp(Student x, Student y)

{

  return (x.summ() > y.summ());

}

 

int main()

{

  SetConsoleCP(1251);

  SetConsoleOutputCP(1251);

  vector <Student> a;

  vvod (a);

  ofstream fout ("output.txt");

  sort(a.begin(),a.end(),comp);

  /** вывод данных в файл с

      использованием геттера**/

  for (int i = 0; i < a.size(); i++){

    fout << a[i].get_fam_gor_summ() << endl;

  }

  return 0;

}

Пример 2.2. Обращение через указатель к членам класса Date (пример 22.5 учебного пособия 10 класса, http://profil.adu.by/mod/book/view.php?id=3574&chapterid=10521#pr5).

Date d4("14.01.2021");

  Date *d5 = &d4;

  d5 -> set_den(17);

  d5 -> set_mesjac(3);

  cout << d4.get_Date_dm() << endl;

Изменения по указателю d5 в данном случае приведут к изменениям объекта d4. В результате выполнения команд будет выведено 17.3.

2.2. Конструкторы

Для начальной инициализации класса используются конструкторы. Конструктор — специальный тип метода класса, который автоматически вызывается при создании объекта этого же класса. Конструкторы обычно используются для инициализации полей объекта класса (пример 2.3).

В отличие от обычных методов конструкторы имеют определенные правила именования:

  • конструктор должен иметь то же имя, что и класс;
  • конструктор не имеет типа возврата.

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

В конструкторе может быть использован список инициализации полей класса. Список инициализации располагается после параметров конструктора. Он начинается с двоеточия (:), а затем значение для каждого поля указывается в круглых скобках. В теле конструктора не нужно выполнять операции присваивания, поэтому тело конструктора может быть пустым. Список инициализации членов класса не заканчивается точкой с запятой (пример 2.4).

Каждый метод класса неявно содержит в качестве поля данных указатель:

ИмяКласса *this;

С его помощью метод класса определяет, с данными какого объекта ему предстоит работать. Указатель this можно использовать в конструкторах (пример 2.5).

Деструктор класса — еще один специальный метод — вызывается при уничтожении объекта. Имя деструктора аналогично имени конструктора, только в начале ставится знак тильды: ~. Деструктор не имеет входных параметров. Деструктор всегда один. Для простых классов (тех, которые только инициализируют значения обычных свойств) деструктор не нужен, так как C++ автоматически выполнит очистку памяти самостоятельно. Деструкторы не вызываются пользователем явно. Однако их могут безопасно вызывать другие методы класса, т. к. объект не уничтожится до тех пор, пока не выполнится деструктор.

Пример 2.3. Добавление конструкторов в класс Student.

class Student

{

  private:

  /** поля для хранения фамилии

      и названия города **/

    string fam, gorod;

  /// поле - год рождения

    int god_r;

    vector <int> otm = vector<int>(3);

  public:

  /// конструктор по умолчанию 

     Student ();

  /// конструктор

   Student (string f, string g, int r,

            int n0, int n1, int n2);

  /// метод для вычисления суммы отметок

    int summ();

  /// сеттеры для задания значения полей

    void set_fam(string f);

    void set_gorod(string g);

    void set_god_r(int r);

    void set_otm(vector <int> d);

  /** геттер для получения строки

     Фамилия Город Сумма баллов**/

    string get_fam_gor_summ();

};

Student::Student()

{

    gorod = "Минск";

    god_r = 2005;

}

 

Student::Student (string f, string g,

      int r, int n0, int n1, int n2)

{

    fam = f;

    gorod = g;

    god_r = r;

    otm[0] = n0;

    otm[1] = n1;

    otm[2] = n2;

}

Функция для ввода данных с использованием конструктора изменится следующим образом:

void vvod1 (vector <Student> &d)

{

  ifstream fin ("input.txt");

  int r;

  fin >> r;

  fin.ignore();

  d.resize(r);

  /** Считывание данных из файла и

      определение объектов с

      использованием конструктора **/

  for (int i = 0; i < r; i++){

    string t_str, g_str;

    fin >> t_str;

    fin >> g_str;

    int t_int;

    fin >> t_int;

    int b0, b1, b2;

    fin >> b0 >> b1 >> b2;

    d[i] = Student(t_str, g_str,

               t_int, b0, b1, b2);

  }

}

Пример 2.4. Использование списка инициализации при описании конструкторов в классе Student.

/// конструктор по умолчанию

  Student () : gorod ("Минск"), god_(2005) {}

  /// конструктор

  Student (string f, string g, int r,

           int n0, int n1, int n2) :

              fam(f), gorod (g), 

              god_r (r), 

              otm ({n0, n1, n2}) {}

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

Пример 2.5. Использование указателя this в конструкторе класса Date.

Date::Date(int t_d, int t_m, int t_g)

{

    this -> d = t_d;

    this -> m = t_m;

    this -> g = t_g;

}

2.3. Целочисленный тип данных

Рассмотрим примеры использования классов.

Пример 2.6. Реализовать класс для описания геометрической фигуры — Figure и его наследников — круг (Circle) и треугольник (Triangle). У базового класса нужно определить общий метод square(), который возвращает площадь геометрической фигуры, а у наследников  реализовать этот метод. Продемонстрировать работу методов.

Этапы выполнения задания

1. Описать базовый класс Figure.

    1. Базовый класс Figure является абстрактным классом, который будет содержать виртуальный метод square(). Виртуальный метод — это метод, который будет переопределен в классах-наследниках. Конкретная реализация метода при вызове будет определяться во время исполнения кода. В данном примере используется чистая виртуальная функция, которая вообще не имеет определения. Для того чтобы описать такую функцию, нужно просто присвоить ей значение 0. Определять ее будем в классах наследниках.
    2. Абстрактный класс должен содержать виртуальный деструктор, который позволит корректно удаляться объектам, созданным при наследовании.

2. Описать класс наследник Circle.

    1. При описании класса Circle нужно указать, что он является наследником класса Figure. Для этого после имени класса через символ «:» указать public Figure.
    2. Круг определяется своим радиусом, поэтому в классе определено поле radius.
    3. Описать конструктор, зависящий от радиуса.
    4. Описать метод вычисления площади. Площадь круга зависит от числа p, которое задано как константа вне класса.

3. Описать класс наследник Triangle.

    1. При описании класса Triangle нужно указать, что он является наследником класса Figure. Для этого после имени класса через символ «:» указать public Figure.
    2. Треугольник определяется длинами сторон, поэтому в классе определены поля a, b, c.
    3. Описать конструктор, зависящий от длин сторон.
    4. Описать метод вычисления площади. Площадь треугольника вычисляется по формуле Герона.

Пример 2.7. Задана дата рождения человека и текущая дата. Определить количество полных лет, прошедших со дня рождения. Для решения задачи использовать класс Data. Разделить объявление и определение класса, создав файлы Date.h и Date.cpp.

Этапы выполнения задания

1. Класс Date был описан в примере 22.5 учебного пособия 10 класса (http://profil.adu.by/mod/book/view.php?id=3574&chapterid=10521#pr5). В класс необходимо добавить функции-геттеры для получения дня, месяца и года.

2. Добавить файлы.

    1. Создать новый файл можно с помощью команды File → New  Empty file.
    2. Сохранить заголовочный файл под именем Date.h (h от англ. header — заголовок).
    3. Добавить еще один файл и сохранить его под именем Date.cpp.
    4. После добавления файлов изменится структура проекта:

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

3. Заголовочный файл содержит объявление класса (реализацию) и описание библиотек (если они необходимы).

4. Файл Date.cpp содержит определение методов класса. Чтобы связать определение класса и его реализацию, необходимо в файле реализации подключить заголовочный файл с объявлением класса: #include "Date.h". Обратите внимание, что при подключении файла его имя берется в кавычки, а не в угловые скобки.

5. В файле main.cpp также необходимо подключить заголовочный файл.

 

Пример 2.6. Программа.

#include <iostream>

#include <cmath>

 

using namespace std;

 

double const pi = acos(-1);

 

/// базовый класс для фигур

class Figure

{

  public:

    virtual ~Figure() {}

    /// метод для вычисления площади

    virtual double square() const = 0;

};

 

/// класс для круга

class Circle : public Figure

{

  private:

    double radius;

  public:

    /// конструктор от радиуса

  Circle(double r) : radius(r) {}

    /// реализация метода square

  double square() const {

    return radius * radius * pi;

  }

};

 

/// класс для треугольника

class Triangle : public Figure

{

  private:

    double a, b, c;

  public:

    /// конструктор от длин сторон

    Triangle(double l1, double l2, double l3) :

        a(l1), b(l2), c(l3)  {}

    /// реализация метода square

    double square() const {

      double p = (+ b + c) / 2;

      double s = p * (- a) * 

            (- b) * (- c);

      return sqrt(s);

    }

 

    }

};

 

int main()

{

  double r;

  cout << "radius = ";

  cin >> r;

  Circle circle(r);

  cout << "S = " << circle.square() 

       << endl;

  double x, y, z;

  cout << "storony" << endl;

  cin >> x >> y >> z;

  Triangle ABC(x, y, z);

  cout << "S = " << ABC.square() << 

       endl;

    return 0;

}

Тестирование:

Абстрактный класс, который содержит только виртуальные методы, называется интерфейсом. Тогда абстрагирование — это необходимость сконцентрироваться на интерфейсе, не обращая внимания на реализацию.

Интерфейсы позволяют наладить множественное наследование объектов и решить проблему ромбовидного наследования (ситуация, когда два класса B и C наследуют от A, а класс D наследует от обоих классов B и C.

Имя интерфейса строится по тем же правилам, что и другие идентификаторы языка программирования. Однако для интерфейсов могут применяться дополнительные правила, которые помогают отличать имя интерфейса от имен других элементов программы. Например, в технологии COM и во всех поддерживающих её языках действует соглашение, следуя которому, имя интерфейса строится по шаблону «I<Имя>», то есть состоит из написанного с заглавной буквы осмысленного имени, которому предшествует заглавная латинская буква I, например IFigure.

Пример 2.7. Содержимое файла main.cpp.

#include <iostream>

#include <string>

#include <windows.h>

#include "Date.h"

 

using namespace std;

 

int main()

{

  SetConsoleCP(1251);

  SetConsoleOutputCP(1251);

  string s1, s2;

  cout << "Дата рождения - ";

  cin >> s1;

  cout << "Текущая дата - ";

  cin >> s2;

  Date d1(s1);

  Date d2(s2);

  int v = d2.get_Date_g() - d1.get_Date_g();

  if (d2.get_Date_m() < d1.get_Date_m())

    v --;

  else

    if (d2.get_Date_m() == d1.get_Date_m() &&

      d2.get_Date_d() < d1.get_Date_d())

        v--;

  cout << "возраст = " << v << endl;

  return 0

}

Содержимое файла Date.h.

#include <iostream>

#include <string>

#include "Date.h"

 

using namespace std;

 

Date::Date(int t_d, int t_m, int t_g)

{

    d = t_d;

    m = t_m;

    g = t_g;

}

 

Date::Date(string s)

{

   d = stoi(s.substr(0,2));

   m = stoi(s.substr(3,2));

   g = stoi(s.substr(6,4));

}

 

void Date::print()

{

cout << d << "." << m << "." << g;

}

 

string Date::get_Date_dm()

{

  string t1 = to_string(d);

  string t2 = to_string(m);

  return t1 + "." + t2;

}

 

int Date::get_Date_d()

{

    return d;

}

 

int Date::get_Date_m()

{

    return m;

}

 

int Date::get_Date_g()

{

    return g;

}

Вопросы к параграфу

1. Какие разделы содержит описание класса?

2. К какому разделу относятся поля и методы, если название раздела явно не прописано?

3. В чем преимущество раздельного объявления и определения методов класса?

4. Для чего используются set- и get-функции?

5. Для чего нужен конструктор?

6. Сколько конструкторов может быть описано в классе?

Упражнения

 

1. Разделите объявление и определение класса Student (примеры 2.1 и 2.3), создав файлы Student.h и Student.cpp.

2. Добавьте для базового класса, описанного в примере 2.6, в качестве наследников трапецию и параллелограмм. Опишите для них методы вычисления площадей.

3. Продумайте иерархию наследников для четырехугольников (количество наследников не менее 2). Реализуйте базовый абстрактный класс и дочерние классы. В качестве методов реализуйте функции для вычисления площади, периметра и длины диагонали.

4. Создайте класс Parallelepiped (прямоугольный параллелепипед). Конструктор принимает длины ребер. Реализуйте функции вычисления площадей основания и боковых граней, объема, длин диагоналей параллелепипеда и диагоналей основания и боковых граней. Сделайте проверку на то, что параллелепипед является кубом.

5. В отделе кадров информация о работниках хранится в следующем виде: фамилия и инициалы, название занимаемой должности, дата поступления на работу. Выведите в алфавитном порядке тех работников, стаж работы которых больше введенного значения на текущую дату (дата вводится). Опишите класс Worker, в котором должен быть реализован метод, вычисляющий стаж работы.

6. Создайте класс Rect для прямоугольников со сторонами, параллельными осям координат. Реализуйте перемещение прямоугольника на плоскости (по горизонтали, по вертикали, в направлении вектора), изменение размеров.