13-bo‘lim
Operatorlar va qiymat semantikasi
O'z turingizni o'rnatilgan turdek ishlatish - solishtirish, arifmetika, indekslash va uch tomonlama operator.
Ushbu bo‘lim mundarijasi
C++ da int maxsus tur emas. Uning barcha imkoniyatlarini
o'z turingizga ham berish mumkin - va shu bilan
"aylanma" (Aylanma) yoki "pul" (Pul) kabi tushunchalar
kodda tabiiy ko'rinadi.
Muammo: yalang'och son #
#include <print>
#include <string>
#include <vector>
#include <compare>
#include <format>
#include <stdexcept>
#include <algorithm>
// Yalang'och sonlar - hech narsa xato qilishga to'sqinlik qilmaydi
long long summa(long long sum, long long dollar)
{
return sum + dollar; // MA'NOSIZ, lekin kompilyatsiya qilinadi
}
int main()
{
long long narx_sum = 150'000;
long long narx_dollar = 12;
std::println("bema'ni qo'shuv: {}", summa(narx_sum, narx_dollar));
std::println("kompilyator hech nima demadi.");
std::println("");
std::println("Yana bir muammo - vergul qayerda:");
long long tiyin = 15'000'050;
std::println(" xom son: {}", tiyin);
std::println(" har safar qo'lda formatlash kerak");
return 0;
}
bema'ni qo'shuv: 150012
kompilyator hech nima demadi.
Yana bir muammo - vergul qayerda:
xom son: 15000050
har safar qo'lda formatlash kerak
Pulni long long bilan ifodalash uch muammo tug'diradi:
| Muammo | Natija |
|---|---|
| Valyuta yo'q | So'm dollarga qo'shiladi |
| Birlik yo'q | So'mmi, tiyinmi - noma'lum |
| Formatlash tarqoq | Har joyda qaytariladi |
Yechim - Pul turini yaratish. U ichida hali ham
long long saqlaydi, lekin qoidalarni o'zi
biladi.
Bu qo'shimcha ishlash xarajati nolga teng:
optimallashtiruvchi kompilyator bunday o'ramni butunlay
yo'q qiladi va mashina kodida oddiy long long qoladi.
Arifmetik operatorlar #
class Pul
{
long long tiyin_{}; // eng kichik birlikda saqlaymiz
public:
Pul() = default;
static Pul somdan(long long som, int tiyin = 0)
{
Pul p;
p.tiyin_ = som * 100 + tiyin;
return p;
}
long long tiyin() const { return tiyin_; }
Pul& operator+=(const Pul& b) { tiyin_ += b.tiyin_; return *this; }
Pul& operator-=(const Pul& b) { tiyin_ -= b.tiyin_; return *this; }
Pul& operator*=(int n) { tiyin_ *= n; return *this; }
std::string matn() const
{
long long musbat = tiyin_ < 0 ? -tiyin_ : tiyin_;
const char* ishora = tiyin_ < 0 ? "-" : "";
return std::format("{}{}.{:02} so'm", ishora, musbat / 100, musbat % 100);
}
};
// Ikki argumentli operatorlar - erkin funksiya
Pul operator+(Pul a, const Pul& b) { a += b; return a; }
Pul operator-(Pul a, const Pul& b) { a -= b; return a; }
Pul operator*(Pul a, int n) { a *= n; return a; }
Pul operator*(int n, Pul a) { a *= n; return a; }
int main()
{
Pul non = Pul::somdan(4'000);
Pul sut = Pul::somdan(12'500, 50);
std::println("non: {}", non.matn());
std::println("sut: {}", sut.matn());
Pul jami = non + sut;
std::println("jami: {}", jami.matn());
Pul uchta = non * 3;
std::println("uchta non: {}", uchta.matn());
Pul teskari = 2 * sut;
std::println("ikki sut: {}", teskari.matn());
Pul qarz = non - sut;
std::println("farq: {}", qarz.matn());
std::println("");
// Pul + 5 ← kompilyatsiya xatosi. Aynan shuni xohlagandik.
return 0;
}
non: 4000.00 so'm
sut: 12500.50 so'm
jami: 16500.50 so'm
uchta non: 12000.00 so'm
ikki sut: 25001.00 so'm
farq: -8500.50 so'm
+= ni a'zo, + ni erkin funksiya qilingBu naqsh sabablari bor:
+= a'zo bo'lishi kerak, chunki u chap tomonni
o'zgartiradi va this ga kirish talab qiladi.
+ erkin bo'lishi kerak, chunki u nosimmetrik
bo'lishi mumkin emas. Agar + a'zo bo'lganida:
Pul p = pul + 5; // ishlaydi (agar aylantirish bo'lsa)
Pul q = 5 + pul; // ISHLAMAYDI - 5 da a'zo funksiya yo'q
Erkin operator+ esa ikkala tomonni teng ko'radi.
Diqqat: operator+ birinchi argumentni qiymat
bo'yicha oladi:
Pul operator+(Pul a, const Pul& b) { a += b; return a; }
Bu ataylab: a allaqachon nusxa, uni o'zgartirib
qaytaramiz. Vaqtinchalik obyekt uzatilsa - u ko'chiriladi,
qo'shimcha nusxa bo'lmaydi.
Uch tomonlama solishtirish #
class Versiya
{
int katta_{};
int orta_{};
int kichik_{};
public:
Versiya(int a, int b, int c) : katta_(a), orta_(b), kichik_(c) {}
// Bitta qator - oltita operator
auto operator<=>(const Versiya&) const = default;
bool operator==(const Versiya&) const = default;
std::string matn() const
{
return std::format("{}.{}.{}", katta_, orta_, kichik_);
}
};
int main()
{
Versiya a{1, 4, 0};
Versiya b{1, 10, 0};
Versiya c{1, 4, 0};
std::println("a = {}", a.matn());
std::println("b = {}", b.matn());
std::println("c = {}", c.matn());
std::println("");
std::println("a < b : {}", a < b);
std::println("a > b : {}", a > b);
std::println("a == c : {}", a == c);
std::println("a != b : {}", a != b);
std::println("a <= c : {}", a <= c);
std::println("");
std::vector<Versiya> ro = {{2, 0, 0}, {1, 4, 0}, {1, 10, 3}, {1, 4, 7}};
std::ranges::sort(ro);
std::print("saralangan:");
for (const auto& v : ro) std::print(" {}", v.matn());
std::println("");
return 0;
}
a = 1.4.0
b = 1.10.0
c = 1.4.0
a < b : true
a > b : false
a == c : true
a != b : true
a <= c : true
saralangan: 1.4.0 1.4.7 1.10.3 2.0.0
= default bo'lgan <=> a'zolarni tartib bilan solishtiradiauto operator<=>(const Versiya&) const = default; bitta
qatorda <, >, <=, >= ni beradi. Ikkinchi qator
== va != ni beradi.
Kompilyator a'zolarni e'lon tartibida solishtiradi:
avval katta_, teng bo'lsa orta_, keyin kichik_.
Bu aynan versiya raqamlari uchun kerakli tartib -
lekin a'zolar tartibiga bog'liqligini unutmang.
C++20 gacha oltita operatorni qo'lda yozish kerak edi - va ular orasida nomuvofiqlik keng tarqalgan xato edi.
Uchta qaytish turi bor:
| Tur | Ma'nosi |
|---|---|
std::strong_ordering | Teng qiymatlar almashtiriladi |
std::weak_ordering | Teng, lekin farqi bor |
std::partial_ordering | Solishtirib bo'lmaydigan juftlik bor |
double uchun partial_ordering - chunki NaN
hech nima bilan solishtirilmaydi.
Qo'lda yozilgan <=> #
class Talaba
{
std::string ism_;
double ball_{};
public:
Talaba(std::string ism, double ball)
: ism_(std::move(ism)), ball_(ball) {}
// Avval ballga ko'ra (kamayish), keyin ismga ko'ra
std::weak_ordering operator<=>(const Talaba& b) const
{
if (ball_ != b.ball_) {
return ball_ > b.ball_ ? std::weak_ordering::less
: std::weak_ordering::greater;
}
return ism_ <=> b.ism_; // strong_ordering -> weak_ordering
}
bool operator==(const Talaba& b) const
{
return ball_ == b.ball_ && ism_ == b.ism_;
}
const std::string& ism() const { return ism_; }
double ball() const { return ball_; }
};
int main()
{
std::vector<Talaba> guruh = {
{"Nodira", 88.5},
{"Husanboy", 94.0},
{"Aziza", 94.0},
{"Kamola", 76.25},
{"Malika", 88.5},
};
std::ranges::sort(guruh);
std::println("{:<10} {}", "ISM", "BALL");
std::println("{}", std::string(20, '-'));
for (const auto& t : guruh) {
std::println("{:<10} {:.2f}", t.ism(), t.ball());
}
std::println("");
std::println("eng yuqori: {}", guruh.front().ism());
std::println("eng past: {}", guruh.back().ism());
return 0;
}
ISM BALL
--------------------
Aziza 94.00
Husanboy 94.00
Malika 88.50
Nodira 88.50
Kamola 76.25
eng yuqori: Aziza
eng past: Kamola
double uchun <=> weak_ordering bermaydiBu funksiyani avval shunday yozgan edik:
if (auto c = b.ball_ <=> ball_; c != 0) return c;
Kompilyator rad etdi. Sabab: double <=> double
std::partial_ordering qaytaradi (chunki NaN
mavjud), va u weak_ordering ga aylanmaydi -
faqat teskarisi mumkin.
Aylanish yo'nalishi bir tomonlama:
| Manba | Nimaga aylanadi |
|---|---|
strong_ordering | weak_ordering, partial_ordering |
weak_ordering | partial_ordering |
partial_ordering | Hech nimaga |
Shuning uchun double maydonini qo'lda solishtirdik va
std::weak_ordering::less / ::greater ni qaytardik.
Ikkinchi qadamdagi ism_ <=> b.ism_ esa
strong_ordering beradi va u weak_ordering ga
o'zi aylanadi.
Muqobil yo'l - butun funksiyani partial_ordering
qaytaradigan qilish, lekin unda NaN ball
saralashni buzadi.
Indekslash va chaqiruv #
class Matritsa
{
std::size_t satr_{};
std::size_t ustun_{};
std::vector<double> ma_;
public:
Matritsa(std::size_t s, std::size_t u)
: satr_(s), ustun_(u), ma_(s * u, 0.0) {}
// Ikki indeksli kirish - operator()
double& operator()(std::size_t s, std::size_t u)
{
if (s >= satr_ || u >= ustun_) throw std::out_of_range{"chegaradan tashqari"};
return ma_[s * ustun_ + u];
}
double operator()(std::size_t s, std::size_t u) const
{
if (s >= satr_ || u >= ustun_) throw std::out_of_range{"chegaradan tashqari"};
return ma_[s * ustun_ + u];
}
std::size_t satrlar() const { return satr_; }
std::size_t ustunlar() const { return ustun_; }
void chop(const std::string& nom) const
{
std::println("{}:", nom);
for (std::size_t s = 0; s < satr_; ++s) {
std::string qator;
for (std::size_t u = 0; u < ustun_; ++u) {
qator += std::format("{:7.2f}", (*this)(s, u));
}
std::println(" {}", qator);
}
}
};
Matritsa operator*(const Matritsa& a, const Matritsa& b)
{
if (a.ustunlar() != b.satrlar()) throw std::invalid_argument{"o'lcham mos emas"};
Matritsa n{a.satrlar(), b.ustunlar()};
for (std::size_t s = 0; s < a.satrlar(); ++s) {
for (std::size_t u = 0; u < b.ustunlar(); ++u) {
double jami = 0;
for (std::size_t k = 0; k < a.ustunlar(); ++k) jami += a(s, k) * b(k, u);
n(s, u) = jami;
}
}
return n;
}
int main()
{
Matritsa a{2, 3};
a(0,0) = 1; a(0,1) = 2; a(0,2) = 3;
a(1,0) = 4; a(1,1) = 5; a(1,2) = 6;
Matritsa b{3, 2};
b(0,0) = 7; b(0,1) = 8;
b(1,0) = 9; b(1,1) = 10;
b(2,0) = 11; b(2,1) = 12;
a.chop("A");
b.chop("B");
Matritsa c = a * b;
c.chop("A * B");
std::println("");
try {
a(5, 0) = 1;
} catch (const std::out_of_range& x) {
std::println("xato tutildi: {}", x.what());
}
return 0;
}
A:
1.00 2.00 3.00
4.00 5.00 6.00
B:
7.00 8.00
9.00 10.00
11.00 12.00
A * B:
58.00 64.00
139.00 154.00
xato tutildi: chegaradan tashqari
operator[] va operator() - chegarani tekshirasizmiStandart konteynerlarda operator[] tekshirmaydi
(tez), at() esa tekshiradi (xavfsiz).
O'z turingizda tanlov sizniki, lekin izchil bo'ling va hujjatlashtiring.
C++23 dan boshlab operator[] bir nechta argument
qabul qila oladi:
double& operator[](std::size_t s, std::size_t u); // C++23
m[1, 2] = 5.0;
Undan oldin operator() yagona yo'l edi. Yangi kodda
operator[] ni ishlating - u ko'proq "indeks" ma'nosini
beradi.
Ikki versiya (const va const bo'lmagan) yozish
kerakligiga e'tibor bering - biri o'qish, biri yozish
uchun.
Oqimga chiqarish va std::formatter #
struct Nuqta
{
double x{};
double y{};
};
// std::format uchun o'z turimizni ro'yxatdan o'tkazamiz
template <>
struct std::formatter<Nuqta>
{
constexpr auto parse(std::format_parse_context& c) { return c.begin(); }
auto format(const Nuqta& n, std::format_context& c) const
{
return std::format_to(c.out(), "({:.2f}, {:.2f})", n.x, n.y);
}
};
Nuqta operator+(const Nuqta& a, const Nuqta& b) { return {a.x + b.x, a.y + b.y}; }
Nuqta operator*(const Nuqta& a, double k) { return {a.x * k, a.y * k}; }
int main()
{
Nuqta a{3.0, 4.0};
Nuqta b{1.5, -2.0};
std::println("a = {}", a);
std::println("b = {}", b);
std::println("a + b = {}", a + b);
std::println("a * 2 = {}", a * 2.0);
std::println("");
std::vector<Nuqta> yol = {{0,0}, {1,1}, {3,2}, {3,5}};
for (std::size_t i = 0; i < yol.size(); ++i) {
std::println(" {}-nuqta: {}", i, yol[i]);
}
return 0;
}
a = (3.00, 4.00)
b = (1.50, -2.00)
a + b = (4.50, 2.00)
a * 2 = (6.00, 8.00)
0-nuqta: (0.00, 0.00)
1-nuqta: (1.00, 1.00)
2-nuqta: (3.00, 2.00)
3-nuqta: (3.00, 5.00)
std::formatter - zamonaviy operator<<Eski C++ da chiqarish uchun operator<< yozilardi:
std::ostream& operator<<(std::ostream& o, const Nuqta& n);
U hali ham ishlaydi va iostream bilan kerak. Lekin
std::print va std::format uchun std::formatter
ixtisoslashtirish kerak.
Ustunliklari:
formatter | operator<< |
|---|---|
| Formatlash spetsifikatorlari | Yo'q |
| Tur xavfsiz, kompilyatsiyada tekshiriladi | Ish paytida |
| Lokal va oqim holatidan mustaqil | Holatga bog'liq |
| Tezroq | Sekinroq |
parse funksiyasi format satridagi {:...} ichini
o'qiydi. Bizning misolimizda uni e'tiborsiz qoldirdik -
c.begin() qaytarish "hech qanday spetsifikator
qabul qilmayman" degani.
Nima yuklash kerak emas #
int main()
{
std::println("{:<24} {}", "OPERATOR", "MASLAHAT");
std::println("{}", std::string(58, '-'));
std::println("{:<24} {}", "&& ||", "YUKLAMANG - qisqa hisob yo'qoladi");
std::println("{:<24} {}", ",", "YUKLAMANG - tartib yo'qoladi");
std::println("{:<24} {}", "& (manzil olish)", "YUKLAMANG - hamma chalg'iydi");
std::println("{:<24} {}", "->", "Faqat aqlli ko'rsatkichda");
std::println("{:<24} {}", "()", "Faqat funksiya-obyektda");
std::println("{:<24} {}", "new delete", "Faqat ajratgich yozayotganda");
std::println("{:<24} {}", "+ - * /", "Faqat MATEMATIK ma'noda");
std::println("{:<24} {}", "<=> ==", "Deyarli har doim = default");
std::println("");
std::println("Oltin qoida:");
std::println(" Operator ma'nosini o'zgartirmang.");
std::println(" Shubha bo'lsa - nomli funksiya yozing.");
std::println(" qoshildi() - g'alati operator+ dan ravshanroq.");
return 0;
}
OPERATOR MASLAHAT
----------------------------------------------------------
&& || YUKLAMANG - qisqa hisob yo'qoladi
, YUKLAMANG - tartib yo'qoladi
& (manzil olish) YUKLAMANG - hamma chalg'iydi
-> Faqat aqlli ko'rsatkichda
() Faqat funksiya-obyektda
new delete Faqat ajratgich yozayotganda
+ - * / Faqat MATEMATIK ma'noda
<=> == Deyarli har doim = default
Oltin qoida:
Operator ma'nosini o'zgartirmang.
Shubha bo'lsa - nomli funksiya yozing.
qoshildi() - g'alati operator+ dan ravshanroq.
&& va || ni yuklamangO'rnatilgan && va || qisqa hisob qiladi: chap
tomon javobni aniqlasa, o'ng tomon umuman
hisoblanmaydi.
if (p != nullptr && p->qiymat > 0) // p null bo'lsa - xavfsiz
Ortiqcha yuklangan operator&& esa oddiy funksiya -
u ikkala argumentni hisoblab, keyin chaqiriladi.
Yuqoridagi kod nol ko'rsatkichni ochib yuborardi.
Bundan tashqari hisoblash tartibi ham kafolatlanmagan.
Xuddi shu sabab operator, uchun ham amal qiladi.
Bu operatorlarni yuklash texnik jihatdan mumkin, lekin natija o'quvchini aldaydi - va aldash eng qimmat xato turi.
- Pulni
long longbilan ifodalab, so'm va dollarni qo'shib ko'ring. Pulturini yarating va+=ni a'zo qilib yozing.operator+ni erkin funksiya sifatida qo'shing.5 * pulishlashi uchun ikkinchi ortiqcha yuk yozing.auto operator<=>(...) const = default;bilan turni saralang.- A'zolar tartibini almashtiring - saralash o'zgardimi?
- Qo'lda
<=>yozib teskari tartibda saralang. operator()bilan ikki indeksli matritsa yozing.constvaconstbo'lmagan ikki versiyani qo'shing.- O'z turingiz uchun
std::formatterixtisoslashtiring.
Xulosa #
- Kuchli turlar xatoni kompilyatsiya paytiga suradi va xarajati nolga teng.
- Chap tomonni o'zgartiradigan operatorlar (
=,+=,[],()) - a'zo. - Simmetriya kerak bo'lganlar (
+,==,<=>) - erkin funksiya. - Standart naqsh:
+=da mantiq,+esa uni chaqiradi. auto operator<=>(...) const = defaultbitta qatorda to'rt solishtirishni beradi.- U a'zolarni e'lon tartibida solishtiradi - tartibga e'tibor bering.
==ni alohida= defaultqilish kerak.- Qo'lda yozilgan
<=>daif (auto c = ...; c != 0) return c;naqshini ishlating. - Ko'p indeksli kirish uchun
operator()yoki C++23 da ko'p argumentlioperator[]. std::formatterixtisoslashtirish -std::printuchun zamonaviy yo'l.&&,||,,va&ni hech qachon yuklamang.
Keyingi bo'limda vorislik va polimorfizm ni ko'ramiz - va ular qachon kerak emasligini.
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.