3-bo‘lim
Turlar va o'zgaruvchilar
Qavsli initsializatsiya, auto, constexpr va enum class - kichik tanlovlar katta farq qiladi.
Ushbu bo‘lim mundarijasi
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 #
#include <print>
#include <string>
#include <vector>
#include <cstdint>
#include <limits>
#include <typeinfo>
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;
}
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 kafolatlanmaganStandart faqat minimal hajmni kafolatlaydi:
| Tur | Kafolatlangan minimal |
|---|---|
char | 1 bayt |
short | 2 bayt |
int | 2 bayt |
long | 4 bayt |
long long | 8 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:
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;
}
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 #
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;
}
a=5 b=5 c=5
int eski = 3.99; natija: 3
int yangi{3.99}; kompilyatsiya qilinmaydi
{} shakli ma'lumot yo'qoladigan aylantirishlarni
kompilyatsiya paytida rad etadi.
| Yozuv | 3.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 #
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;
}
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 tuzoqlariauto foydali, lekin ba'zan kutilmagan tur beradi:
| Yozuv | Chiqadigan tur | Kutilgan bo'lishi mumkin |
|---|---|---|
auto x = "matn"; | const char* | std::string |
auto x = v[0]; | Elementning nusxasi | Havola |
auto x = 1.0f; | float | double |
auto x = obj.katta(); | Katta obyektning nusxasi | Havola |
Ikkinchi va to'rtinchi qatorlar tezlikka ta'sir qiladi: katta obyekt nusxalanadi.
Havola kerak bo'lsa aniq yozing:
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.
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;
}
auto bilan: ["Husanboy", "Malika", "Nodira"]
auto& bilan: ["Husanboy!", "Malika!", "Nodira!"]
9 7 7 <- uzunliklar
const va constexpr #
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;
}
oddiy: 10
doimiy: 20
massiv: 20 element
kvadrat: 144 (kompilyatsiya paytida hisoblangan)
ish vaqtida: 49
const, constexpr va consteval farqi| Kalit so'z | Ma'nosi | Qachon hisoblanadi |
|---|---|---|
const | O'zgartirib bo'lmaydi | Ish vaqtida ham mumkin |
constexpr | Kompilyatsiya paytida bo'lishi mumkin | Ikkalasi ham |
consteval | Faqat kompilyatsiya paytida | Kompilyatsiya |
constexpr funksiya - ikki xil ishlaydi: argument
kompilyatsiya paytida ma'lum bo'lsa, natija ham o'shanda
hisoblanadi; aks holda oddiy funksiya kabi.
Foydasi:
| Foyda | Izoh |
|---|---|
| Ish vaqtida hisob yo'q | Natija dasturga yozib qo'yiladi |
| Massiv o'lchami bo'la oladi | const bunday qila olmaydi |
| Xatolar erta topiladi | Kompilyatsiya paytida |
Zamonaviy C++ da constexpr juda keng qo'llanadi - hatto
std::string va std::vector ham C++20 dan boshlab
kompilyatsiya paytida ishlatilishi mumkin.
enum class #
// 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;
}
eski enum int ga aylandi: 0
yangi enum: 0
Rang::Kok = 2
Holat::Tugagan = 2
enum ikki muammo tug'diradi1. 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:
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:
enum class Bayroq : std::uint8_t { Yoq = 0, Bor = 1 };
Bu hajmni kafolatlaydi - tarmoq protokollari va fayl formatlarida muhim.
nullptr #
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;
}
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 unutingC 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.
| Yozuv | Turi | Muammo |
|---|---|---|
0 | int | Ko'rsatkich emas |
NULL | Odatda 0 | Bir xil muammo |
nullptr | std::nullptr_t | Yo'q |
Zamonaviy kodda NULL umuman ishlatilmaydi.
using va tur taxalluslari #
#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;
}
Husanboy 3 ta baho, o'rtacha 85.0
Malika 3 ta baho, o'rtacha 91.3
auto& [ism, royxat] = juftlik yozuvi juftlikni ikkita
nomga ajratadi.
Eskisi bilan solishtiring:
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 #
#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;
}
0.1 + 0.2 = 0.30000000000000004441
0.3 = 0.29999999999999998890
a == b ? false
farq: 5.55e-17
|a-b| < eps ? true
== bilan kasr sonlarni solishtirmang0.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.
| Yondashuv | Qachon |
|---|---|
a == b | Hech qachon (kasr sonlar uchun) |
std::abs(a-b) < eps | Umumiy holat |
| Nisbiy farq | Katta sonlar uchun |
| Butun sonlarda saqlash | Pul - tiyinlarda saqlang |
Oxirgi qator amaliy: moliyaviy hisob-kitobda double
ishlatmang. Summani eng kichik birlikda butun son
sifatida saqlang.
- Barcha asosiy turlarning hajmini chiqaring.
<cstdint>dagi aniq hajmli turlarni sinab ko'ring.int a = 3.99vaint c{3.99}ni yozib solishtiring.- Ikkinchisi nima uchun kompilyatsiya qilinmasligini yozing.
auto,auto&vaconst auto&bilan halqa yozing.- Uchalasi qanday farq qilishini natijaga qarab tushuntiring.
constexprfunksiya yozing va uni massiv o'lchamida ishlating.enumvaenum classni solishtiring.ishla(0)vaishla(nullptr)qaysi versiyani chaqirishini tekshiring.0.1 + 0.2 == 0.3natijasini ko'ring va to'g'ri solishtirishni yozing.
Xulosa #
intning hajmi kafolatlanmagan - aniq hajm kerak bo'lsa<cstdint>.- Initsializatsiyaning uch usuli bor va
{}eng xavfsizi. - Qavsli initsializatsiya torayishni kompilyatsiya paytida rad etadi.
autouzun turlarni qisqartiradi, lekin nusxa yaratishi mumkin.- Halqalarda standart tanlov -
const auto&. constexprfunksiya kompilyatsiya paytida ham, ish vaqtida ham ishlaydi.enum classnomlarni o'z sohasida saqlaydi vaintga aylanmaydi.NULLva0o'rniganullptr- u faqat ko'rsatkichga mos keladi.typedefo'rnigausing- o'qish osonroq.- Kasr sonlarni
==bilan solishtirmang; pulni butun sonda saqlang.
Keyingi bo'limda C++ ning eng muhim tushunchalaridan biri - havolalar bilan tanishamiz.
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.