Заагч
·
Заагч хэмээх үгийн утга санаа
·
Динамик
массив
·
Олон
хэмжээст динамик массив
Энэ удаагийн
лекцээр бид програмчлалын хэлний нэгэн чухал ойлголт болох заагчийн тухай үзнэ.
Заагч нь Си хэл төдийгүй Си++ хэлэнд чухал үүргийг гүйцэтгэдэг юм.
Заагч гэж юу
болох талаар ойлголт авахын тулд эхлээд хувьсагч гэж юу болох талаар санах
хэрэгтэй (бид хувьсагчийн тухай “Си хэлний үндсэн ойлголтууд” лекц дээр үзсэн).
Хувьсагч гэдэг
бол утгыг нь өөрчлөх боломжтой, тодорхой төрөлд хамаарах, нэр бүхий өгөгдөл юм.
Хувьсагчийг ашиглахын тулд эхлээд түүнийг зарлана. Зарлагдсан хувьсагчтай
түүний нэрээр дамжуулан ажиллана. Ж.нь хувьсагчид утга оноохын тулд түүний
нэрэнд утга оноож, хувьсагчийн утгыг хэвлэхийн тулд түүний нэрийг гаралтанд
өгдөг:
...
int i;
i=10;
printf (“%d”, i);
...
Ингээд хөрвүүлэгчээр эх кодыг боловсруулах үед хөрвүүлэгч нь зарлагдсан хувьсагчид зориулан санах ойд “байр” гаргаж өгдөг. “Байрны” хэмжээ буюу нүдийн тоо хувьсагчийн төрлөөр (int, double г.м.) тодорхойлогдоно. Түүнчлэн хөрвүүлэгч нь эх кодонд бичигдсэн хувьсагчийн нэрийг санах ой дахь “байрны” хаягаар нь сольдог. Хаяг (address) гэдэг нь санах ойн нүдний дугаар бөгөөд эерэг бүхэл тоо байдаг. Хэрэв хувьсагчид утга оноосон бол тэр нь харгалзах хаягаар орших “байранд” очиж сууна.
Ө.х. хөрвүүлсний дараагаар, хувьсагчийн нэр нь санах ойн “байрны” хаяг, харин хувьсагчийн утга нь тэр “байранд” байрлах ёстой утга болон хувирдаг байна. Үүнийг схемчлэн үзүүлвээс:
|
Эх код |
|
|
|
|
|
|
|
Хувьсагч |
|
Нэр |
Утга |
|
|
|
Санах ой дахь “байр” |
|
Хаяг |
Утга |
|
|
Машины код |
|
|
|
|
|
Хувьсагчийн нэр,
утга ба санах ойн хаягийн хоорондын улдаа холбоог ойлгохын тулд дараах жишээг
харцгаая:
char ch=’G’;
unsigned int date=1979;
float niilber=2007.18;
Энд гурван өөр төрлийн хувьсагчийг дараалан зарлаж байна. Тэгвэл уг кодыг санах ой оролцуулан схемчилж дүрсэлбэл:
|
Санах ойн хаягууд |
1A2B16 |
1A2C16 |
1A2D16 |
1A2E16 |
1A2F16 |
1A3016 |
1A3116 |
1A3216 |
|
“Байрны” хэмжээ |
байт |
байт |
байт |
байт |
байт |
байт |
байт |
байт |
|
Утга |
‘G’ |
1979 |
2007.18 |
|
||||
|
Хувьсагчийн нэр |
ch |
Date |
Niilber |
|
||||
Энд үзүүлснээр
бол зарлагдсан хувьсагчууд санах ойд 1A2B16 гэсэн хаяг бүхий
нүдээс эхлэн байрлажээ. Яг ямар хаяг бүхий нүдээс байрлуулж эхлэхийг хөрвүүлэгч
өөрөө шийдэж байгаа. Бүхэл char төрлийн ch хувьсагч 1 байт хэмжээтэй “байр” эзэлсэн бөгөөд
хаяг нь 1A2B16, бүхэл
unsigned int төрлийн date
хувьсагч 2 байтын хэмжээтэй “байр” эзэлсэн бөгөөд хаяг нь 1A2C16,
бодит float төрлийн niilber хувьсагч 4 байтын хэмжээтэй “байр”
эзэлж, хаяг нь 1A2E16 болно. Эндээс харахад хаяг бол өөрөө бас тоон утга байна. Тэгэхээр түүнийг
ямар нэг хувьсагчид утга болгон оноож болно гэсэн үг. Си хэлэнд хаягийг ихэвчлэн 16-тын тоогоор бичдэг.
Ингэж санах ойн
хаягийг утга болгон оноох зориулалттай хувьсагчийг Си хэлэнд заагч төрлийн хувьсагч (pointer-type variable) буюу товчоор
зүгээр л заагч (pointer)
гэж нэрлэдэг байна.
Яагаад заагч
хэмээн нэрийдсэн юм бол оо? Ямар нэг юмыг заадаг учраас л тэр байх. Чухам юуг
заадаг болохоор тэр вэ?
Заагчийн утга нь
санах ойн дахь хаяг байдаг гэдгийг дахин хэлье. Үүнийг, заагч нь өгөгдсөн хаяг
бүхий санах ойн “байрыг” зааж байна хэмээн хэлж болох нь. Эсвэл, тэр “байранд”
орших өгөгдлийг (утгыг) зааж байгаа гэж хэлж бас болно.
Заагч төрлийн
хувьсагчийг ашиглахын тулд мэдээж эхлээд түүнийг зарлах хэрэгтэй. Гэхдээ энд
нэг онцлог бий. Юу гэвээс, заагч
хувьсагчийг хамааруулах бие даасан өгөгдлийн төрөл гэж байхгүй ажээ. Ө.х. заагчийн
төрлийг илэрхийлсэн тусгай албаны үг Си хэлэнд байдаггүй. Тэгвэл яаж зарлах вэ?
Заагчийг дараах
загварын дагуу зарлана:
өгөгдлийн_төрөл
*заагчийн_нэр;
Энд:
·
өгөгдлийн_төрөл – заагчийн зааж буй өгөгдлийн төрөл (заагчийн
төрөл биш). Ж.нь char, short, int, long болон
эдгээрийн unsigned хэлбэрүүд, float, double байж болно.
·
заагчийн_нэр – заагчийг нэрлэхийн тулд програм зохиогчийн сонгож авсан чөлөөт
идентификатор.
Заагчийг зарлахдаа түүний
заах ёстой өгөгдлийн төрлийг эхэлж бичдэг болох нь харагдаж байна. Тэгээд
тусгаарлагч * тэмдгийг бичээд, ард нь заагчийн нэрийг бичиж
байна. Жишээ авч үзье:
char *z;
int *k;
float *f;
double *ptr;
Энд z бол char төрлийн өгөгдлийг зааж буй заагч, k бол int төрлийн өгөгдлийг зааж буй заагч, f бол float төрлийн өгөгдлийг зааж буй заагч, ptr бол double төрлийн өгөгдлийг зааж буй заагч.
Өөр нэгэн жишээ:
int *k, *i;
Энд k болон i нь int төрлийн өгөгдлүүдийг зааж буй заагчууд юм. Ө.х. нэгэн ижил өгөгдлийн төрлийг заасан хоёр заагчийг зэрэг зарлаж байна. * тэмдэг бол заагчийн нэртэй хамт явах ёстой юм. Тиймээс олон заагчийг зэрэг зарлах тохиолдолд заагч бүрийн нэрний өмнө * тэмдгийг бичих ёстой гэдэг нь дээрх жишээнээс харагдаж байна.
Уг нь заагчийг илэрхийлэх
бие даасан өгөгдлийн төрөл байхгүй гэдгийг өмнөх сэдвийн эхэнд дурдсан байгаа. Тэгвэл
бүгдээрээ дараах жишээг авч үзье:
char *z;
Си хэлний хөрвүүлэгч нь дээрх операторыг, char * гэсэн төрөлд хамаарах z гэсэн заагч-хувьсагчийг зарлаж байна гэж үздэг байна. Тэгэхээр:
int *k;
float *f;
double *ptr;
г.м. операторууд бол харгалзан int *, float*, double * гэсэн төрлийн заагч хувьсагчуудыг зарлаж байна гэсэн үг.
Эндээс ямар дүгнэлт
хийж болох вэ? Хэдийгээр заагчийг илэрхийлэх бие даасан төрөл байхгүй ч, стандарт
өгөгдлийн төрлүүдийг ашиглан заагчийг төрөлжүүлж болдог байх нь ээ.
Заагч төрлийн хувьсагчийг
зарлахад оролцож буй * тэмдгийг
“хаягаар хандах үйлдэл” хэмээн нэрийддэг.
Хаягаар хандах үйлдэл нь унар үйлдэл (оператор) байна. Учир нь түүний зүүн талд ямар нэг операнд бичигдээгүй, харин баруун талд нэг операнд бичигдсэн байна. Тэр нь нөгөө зарлагдсан заагч маань юм. Харин заагчийн утга бол хаяг байна. Ө.х.
![]()
Тиймээс хаягаар хандах үйлдлийн үр дүн нь тухайн хаягаар орших (ө.х. заагчийн зааж буй) “байр” буюу эсвэл тэр “байранд” байрлах өгөгдөл байна. Түүний төрөл нь зарлах бичиглэлд буй өгөгдлийн төрөл байна. Ж.нь:
char *z;
гэсэн операторт бол *z нь z-ийн зааж буй өгөгдөл
юм. Түүний төрөл нь char байна. Зураглан
харуулваас:

Үүний
адилаар:
int *k, *i;
гэсэн операторт бол *k ба *i нь харгалзан k, i-ийн зааж буй өгөгдлүүд юм. Тэдгээрийн төрөл нь int байна.
Зарлагдсан
заагчийг ашиглахаасаа өмнө заагчаа идэвхжүүлэх нь зүй. Заагчийн авах утга бол
мэдээж санах ойн хаяг байж таарна.
Заагчид хэд хэдэн
янзаар утга оноож болно. Эдгээрийн заримтай танилцъя.
Заагчид ямар нэг
хувьсагчийн хаягийг оноохын өмнө эхлээд тэр хаягаа олж тодорхойлох хэрэгтэй. Үүний
тулд хаяг олох үйлдлийг (reference
operation) хийнэ. Энэ нь &
гэсэн унар үйлдэл байдаг. Үйлдэлд оролцох операнд нь ямагт
хувьсагч байх ёстой. Ө.х.:
&хувьсагч
Үйлдлийн үр
дүн нь хувьсагчийн хаяг байх болно. Тиймээс:
заагч = &хувьсагч;
Ингэснээр хувьсагчийн санах ой дахь хаяг нь заагчийн
утга болно. Ж.нь дараах кодыг
харцгаая:
int a, *p; /* a гэсэн бүхэл хувьсагч ба p гэсэн заагч зарлаж байна */
p = &a; /*
a-ийн хаягийг p-д оноож байна */
Энд p
заагчид a хувьсагчийн хаягийг оноохын тулд эхлээд тэр
хувьсагчаа зарлаж байна. Ингэснээр, a хувьсагч маань санах ойд тодорхой хаяг
бүхий “байртай” болно. Тиймээс a-ийн хаягийг p-д оноох боломжтой болж байгаа юм.
Заагчид санах ойн
хаягийг шууд тоон утга хэлбэрээр нь оноож болно. Ө.х.:
заагч = хаяг;
Ж.нь дараах кодыг
харцгаая:
char *ptr; /* бүхэл char төрлийн өгөгдлийг
заасан ptr заагчийг зарлаж байна */
ptr = (char *) 0xB8000000; /* түүнд 0xB8000000 гэсэн тоон утгыг оноож байна */
Энд 0xB8000000[1] бол 16-тын бүхэл тоо (Си хэлэнд 16-тын тоог бичихдээ урд нь 0x гэсэн угтвар залгадгийг сануулья). Ө.х. ийм дугаар бүхий хаягийг заагчид оноож байна.
Гэхдээ Си хэлний
хөрвүүлэгч нь заагчийг төрөлжсөн мэтээр ойлгодгийг санацгаая (“Заагчийг
төрөлжүүлэх нь” хэсгийг хар). Манай
тохиолдолд ptr заагч char * гэсэн төрөлтэй байна. Гэтэл
0xB8000000 бол unsigned long int
төрлийн тоо байна. Тийм учраас төрөл илээр хувиргах (unsigned
long int төрлийг char * төрөлд хувиргах) үйлдлийг дунд нь хийж өгсөн байна:
(char *) 0xB8000000
Заримдаа заагчид санах
ойн ямар ч нүдний хаягтай үл тэнцэх тийм утгыг оноож болно. Ийм утгыг хоосон
хаяг хэмээн нэрийддэг. Ө.х. байхгүй хаяг гэсэн үг.
Хоосон хаягийг
илэрхийлэхийн тулд stdio.h, stddef.h г.м. толгой файлуудад NULL хэмээх тусгай тогтмол тодорхойлогдсон байдаг. Түүнийг
тэг-заагч (null-pointer) гэнэ. ANSI-стандартад тэг-заагч нь 0 юм уу 0L гэсэн тоон утгатай байдаг. Ж.нь:
int *ea;
ea = NULL;
Програмд тэг-заагчийг,
өгөгдсөн заагч зөв идэвхжсэн байна
уу ө.х. санах ойн тодорхой “байрыг” зааж байна уу үгүй юу гэдгийг тогтооход ашиглах
боломжтой.
Заагчид бас өөр
нэг заагчийг утга болгон оноож болно. Ингэхийн тулд утга болж буй заагч нь
өөрөө эхлээд идэвхжсэн байх ёстой. Ж.нь:
int a, *p; /* a гэсэн бүхэл хувьсагч ба p гэсэн заагч зарлаж байна */
int* r; /* бүхэл өгөгдлийг заасан r гэсэн заагч зарлаж байна */
p = &a; /*
p заагчийг идэвхжүүлээд */
r = p; /* r заагчид утга болгон оноож байна */
Заагч нь өөрөө
хувьсагч юм чинь хөрвүүлэлтийн дараа мөн л санах ойд тодорхой “байранд” байрлаж
таарна. Энэ байр нь мэдээж хаягтай байж таарна. Тэр хаягийг нь бид өөр нэг
заагчид утга болгон оноож болно.
Ингэж заагчийн хаягийг
утга болгон авах зориулалттай заагчийг заагчийн
заагч (pointer to pointer) хэмээн нэрийддэг. Зарлахдаа:
өгөгдлийн_төрөл
**заагчийн_заагч;
гэж зарлана. Харин заагчийн хаяг бол хаяг олох оператороор (&) олдоно. Ж.нь:
char x, *ptr1, **ptr2; /* энгийн хувьсагч, заагч, заагчийн заагч
зарлаж байна */
x = ‘M’; /* энгийн хувьсагчид утга оноож байна */
ptr1 = &x; /* хувьсагчийн хаягийг заагчид оноож байна */
ptr2 = &ptr1; /* заагчийн хаягийг заагчийн заагчид оноож байна */
Энд ptr1
бол x-ийг зааж буй заагч, харин ptr2 бол ptr1-ийг зааж
буй заагч буюу заагчийн заагч байна:

Зарлагдаж байгаа
хэлбэрийг нь харахад заагчийн заагчийг бас л төрөлжүүлж болох байна (char **, int **, float **, double ** г.м.). Тухайлбал дээрх жишээний
ptr2 бол char ** гэсэн төрөлтэй юм.
Заагчийн заагч нь
өөрөө бас л хувьсагч байна. Тиймээс мэдээж санах ойд тодорхой хаягаар байрлана.
Тэр хаягийг нь олж өөр нэг заагчид оноож болно. Ингэж заагчийн заагчийн хаягийг
авах хувьсагчийг заагчийн заагчийн заагч
(pointer to pointer to pointer) гэнэ. Зарлахдаа:
өгөгдлийн_төрөл
***заагчийн_заагчийн_заагч;
гэж зарлана.
Гэх мэтээр бид заагчийн
... заагчийг тодорхойлж болно. Си хэлний хөрвүүлэгч заагчийн ... заагчийг мөн л
төрөлжүүлэн үздэг.
Заагч дээр дараах
үйлдлүүдийг хийж болно:
·
хаягаар
хандах үйлдэл
·
утга
оноох үйлдэл
·
төрөл
хувиргах үйлдэл
·
заагчийн
хаягийг олох үйлдэл
·
арифметик
үйлдлүүд
·
жиших
үйлдлүүд
Хаягаар хандах
үйлдэл нь заагчийн зааж буй санах ойн “байрыг” илэрхийлдэг гэдгийг бид мэдэж
авсан (“* тэмдгийн тухай” хэсгийг хар).
Тиймээс бид тэр “байр” луу утга “хийж” болно:
*заагч = утга;
Гэхдээ үүний тулд
эхлээд заагч өөрөө идэвхжсэн байх ёстой. Идэвхжүүлэх утга нь зөвхөн хувьсагчийн
хаяг байх ёстой. Эс тэгвээс алдаа заана. Ж.нь:
char a, *ptr; /* энгийн хувьсагч, заагч хоёр зарлаж байна */
ptr = &a; /* энгийн хувьсагчийн хаягийг олоод заагчид оноож байна */
*ptr = ‘Y’; /*
хаягаар хандах үйлдлээр заагчийн
заах “байранд” утга хийж байна */
printf (“%c”, *ptr);
/* заагчийн заах өгөгдлийг хэвлэж гаргая */
Энд *ptr нь char төрөлтэй учраас түүнд ‘Y’ гэсэн тэмдэгт утга оноож байгаа юм. Програмыг биелүүлэх юм дэлгэцэн дээр Y гэж гарах болно.
Тэмдэглэж хэлэх зүйл
байна. Дээрх жишээнд, *ptr бол ptr-ийн зааж буй санах ойн “байр” билээ. Гэтэл:
ptr = &a;
гэсэн үйлдлийн
дараа ptr нь a-гийн санах ой дахь “байрыг” заадаг болж байгаа. Ө.х. ptr заагч идэвхжсэний дараагаар *ptr = a болж байгаа юм. Тиймээс:
*ptr = ‘Y’;
гэсэн үйлдэл бол үнэн хэрэгтээ:
a = ‘Y’;
гэсэнтэй
адилхан ажээ. Гаралтанд *ptr-ийг
биш харин a-г өгөөд үүнийг мэдэж болно:
printf (“%c”, a);
Энэ нь
дэлгэцэн дээр мөн л Y гэж гаргах болно.
Заагчийн заагчийн
хувьд хаягаар хандах үйлдэл нь юуг илэрхийлэх вэ? Тухайлбал:
char **z;
гэсэн операторыг авч үзье. p=*z гэсэн орлуулга хийвэл **z-ийг *p гэж бичнэ. Мэдээж p бол z-ийн заах санах ойн “байрыг” илэрхийлнэ. Гэтэл z нь заагчийг заах ёстой учраас p нь заагч болж таарна. Харин *p бол p-ийн заах санах ойн “байрыг” илэрхийлнэ. Эндээс, **z бол z-ийн заах заагчийн заах санах ойн “байрыг” илэрхийлнэ гэдэг нь илт байна.
Тиймээс бид тэр
“байр” луу утга “хийж” болно:
**заагчийн_заагч = утга;
Гэхдээ үүний тулд эхлээд
заагчийн заагч өөрөө идэвхжсэн байх ёстой. Идэвхжүүлэх
утга нь зөвхөн ямар нэг заагчийн хаяг байх ёстой. Гэтэл үүний тулд тэр
заагч нь бас идэвхжсэн байх ёстой. Идэвхжүүлэх утга нь зөвхөн ямар нэг хувьсагчийн
хаяг байх ёстой. Эс тэгвээс алдаа заана. Ж.нь:
char x, *p1, **p2;
p1 = &x; /* заагч идэвхжүүлж байна */
p2 = &p1; /* заагчийн заагч идэвхжүүлж байна */
**p2 = 'M'; /* хаягаар хандах үйлдлээр заагчийн
заагчийн заах “байр” луу утга хийх */
printf
("%c", **p2); /* заагчийн
заагчийн заах өгөгдлийг
хэвлэж гаргая */
Энд байгаа:
**p2 = 'M';
гэсэн үйлдэл үнэн хэрэгтээ:
x = ‘M’;
гэсэнтэй
адилхан гэдгийг сануулъя. Яагаад тэгж байгаа билээ?
Мэдээж энэ бол
заагчид ямар нэг хаягийг утга болгож оноох үйлдэл. Ингэхдээ утга оноох оператор
“=”-г ашиглаж байгаа.
Хэрэв заагчид
оноох гэж буй утга нь заагчийн төрлөөс өөр төрөлтэй байвал яах вэ? Мэдээж төрөл
хувиргах үйлдлийг хийдэг байна. Ж.нь:
char *z; /* z заагчийн
төрөл нь char * байна */
int *k; /* k заагчийн
төрөл нь int * байна */
z = (char *) k; /* тийм учраас k-ийн төрлийг z-ийн төрөл рүү хувиргаж байна */
Өөр нэг жишээг “Заагчийг идэвхжүүлэх” хэсгийн “Ил хаяг оноох” гэсэн дэд хэсгээс харж болно.
Хаяг олох оператороор
заагчийн хаягийг олдог. Олсон утгыг заагчийн заагчид оноодог. Энэ тухай дээр
үзсэн (“Заагчийн ... заагч” хэсэгт болон энэ хэсгийн “Хаягаар хандах үйлдэл” дэд хэсэгт).
Заагч дээр хийх
арифметик үйлдлүүд бол:
·
++
·
--
·
+
·
-
гэсэн үйлдлүүд байна.
++ ба – гэсэн унар
үйлдлүүд заагчийн утгыг “нэгж уртаар” өөрчилдөг. “Нэгж урт” гэдэг нь заагчийн
зааж буй өгөгдлийн төрлийн урт (байтаар) юм. Бичих загвар нь:
заагч++ /* постфикс хэлбэр*/
++заагч /* префикс хэлбэр*/
заагч-- /* постфикс хэлбэр*/
--заагч /* префикс хэлбэр*/
Ж.нь дараах кодыг
харцгаая. Энд дандаа постфикс
хэлбэрүүдийг ашигласан болно:
char *cp, c;
long *lp, L;
double *dp, d;
cp = &c; /* cp заагчийг идэвхжүүлье */
cp++; /* утга нь 1-ээр нэмэгдэнэ, учир нь түүний заах char төрлийн урт 1 байт байдаг */
lp = &L; /* lp заагчийг идэвхжүүлье */
lp--; /* утга нь 4-өөр багасна, учир нь түүний заах long төрлийн урт 4 байт байдаг*/
dp = &d; /* dp заагчийг идэвхжүүлье */
dp++; /* утга нь 8-аар нэмэгдэнэ, учир нь түүний заах double төрлийн урт 8 байт байдаг */
Энд хэрэв cp-г идэвхжүүлэхэд утга нь 235915510 болсон гэвэл cp++;
гэсний дараа утга нь 235915610
болно гэсэн үг. Түүнчлэн lp=235914410 байсан гэвэл lp--; гэсний
дараа lp=235914010, dp=235912810 байсан гэвэл dp++; гэсний
дараа dp=235913610 болох юм.
|
|
|
+ ба – гэсэн бинар
үйлдлүүд нь заагч дээр бүхэл тоо нэмэх, хасах, мөн заагчаас заагчийг хасахад
хэрэглэгдэнэ. Заагч дээр заагчийг нэмэх үйлдэл гэж Си хэлэнд байдаггүй.
+ тэмдэг ашиглан
заагч дээр бүхэл тоо нэмдэг. Ингэхэд гарах нийлбэр нь бүхэл тоог “нэгж уртаар”
үржсэн хэмжээгээр заагчийн утгаас их байна. Ж.нь:
float *fp1, *fp2, f;
fp1 = &f; /*
эхлээд fp1 заагчийг
идэвхжүүлье */
fp2 = fp1 + 2; /* харин fp2-ын утга fp1-ээс 8-аар их болно */
Энд хэрэв fp1=235914810 байсан гэвэл fp2=235915610 болох юм. Учир нь fp1-ийн зааж буй float төрөл 4 байтын урттай байдаг.
Тиймээс 4 байтыг 2-оор үржээд 8 байт гарах юм.

- тэмдэг ашиглан
заагчаас бүхэл тоо хасахад гарах ялгавар бол бүхэл тоог “нэгж уртаар” үржсэн
хэмжээгээр заагчийн утгаас бага байна.
- тэмдэг ашиглан нэг
заагчаас нөгөө заагчийг хасч болдог. Ингэхэд гарах ялгавар нь заагчуудын утгын ялгаврыг
“нэгж уртад” хуваасан утга байна. Ж.нь:
float *fp1, *fp2, f1, f2;
int d;
fp1 = &f1; /* эхлээд fp1 заагчийг
идэвхжүүлье */
fp2 = &f2; /* дараа нь fp2 заагчийг
идэвхжүүлье */
d = fp1 - fp2; /* нэг заагчаас нөгөө заагчийг хасъя */
Энд хэрэв fp1=235914810,
fp2=235914410 байсан гэвэл d=(2359148-2359144)/4=1 гэж гарах болно.
Дээр дурдсан
үйлдлүүдийг заагч дээр хийхдээ үйлдлийн эрэмбийг сайтар анхаарах ёстой юм.
Унар үйлдлүүд
болох хаягаар хандах * үйлдэл, хаяг олох & үйлдэл мөн ++ ба -- гэсэн
үйлдлүүд (префикс хэлбэрүүд нь) адил эрэмбэтэй байдаг. Тиймээс илэрхийлэл дотор
зэрэгцэн байрласан тохиолдолд баруунаасаа зүүн тийш хийгдэж эхлэнэ. Ж.нь:
int d, *p;
p = &d;
*++p = 10;
Хамгийн сүүлийн мөрийг нягтлан харъя. Энд хаягаар хандах * үйлдэл ба префикс ++ гэсэн унар үйлдлүүд зэрэгцэн орсон байна. Ингээд баруун талаасаа эхлэн ++p хийгдээд p-ийн утга өмнөхөөсөө “нэгж утгаар” нэмэгдэнэ. Дараа нь энэхүү шинэ утгаар (хаягаар) хандан p-ийн заах байр луу 10 гэсэн утгыг хийж байна. Ө.х. *p=10. Бид энэ мөрийг:
++p;
*p = 10;
гэж задлан бичиж болох юм.
+ ба – гэсэн
бинар үйлдлүүд хоорондоо адил эрэмбэтэй, харин унар үйлдлүүдээс бага эрэмбэтэй
байдаг. Тиймээс илэрхийлэл дотор бинар үйлдлүүд зэрэгцэн орсон тохиолдолд
зүүнээсээ баруун тийш хийгдэж эхлэнэ. Харин унар ба бинар үйлдүүд зэрэгцэн
орсон тохиолдолд мэдээж унар үйлдлүүд түрүүлж хийгдэнэ. Ж.нь:
int *p, a=2;
p = &a;
a = *p+1;
Хамгийн
сүүлийн мөрийг нягтлан харъя. Эхлээд *p үйлдэл хийгдэнэ. p нь a-г заах учраас *p=2
гарна. Дараа нь түүн дээр 1-ийг нэмнэ. Ө.х. *p+1=3 гарна. Эцэст нь энэ утгаа эргүүлээд
a-д өгч байна. Тиймээс a=3 гарах юм.
Заагч дээр:
·
== -
тэнцүү
·
!= - тэнцүү биш
·
< -
бага
·
<=
- бага буюу тэнцүү
·
> -
их
·
>=
- их буюу тэнцүү
гэсэн жиших үйлдлүүдийг хийх боломжтой. Ө.х. заагч оролцсон нөхцөлт илэрхийлэл бичиж болно гэсэн үг.
Гэхдээ заагчийг зөвхөн түүнтэй адил төрлийн
заагчтай л жишиж болно. Эсвэл тэг-заагчтай (NULL) жишиж болно.
Бид массивтай
өмнө нь танилцаж байсан билээ. Одоо харин массив нь заагчтай ямар холбоотой
болохыг үзье.
Си хэлний дүрэм
ёсоор бол индексгүй бичигдсэн массивын нэр нь массивын хамгийн эхний элементийн
хаягийг илэрхийлдэг байна.
Ж.нь:
char a[10];
гэсэн
массив байна гэе. Энд:
·
a - массивын нэр
·
10 –
массивын элементийн тоо буюу урт.
Зураглан үзүүлвээс:

Энэ массивын хамгийн эхний элемент нь a[0] байна. Түүний
хаяг нь &a[0] болно. Тэгвэл дээр дурдсан ёсоор a бол &a[0]-ыг илэрхийлдэг
байна. Ө.х. a=&a[0] юм. Элементийн
хаяг бол тогтмол утга байх учраас a бол тогтмол хэмжигдхүүн байж таарна. Тиймээс a-ийн утгыг өөрчилж
болохгүй.
Одоо харин a-ийн
утга дээр “нэгж уртыг“ нэмээд үзье:
a+1
Манай тохиолдолд “нэгж урт” бол 1-тэй тэнцүү. Тиймээс
энэ нь a-ийн заах нүдний дараагийн нүдний хаяг болж таарна. Гэтэл a-ийн заах
нүдний дараагийн нүдэнд a[1] элемент байрлаж байгаа биз дээ (дээрх зургийг хар).
Тиймээс a+1=&a[1] байх нь ээ. Тиймээс энэ хаягаар хандалт хийвэл a[1]
элементэд хүрч чадна. Ө.х. *(a+1)=a[1] байх нь. Дахиад цаашаа хөдөлвөл *(a+2)=a[2] байна. Дахиад цаашаа
хөдөлвөл *(a+3)=a[3] байна. Г.м.-ээр бид a массивын дурын i-р элементэд *(a+i) гэж хандаж болно. Ө.х. бид a массивын дурын элементэд хандахдаа:
a[i]
гэсэн
индекстэй бичиглэлийн оронд заагч бүхий:
*(a+i)
гэсэн
бичиглэл ашиглаж болох юм.
Жишээ болгоод ийм
нэг бодлогыг авч үзье. Дээр зарлагдсан a массивыг индекс хэрэглэхгүйгээр 0-ээс
9 хүртэлх тоогоор идэвхжүүл. Үүнийг гүйцэтгэх програмын код нь:
#include <stdio.h>
main ( )
{
int
a[10];
int
i;
for
(i = 0; i <= 9; i++) *(a+i) = i; /*
массиваа идэвхжүүлэх */
for
(i = 0; i <= 9; i++) printf ("%d\n",
a[i]); /* гаралтын үйлдэл */
getchar
( );
}