12-bo‘lim

Ko'rsatkichlar

Manzil va ko'rsatkich tushunchasi, & va * operatorlari, NULL, funksiyaga ko'rsatkich uzatish va eng ko'p uchraydigan xatolar.

🕑 19 daqiqa o‘qish 📄 838 so‘z 👁 5 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Xotira va manzil
  2. & - manzilni olish
  3. Ko'rsatkich yaratish
  4. Ko'rsatkich hajmi
  5. NULL - hech narsaga ko'rsatmaydi
  6. Ishga tushirilmagan ko'rsatkich
  7. Funksiyaga ko'rsatkich uzatish
  8. Ikki qiymatni almashtirish
  9. Bir nechta qiymat qaytarish
  10. const va ko'rsatkichlar
  11. Ko'rsatkichga ko'rsatkich
  12. Eng ko'p uchraydigan xatolar
  13. Ko'rsatkichni tasavvur qilish
  14. Xulosa

Ko'rsatkich - C ning eng kuchli va eng ko'p qo'rqiladigan imkoniyati. Aslida g'oya juda oddiy: ko'rsatkich - bu manzilni saqlaydigan o'zgaruvchi.

Xotira va manzil #

Xotira - raqamlangan yacheykalar qatori ... 1996 25 2000 yosh ... 2004 ... 2008 2000 2012 kursatkich Ko'rsatkich - bu shunchaki manzilni saqlaydigan o'zgaruvchi int *kursatkich = &yosh; → kursatkich ichida 2000 turadi
Xotiradagi har bir bayt o'z raqamiga - manziliga ega

& - manzilni olish #

C
#include <stdio.h>

int main(void) {
    int yosh = 25;

    printf("Qiymat  : %d\n", yosh);
    printf("Manzil  : %p\n", (void *) &yosh);
    printf("Hajmi   : %zu bayt\n", sizeof(yosh));

    return 0;
}
Natija
Qiymat  : 25
Manzil  : 0x7ffd4a2b3c5c
Hajmi   : 4 bayt
Manzil har safar boshqacha

Yuqoridagi 0x7ffd... raqami sizda boshqacha bo'ladi va har safar dasturni ishga tushirganda ham o'zgaradi.

Bu ataylab qilingan: operatsion tizim xavfsizlik uchun dasturni har safar xotiraning boshqa joyiga joylashtiradi (ASLR). Shuning uchun aniq manzilga hech qachon tayanmang.

Ko'rsatkich yaratish #

C
#include <stdio.h>

int main(void) {
    int yosh = 25;
    int *kursatkich = &yosh;    /* yosh ning manzilini saqlaydi */

    printf("yosh qiymati       : %d\n", yosh);
    printf("*kursatkich        : %d\n", *kursatkich);
    printf("Manzillar tengmi?  : %s\n", (kursatkich == &yosh) ? "ha" : "yo'q");

    *kursatkich = 30;           /* manzil orqali o'zgartiramiz */
    printf("O'zgartirgandan keyin yosh = %d\n", yosh);

    return 0;
}
Natija
yosh qiymati       : 25
*kursatkich        : 25
Manzillar tengmi?  : ha
O'zgartirgandan keyin yosh = 30
Ikki operator, ikki yo'nalish & - manzilni ol &yosh «yosh qayerda yotibdi?» qiymat → manzil * - qiymatni ol *kursatkich «bu manzilda nima bor?» manzil → qiymat
Ular bir-birining teskarisi
* belgisining ikki ma'nosi
C
int *kursatkich = &yosh;   /* 1. E'LON: "int ga ko'rsatkich" */
*kursatkich = 30;          /* 2. ISHLATISH: "manzildagi qiymat" */

Bu boshlovchilarni eng ko'p chalkashtiradigan narsa. E'londa * turning bir qismi, keyinroq esa amal.

Ba'zi dasturchilar * ni turga yaqin yozadi:

C
int* kursatkich;      /* ba'zilar shunday yozadi */
int *kursatkich;      /* C da odatiy shakl */

Ikkinchisi afzalroq, chunki bu chalkashlikning oldini oladi:

C
int* a, b;      /* a - ko'rsatkich, b - oddiy int! */
int *a, *b;     /* ikkalasi ham ko'rsatkich - aniq ko'rinadi */

Ko'rsatkich hajmi #

C
#include <stdio.h>

int main(void) {
    int son = 5;
    double kasr = 3.14;
    char harf = 'A';

    int *ki = &son;
    double *kd = &kasr;
    char *kc = &harf;

    printf("int *    : %zu bayt\n", sizeof(ki));
    printf("double * : %zu bayt\n", sizeof(kd));
    printf("char *   : %zu bayt\n", sizeof(kc));

    printf("Ular ko'rsatayotgan qiymatlar hajmi:\n");
    printf("  *ki : %zu bayt\n", sizeof(*ki));
    printf("  *kd : %zu bayt\n", sizeof(*kd));
    printf("  *kc : %zu bayt\n", sizeof(*kc));

    return 0;
}
Natija
int *    : 8 bayt
double * : 8 bayt
char *   : 8 bayt
Ular ko'rsatayotgan qiymatlar hajmi:
  *ki : 4 bayt
  *kd : 8 bayt
  *kc : 1 bayt
Barcha ko'rsatkichlar bir xil hajmda

64-bitli tizimda har qanday ko'rsatkich 8 bayt - u nimaga ko'rsatishidan qat'i nazar. Chunki u shunchaki manzil raqamini saqlaydi.

Unda int * va double * orasidagi farq nima?

Farq kompilyator uchun: u *kursatkich yozganingizda o'sha manzildan necha bayt o'qishni va ularni qanday talqin qilishni biladi.

NULL - hech narsaga ko'rsatmaydi #

C
#include <stdio.h>

int main(void) {
    int *kursatkich = NULL;

    if (kursatkich == NULL) {
        printf("Ko'rsatkich bo'sh - ishlatib bo'lmaydi\n");
    }

    int son = 42;
    kursatkich = &son;

    if (kursatkich != NULL) {
        printf("Endi ko'rsatmoqda: %d\n", *kursatkich);
    }

    return 0;
}
Natija
Ko'rsatkich bo'sh - ishlatib bo'lmaydi
Endi ko'rsatmoqda: 42
NULL ni ochish - dastur qulaydi
C
int *kursatkich = NULL;
printf("%d\n", *kursatkich);    /* SEGMENTATION FAULT */

Bu C dagi eng mashhur qulash sababi. Xabari odatda shunday bo'ladi:

Natija
Segmentation fault (core dumped)

Himoya: ko'rsatkichni ishlatishdan oldin tekshiring.

C
if (kursatkich != NULL) {
    printf("%d\n", *kursatkich);
}

Ishga tushirilmagan ko'rsatkich #

C
int *kursatkich;           /* qayerga ko'rsatishi noma'lum */
*kursatkich = 42;          /* XAVFLI: begona xotiraga yozadi */
Bu NULL dan ham xavfliroq

NULL ko'rsatkichni ochsangiz, dastur darhol qulaydi - siz xatoni darhol ko'rasiz.

Ishga tushirilmagan ko'rsatkichda esa axlat manzil bo'ladi. U:

  • boshqa o'zgaruvchini buzishi mumkin;
  • dastur ancha keyin, butunlay boshqa joyda qulashi mumkin;
  • ba'zan hech qanday alomat bermay ishlashi mumkin.

Bunday xatoni topish soatlab vaqt oladi.

Qoida: ko'rsatkichni doim ishga tushiring.

C
int *kursatkich = NULL;     /* xavfsiz boshlanish */

Funksiyaga ko'rsatkich uzatish #

Bu ko'rsatkichlarning eng muhim ishlatilishi.

C
#include <stdio.h>

void ozgartirmaydi(int son) {
    son = 100;
    printf("  nusxa ichida  : %d\n", son);
}

void ozgartiradi(int *son) {
    *son = 100;
    printf("  manzil ichida : %d\n", *son);
}

int main(void) {
    int qiymat = 5;

    ozgartirmaydi(qiymat);
    printf("Nusxa uzatildi     : %d\n", qiymat);

    ozgartiradi(&qiymat);
    printf("Manzil uzatildi    : %d\n", qiymat);

    return 0;
}
Natija
  nusxa ichida  : 100
Nusxa uzatildi     : 5
  manzil ichida : 100
Manzil uzatildi    : 100

Ikkala funksiya ham ichkarida 100 ni ko'rdi. Lekin faqat ikkinchisi asl o'zgaruvchini o'zgartira oldi.

Ikki qiymatni almashtirish #

C
#include <stdio.h>

void almashtir(int *a, int *b) {
    int vaqtinchalik = *a;
    *a = *b;
    *b = vaqtinchalik;
}

int main(void) {
    int birinchi = 10;
    int ikkinchi = 20;

    printf("Oldin  : %d, %d\n", birinchi, ikkinchi);
    almashtir(&birinchi, &ikkinchi);
    printf("Keyin  : %d, %d\n", birinchi, ikkinchi);

    return 0;
}
Natija
Oldin  : 10, 20
Keyin  : 20, 10
Endi scanf dagi & tushunarli
C
scanf("%d", &yosh);

scanf yosh ning manzilini oladi, chunki u o'qigan qiymatni o'sha yerga yozishi kerak. Manzilsiz u faqat nusxani o'zgartirar edi - bu esa foydasiz.

Endi nima uchun satr o'qiganda & kerak emasligi ham tushunarli bo'ladi: massiv nomi allaqachon manzil. Buni keyingi bo'limda ko'ramiz.

Bir nechta qiymat qaytarish #

C
#include <stdio.h>

void statistika(const int massiv[], int soni,
                int *eng_kichik, int *eng_katta, double *ortacha) {
    if (soni <= 0) {
        return;
    }

    int min = massiv[0];
    int maks = massiv[0];
    int yigindi = 0;

    for (int i = 0; i < soni; i++) {
        if (massiv[i] < min)  min = massiv[i];
        if (massiv[i] > maks) maks = massiv[i];
        yigindi += massiv[i];
    }

    *eng_kichik = min;
    *eng_katta = maks;
    *ortacha = (double) yigindi / soni;
}

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

    int min, maks;
    double ortacha;

    statistika(ballar, 6, &min, &maks, &ortacha);

    printf("Eng kichik : %d\n", min);
    printf("Eng katta  : %d\n", maks);
    printf("O'rtacha   : %.2f\n", ortacha);

    return 0;
}
Natija
Eng kichik : 61
Eng katta  : 95
O'rtacha   : 83.17

const va ko'rsatkichlar #

C
const int *a;        /* qiymatni o'zgartirib bo'lmaydi */
int *const b;        /* ko'rsatkichni o'zgartirib bo'lmaydi */
const int *const c;  /* ikkalasini ham */
C
#include <stdio.h>

void chiqar(const int *son) {
    printf("Qiymat: %d\n", *son);
    /* *son = 100;   XATO: const */
}

int main(void) {
    int qiymat = 42;
    chiqar(&qiymat);

    return 0;
}
Natija
Qiymat: 42
const int * ni qanday o'qish kerak

O'ngdan chapga o'qing:

YozuvO'qilishi
const int *aa - o'zgarmas int ga ko'rsatkich
int *const bb - int ga o'zgarmas ko'rsatkich
const int *const cc - o'zgarmas int ga o'zgarmas ko'rsatkich

Amalda birinchisi eng ko'p ishlatiladi: funksiya ma'lumotni o'qiydi, lekin o'zgartirmaydi.

Ko'rsatkichga ko'rsatkich #

C
#include <stdio.h>

int main(void) {
    int son = 42;
    int *k1 = &son;
    int **k2 = &k1;

    printf("son      : %d\n", son);
    printf("*k1      : %d\n", *k1);
    printf("**k2     : %d\n", **k2);

    **k2 = 100;
    printf("O'zgartirgandan keyin son = %d\n", son);

    return 0;
}
Natija
son      : 42
*k1      : 42
**k2     : 42
O'zgartirgandan keyin son = 100
Bu qayerda kerak?

Funksiya ko'rsatkichning o'zini o'zgartirishi kerak bo'lganda:

C
void xotira_ajrat(int **kursatkich, int soni) {
    *kursatkich = malloc(soni * sizeof(int));
}

int *massiv = NULL;
xotira_ajrat(&massiv, 10);      /* massiv endi yangi manzilga ko'rsatadi */

Agar int * uzatsak, funksiya faqat nusxani o'zgartirardi va massiv NULL bo'lib qolardi. Buni 14-bo'limda ko'ramiz.

Eng ko'p uchraydigan xatolar #

Beshta klassik xato

1. & ni unutish

C
int son;
scanf("%d", son);        /* & yo'q - qulaydi */

2. * ni unutish

C
void ozgartir(int *son) {
    son = 100;           /* ko'rsatkichni o'zgartirdi, qiymatni emas */
    *son = 100;          /* to'g'ri */
}

3. Ishga tushirilmagan ko'rsatkich

C
int *k;
*k = 5;                  /* qayerga yozilyapti - noma'lum */

4. NULL ni tekshirmaslik

C
int *k = topish(...);
printf("%d\n", *k);      /* topish() NULL qaytargan bo'lsa - qulaydi */

5. Mahalliy o'zgaruvchining manzilini qaytarish

C
int *xato(void) {
    int mahalliy = 42;
    return &mahalliy;    /* funksiya tugagach bu xotira yo'q bo'ladi */
}

Oxirgisi ayniqsa xavfli - kompilyator ogohlantiradi, lekin dastur ba'zan «ishlaydi» ham. Bu osilib qolgan ko'rsatkich (dangling pointer) deb ataladi.

Ko'rsatkichni tasavvur qilish #

Kalit tushuncha

Ko'rsatkichni uy manzili deb tasavvur qiling:

HayotdaC da
UyO'zgaruvchi
Uy manzili&yosh
Manzil yozilgan qog'ozKo'rsatkich
Manzil bo'yicha borib, uyni ko'rish*kursatkich
Bo'sh qog'ozNULL
Noto'g'ri manzil yozilgan qog'ozIshga tushirilmagan ko'rsatkich

Qog'ozni boshqa odamga bersangiz, u sizning uyingizga kela oladi - xuddi funksiyaga manzil uzatgandek. Uyning fotosuratini bersangiz, u faqat rasmga qaraydi - bu esa qiymat bo'yicha uzatish.

Amaliy topshiriq
  1. O'zgaruvchi yaratib, uning manzilini %p bilan chiqaring.
  2. Dasturni ikki marta ishga tushirib, manzillar farqini ko'ring.
  3. Ko'rsatkich yaratib, * orqali qiymatni o'zgartiring.
  4. Turli turdagi ko'rsatkichlar hajmini solishtiring.
  5. NULL ko'rsatkichni ochib, qulashni o'z ko'zingiz bilan ko'ring.
  6. Ikki sonni almashtiruvchi funksiya yozing.
  7. Uch qiymat qaytaruvchi funksiya yozing (min, maks, o'rtacha).
  8. const int * bilan qiymatni o'zgartirishga urinib, xatoni o'qing.
  9. Ko'rsatkichga ko'rsatkich yaratib, ** bilan qiymatga yeting.
  10. Mahalliy o'zgaruvchi manzilini qaytaruvchi funksiya yozib, kompilyator nima deyishini ko'ring.

Xulosa #

  • Xotiradagi har bir baytning manzili bor.
  • & - manzilni oladi, * - manzildagi qiymatni oladi.
  • Ko'rsatkich - bu manzilni saqlaydigan o'zgaruvchi.
  • Barcha ko'rsatkichlar bir xil hajmda (64-bitda 8 bayt).
  • Tur kompilyatorga necha bayt o'qishni aytadi.
  • NULL - «hech narsaga ko'rsatmaydi» degani; uni ochish qulashga olib keladi.
  • Ko'rsatkichni doim ishga tushiring - NULL bilan bo'lsa ham.
  • Ishlatishdan oldin NULL ga tekshiring.
  • Funksiyaga manzil uzatsangiz, u aslini o'zgartira oladi.
  • Shu tufayli funksiya bir nechta qiymat qaytara oladi.
  • Mahalliy o'zgaruvchining manzilini qaytarmang.

Keyingi bo'limda ko'rsatkichlar va massivlarning chuqur bog'liqligini ko'ramiz.

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.