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

§ 2. Класы

Сайт: Профильное обучение
Курс: Інфарматыка. 11 клас (Павышаны ўзровень)
Книга: § 2. Класы
Напечатано:: Гость
Дата: Понедельник, 29 Апрель 2024, 06:25

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

 

Нагадаем асноўныя паняцці, звязаныя з апісаннем класа. У прыкладзе 2.1 выкарыстоўваецца клас Student.

Імя класа прынята пісаць з вялікай літары. У агульным выглядзе апісанне класа выглядае наступным чынам:

class iмя_класа

{

  private:

  /* спіс уласцівасцей і метадаў для

  выкарыстання ўнутры класа */

  public:

  /* спіс метадаў, даступных іншым 

  функцыям і аб'ектам праграмы */

  protected:

  /*спіс сродкаў, даступных для 

  дружалюбных класаў і наследнікаў*/

};

Усе ўласцівасці і метады класаў маюць правы доступу. Па змоўчанні ўсё змесціва класа з'яўляецца даступным для чытання і запісу толькі для яго самога. Для таго каб дазволіць доступ да даных класа звонку, выкарыстоўваюць мадыфікатар доступу public. Усе функцыі і пераменныя, якія знаходзяцца пасля мадыфікатара public, становяцца даступнымі з усіх частак праграмы.

Закрытыя даныя класа размяшчаюцца пасля мадыфікатара доступу private. Калі адсутнічае мадыфікатар public, то ўсе функцыі і пераменныя па змоўчанні з'яўляюцца зачыненымі.

Звычайна прыватнымі робяць усе палі класа, а публічнымі — яго метады. Усе дзеянні з зачыненымі палямі класа рэалізуюцца праз яго метады.

Паколькі метад у класе з'яўляецца функцыяй, то і апісваюць метады гэтак жа, як апісваюць функцыі. Аб'яўляюць метады ўнутры класа, а вызначэнне метадаў можа быць як унутры класа, так і па-за ім. Вызначэнне метадаў па-за класа дазваляе пры неабходнасці лёгка вынесці іх у асобны файл.

Вызначэннi метадаў класа вельмі падобныя на звычайныя вызначэннi функцый. Кожны з метадаў мае загаловак і цела, можа мець тып значэння, якое вяртаецца, і параметры. Але ёсць і адрозненні:

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

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

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

На аб'екты класаў, як і на аб'екты іншых тыпаў, можна вызначаць паказальнікі. Затым праз паказальнік можна звяртацца да членаў класа  — да пераменных і метадаў (прыклад 2.2). Аднак калі пры звароце праз звычайную пераменную выкарыстоўваецца сімвал кропка, то для звароту да членаў класа праз паказальнік прымяняецца стрэлка «–>» (знакi «мiнус» і «больш»).


[1] Значок пазначае аперацыю «раскрыццё вобласці бачнасцi».

Прыклад 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-га класа, $@BOOKVIEWBYIDCH*3574*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).

У адрозненне ад звычайных метадаў канструктары маюць пэўныя правілы наймення:

  • канструктар павінен мець тое ж імя, што і клас;
  • канструктар не мае тыпу вяртання.

У класе можа быць некалькі канструктараў. Канструктар, які не мае параметраў, называецца канструктарам па змоўчанні. Ён выклікаецца, калі карыстальнікам не пазначаны значэннi для ініцыялізацыі. Канструктары таксама могуць быць абвешчаныя з параметрамі, якія выкарыстоўваюцца для ініцыялізацыі палёў. У класе можна вызначыць любую колькасць канструктараў, кожны з якіх павінен мець унікальныя параметры.

У канструктару можа быць выкарыстаны cпіс ініцыялізацыі палёў класа. Спіс ініцыялізацыі размяшчаецца пасля параметраў канструктара. Ён пачынаецца з двукроп'я (:), а затым значэнне для кожнага поля паказваецца ў круглых дужках. У целе канструктара не трэба выконваць аперацыі прысвойвання, таму цела канструктара можа быць пустым. Спіс ініцыялізацыі членаў класа не заканчваецца кропкай з коскай (прыклад 2.4).

Кожны метад класа няяўна ўтрымлівае ў якасці поля даных паказальнік:

IмяКласа *this;

З яго дапамогай метад класа вызначае, з данымі якога аб'екта яму трэба будзе працаваць. Паказальнік this можна выкарыстоўваць у канструктарах (прыклад 2.5).

Дэструктар класа — яшчэ адзін спецыяльны метад — выклікаецца пры знішчэнні аб'екта. Імя дэструктара аналагічна іменi канструктара, толькі ў пачатку ставіцца знак тыльды: ~. Дэструктар не мае ўваходных параметраў. Дэструктар заўсёды адзін. Для простых класаў (тых, якія толькі ініцыялізуюць значэннi звычайных уласцівасцей) дэструктар не патрэбны, так як 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 = "Мiнск";

    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 ("Мiнск"), 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}) {}

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

Прыклад 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) і трохвугольнiк (Triangle). У базавага класа трэба вызначыць агульны метад square (), які вяртае плошчу геаметрычнай фігуры, а ў наследнiкаў рэалізаваць гэты метад. Прадэманстраваць працу метадаў.

Этапы выканання задання

1. Апісаць базавы клас Figure.

    1. Базавы клас Figure з'яўляецца абстрактным класам, які будзе ўтрымліваць віртуальны метад square(). Віртуальны метад — гэта метад, які будзе перавызначаны ў класах-наследніках. Канкрэтная рэалізацыя метаду пры выкліку будзе вызначацца падчас выканання кода. У дадзеным прыкладзе выкарыстоўваецца чыстая віртуальная функцыя, якая наогул не мае вызначэння. Для таго каб апісаць такую функцыю, трэба проста прысвоіць ёй значэнне 0. Вызначаць яе будзем у класах наследніках.
    2. Абстрактны клас павінен змяшчаць віртуальны дэструктар, які дазволіць карэктна выдаляцца аб'ектам, створаным пры наследаваннi.

2. Апісаць клас наследнік Circle.

    1. Пры апісанні класа Circle трэба паказаць, што ён з'яўляецца наследнікам класа Figure. Для гэтага пасля іменi класа праз сімвал «:» пазначыць public Figure.
    2. Круг вызначаецца сваім радыусам, таму ў класе вызначана поле radius.
    3. Апісаць канструктар, які залежыць ад радыусу.
    4. Апісаць метад вылічэння плошчы. Плошча круга залежыць ад колькасці p, якая зададзена як канстанта па-за класа.

3. Апісаць клас наследнік Triangle.

    1. Пры апісанні класа Triangle трэба паказаць, што ён з'яўляецца наследнікам класа Figure. Для гэтага пасля іменi класа праз сімвал «:» пазначыць public Figure.
    2. Трохвугольнік вызначаецца даўжынямі бакоў, таму ў класе вызначаны палi a, b, c.
    3. Апісаць канструктар, які залежыць ад даўжынi бакоў.
    4. Апісаць метад вылічэння плошчы. Плошча трохвугольніка вылічаецца па формуле Герона.

Прыклад 2.7. Зададзена дата нараджэння чалавека і бягучая дата. Вызначыць колькасць поўных гадоў, якія прайшлі з дня нараджэння. Для вырашэння задачы выкарыстоўваць клас Data. Падзяліць аб'яву і вызначэнне класа, стварыўшы файлы Date.h і Date.cpp.

Этапы выканання задання

1. Клас Date быў апісаны ў прыкладзе 22.5 навучальнага дапаможніка 10-га класа ($@BOOKVIEWBYIDCH*3574*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:

    /// канструктар ад даўжынi бакоў

    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).

Імя інтэрфейса будуецца па тых жа правілах, што і іншыя ідэнтыфікатары мовы праграмiравання. Аднак для інтэрфейсаў могуць прымяняцца дадатковыя правілы, якія дапамагаюць адрозніваць імя інтэрфейса ад імёнаў іншых элементаў праграмы. Напрыклад, у тэхналогіі COM і ва ўсіх мовах, якія падтрымліваюць яе, дзейнічае пагадненне, вынікаючы якому, імя інтэрфейса будуецца па шаблоне «I<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;

}

Пытаннi да параграфа

1. Якія раздзелы змяшчае апісанне класа?

2. Да якога раздзелу адносяцца палі і метады, калі назва раздзела яўна не прапісана?

3. У чым перавага паасобнай аб'явы і вызначэння метадаў класа?

4. Для чаго выкарыстоўваюцца set- i get-функцыі?

5. Для чаго патрэбен канструктар?

6. Колькі канструктараў можа быць апісана ў класе?

Практыкаваннi

 

1. Падзяліце аб'яву і вызначэнне класа Student (прыклады 2.1 і 2.3), стварыўшы файлы Student.h і Student.cpp.

2. Дадайце для базавага класа, апісанага ў прыкладзе 2.6, у якасці наследнікаў трапецыю і паралелаграм. Апішыце для іх метады вылічэння плошчаў.

3. Прадумайце іерархію наследнікаў для чатырохвугольнікаў (колькасць наследнікаў не менш 2). Рэалізуйце базавы абстрактны клас і даччыныя класы. У якасці метадаў рэалізуйце функцыі для вылічэння плошчы, перыметра і даўжыні дыяганаліі.

4. Стварыце клас Parallelepiped (прамавугольны паралелепіпед). Канструктар прымае даўжыні рэбраў. Рэалізуйце функцыі вылічэння плошчаў заснавання і бакавых граняў, аб'ёму, даўжынi дыяганалей паралелепіпеда і дыяганалей заснавання і бакавых граняў. Зрабіце праверку на тое, што паралелепіпед з'яўляецца кубам.

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

6. Стварыце клас Rect для прамавугольнікаў з бакамі, паралельнымі восям каардынатаў. Рэалізуйце перамяшчэнне прамавугольніка на плоскасці (па гарызанталі, па вертыкалі, у кірунку вектара), змяненне памераў.