13-bo‘lim

Ko'rsatkichlar va massivlar

Massiv nomi nima uchun manzil, ko'rsatkich arifmetikasi, satrlar bilan ishlash, ko'rsatkichlar massivi va funksiyaga ko'rsatkich.

🕑 18 daqiqa o‘qish 📄 709 so‘z 👁 3 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Massiv nomi - bu manzil
  2. Ko'rsatkich arifmetikasi
  3. Massivni ko'rsatkich bilan aylanish
  4. Massiv funksiyaga uzatilganda
  5. Satrlar va ko'rsatkichlar
  6. Satrni nusxalash
  7. Ko'rsatkichlar massivi
  8. Buyruq qatori argumentlari
  9. Funksiyaga ko'rsatkich
  10. Amallar jadvali
  11. Ikki o'lchamli massiv va ko'rsatkich
  12. Xavfli amallar
  13. Xulosa

C da massiv va ko'rsatkich chuqur bog'langan. Bu bog'liqlikni tushunsangiz, tilning ko'p «g'alati» tomonlari mantiqiy bo'lib qoladi.

Massiv nomi - bu manzil #

C
#include <stdio.h>

int main(void) {
    int ballar[5] = {85, 92, 78, 95, 88};

    printf("ballar     == &ballar[0] ? %s\n",
           (ballar == &ballar[0]) ? "ha" : "yo'q");

    printf("*ballar     = %d\n", *ballar);
    printf("ballar[0]   = %d\n", ballar[0]);
    printf("*(ballar+2) = %d\n", *(ballar + 2));
    printf("ballar[2]   = %d\n", ballar[2]);

    return 0;
}
Natija
ballar     == &ballar[0] ? ha
*ballar     = 85
ballar[0]   = 85
*(ballar+2) = 78
ballar[2]   = 78
Indekslash aslida nima?
C
massiv[i]    ⟷    *(massiv + i)

Kompilyator birinchisini ikkinchisiga aylantiradi. Kvadrat qavs - shunchaki qulay yozuv.

Bundan kulgili natija chiqadi:

C
massiv[2]  ⟷  *(massiv + 2)  ⟷  *(2 + massiv)  ⟷  2[massiv]

Ha, 2[massiv] ham ishlaydi! Lekin uni hech qachon yozmang - bu faqat til qanday ishlashini ko'rsatuvchi qiziqarli fakt.

Ko'rsatkich arifmetikasi #

k + 1 turga qarab siljiydi int *k 1000 1004 1008 k + 1 → 4 bayt siljiydi char *k 1000 1001 1002 1003 k + 1 → 1 bayt siljiydi double *k 1000 1008 k + 1 → 8 bayt Shuning uchun ko'rsatkichning turi muhim
k + 1 «bitta element keyingi» degani, «bitta bayt keyingi» emas
C
#include <stdio.h>

int main(void) {
    int ballar[5] = {85, 92, 78, 95, 88};
    int *k = ballar;

    printf("Boshlanish : %d\n", *k);

    k++;
    printf("k++ keyin  : %d\n", *k);

    k += 2;
    printf("k += 2     : %d\n", *k);

    k--;
    printf("k-- keyin  : %d\n", *k);

    printf("\nFarq: %ld element\n", k - ballar);

    return 0;
}
Natija
Boshlanish : 85
k++ keyin  : 92
k += 2     : 95
k-- keyin  : 78

Farq: 2 element

Massivni ko'rsatkich bilan aylanish #

C
#include <stdio.h>

int main(void) {
    int ballar[5] = {85, 92, 78, 95, 88};
    int soni = 5;

    printf("Indeks bilan   : ");
    for (int i = 0; i < soni; i++) {
        printf("%d ", ballar[i]);
    }

    printf("\nKo'rsatkich b. : ");
    for (int *k = ballar; k < ballar + soni; k++) {
        printf("%d ", *k);
    }

    printf("\n");
    return 0;
}
Natija
Indeks bilan   : 85 92 78 95 88 
Ko'rsatkich b. : 85 92 78 95 88 
Qaysi biri afzal?

Zamonaviy kompilyatorlar ikkalasini ham bir xil tezlikda bajaradi. Indeks varianti o'qishga osonroq, shuning uchun uni tanlang.

Ko'rsatkich varianti bog'langan ro'yxat kabi tuzilmalarda va standart kutubxona kodida ko'p uchraydi - shuning uchun uni o'qiy bilish kerak.

Massiv funksiyaga uzatilganda #

C
#include <stdio.h>

void tekshir(int massiv[]) {
    printf("Funksiya ichida sizeof: %zu\n", sizeof(massiv));
}

int main(void) {
    int ballar[5] = {85, 92, 78, 95, 88};

    printf("main ichida sizeof   : %zu\n", sizeof(ballar));
    tekshir(ballar);

    return 0;
}
Natija
main ichida sizeof   : 20
Funksiya ichida sizeof: 8
Massiv ko'rsatkichga «yemiriladi»

Funksiyaga uzatilganda massiv ko'rsatkichga aylanadi (array decay). Hajm ma'lumoti yo'qoladi.

Bu uchta e'lon butunlay bir xil:

C
void f(int massiv[5]);    /* 5 raqami e'tiborga olinmaydi! */
void f(int massiv[]);
void f(int *massiv);

Shuning uchun hajmni doim alohida parametr sifatida uzating:

C
void f(const int massiv[], size_t soni);

Bu C ning eng ko'p tanqid qilinadigan xususiyati. Boshqa tillarda massiv o'z uzunligini biladi - C da esa yo'q.

Satrlar va ko'rsatkichlar #

C
#include <stdio.h>

size_t uzunlik(const char *s) {
    const char *boshi = s;

    while (*s != '\0') {
        s++;
    }

    return (size_t) (s - boshi);
}

int main(void) {
    const char *ism = "Husanboy";

    printf("Satr    : %s\n", ism);
    printf("Uzunlik : %zu\n", uzunlik(ism));
    printf("Birinchi: %c\n", *ism);
    printf("Uchinchi: %c\n", *(ism + 2));

    return 0;
}
Natija
Satr    : Husanboy
Uzunlik : 8
Birinchi: H
Uchinchi: s
strlen shunday yozilgan

Yuqoridagi uzunlik funksiyasi - bu aslida strlen ning soddalashtirilgan varianti. U nolgacha yurib, qadamlarni sanaydi.

Endi tushunarli bo'ladi: strlen har safar butun satrni aylanadi. Shuning uchun uni sikl shartida chaqirmaslik kerak.

Satrni nusxalash #

C
#include <stdio.h>

void nusxala(char *maqsad, const char *manba) {
    while (*manba != '\0') {
        *maqsad = *manba;
        maqsad++;
        manba++;
    }
    *maqsad = '\0';        /* nolni ham qo'yamiz */
}

int main(void) {
    char natija[50];

    nusxala(natija, "Husanboy Qodirov");
    printf("Nusxa: %s\n", natija);

    return 0;
}
Natija
Nusxa: Husanboy Qodirov
K&R uslubidagi qisqa shakl

Kitoblarda strcpy ko'pincha shunday yoziladi:

C
while ((*maqsad++ = *manba++) != '\0')
    ;

Bu bir qatorda hamma ishni bajaradi: nusxalaydi, ikkala ko'rsatkichni oshiradi va nolga yetganda to'xtaydi.

Chiroyli, lekin o'qish qiyin. Zamonaviy kodda yuqoridagi ochiq variant afzal.

Ko'rsatkichlar massivi #

C
#include <stdio.h>
#include <string.h>

int main(void) {
    const char *shaharlar[] = {
        "Namangan",
        "Toshkent",
        "Samarqand",
        "Buxoro",
        "Andijon"
    };

    int soni = sizeof(shaharlar) / sizeof(shaharlar[0]);

    for (int i = 0; i < soni; i++) {
        printf("%d. %-12s (%zu harf)\n", i + 1, shaharlar[i], strlen(shaharlar[i]));
    }

    return 0;
}
Natija
1. Namangan     (8 harf)
2. Toshkent     (8 harf)
3. Samarqand    (9 harf)
4. Buxoro       (6 harf)
5. Andijon      (7 harf)
Ko'rsatkichlar massivi xotirada shaharlar [0] → [1] → [2] → "Namangan\0" "Toshkent\0" "Samarqand\0" satrlar xotiraning boshqa joyida Massivda faqat manzillar, satrlar esa alohida yotadi
Har xil uzunlikdagi satrlarni saqlashning eng oson yo'li

Buyruq qatori argumentlari #

C
#include <stdio.h>

int main(int argc, char *argv[]) {
    printf("Dastur nomi     : %s\n", argv[0]);
    printf("Argumentlar soni: %d\n", argc - 1);

    for (int i = 1; i < argc; i++) {
        printf("  %d-argument: %s\n", i, argv[i]);
    }

    return 0;
}
Terminal
gcc -std=c17 -Wall argumentlar.c -o argumentlar
./argumentlar salom dunyo 42
Natija
Dastur nomi     : ./argumentlar
Argumentlar soni: 3
  1-argument: salom
  2-argument: dunyo
  3-argument: 42
argv nima?

argv - bu ko'rsatkichlar massivi. Har biri bitta argument satriga ko'rsatadi.

argv[0] - doim dasturning o'zi nomi. Haqiqiy argumentlar argv[1] dan boshlanadi.

argv[argc] esa doim NULL - shuning uchun sikl ikki xil yozilishi mumkin:

C
for (int i = 1; i < argc; i++) { ... }
for (char **a = argv + 1; *a != NULL; a++) { ... }

Funksiyaga ko'rsatkich #

C
#include <stdio.h>

int qoshish(int a, int b)      { return a + b; }
int ayirish(int a, int b)      { return a - b; }
int kopaytirish(int a, int b)  { return a * b; }

int main(void) {
    int (*amal)(int, int);      /* funksiyaga ko'rsatkich */

    amal = qoshish;
    printf("qoshish     : %d\n", amal(10, 3));

    amal = ayirish;
    printf("ayirish     : %d\n", amal(10, 3));

    amal = kopaytirish;
    printf("kopaytirish : %d\n", amal(10, 3));

    return 0;
}
Natija
qoshish     : 13
ayirish     : 7
kopaytirish : 30

Amallar jadvali #

C
#include <stdio.h>

int qoshish(int a, int b)     { return a + b; }
int ayirish(int a, int b)     { return a - b; }
int kopaytirish(int a, int b) { return a * b; }

struct Amal {
    char belgi;
    int (*bajar)(int, int);
};

int main(void) {
    struct Amal amallar[] = {
        {'+', qoshish},
        {'-', ayirish},
        {'*', kopaytirish}
    };

    int soni = sizeof(amallar) / sizeof(amallar[0]);
    int a = 12, b = 4;

    for (int i = 0; i < soni; i++) {
        printf("%d %c %d = %d\n", a, amallar[i].belgi, b,
               amallar[i].bajar(a, b));
    }

    return 0;
}
Natija
12 + 4 = 16
12 - 4 = 8
12 * 4 = 48
Bu qayerda ishlatiladi?

Funksiyaga ko'rsatkich - C dagi «qayta chaqiruv» (callback) mexanizmi:

  • qsort - saralash uchun taqqoslash funksiyasini qabul qiladi;
  • bsearch - qidiruv uchun ham xuddi shunday;
  • Menyu tizimlari - har bir tanlovga bitta funksiya;
  • Hodisalarni qayta ishlash - tugma bosilganda nima bo'lishi.

18-bo'limda qsort ni ko'rganingizda bu tushuncha juda kerak bo'ladi.

Ikki o'lchamli massiv va ko'rsatkich #

C
#include <stdio.h>

void jadval_chiqar(int qatorlar, int ustunlar, int jadval[qatorlar][ustunlar]) {
    for (int i = 0; i < qatorlar; i++) {
        for (int j = 0; j < ustunlar; j++) {
            printf("%4d", jadval[i][j]);
        }
        printf("\n");
    }
}

int main(void) {
    int matritsa[3][4] = {
        {1,  2,  3,  4},
        {5,  6,  7,  8},
        {9, 10, 11, 12}
    };

    jadval_chiqar(3, 4, matritsa);

    /* xotirada ketma-ket yotgani uchun bitta ko'rsatkich bilan ham bo'ladi */
    int *tekis = &matritsa[0][0];
    printf("\nTekis ko'rinish: ");
    for (int i = 0; i < 12; i++) {
        printf("%d ", tekis[i]);
    }
    printf("\n");

    return 0;
}
Natija
   1   2   3   4
   5   6   7   8
   9  10  11  12

Tekis ko'rinish: 1 2 3 4 5 6 7 8 9 10 11 12 
O'zgaruvchan uzunlikdagi massiv (VLA)
C
void jadval_chiqar(int qatorlar, int ustunlar, int jadval[qatorlar][ustunlar]);

Bu C99 ning imkoniyati - massiv o'lchamini parametr sifatida olish. Juda qulay, lekin C11 dan boshlab ixtiyoriy bo'lib qoldi, ya'ni ba'zi kompilyatorlar uni qo'llab-quvvatlamasligi mumkin (masalan MSVC).

Ko'chma kod yozmoqchi bo'lsangiz, bir o'lchamli massiv ishlatib, indeksni qo'lda hisoblang:

C
jadval[i * ustunlar + j]

Xavfli amallar #

Ko'rsatkich arifmetikasi chegarani bilmaydi
C
int massiv[5];
int *k = massiv;

k += 100;          /* massivdan ancha uzoqda */
*k = 42;           /* begona xotirani buzadi */

C sizni to'xtatmaydi. Ko'rsatkich massiv chegarasidan chiqmasligini o'zingiz kuzatishingiz kerak.

Standart bo'yicha ko'rsatkich massiv ichida yoki uning oxiridan bir element keyin bo'lishi mumkin (bu sikl sharti uchun kerak). Undan narisi - aniqlanmagan xatti-harakat.

Amaliy topshiriq
  1. massiv == &massiv[0] ekanini isbotlang.
  2. massiv[i] va *(massiv + i) bir xil natija berishini ko'rsating.
  3. int * va char * ko'rsatkichlarni oshirib, manzillar farqini ko'ring.
  4. Massivni ko'rsatkich bilan aylanib, yig'indisini hisoblang.
  5. Funksiya ichida sizeof(massiv) nima berishini tekshiring.
  6. O'z strlen funksiyangizni ko'rsatkich bilan yozing.
  7. O'z strcpy funksiyangizni yozing.
  8. Shaharlar ro'yxatini ko'rsatkichlar massivida saqlang.
  9. Buyruq qatori argumentlarini chiqaruvchi dastur yozing.
  10. To'rt amalni funksiyaga ko'rsatkich orqali tanlaydigan kalkulyator yozing.

Xulosa #

  • Massiv nomi - bu birinchi elementning manzili.
  • massiv[i] aslida *(massiv + i) ning qulay yozuvi.
  • Ko'rsatkich arifmetikasi element hisobida ishlaydi, bayt hisobida emas.
  • Massiv funksiyaga uzatilganda ko'rsatkichga yemiriladi va hajmi yo'qoladi.
  • void f(int m[5]), void f(int m[]) va void f(int *m) - bir xil.
  • Satrlar bilan ishlash aslida char * bilan ishlash.
  • Ko'rsatkichlar massivi har xil uzunlikdagi satrlarni saqlash uchun.
  • argv - buyruq qatori argumentlarining ko'rsatkichlar massivi.
  • Funksiyaga ko'rsatkich qayta chaqiruv (callback) uchun ishlatiladi.
  • Ko'rsatkich arifmetikasida chegarani o'zingiz kuzating.

Keyingi bo'limda dinamik xotira - malloc va free ni o'rganamiz.

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.