11-bo‘lim

Sinflar va konstruktorlar

Invariantni himoya qiluvchi tur qurish - konstruktor, destruktor, a'zolarni ishga tushirish ro'yxati va kirish darajalari.

🕑 14 daqiqa o‘qish 📄 910 so‘z 👁 2 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Struktura va sinf
  2. A'zolarni ishga tushirish ro'yxati
  3. Konstruktorlar to'plami
  4. Yo'q qilish tartibi
  5. = default va = delete
  6. const a'zo funksiyalar
  7. To'liq misol: guruh jurnali
  8. Xulosa

Sinf - bu "ma'lumot to'plami" emas. Sinf - bu invariant o'rnatadigan tur: yaratilgan paytdan boshlab yo'q qilingunicha rost bo'lib qoladigan qoida.

Struktura va sinf #

C++
#include <print>
#include <string>
#include <vector>
#include <stdexcept>
#include <algorithm>
C++
// Struktura - invariant yo'q, barcha qiymat to'plamlari to'g'ri
struct Nuqta
{
    double x{};
    double y{};
};

// Sinf - invariant bor: radius har doim musbat
class Doira
{
    double radius_;          // private (standart)

public:
    explicit Doira(double r) : radius_(r)
    {
        if (r <= 0) {
            throw std::invalid_argument{"radius musbat bo'lishi kerak"};
        }
    }

    double radius() const { return radius_; }
    double yuza() const { return 3.14159265358979 * radius_ * radius_; }
};

int main()
{
    Nuqta n{3.0, 4.0};
    std::println("nuqta: ({}, {})", n.x, n.y);

    Doira d{2.5};
    std::println("doira radiusi: {}, yuzasi: {:.4f}", d.radius(), d.yuza());

    try {
        Doira yomon{-1.0};
        std::println("bu yerga yetib kelmaydi");
    } catch (const std::invalid_argument& x) {
        std::println("xato tutildi: {}", x.what());
    }

    return 0;
}
Natija
nuqta: (3, 4)
doira radiusi: 2.5, yuzasi: 19.6350
xato tutildi: radius musbat bo'lishi kerak
struct va class orasidagi yagona farq

Texnik jihatdan bitta farq bor:

Kalit so'zStandart kirishStandart vorislik
structpublicpublic
classprivateprivate

Boshqa hech qanday farq yo'q. struct ham konstruktor, destruktor, virtual funksiya va vorislikka ega bo'lishi mumkin.

Amaliy an'ana:

IshlatingQachon
structBarcha a'zolar ochiq, invariant yo'q
classInvariant bor, a'zolar yashirin

Nuqta uchun struct to'g'ri: har qanday x va y juftligi mazmunli. Doira uchun class to'g'ri: manfiy radius mazmunsiz va sinf buni kafolatlaydi.

A'zolarni ishga tushirish ro'yxati #

C++
struct Kuzatuvchi
{
    std::string nom;

    explicit Kuzatuvchi(std::string n) : nom(std::move(n))
    { std::println("    konstruktor: {}", nom); }

    Kuzatuvchi(const Kuzatuvchi& b) : nom(b.nom)
    { std::println("    NUSXA: {}", nom); }

    Kuzatuvchi& operator=(const Kuzatuvchi& b)
    {
        nom = b.nom;
        std::println("    TAYINLASH: {}", nom);
        return *this;
    }
};

class Yomon
{
    Kuzatuvchi a_;
public:
    explicit Yomon(const Kuzatuvchi& k) : a_{"bo'sh"}
    {
        a_ = k;               // tanada TAYINLASH
    }
};

class Yaxshi
{
    Kuzatuvchi a_;
public:
    explicit Yaxshi(const Kuzatuvchi& k) : a_(k) {}   // ro'yxatda
};

int main()
{
    Kuzatuvchi asl{"asl"};

    std::println("");
    std::println("Yomon (tanada tayinlash):");
    Yomon y{asl};

    std::println("");
    std::println("Yaxshi (ishga tushirish ro'yxati):");
    Yaxshi ya{asl};

    return 0;
}
Natija
    konstruktor: asl

Yomon (tanada tayinlash):
    konstruktor: bo'sh
    TAYINLASH: asl

Yaxshi (ishga tushirish ro'yxati):
    NUSXA: asl
A'zolarni ro'yxatda ishga tushiring

Yomon ikki amal bajardi: avval "bo'sh" satrini yaratdi, keyin uning ustiga asl ni tayinladi. Birinchi qurish behuda ketdi. Yaxshi esa bitta amal bajardi - darhol to'g'ri qiymatdan nusxa oldi.

Sabab: konstruktor tanasi boshlanguncha barcha a'zolar allaqachon yaratilgan bo'ladi. Tanadagi = yaratish emas - tayinlash.

Bundan tashqari ba'zi a'zolarni tanada umuman ishga tushirib bo'lmaydi:

A'zo turiTanada
const a'zoMumkin emas
Havola a'zo (T&)Mumkin emas
Standart konstruktorsiz turMumkin emas
Bazaviy sinfMumkin emas

Shuning uchun ro'yxat - istisno emas, qoida.

Ro'yxat tartibi e'lon tartibiga bo'ysunadi
KOD
class Xato {
    int b_;
    int a_;
public:
    Xato(int x) : a_(x), b_(a_ * 2) {}   // b_ AVVAL ishga tushadi
};

b_ a_ dan oldin e'lon qilingan, shuning uchun birinchi b_ ishga tushadi - a_ hali qiymatga ega emas. Natija - aniqlanmagan xatti-harakat.

Ro'yxatdagi yozuv tartibi hech narsani o'zgartirmaydi. -Wall bu haqda ogohlantiradi (-Wreorder).

Qoida: ro'yxatni e'lon tartibida yozing va bir a'zoni ikkinchisidan hisoblashdan qoching.

Konstruktorlar to'plami #

C++
class Vaqt
{
    int soat_{};
    int daqiqa_{};

public:
    Vaqt() = default;                          // 00:00

    explicit Vaqt(int soat) : soat_(soat)      // faqat soat
    { tekshir(); }

    Vaqt(int soat, int daqiqa) : soat_(soat), daqiqa_(daqiqa)
    { tekshir(); }

    std::string matn() const
    {
        return std::format("{:02}:{:02}", soat_, daqiqa_);
    }

    int daqiqalarSoni() const { return soat_ * 60 + daqiqa_; }

private:
    void tekshir() const
    {
        if (soat_ < 0 || soat_ > 23 || daqiqa_ < 0 || daqiqa_ > 59) {
            throw std::out_of_range{"noto'g'ri vaqt"};
        }
    }
};

int main()
{
    Vaqt bosh;
    Vaqt tush{12};
    Vaqt uchrashuv{14, 30};

    std::println("bosh:      {}", bosh.matn());
    std::println("tush:      {}", tush.matn());
    std::println("uchrashuv: {}", uchrashuv.matn());

    std::println("");
    std::println("uchrashuvgacha: {} daqiqa",
                 uchrashuv.daqiqalarSoni() - tush.daqiqalarSoni());

    try {
        Vaqt xato{25, 0};
    } catch (const std::out_of_range& x) {
        std::println("xato: {}", x.what());
    }

    return 0;
}
Natija
bosh:      00:00
tush:      12:00
uchrashuv: 14:30

uchrashuvgacha: 150 daqiqa
xato: noto'g'ri vaqt
Bir argumentli konstruktorni explicit qiling

explicit bo'lmasa, kompilyator jimgina aylantirish qiladi:

KOD
void kutish(Vaqt v);
kutish(5);          // explicit bo'lmasa - kompilyatsiya qilinadi!

5 nima? Besh soatmi? Besh daqiqami? Kod aytmaydi.

explicit bilan bu xato bo'ladi va yozish kerak:

KOD
kutish(Vaqt{5});    // niyat ko'rinadi

Qoida: bir argument oladigan har bir konstruktorni explicit qiling - aylantirishni ataylab xohlamasangiz.

C++20 dan boshlab explicit(shart) ham bor - shartli oshkoralik uchun.

Obyektning hayot yo'li 1. Xotira ajratiladi Stekda yoki uyumda - obyekt hajmicha joy. Hali hech narsa ishga tushmagan. a'zolar: axlat qiymatlar 2. Ishga tushirish ro'yxati Bazaviy sinflar, keyin a'zolar - E'LON TARTIBIDA, ro'yxat tartibida emas. Doira(double r) : radius_(r), nom_("d") const va havola a'zolar FAQAT shu yerda ishga tushadi. 3. Konstruktor tanasi A'zolar allaqachon tayyor. Bu yerda faqat tekshiruv va qo'shimcha ish. Istisno tashlansa - allaqachon yaratilgan a'zolar teskari tartibda yo'q qilinadi. 4. Obyekt YASHAYDI Invariant rost. A'zo funksiyalar ishlaydi. Bu yerda obyekt har doim to'g'ri holatda - chunki konstruktor buni kafolatladi. 5. Destruktor Tana ishlaydi, keyin a'zolar teskari tartibda yo'q qilinadi, keyin bazaviy sinflar. Destruktordan istisno tashlamang.
Konstruktor invariantni o'rnatadi, destruktor resursni qaytaradi

Yo'q qilish tartibi #

C++
struct Belgi
{
    std::string nom;
    explicit Belgi(std::string n) : nom(std::move(n))
    { std::println("  + {}", nom); }
    ~Belgi() { std::println("  - {}", nom); }
};

class Konteyner
{
    Belgi birinchi_{"a'zo-1"};
    Belgi ikkinchi_{"a'zo-2"};
    Belgi uchinchi_{"a'zo-3"};

public:
    Konteyner() { std::println("  [konstruktor tanasi]"); }
    ~Konteyner() { std::println("  [destruktor tanasi]"); }
};

int main()
{
    std::println("yaratish:");
    {
        Konteyner k;
        std::println("obyekt ishlamoqda");
        std::println("");
        std::println("yo'q qilish:");
    }
    std::println("tugadi");

    return 0;
}
Natija
yaratish:
  + a'zo-1
  + a'zo-2
  + a'zo-3
  [konstruktor tanasi]
obyekt ishlamoqda

yo'q qilish:
  [destruktor tanasi]
  - a'zo-3
  - a'zo-2
  - a'zo-1
tugadi
Tartib har doim aynan teskari

A'zolar e'lon tartibida yaratiladi va aynan teskari tartibda yo'q qilinadi. Bu tasodif emas - kafolat.

Sabab: keyingi a'zo oldingisiga bog'liq bo'lishi mumkin. Masalan:

KOD
class Ulanish {
    Rozetka rozetka_;      // avval ochiladi
    Oqim oqim_{rozetka_};  // rozetkaga bog'liq
};

Yo'q qilishda oqim_ avval yopiladi, keyin rozetka_ - to'g'ri tartib.

Destruktor tanasi esa a'zolardan oldin ishlaydi - ya'ni tanada a'zolar hali tirik va ishlatsa bo'ladi.

= default va = delete #

C++
class Sozlama
{
    std::string kalit_;
    int qiymat_{};

public:
    Sozlama() = default;                    // kompilyator yozsin

    Sozlama(std::string k, int q)
        : kalit_(std::move(k)), qiymat_(q) {}

    const std::string& kalit() const { return kalit_; }
    int qiymat() const { return qiymat_; }
};

// Nusxalanmaydigan tur
class Ulanish
{
    int tutqich_{};

public:
    explicit Ulanish(int t) : tutqich_(t) {}

    Ulanish(const Ulanish&) = delete;             // nusxa YO'Q
    Ulanish& operator=(const Ulanish&) = delete;

    Ulanish(Ulanish&& b) noexcept : tutqich_(b.tutqich_)
    { b.tutqich_ = 0; }                           // ko'chirish MUMKIN

    int tutqich() const { return tutqich_; }
};

int main()
{
    Sozlama bosh;
    Sozlama til{"til", 1};

    std::println("bosh:  '{}' = {}", bosh.kalit(), bosh.qiymat());
    std::println("til:   '{}' = {}", til.kalit(), til.qiymat());

    std::println("");
    Ulanish u{42};
    std::println("ulanish tutqichi: {}", u.tutqich());

    Ulanish v{std::move(u)};
    std::println("ko'chirildi:      {}", v.tutqich());
    std::println("manba bo'shadi:   {}", u.tutqich());

    // Ulanish w = v;   ← kompilyatsiya xatosi (delete)

    return 0;
}
Natija
bosh:  '' = 0
til:   'til' = 1

ulanish tutqichi: 42
ko'chirildi:      42
manba bo'shadi:   0
= delete - niyatni bildirish

= delete funksiyani o'chirmaydi - uni "mavjud, lekin ishlatib bo'lmaydi" qiladi. Farqi katta:

UsulXato qachon
private: ga yashirishBog'lash paytida, tushunarsiz
= deleteKompilyatsiya paytida, aniq xabar

= delete ni boshqa maqsadda ham ishlatish mumkin:

KOD
void chop(int);
void chop(double) = delete;     // double bilan chaqirmang

= default esa "kompilyator standart versiyani yozsin" degani, lekin uni ko'rinadigan qiladi. = default yozilgan tur hali ham trivial bo'lib qolishi mumkin - qo'lda yozilgan bo'sh tana esa buni buzadi.

const a'zo funksiyalar #

C++
class Hisob
{
    std::string egasi_;
    long long balans_{};

public:
    Hisob(std::string egasi, long long boshlangich)
        : egasi_(std::move(egasi)), balans_(boshlangich) {}

    // const - obyektni o'zgartirmaydi
    const std::string& egasi() const { return egasi_; }
    long long balans() const { return balans_; }

    bool yetarlimi(long long summa) const { return balans_ >= summa; }

    // const emas - o'zgartiradi
    void kirim(long long summa) { balans_ += summa; }

    bool chiqim(long long summa)
    {
        if (!yetarlimi(summa)) return false;
        balans_ -= summa;
        return true;
    }
};

void hisobot(const Hisob& h)          // const havola
{
    std::println("  {} : {} so'm", h.egasi(), h.balans());
    // h.kirim(100);   ← kompilyatsiya xatosi
}

int main()
{
    Hisob h{"Husanboy Qodirov", 500'000};
    hisobot(h);

    h.kirim(250'000);
    std::println("kirimdan keyin:");
    hisobot(h);

    std::println("");
    std::println("1'000'000 yechish: {}", h.chiqim(1'000'000));
    std::println("300'000 yechish:   {}", h.chiqim(300'000));
    hisobot(h);

    return 0;
}
Natija
  Husanboy Qodirov : 500000 so'm
kirimdan keyin:
  Husanboy Qodirov : 750000 so'm

1'000'000 yechish: false
300'000 yechish:   true
  Husanboy Qodirov : 450000 so'm
const bo'lmagan a'zo funksiya - const& ni yopadi

hisobot funksiyasi const Hisob& oladi. Agar balans() const bo'lmaganida, uni chaqirib bo'lmasdi.

Shuning uchun qoida: obyektni o'zgartirmaydigan har bir a'zo funksiyani const qiling. Buni keyinroq qo'shish qiyin - butun zanjir bo'ylab tarqaladi.

Amaliy tartib:

Funksiyaconst
Qiymat qaytaradiHa
TekshiradiHa
Chop etadiHa
A'zoni o'zgartiradiYo'q
Nostatik havola qaytaradiYo'q (yoki ikki versiya)

mutable kalit so'zi ham bor - u a'zoni const funksiyada ham o'zgartirishga ruxsat beradi. Faqat kesh va mutekslar uchun ishlating.

To'liq misol: guruh jurnali #

C++
class Talaba
{
    std::string ism_;
    std::vector<int> baholar_;

public:
    explicit Talaba(std::string ism) : ism_(std::move(ism))
    {
        if (ism_.empty()) throw std::invalid_argument{"ism bo'sh"};
    }

    const std::string& ism() const { return ism_; }

    void bahoQoy(int b)
    {
        if (b < 1 || b > 5) throw std::out_of_range{"baho 1..5"};
        baholar_.push_back(b);
    }

    std::size_t soni() const { return baholar_.size(); }

    double ortacha() const
    {
        if (baholar_.empty()) return 0.0;
        int jami = 0;
        for (int b : baholar_) jami += b;
        return static_cast<double>(jami) / static_cast<double>(baholar_.size());
    }
};

class Guruh
{
    std::string nom_;
    std::vector<Talaba> talabalar_;

public:
    explicit Guruh(std::string nom) : nom_(std::move(nom)) {}

    void qosh(Talaba t) { talabalar_.push_back(std::move(t)); }

    const std::string& nom() const { return nom_; }
    std::size_t soni() const { return talabalar_.size(); }

    double ortacha() const
    {
        if (talabalar_.empty()) return 0.0;
        double jami = 0;
        for (const auto& t : talabalar_) jami += t.ortacha();
        return jami / static_cast<double>(talabalar_.size());
    }

    const Talaba& eng() const
    {
        return *std::ranges::max_element(talabalar_,
            [](const Talaba& a, const Talaba& b) {
                return a.ortacha() < b.ortacha();
            });
    }

    void hisobot() const
    {
        std::println("{} guruhi - {} talaba", nom_, talabalar_.size());
        for (const auto& t : talabalar_) {
            std::println("  {:<10} {} baho, o'rtacha {:.2f}",
                         t.ism(), t.soni(), t.ortacha());
        }
    }
};

int main()
{
    Guruh g{"KI-21"};

    Talaba malika{"Malika"};
    malika.bahoQoy(5); malika.bahoQoy(4); malika.bahoQoy(5);

    Talaba nodira{"Nodira"};
    nodira.bahoQoy(4); nodira.bahoQoy(4); nodira.bahoQoy(3);

    Talaba husanboy{"Husanboy"};
    husanboy.bahoQoy(5); husanboy.bahoQoy(5); husanboy.bahoQoy(5);

    g.qosh(std::move(malika));
    g.qosh(std::move(nodira));
    g.qosh(std::move(husanboy));

    g.hisobot();

    std::println("");
    std::println("guruh o'rtachasi: {:.3f}", g.ortacha());
    std::println("eng yaxshi:       {} ({:.2f})",
                 g.eng().ism(), g.eng().ortacha());

    std::println("");
    try {
        Talaba bosh{""};
    } catch (const std::invalid_argument& x) {
        std::println("nomsiz talaba: {}", x.what());
    }

    return 0;
}
Natija
KI-21 guruhi - 3 talaba
  Malika     3 baho, o'rtacha 4.67
  Nodira     3 baho, o'rtacha 3.67
  Husanboy   3 baho, o'rtacha 5.00

guruh o'rtachasi: 4.444
eng yaxshi:       Husanboy (5.00)

nomsiz talaba: ism bo'sh
Amaliy topshiriq
  1. struct va class yozing - standart kirish darajasini tekshiring.
  2. Konstruktorda invariant tekshirib istisno tashlang.
  3. A'zoni tanada tayinlang va ro'yxatda ishga tushiring - chiqishni solishtiring.
  4. Uch a'zoli sinf yozing va yo'q qilish tartibini kuzating.
  5. Bir argumentli konstruktorga explicit qo'shing va olib tashlang.
  6. = default bilan standart konstruktor e'lon qiling.
  7. Nusxa konstruktorini = delete qiling, nusxalashga urinib ko'ring.
  8. Faqat ko'chiriladigan tur yozing.
  9. O'zgartirmaydigan barcha a'zo funksiyalarni const qiling.
  10. const T& oluvchi funksiya yozing va const bo'lmagan a'zo chaqiring.

Xulosa #

  • Sinf - ma'lumot to'plami emas, invariant o'rnatadigan tur.
  • struct va class orasidagi yagona farq - standart kirish darajasi.
  • A'zolarni ishga tushirish ro'yxatida ishga tushiring, tanada emas.
  • Ro'yxat e'lon tartibiga bo'ysunadi - yozuv tartibiga emas.
  • const va havola a'zolarni faqat ro'yxatda ishga tushirish mumkin.
  • A'zolar e'lon tartibida yaratiladi va teskari tartibda yo'q qilinadi.
  • Bir argumentli konstruktorni explicit qiling - jimgina aylantirishdan saqlaydi.
  • = default kompilyator versiyasini ko'rinadigan qiladi.
  • = delete aniq kompilyatsiya xatosi beradi - private ga yashirishdan yaxshiroq.
  • O'zgartirmaydigan a'zo funksiyalarni const qiling - keyinroq qo'shish qiyin.

Keyingi bo'limda nusxalash va ko'chirish semantikasini o'rganamiz.

Xatolik topdingizmi?

Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.