12-bo‘lim
Ko'rsatkichlar
Manzil va ko'rsatkich tushunchasi, & va * operatorlari, NULL, funksiyaga ko'rsatkich uzatish va eng ko'p uchraydigan xatolar.
Ushbu bo‘lim mundarijasi
- Xotira va manzil
- & - manzilni olish
- Ko'rsatkich yaratish
- Ko'rsatkich hajmi
- NULL - hech narsaga ko'rsatmaydi
- Ishga tushirilmagan ko'rsatkich
- Funksiyaga ko'rsatkich uzatish
- Ikki qiymatni almashtirish
- Bir nechta qiymat qaytarish
- const va ko'rsatkichlar
- Ko'rsatkichga ko'rsatkich
- Eng ko'p uchraydigan xatolar
- Ko'rsatkichni tasavvur qilish
- 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 #
& - manzilni olish #
#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;
}
Qiymat : 25
Manzil : 0x7ffd4a2b3c5c
Hajmi : 4 bayt
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 #
#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;
}
yosh qiymati : 25
*kursatkich : 25
Manzillar tengmi? : ha
O'zgartirgandan keyin yosh = 30
* belgisining ikki ma'nosiint *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:
int* kursatkich; /* ba'zilar shunday yozadi */
int *kursatkich; /* C da odatiy shakl */
Ikkinchisi afzalroq, chunki bu chalkashlikning oldini oladi:
int* a, b; /* a - ko'rsatkich, b - oddiy int! */
int *a, *b; /* ikkalasi ham ko'rsatkich - aniq ko'rinadi */
Ko'rsatkich hajmi #
#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;
}
int * : 8 bayt
double * : 8 bayt
char * : 8 bayt
Ular ko'rsatayotgan qiymatlar hajmi:
*ki : 4 bayt
*kd : 8 bayt
*kc : 1 bayt
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 #
#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;
}
Ko'rsatkich bo'sh - ishlatib bo'lmaydi
Endi ko'rsatmoqda: 42
NULL ni ochish - dastur qulaydiint *kursatkich = NULL;
printf("%d\n", *kursatkich); /* SEGMENTATION FAULT */
Bu C dagi eng mashhur qulash sababi. Xabari odatda shunday bo'ladi:
Segmentation fault (core dumped)
Himoya: ko'rsatkichni ishlatishdan oldin tekshiring.
if (kursatkich != NULL) {
printf("%d\n", *kursatkich);
}
Ishga tushirilmagan ko'rsatkich #
int *kursatkich; /* qayerga ko'rsatishi noma'lum */
*kursatkich = 42; /* XAVFLI: begona xotiraga yozadi */
NULL dan ham xavfliroqNULL 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.
int *kursatkich = NULL; /* xavfsiz boshlanish */
Funksiyaga ko'rsatkich uzatish #
Bu ko'rsatkichlarning eng muhim ishlatilishi.
#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;
}
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 #
#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;
}
Oldin : 10, 20
Keyin : 20, 10
scanf dagi & tushunarliscanf("%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 #
#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;
}
Eng kichik : 61
Eng katta : 95
O'rtacha : 83.17
const va ko'rsatkichlar #
const int *a; /* qiymatni o'zgartirib bo'lmaydi */
int *const b; /* ko'rsatkichni o'zgartirib bo'lmaydi */
const int *const c; /* ikkalasini ham */
#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;
}
Qiymat: 42
const int * ni qanday o'qish kerakO'ngdan chapga o'qing:
| Yozuv | O'qilishi |
|---|---|
const int *a | a - o'zgarmas int ga ko'rsatkich |
int *const b | b - int ga o'zgarmas ko'rsatkich |
const int *const c | c - 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 #
#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;
}
son : 42
*k1 : 42
**k2 : 42
O'zgartirgandan keyin son = 100
Funksiya ko'rsatkichning o'zini o'zgartirishi kerak bo'lganda:
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 #
1. & ni unutish
int son;
scanf("%d", son); /* & yo'q - qulaydi */
2. * ni unutish
void ozgartir(int *son) {
son = 100; /* ko'rsatkichni o'zgartirdi, qiymatni emas */
*son = 100; /* to'g'ri */
}
3. Ishga tushirilmagan ko'rsatkich
int *k;
*k = 5; /* qayerga yozilyapti - noma'lum */
4. NULL ni tekshirmaslik
int *k = topish(...);
printf("%d\n", *k); /* topish() NULL qaytargan bo'lsa - qulaydi */
5. Mahalliy o'zgaruvchining manzilini qaytarish
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 #
Ko'rsatkichni uy manzili deb tasavvur qiling:
| Hayotda | C da |
|---|---|
| Uy | O'zgaruvchi |
| Uy manzili | &yosh |
| Manzil yozilgan qog'oz | Ko'rsatkich |
| Manzil bo'yicha borib, uyni ko'rish | *kursatkich |
| Bo'sh qog'oz | NULL |
| Noto'g'ri manzil yozilgan qog'oz | Ishga 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.
- O'zgaruvchi yaratib, uning manzilini
%pbilan chiqaring. - Dasturni ikki marta ishga tushirib, manzillar farqini ko'ring.
- Ko'rsatkich yaratib,
*orqali qiymatni o'zgartiring. - Turli turdagi ko'rsatkichlar hajmini solishtiring.
NULLko'rsatkichni ochib, qulashni o'z ko'zingiz bilan ko'ring.- Ikki sonni almashtiruvchi funksiya yozing.
- Uch qiymat qaytaruvchi funksiya yozing (min, maks, o'rtacha).
const int *bilan qiymatni o'zgartirishga urinib, xatoni o'qing.- Ko'rsatkichga ko'rsatkich yaratib,
**bilan qiymatga yeting. - 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 -
NULLbilan bo'lsa ham. - Ishlatishdan oldin
NULLga 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.
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.