3-bo‘lim

Turlar va o'zgaruvchilar

Qavsli initsializatsiya, auto, constexpr va enum class - kichik tanlovlar katta farq qiladi.

🕑 17 daqiqa o‘qish 📄 1 011 so‘z 👁 4 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Asosiy turlar
  2. Initsializatsiyaning uch usuli
  3. auto
  4. const va constexpr
  5. enum class
  6. nullptr
  7. using va tur taxalluslari
  8. Kasr sonlarni solishtirish
  9. Xulosa

C++ da o'zgaruvchi e'lon qilishning bir necha usuli bor va ular bir xil emas.

Bu bo'limda qaysi birini tanlash kerakligini ko'ramiz.

Asosiy turlar #

C++
#include <print>
#include <string>
#include <vector>
#include <cstdint>
#include <limits>
#include <typeinfo>
C++
int main()
{
    std::println("{:<12} {:>5} {}", "TUR", "BAYT", "DIAPAZON");
    std::println("{}", std::string(52, '-'));

    std::println("{:<12} {:>5} {} .. {}", "char", sizeof(char),
                 static_cast<int>(std::numeric_limits<char>::min()),
                 static_cast<int>(std::numeric_limits<char>::max()));
    std::println("{:<12} {:>5} {} .. {}", "short", sizeof(short),
                 std::numeric_limits<short>::min(),
                 std::numeric_limits<short>::max());
    std::println("{:<12} {:>5} {} .. {}", "int", sizeof(int),
                 std::numeric_limits<int>::min(),
                 std::numeric_limits<int>::max());
    std::println("{:<12} {:>5} {} .. {}", "long long", sizeof(long long),
                 std::numeric_limits<long long>::min(),
                 std::numeric_limits<long long>::max());
    std::println("{:<12} {:>5} ~{:.1e}", "float", sizeof(float),
                 std::numeric_limits<float>::max());
    std::println("{:<12} {:>5} ~{:.1e}", "double", sizeof(double),
                 std::numeric_limits<double>::max());
    std::println("{:<12} {:>5}", "bool", sizeof(bool));

    return 0;
}
Natija
TUR           BAYT DIAPAZON
----------------------------------------------------
char             1 -128 .. 127
short            2 -32768 .. 32767
int              4 -2147483648 .. 2147483647
long long        8 -9223372036854775808 .. 9223372036854775807
float            4 ~3.4e+38
double           8 ~1.8e+308
bool             1
int ning hajmi kafolatlanmagan

Standart faqat minimal hajmni kafolatlaydi:

TurKafolatlangan minimal
char1 bayt
short2 bayt
int2 bayt
long4 bayt
long long8 bayt

Amalda ish stoli va serverlarda int 4 bayt, lekin o'rnatilgan tizimlarda 2 bayt bo'lishi mumkin. long esa Windows da 4, Linux da 8 bayt.

Aniq hajm kerak bo'lsa <cstdint> dan foydalaning:

C++
int main()
{
    std::int8_t   a = 127;
    std::int16_t  b = 32767;
    std::int32_t  c = 2147483647;
    std::int64_t  d = 9223372036854775807;
    std::uint32_t e = 4294967295;

    std::println("int8_t   {} bayt, qiymat {}", sizeof(a), a);
    std::println("int16_t  {} bayt, qiymat {}", sizeof(b), b);
    std::println("int32_t  {} bayt, qiymat {}", sizeof(c), c);
    std::println("int64_t  {} bayt, qiymat {}", sizeof(d), d);
    std::println("uint32_t {} bayt, qiymat {}", sizeof(e), e);

    // Indekslar va o'lchamlar uchun
    std::vector<int> v{1, 2, 3};
    std::println("size_t   {} bayt, v.size() = {}", sizeof(std::size_t),
                 v.size());

    return 0;
}
Natija
int8_t   1 bayt, qiymat 127
int16_t  2 bayt, qiymat 32767
int32_t  4 bayt, qiymat 2147483647
int64_t  8 bayt, qiymat 9223372036854775807
uint32_t 4 bayt, qiymat 4294967295
size_t   8 bayt, v.size() = 3

Initsializatsiyaning uch usuli #

C++
int main()
{
    int a = 5;        // nusxa initsializatsiyasi (C dan meros)
    int b(5);         // to'g'ridan-to'g'ri
    int c{5};         // qavsli - C++11 dan, TAVSIYA ETILADI

    std::println("a={} b={} c={}", a, b, c);

    // Farq shu yerda ko'rinadi
    double kasr = 3.99;

    int eski = kasr;          // jimgina kesiladi
    std::println("int eski = 3.99;  natija: {}", eski);

    // int yangi{kasr};       // KOMPILYATSIYA XATOSI - torayish
    std::println("int yangi{{3.99}};  kompilyatsiya qilinmaydi");

    return 0;
}
Natija
a=5 b=5 c=5
int eski = 3.99;  natija: 3
int yangi{3.99};  kompilyatsiya qilinmaydi
Qavsli initsializatsiya torayishni taqiqlaydi

{} shakli ma'lumot yo'qoladigan aylantirishlarni kompilyatsiya paytida rad etadi.

Yozuv3.99 dan int ga
int a = 3.99;Jimgina 3 bo'ladi
int b(3.99);Jimgina 3 bo'ladi
int c{3.99};Kompilyatsiya xatosi

Bu kichik farq katta ahamiyatga ega: torayish jimgina bo'lganda xato yillar davomida sezilmasligi mumkin.

Shuning uchun amaliy qoida:

Imkoni bo'lsa doim {} ishlating.

Istisno: std::vector<int> v(10) va std::vector<int> v{10} turlicha ishlaydi - buni 7-bo'limda ko'ramiz.

auto #

C++
int main()
{
    auto son = 42;                    // int
    auto kasr = 3.14;                 // double
    auto belgi = 'A';                 // char
    auto rost = true;                 // bool
    auto matn = std::string{"salom"}; // std::string

    std::println("son:   {} ({} bayt)", son, sizeof(son));
    std::println("kasr:  {} ({} bayt)", kasr, sizeof(kasr));
    std::println("belgi: {} ({} bayt)", belgi, sizeof(belgi));
    std::println("rost:  {}", rost);
    std::println("matn:  {} ({} belgi)", matn, matn.size());

    // auto uzun turlarni qisqartiradi
    std::vector<std::string> ismlar{"Husanboy", "Malika"};

    for (auto it = ismlar.begin(); it != ismlar.end(); ++it) {
        std::println("iterator orqali: {}", *it);
    }

    return 0;
}
Natija
son:   42 (4 bayt)
kasr:  3.14 (8 bayt)
belgi: A (1 bayt)
rost:  true
matn:  salom (5 belgi)
iterator orqali: Husanboy
iterator orqali: Malika
auto ning tuzoqlari

auto foydali, lekin ba'zan kutilmagan tur beradi:

YozuvChiqadigan turKutilgan bo'lishi mumkin
auto x = "matn";const char*std::string
auto x = v[0];Elementning nusxasiHavola
auto x = 1.0f;floatdouble
auto x = obj.katta();Katta obyektning nusxasiHavola

Ikkinchi va to'rtinchi qatorlar tezlikka ta'sir qiladi: katta obyekt nusxalanadi.

Havola kerak bo'lsa aniq yozing:

KOD
auto& havola = v[0];          // o'zgartirish mumkin
const auto& faqatOqish = v[0];   // nusxa yo'q, o'zgartirib bo'lmaydi

Amaliy qoida: halqalarda const auto& ni standart deb oling, nusxa haqiqatan kerak bo'lgandagina auto yozing.

C++
int main()
{
    std::vector<std::string> ismlar{"Husanboy", "Malika", "Nodira"};

    // Nusxa - har elementda satr ko'chiriladi
    for (auto ism : ismlar) {
        ism += "!";               // asl nusxaga ta'sir qilmaydi
    }
    std::println("auto bilan:        {}", ismlar);

    // Havola - nusxa yo'q, o'zgartirish mumkin
    for (auto& ism : ismlar) {
        ism += "!";
    }
    std::println("auto& bilan:       {}", ismlar);

    // Faqat o'qish - nusxa ham yo'q, o'zgartirish ham
    for (const auto& ism : ismlar) {
        std::print("{} ", ism.size());
    }
    std::println("<- uzunliklar");

    return 0;
}
Natija
auto bilan:        ["Husanboy", "Malika", "Nodira"]
auto& bilan:       ["Husanboy!", "Malika!", "Nodira!"]
9 7 7 <- uzunliklar

const va constexpr #

C++
constexpr int kvadrat(int x)
{
    return x * x;
}

int main()
{
    const int oddiy = 10;              // ish vaqtida o'zgarmas
    constexpr int doimiy = 20;         // KOMPILYATSIYA paytida ma'lum

    // constexpr qiymat massiv o'lchami bo'la oladi
    int massiv[doimiy];
    massiv[0] = 1;

    // constexpr funksiya kompilyatsiya paytida hisoblanadi
    constexpr int natija = kvadrat(12);

    std::println("oddiy:   {}", oddiy);
    std::println("doimiy:  {}", doimiy);
    std::println("massiv:  {} element", std::size(massiv));
    std::println("kvadrat: {} (kompilyatsiya paytida hisoblangan)", natija);

    // O'sha funksiya ish vaqtida ham ishlaydi
    int kiritma = 7;
    std::println("ish vaqtida: {}", kvadrat(kiritma));

    return 0;
}
Natija
oddiy:   10
doimiy:  20
massiv:  20 element
kvadrat: 144 (kompilyatsiya paytida hisoblangan)
ish vaqtida: 49
const, constexpr va consteval farqi
Kalit so'zMa'nosiQachon hisoblanadi
constO'zgartirib bo'lmaydiIsh vaqtida ham mumkin
constexprKompilyatsiya paytida bo'lishi mumkinIkkalasi ham
constevalFaqat kompilyatsiya paytidaKompilyatsiya

constexpr funksiya - ikki xil ishlaydi: argument kompilyatsiya paytida ma'lum bo'lsa, natija ham o'shanda hisoblanadi; aks holda oddiy funksiya kabi.

Foydasi:

FoydaIzoh
Ish vaqtida hisob yo'qNatija dasturga yozib qo'yiladi
Massiv o'lchami bo'la oladiconst bunday qila olmaydi
Xatolar erta topiladiKompilyatsiya paytida

Zamonaviy C++ da constexpr juda keng qo'llanadi - hatto std::string va std::vector ham C++20 dan boshlab kompilyatsiya paytida ishlatilishi mumkin.

Uch xil initsializatsiya - bir xil emas int a = 3.99; C dan meros a = 3 (jimgina) Ma'lumot yo'qoldi kompilyator jim turdi int b(3.99); to'g'ridan-to'g'ri b = 3 (jimgina) Bir xil muammo + "most vexing parse" int c{3.99}; qavsli - C++11 KOMPILYATSIYA XATOSI Torayish taqiqlangan xato darhol topiladi "Most vexing parse" - qavsning yana bir tuzog'i Vector v(); // o'zgaruvchi EMAS - bu funksiya e'loni! Vector v{}; // to'g'ri: bo'sh obyekt Amaliy qoida Standart tanlov - {}. Konteynerlarda o'lcham berish kerak bo'lsa - () (7-bo'lim).
Qavsli initsializatsiya xatolarni kompilyatsiya paytiga ko'chiradi

enum class #

C++
// Eski uslub - nomlar tashqariga chiqadi
enum EskiRang { Qizil, Yashil, Kok };

// Zamonaviy - nomlar o'z sohasida qoladi
enum class Rang { Qizil, Yashil, Kok };
enum class Holat { Yangi, Qizil, Tugagan };   // Qizil takrorlandi - muammo yo'q

int main()
{
    EskiRang eski = Qizil;
    Rang yangi = Rang::Qizil;

    // Eski enum jimgina int ga aylanadi
    int son = eski;
    std::println("eski enum int ga aylandi: {}", son);

    // Yangisi aylanmaydi - aniq yozish kerak
    std::println("yangi enum: {}", static_cast<int>(yangi));

    // int songa = yangi;      // KOMPILYATSIYA XATOSI

    // Solishtirishda ham xavfsiz
    // if (yangi == Holat::Qizil)   // KOMPILYATSIYA XATOSI - turlar boshqa

    std::println("Rang::Kok      = {}", static_cast<int>(Rang::Kok));
    std::println("Holat::Tugagan = {}", static_cast<int>(Holat::Tugagan));

    return 0;
}
Natija
eski enum int ga aylandi: 0
yangi enum: 0
Rang::Kok      = 2
Holat::Tugagan = 2
Oddiy enum ikki muammo tug'diradi

1. Nomlar tashqariga chiqadi. enum EskiRang { Qizil, ... } dan keyin Qizil global nomga aylanadi. Boshqa enum da ham Qizil bo'lsa - to'qnashuv.

2. Jimgina int ga aylanadi. Bu mantiqsiz kodni kompilyatsiya qilishga ruxsat beradi:

KOD
if (rang == holat)         // ikki BOSHQA enum solishtirildi
int x = rang * 2;          // rangni ko'paytirish?

enum class ikkalasini ham to'xtatadi.

Qiymat kerak bo'lsa - static_cast bilan aniq so'raysiz. Bu qo'shimcha yozuv, lekin u niyatni ko'rsatadi.

Asosiy turini ham belgilash mumkin:

KOD
enum class Bayroq : std::uint8_t { Yoq = 0, Bor = 1 };

Bu hajmni kafolatlaydi - tarmoq protokollari va fayl formatlarida muhim.

nullptr #

C++
void ishla(int son)
{
    std::println("ishla(int) chaqirildi, son = {}", son);
}

void ishla(const char* matn)
{
    std::println("ishla(const char*) chaqirildi, matn = {}",
                 matn == nullptr ? "(null)" : matn);
}

int main()
{
    ishla(42);
    ishla("salom");

    // NULL yoki 0 - qaysi funksiya chaqiriladi?
    ishla(0);            // int versiyasi!

    // nullptr - aniq ko'rsatkich
    ishla(nullptr);      // const char* versiyasi

    return 0;
}
Natija
ishla(int) chaqirildi, son = 42
ishla(const char*) chaqirildi, matn = salom
ishla(int) chaqirildi, son = 0
ishla(const char*) chaqirildi, matn = (null)
NULL va 0 ni unuting

C da NULL odatda 0 deb ta'riflanadi. Demak ishla(NULL) butun son versiyasini chaqiradi - bu deyarli har doim xato.

nullptr alohida turga ega (std::nullptr_t) va u faqat ko'rsatkichga mos keladi.

YozuvTuriMuammo
0intKo'rsatkich emas
NULLOdatda 0Bir xil muammo
nullptrstd::nullptr_tYo'q

Zamonaviy kodda NULL umuman ishlatilmaydi.

using va tur taxalluslari #

C++
#include <map>

int main()
{
    // Eski usul
    typedef std::map<std::string, std::vector<int>> EskiTur;

    // Zamonaviy usul - o'qish osonroq
    using BaholarJadvali = std::map<std::string, std::vector<int>>;

    BaholarJadvali baholar{
        {"Husanboy", {85, 92, 78}},
        {"Malika", {95, 88, 91}},
    };

    for (const auto& [ism, royxat] : baholar) {   // strukturaviy bog'lash
        int yigindi = 0;
        for (int b : royxat) {
            yigindi += b;
        }
        std::println("{:<10} {} ta baho, o'rtacha {:.1f}",
                     ism, royxat.size(),
                     static_cast<double>(yigindi) / royxat.size());
    }

    return 0;
}
Natija
Husanboy   3 ta baho, o'rtacha 85.0
Malika     3 ta baho, o'rtacha 91.3
Strukturaviy bog'lash (C++17)

auto& [ism, royxat] = juftlik yozuvi juftlikni ikkita nomga ajratadi.

Eskisi bilan solishtiring:

KOD
for (const auto& juft : baholar) {
    const std::string& ism = juft.first;
    const std::vector<int>& royxat = juft.second;
    ...
}

Yangisi qisqaroq va first/second kabi ma'nosiz nomlarni yo'q qiladi.

U std::pair, std::tuple, massivlar va oddiy strukturalar bilan ishlaydi.

Kasr sonlarni solishtirish #

C++
#include <cmath>

int main()
{
    double a = 0.1 + 0.2;
    double b = 0.3;

    std::println("0.1 + 0.2 = {:.20f}", a);
    std::println("0.3       = {:.20f}", b);
    std::println("a == b ?    {}", a == b);
    std::println("farq:       {:.2e}", std::abs(a - b));

    // To'g'ri solishtirish - epsilon bilan
    constexpr double epsilon = 1e-9;
    std::println("|a-b| < eps ? {}", std::abs(a - b) < epsilon);

    return 0;
}
Natija
0.1 + 0.2 = 0.30000000000000004441
0.3       = 0.29999999999999998890
a == b ?    false
farq:       5.55e-17
|a-b| < eps ? true
== bilan kasr sonlarni solishtirmang

0.1 va 0.2 ikkilik sanoq sistemasida aniq ifodalanmaydi. Ularning yig'indisi 0.3 dan bir oz farq qiladi.

Bu C++ ning kamchiligi emas - bu IEEE 754 standarti va u deyarli barcha tillarda bir xil.

YondashuvQachon
a == bHech qachon (kasr sonlar uchun)
std::abs(a-b) < epsUmumiy holat
Nisbiy farqKatta sonlar uchun
Butun sonlarda saqlashPul - tiyinlarda saqlang

Oxirgi qator amaliy: moliyaviy hisob-kitobda double ishlatmang. Summani eng kichik birlikda butun son sifatida saqlang.

Amaliy topshiriq
  1. Barcha asosiy turlarning hajmini chiqaring.
  2. <cstdint> dagi aniq hajmli turlarni sinab ko'ring.
  3. int a = 3.99 va int c{3.99} ni yozib solishtiring.
  4. Ikkinchisi nima uchun kompilyatsiya qilinmasligini yozing.
  5. auto, auto& va const auto& bilan halqa yozing.
  6. Uchalasi qanday farq qilishini natijaga qarab tushuntiring.
  7. constexpr funksiya yozing va uni massiv o'lchamida ishlating.
  8. enum va enum class ni solishtiring.
  9. ishla(0) va ishla(nullptr) qaysi versiyani chaqirishini tekshiring.
  10. 0.1 + 0.2 == 0.3 natijasini ko'ring va to'g'ri solishtirishni yozing.

Xulosa #

  • int ning hajmi kafolatlanmagan - aniq hajm kerak bo'lsa <cstdint>.
  • Initsializatsiyaning uch usuli bor va {} eng xavfsizi.
  • Qavsli initsializatsiya torayishni kompilyatsiya paytida rad etadi.
  • auto uzun turlarni qisqartiradi, lekin nusxa yaratishi mumkin.
  • Halqalarda standart tanlov - const auto&.
  • constexpr funksiya kompilyatsiya paytida ham, ish vaqtida ham ishlaydi.
  • enum class nomlarni o'z sohasida saqlaydi va int ga aylanmaydi.
  • NULL va 0 o'rniga nullptr - u faqat ko'rsatkichga mos keladi.
  • typedef o'rniga using - o'qish osonroq.
  • Kasr sonlarni == bilan solishtirmang; pulni butun sonda saqlang.

Keyingi bo'limda C++ ning eng muhim tushunchalaridan biri - havolalar bilan tanishamiz.

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.