11-bo‘lim
Sinflar va konstruktorlar
Invariantni himoya qiluvchi tur qurish - konstruktor, destruktor, a'zolarni ishga tushirish ro'yxati va kirish darajalari.
Ushbu bo‘lim mundarijasi
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 #
#include <print>
#include <string>
#include <vector>
#include <stdexcept>
#include <algorithm>
// 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;
}
nuqta: (3, 4)
doira radiusi: 2.5, yuzasi: 19.6350
xato tutildi: radius musbat bo'lishi kerak
struct va class orasidagi yagona farqTexnik jihatdan bitta farq bor:
| Kalit so'z | Standart kirish | Standart vorislik |
|---|---|---|
struct | public | public |
class | private | private |
Boshqa hech qanday farq yo'q. struct ham konstruktor,
destruktor, virtual funksiya va vorislikka ega bo'lishi
mumkin.
Amaliy an'ana:
| Ishlating | Qachon |
|---|---|
struct | Barcha a'zolar ochiq, invariant yo'q |
class | Invariant 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 #
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;
}
konstruktor: asl
Yomon (tanada tayinlash):
konstruktor: bo'sh
TAYINLASH: asl
Yaxshi (ishga tushirish ro'yxati):
NUSXA: asl
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 turi | Tanada |
|---|---|
const a'zo | Mumkin emas |
Havola a'zo (T&) | Mumkin emas |
| Standart konstruktorsiz tur | Mumkin emas |
| Bazaviy sinf | Mumkin emas |
Shuning uchun ro'yxat - istisno emas, qoida.
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 #
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;
}
bosh: 00:00
tush: 12:00
uchrashuv: 14:30
uchrashuvgacha: 150 daqiqa
xato: noto'g'ri vaqt
explicit qilingexplicit bo'lmasa, kompilyator jimgina aylantirish
qiladi:
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:
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.
Yo'q qilish tartibi #
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;
}
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
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:
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 #
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;
}
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:
| Usul | Xato qachon |
|---|---|
private: ga yashirish | Bog'lash paytida, tushunarsiz |
= delete | Kompilyatsiya paytida, aniq xabar |
= delete ni boshqa maqsadda ham ishlatish mumkin:
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 #
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;
}
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 yopadihisobot 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:
| Funksiya | const |
|---|---|
| Qiymat qaytaradi | Ha |
| Tekshiradi | Ha |
| Chop etadi | Ha |
| A'zoni o'zgartiradi | Yo'q |
| Nostatik havola qaytaradi | Yo'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 #
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;
}
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
structvaclassyozing - standart kirish darajasini tekshiring.- Konstruktorda invariant tekshirib istisno tashlang.
- A'zoni tanada tayinlang va ro'yxatda ishga tushiring - chiqishni solishtiring.
- Uch a'zoli sinf yozing va yo'q qilish tartibini kuzating.
- Bir argumentli konstruktorga
explicitqo'shing va olib tashlang. = defaultbilan standart konstruktor e'lon qiling.- Nusxa konstruktorini
= deleteqiling, nusxalashga urinib ko'ring. - Faqat ko'chiriladigan tur yozing.
- O'zgartirmaydigan barcha a'zo funksiyalarni
constqiling. const T&oluvchi funksiya yozing vaconstbo'lmagan a'zo chaqiring.
Xulosa #
- Sinf - ma'lumot to'plami emas, invariant o'rnatadigan tur.
structvaclassorasidagi 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.
constva havola a'zolarni faqat ro'yxatda ishga tushirish mumkin.- A'zolar e'lon tartibida yaratiladi va teskari tartibda yo'q qilinadi.
- Bir argumentli konstruktorni
explicitqiling - jimgina aylantirishdan saqlaydi. = defaultkompilyator versiyasini ko'rinadigan qiladi.= deleteaniq kompilyatsiya xatosi beradi -privatega yashirishdan yaxshiroq.- O'zgartirmaydigan a'zo funksiyalarni
constqiling - keyinroq qo'shish qiyin.
Keyingi bo'limda nusxalash va ko'chirish semantikasini o'rganamiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
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.