17-bo‘lim

Standart kutubxona

Vaqt, sana, fayl tizimi, tasodifiy sonlar va formatlash - tashqi kutubxonasiz.

🕑 14 daqiqa o‘qish 📄 983 so‘z 👁 2 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. std::chrono - vaqt oralig'i
  2. Sana va kalendar (C++20)
  3. Vaqtni o'lchash
  4. std::filesystem
  5. std::random
  6. Formatlash imkoniyatlari
  7. Xulosa

C++ standart kutubxonasi konteynerlar va algoritmlardan iborat emas. Unda vaqt hisobi, sana kalendari, fayl tizimi va yuqori sifatli tasodifiy sonlar generatorlari ham bor - hech qanday tashqi bog'liqliksiz.

std::chrono - vaqt oralig'i #

C++
#include <print>
#include <string>
#include <vector>
#include <chrono>
#include <filesystem>
#include <fstream>
#include <random>
#include <numeric>
#include <algorithm>
C++
using namespace std::chrono;

int main()
{
    // Vaqt oraliqlari - TURLI, aralashtirib bo'lmaydi
    seconds soniya{90};
    minutes daqiqa{5};
    hours soat{2};

    std::println("90 soniya  = {}", soniya);
    std::println("5 daqiqa   = {}", daqiqa);
    std::println("2 soat     = {}", soat);

    std::println("");
    std::println("Avtomatik aylantirish (aniqlik yo'qolmasa):");
    seconds jami = soniya + daqiqa + soat;
    std::println("  yig'indi: {}", jami);

    std::println("");
    std::println("Aniqlik yo'qoladigan aylantirish OSHKORA bo'lishi kerak:");
    auto daqiqada = duration_cast<minutes>(jami);
    std::println("  duration_cast<minutes>: {}", daqiqada);
    std::println("  (kasr qismi tashlandi)");

    std::println("");
    std::println("Kasrli aylantirish:");
    auto aniq = duration<double, std::ratio<60>>(jami);
    std::println("  {:.4f} daqiqa", aniq.count());

    std::println("");
    std::println("Literal qo'shimchalari:");
    using namespace std::chrono_literals;
    auto kun = 24h;
    auto hafta = 7 * kun;
    std::println("  24h        = {}", kun);
    std::println("  7 * 24h    = {}", hafta);
    std::println("  hafta soniyada = {}", duration_cast<seconds>(hafta));

    return 0;
}
Natija
90 soniya  = 90s
5 daqiqa   = 5min
2 soat     = 2h

Avtomatik aylantirish (aniqlik yo'qolmasa):
  yig'indi: 7590s

Aniqlik yo'qoladigan aylantirish OSHKORA bo'lishi kerak:
  duration_cast<minutes>: 126min
  (kasr qismi tashlandi)

Kasrli aylantirish:
  126.5000 daqiqa

Literal qo'shimchalari:
  24h        = 24h
  7 * 24h    = 168h
  hafta soniyada = 604800s
chrono - kuchli turlarning eng yaxshi namunasi

int soniya = 90; va seconds soniya{90}; orasidagi farq katta.

Birinchisi bilan bu xato kompilyatsiya qilinadi:

KOD
int kutish = 5;        // daqiqami, soniyami?
uxla(kutish);          // kim biladi

Ikkinchisi bilan esa:

KOD
uxla(5min);            // aniq
uxla(5s);              // ham aniq
uxla(5);               // KOMPILYATSIYA XATOSI

chrono avtomatik aylantiradi, lekin faqat aniqlik yo'qolmasa:

AylantirishAvtomatikmi
minutessecondsHa
secondsminutesYo'q - duration_cast kerak
hoursmillisecondsHa
millisecondssecondsYo'q

Bu aynan biz 13-bo'limda Pul turi bilan qilgan ish - faqat standart kutubxona darajasida.

Sana va kalendar (C++20) #

C++
using namespace std::chrono;

int main()
{
    // Sana - kalendar turi
    year_month_day mustaqillik{2024y, September, 1d};

    std::println("sana: {}", mustaqillik);
    std::println("yil:  {}", static_cast<int>(mustaqillik.year()));
    std::println("oy:   {}", static_cast<unsigned>(mustaqillik.month()));
    std::println("kun:  {}", static_cast<unsigned>(mustaqillik.day()));

    std::println("");
    std::println("to'g'ri sanami: {}", mustaqillik.ok());
    year_month_day yomon{2023y, February, 30d};
    std::println("2023-02-30 to'g'rimi: {}", yomon.ok());

    std::println("");
    // Hafta kuni
    weekday kun{sys_days{mustaqillik}};
    std::println("2024-09-01 hafta kuni: {}", kun);

    std::println("");
    std::println("Sanalar farqi:");
    year_month_day boshlanish{2024y, January, 1d};
    auto kunlar = sys_days{mustaqillik} - sys_days{boshlanish};
    std::println("  1-yanvardan 1-sentyabrgacha: {}", kunlar);

    std::println("");
    std::println("Kabisa yillar:");
    for (int y : {2020, 2023, 2024, 2100, 2000}) {
        std::println("  {} kabisami: {}", y, year{y}.is_leap());
    }

    std::println("");
    std::println("Oyning oxirgi kuni:");
    for (auto oy : {January, February, April}) {
        year_month_day_last oxirgi{2024y, month_day_last{oy}};
        std::println("  {} 2024: {} kun", oy,
                     static_cast<unsigned>(oxirgi.day()));
    }

    return 0;
}
Natija
sana: 2024-09-01
yil:  2024
oy:   9
kun:  1

to'g'ri sanami: true
2023-02-30 to'g'rimi: false

2024-09-01 hafta kuni: Sun

Sanalar farqi:
  1-yanvardan 1-sentyabrgacha: 244d

Kabisa yillar:
  2020 kabisami: true
  2023 kabisami: false
  2024 kabisami: true
  2100 kabisami: false
  2000 kabisami: true

Oyning oxirgi kuni:
  Jan 2024: 31 kun
  Feb 2024: 29 kun
  Apr 2024: 30 kun
C++20 kalendari - tashqi kutubxonasiz

C++20 gacha sana bilan ishlash uchun boost::date_time yoki qo'lda yozilgan kod kerak edi. <ctime> esa C merosi - xavfsiz emas va lokal holatga bog'liq.

Asosiy turlar:

TurNima
year, month, dayAlohida qismlar
year_month_dayTo'liq sana
weekdayHafta kuni
sys_daysKunlar sonidagi sana (arifmetika uchun)
hh_mm_ssKun ichidagi vaqt
zoned_timeVaqt mintaqasi bilan

Naqsh: kalendar turlari o'qish uchun, sys_days hisoblash uchun.

ok() metodi sanani tekshiradi - 2023-02-30 kabi mavjud bo'lmagan sanalar yaratilishi mumkin, lekin ok() false qaytaradi.

Vaqtni o'lchash #

C++
using namespace std::chrono;

long long ogirIsh(int n)
{
    long long jami = 0;
    for (int i = 1; i <= n; ++i) jami += static_cast<long long>(i) * i;
    return jami;
}

int main()
{
    auto boshlanish = steady_clock::now();
    long long natija = ogirIsh(2'000'000);
    auto tugash = steady_clock::now();

    auto ketgan = duration_cast<microseconds>(tugash - boshlanish);

    std::println("natija: {}", natija);
    std::println("hisoblash bajarildi");
    std::println("vaqt musbatmi: {}", ketgan.count() >= 0);
    std::println("bir soniyadan kammi: {}", ketgan < 1s);

    std::println("");
    std::println("Uch xil soat:");
    std::println("  steady_clock   - o'lchash uchun, ORQAGA KETMAYDI");
    std::println("  system_clock   - haqiqiy sana/vaqt, sozlanishi mumkin");
    std::println("  high_resolution_clock - odatda steady_clock ning taxallusi");

    std::println("");
    std::println("steady_clock monotonmi: {}", steady_clock::is_steady);
    std::println("system_clock monotonmi: {}", system_clock::is_steady);

    return 0;
}
Natija
natija: 2666668666667000000
hisoblash bajarildi
vaqt musbatmi: true
bir soniyadan kammi: true

Uch xil soat:
  steady_clock   - o'lchash uchun, ORQAGA KETMAYDI
  system_clock   - haqiqiy sana/vaqt, sozlanishi mumkin
  high_resolution_clock - odatda steady_clock ning taxallusi

steady_clock monotonmi: true
system_clock monotonmi: false
O'lchash uchun steady_clock ishlating

system_clock foydalanuvchi yoki NTP xizmati tomonidan o'zgartirilishi mumkin - hatto orqaga. Agar o'lchov o'rtasida shunday bo'lsa, manfiy davomiylik olasiz.

steady_clock esa faqat oldinga ketadi. U haqiqiy sanani bilmaydi - lekin bizga u kerak emas.

VazifaSoat
"Qancha vaqt ketdi"steady_clock
"Hozir soat nechchi"system_clock
Taymautsteady_clock
Fayl vaqtifile_clock

Bu misolda biz ketgan vaqtni chop etmadik - faqat uning musbat va bir soniyadan kam ekanini tekshirdik. Sabab: aniq raqam har ishga tushirishda boshqacha bo'ladi va bu darslikning barcha misollari takrorlanadigan bo'lishi kerak.

O'zingizning o'lchovlaringizda albatta raqamni chop eting - lekin bir necha marta o'lchab, o'rtachasini oling.

Standart kutubxona - tashqi bog'liqliksiz <chrono> Vaqt oralig'i, sana, kalendar Vaqt mintaqalari (C++20) Monoton o'lchash steady_clock, year_month_day <filesystem> Yo'llar, kataloglar, o'lchamlar Rekursiv aylanish Platformalararo path, directory_iterator <random> Generator + taqsimot Mersenne Twister Takrorlanadigan urug' mt19937, uniform_int_distribution <format> <print> Tur xavfsiz formatlash Kenglik, aniqlik, tekislash O'z turlaringiz uchun ham format, print, formatter <numeric> Yig'indi, ko'paytma, farqlar EKUB, EKUK, o'rta nuqta Prefiks yig'indilar accumulate, gcd, midpoint <bit> <cmath> Bitlarni sanash, aylantirish Xavfsiz tur o'zgartirish Matematik funksiyalar popcount, bit_cast, lerp Bularning hammasi kompilyator bilan birga keladi Paket menejeri, versiya nizosi va litsenziya masalasi yo'q. Tashqi kutubxona qidirishdan oldin standartni qarang Ko'p dasturchi C++17/20/23 nimalar qo'shganini bilmaydi va g'ildirakni qayta ixtiro qiladi.
Standart kutubxona - eng yaxshi tekshirilgan bog'liqlik

std::filesystem #

C++
namespace fs = std::filesystem;

int main()
{
    fs::path asos{"/tmp/sr_cpp_17"};

    // Tozalab boshlaymiz
    fs::remove_all(asos);
    fs::create_directories(asos / "hujjatlar" / "2024");

    // Fayllar yaratamiz
    for (const auto& [nom, matn] : std::vector<std::pair<std::string, std::string>>{
             {"hujjatlar/oqish.txt", "Birinchi fayl"},
             {"hujjatlar/2024/hisobot.txt", "Yillik hisobot matni"},
             {"sozlama.json", "{}"},
         }) {
        std::ofstream f{asos / nom};
        f << matn;
    }

    std::println("Yo'l qismlari:");
    fs::path p = asos / "hujjatlar" / "2024" / "hisobot.txt";
    std::println("  to'liq:      {}", p.string());
    std::println("  ota katalog: {}", p.parent_path().string());
    std::println("  fayl nomi:   {}", p.filename().string());
    std::println("  kengaytma:   {}", p.extension().string());
    std::println("  kengaytmasiz:{}", p.stem().string());

    std::println("");
    std::println("Tekshiruvlar:");
    std::println("  mavjudmi:    {}", fs::exists(p));
    std::println("  oddiy fayl:  {}", fs::is_regular_file(p));
    std::println("  katalogmi:   {}", fs::is_directory(p));
    std::println("  hajmi:       {} bayt", fs::file_size(p));

    std::println("");
    std::println("Rekursiv aylanish:");
    std::vector<std::string> topilgan;
    for (const auto& yozuv : fs::recursive_directory_iterator(asos)) {
        std::string turi = yozuv.is_directory() ? "katalog" : "fayl";
        topilgan.push_back(std::format("{:<8} {}", turi,
                           fs::relative(yozuv.path(), asos).string()));
    }
    std::ranges::sort(topilgan);
    for (const auto& s : topilgan) std::println("  {}", s);

    std::println("");
    std::println("Faqat .txt fayllar:");
    int soni = 0;
    std::uintmax_t jamiHajm = 0;
    for (const auto& yozuv : fs::recursive_directory_iterator(asos)) {
        if (yozuv.is_regular_file() && yozuv.path().extension() == ".txt") {
            ++soni;
            jamiHajm += yozuv.file_size();
        }
    }
    std::println("  {} ta fayl, jami {} bayt", soni, jamiHajm);

    fs::remove_all(asos);
    std::println("");
    std::println("tozalandi, mavjudmi: {}", fs::exists(asos));

    return 0;
}
Natija
Yo'l qismlari:
  to'liq:      /tmp/sr_cpp_17/hujjatlar/2024/hisobot.txt
  ota katalog: /tmp/sr_cpp_17/hujjatlar/2024
  fayl nomi:   hisobot.txt
  kengaytma:   .txt
  kengaytmasiz:hisobot

Tekshiruvlar:
  mavjudmi:    true
  oddiy fayl:  true
  katalogmi:   false
  hajmi:       20 bayt

Rekursiv aylanish:
  fayl     hujjatlar/2024/hisobot.txt
  fayl     hujjatlar/oqish.txt
  fayl     sozlama.json
  katalog  hujjatlar
  katalog  hujjatlar/2024

Faqat .txt fayllar:
  2 ta fayl, jami 33 bayt

tozalandi, mavjudmi: false
path ni satr sifatida yig'ishtirmang
KOD
std::string yol = katalog + "/" + nom;      // YOMON
fs::path yol = fs::path{katalog} / nom;     // YAXSHI

operator/ platformaga mos ajratgichni qo'yadi va takroriy // ni oldini oladi.

Foydali metodlar:

MetodNima qaytaradi
filename()hisobot.txt
stem()hisobot
extension().txt
parent_path()Ota katalog
is_absolute()Mutlaq yo'lmi
lexically_normal().. va . ni yechadi

Diqqat: fs funksiyalari xato holatida istisno tashlaydi. Istisnosiz variant ham bor:

KOD
std::error_code xato;
auto hajm = fs::file_size(p, xato);
if (xato) { ... }

Fayl tizimi bilan ishlaganda poyga holati doim mavjud: exists() va ochish orasida fayl o'chirilishi mumkin. Shuning uchun ochishga urinib ko'rish ko'pincha tekshirishdan yaxshiroq.

std::random #

C++
int main()
{
    // Takrorlanadigan urug' - sinov uchun
    std::mt19937 generator{20240901u};

    std::println("Bir xil urug' - bir xil ketma-ketlik:");
    std::uniform_int_distribution<int> zar{1, 6};
    std::print("  zarlar:");
    for (int i = 0; i < 10; ++i) std::print(" {}", zar(generator));
    std::println("");

    std::mt19937 ikkinchi{20240901u};
    std::print("  qayta: ");
    for (int i = 0; i < 10; ++i) std::print(" {}", zar(ikkinchi));
    std::println("");

    std::println("");
    std::println("Turli taqsimotlar:");

    std::mt19937 g{42u};
    std::uniform_real_distribution<double> tekis{0.0, 1.0};
    std::print("  tekis [0,1):");
    for (int i = 0; i < 4; ++i) std::print(" {:.5f}", tekis(g));
    std::println("");

    std::normal_distribution<double> normal{100.0, 15.0};
    std::print("  normal (100,15):");
    for (int i = 0; i < 4; ++i) std::print(" {:.3f}", normal(g));
    std::println("");

    std::bernoulli_distribution tanga{0.5};
    std::print("  tanga:");
    for (int i = 0; i < 8; ++i) std::print(" {}", tanga(g) ? "O" : "P");
    std::println("");

    std::println("");
    std::println("Zar taqsimoti tekshiruvi (60000 tashlash):");
    std::mt19937 sinov{7u};
    std::vector<int> hisob(7, 0);
    for (int i = 0; i < 60'000; ++i) ++hisob[static_cast<std::size_t>(zar(sinov))];

    for (int yuz = 1; yuz <= 6; ++yuz) {
        double foiz = 100.0 * hisob[static_cast<std::size_t>(yuz)] / 60'000.0;
        bool meyorda = foiz > 16.0 && foiz < 17.4;
        std::println("  {} yuzi: {:>5} marta ({:.2f}%) meyorda: {}",
                     yuz, hisob[static_cast<std::size_t>(yuz)], foiz, meyorda);
    }

    std::println("");
    std::println("jami: {}", std::accumulate(hisob.begin(), hisob.end(), 0));

    return 0;
}
Natija
Bir xil urug' - bir xil ketma-ketlik:
  zarlar: 4 2 2 3 6 6 4 3 1 1
  qayta:  4 2 2 3 6 6 4 3 1 1

Turli taqsimotlar:
  tekis [0,1): 0.79654 0.18343 0.77969 0.59685
  normal (100,15): 86.248 98.138 69.836 92.608
  tanga: O P O P P O P P

Zar taqsimoti tekshiruvi (60000 tashlash):
  1 yuzi: 10138 marta (16.90%) meyorda: true
  2 yuzi:  9960 marta (16.60%) meyorda: true
  3 yuzi:  9832 marta (16.39%) meyorda: true
  4 yuzi:  9989 marta (16.65%) meyorda: true
  5 yuzi: 10162 marta (16.94%) meyorda: true
  6 yuzi:  9919 marta (16.53%) meyorda: true

jami: 60000
rand() ni ishlatmang

C dan meros qolgan rand() uch sababdan yaroqsiz:

MuammoIzoh
Sifati pastBa'zi amalga oshirishlarda davri juda qisqa
% n siljish beradiDiapazon teng taqsimlanmaydi
Global holatKo'p oqimda xavfsiz emas

rand() % 6 + 1 klassik xato: RAND_MAX 6 ga bo'linmasa, kichik raqamlar ko'proq chiqadi.

To'g'ri naqsh - generator va taqsimot alohida:

KOD
std::mt19937 g{urug};                          // generator
std::uniform_int_distribution<int> zar{1, 6};  // taqsimot
int natija = zar(g);

Urug' tanlash:

MaqsadUrug'
Sinov, takrorlanishAniq son (42u)
Haqiqiy tasodifiylikstd::random_device{}()
KriptografiyaHech qaysisi - OS API ishlating

Oxirgi qator muhim: mt19937 kriptografik jihatdan xavfsiz emas. 624 ta chiqishni ko'rgan hujumchi butun keyingi ketma-ketlikni tiklay oladi.

Formatlash imkoniyatlari #

C++
int main()
{
    std::println("Tekislash va kenglik:");
    std::println("  [{:<12}] chapga", "matn");
    std::println("  [{:>12}] o'ngga", "matn");
    std::println("  [{:^12}] markazga", "matn");
    std::println("  [{:*^12}] to'ldirgich bilan", "matn");

    std::println("");
    std::println("Sonlar:");
    std::println("  [{:8.3f}]  kasr aniqligi", 3.14159265);
    std::println("  [{:+}]     ishora majburiy", 42);
    std::println("  [{:08.2f}] nol bilan to'ldirish", 3.5);
    std::println("  [{:.0f}]   yaxlitlash", 2.5);
    std::println("  [{:.0f}]   yaxlitlash", 3.5);

    std::println("");
    std::println("Sanoq sistemalari:");
    int n = 255;
    std::println("  o'nlik:      {}", n);
    std::println("  ikkilik:     {:b}", n);
    std::println("  sakkizlik:   {:o}", n);
    std::println("  o'n oltilik: {:x}", n);
    std::println("  prefiks bn:  {:#b}", n);
    std::println("  prefiks 16:  {:#x}", n);

    std::println("");
    std::println("Argument raqami bilan:");
    std::println("  {0} - {1} - {0}", "takror", "o'rta");

    std::println("");
    std::println("Ichma-ich kenglik:");
    for (int k : {6, 10, 14}) {
        std::println("  [{:>{}}] kenglik {}", "x", k, k);
    }

    std::println("");
    std::println("Konteynerlar (C++23):");
    std::vector<int> v{1, 2, 3, 4};
    std::println("  vector: {}", v);
    std::println("  juftlik: {}", std::pair{1, "bir"});

    return 0;
}
Natija
Tekislash va kenglik:
  [matn        ] chapga
  [        matn] o'ngga
  [    matn    ] markazga
  [****matn****] to'ldirgich bilan

Sonlar:
  [   3.142]  kasr aniqligi
  [+42]     ishora majburiy
  [00003.50] nol bilan to'ldirish
  [2]   yaxlitlash
  [4]   yaxlitlash

Sanoq sistemalari:
  o'nlik:      255
  ikkilik:     11111111
  sakkizlik:   377
  o'n oltilik: ff
  prefiks bn:  0b11111111
  prefiks 16:  0xff

Argument raqami bilan:
  takror - o'rta - takror

Ichma-ich kenglik:
  [     x] kenglik 6
  [         x] kenglik 10
  [             x] kenglik 14

Konteynerlar (C++23):
  vector: [1, 2, 3, 4]
  juftlik: (1, "bir")
Bankir yaxlitlashi

Yuqoridagi chiqishda {:.0f} 2.5 ni 2 ga, 3.5 ni esa 4 ga yaxlitladi.

Bu xato emas - bu juftga yaxlitlash (IEEE 754 standarti). Aniq yarim bo'lganda eng yaqin juft songa yaxlitlanadi.

Sabab: har doim yuqoriga yaxlitlash uzoq ketma-ketlikda siljish to'playdi. Juftga yaxlitlash bu siljishni o'rtacha nolga tushiradi.

Moliyaviy hisob-kitobda bu muhim. Agar sizga boshqa qoida kerak bo'lsa - o'zingiz yozing:

KOD
double yarimYuqoriga(double q) {
    return std::floor(q + 0.5);
}

Yana bir sabab kasr sonlarni pul uchun ishlatmaslik kerakligi: 0.1 + 0.2 != 0.3. 13-bo'limdagi Pul turi butun sonda tiyin saqlagani bejiz emas.

Amaliy topshiriq
  1. seconds, minutes, hours ni qo'shing - qaysi turga aylandi?
  2. duration_cast siz seconds dan minutes ga o'ting - nima bo'ladi?
  3. year_month_day bilan sana yarating va ok() ni tekshiring.
  4. Ikki sana orasidagi kunlarni hisoblang.
  5. steady_clock bilan funksiya vaqtini o'lchang.
  6. fs::path ning stem(), extension(), parent_path() ni chiqaring.
  7. Katalogni rekursiv aylanib .txt fayllarni sanang.
  8. mt19937 ni bir xil urug' bilan ikki marta ishga tushiring.
  9. Zar taqsimotini 60 000 tashlash bilan tekshiring.
  10. {:*^12}, {:#x}, {:>{}} formatlarini sinab ko'ring.

Xulosa #

  • chrono vaqt oraliqlarini turga aylantiradi - seconds va minutes aralashmaydi.
  • Aniqlik yo'qoladigan aylantirish oshkora duration_cast talab qiladi.
  • C++20 kalendari (year_month_day, weekday) tashqi kutubxonasiz sana hisobini beradi.
  • Kalendar turlari o'qish uchun, sys_days hisoblash uchun.
  • O'lchash uchun steady_clock - system_clock orqaga ketishi mumkin.
  • fs::path ni operator/ bilan yig'ing, satr birlashtirish bilan emas.
  • fs funksiyalari istisno tashlaydi; std::error_code varianti ham bor.
  • rand() o'rniga generator + taqsimot juftligini ishlating.
  • Bir xil urug' - bir xil ketma-ketlik; bu sinovlar uchun zarur.
  • mt19937 kriptografik jihatdan xavfsiz emas.
  • std::format kenglik, aniqlik, sanoq sistemasi va ichma-ich parametrlarni qo'llab-quvvatlaydi.
  • {:.0f} juftga yaxlitlaydi - bu IEEE 754 qoidasi.

Keyingi bo'limda ko'p oqimlilik 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.