Զրույցներ C ծրագրավորման լեզվի մասին

Զրույց երկրորդ

Այս զրույցում ես պատմում եմ C լեզվի փոփոխականների, դրանց հայտարարման ու արժեքավորման մասին։ Ինչպես նաև այն մասին, թե ինչպես փոփոխականին վերագրել ստեղնաշարից ներմուծված արժեքը։

C լեզվով գրված ամեն մի ծրագիր, բացառությամբ պարզագույն դեպքերի, պարունակում է փոփոխականներ։ Փոփոխականը մի օբյեկտ է, որը ծրագրի կատարման տարբեր պահերի կարող է պարունակել նախապես որոշված արժեքների տիրույթի (domain) մի որևէ արժեք։ Յուրաքանչյուր փոփոխական, որ հանդիպում է ծրագրում, պետք է նախապես սահմանված լինի։ Փոփոխականի սահմանումը բաղկացած է նրա տիպը որոշող ծառայողական բառից և փոփոխականի անունից։ Օրինակ,

double a;      /* a-ն կրկնակի ճշտությամբ իրական թիվ է */
float b, c;    /* b-ն և c-ն սովորական ճշտության իրական թվեր են */
int d0, d1;    /* d0-ն և d1-ը ամբողջ թվեր են */
char sym;      /* sym-ը նիշ է (character) */
long int e1a;  /* e1a-ն երկար ամբողջ թիվ է */

Այստեղ double, float, int, char և long բառերը ծառայողական բառեր են և ներդրված տիպերի անուններ։ Իսկ a, b, c, d0, d1, sym և e1a բառերը իդենտիֆիկատորներ են։ C լեզվի իդենտիֆիկատորը տառով կամ ընդգծման նիշով՝ «_», սկսվող տառերի ու թվանշանների հաջորդականություն է։ Պետք է հիշել, որ C լեզվում մեծատառերն ու փոքրատառերը տարբերվում են, այսինքն՝ abc0 և aBc0 բառերը տարբեր իդենտիֆիկատորներ են։

Փոփոխականի տիպով որոշվում է հիշողությունում նրա զբաղեցրած բայթերի քանակը և նրա հետ կատարվող թույլատրելի գործողությունները։ Օրինակ, իմ համակարգում int տիպ ունեցող փոփոխականը զբաղեցնում է 4 բայթ, double տիպ ունեցող փոփոխականը՝ 8 բայթ, իսկ char տիպի փոփոխականը՝ 1 բայթ։ Ծրագրի կատարման ժամանակ փոփոխականի զբողեցրած հիշողության չափը կարելի է ստանալ sizeof գործողության օգնությամբ։ Օրինակ, եթե a0double տիպի փոփոխական է, ապա sizeof(a0) արտհայտության արժեքը 8 է։ sizeof գործողության արգումենտը կարող է լինել ոչ միայն փոփոխականի անուն, այլ նաև C լեզվի տիպի անուն։ Օրինակ, sizeof(unsigned int) արտահայտության արժեքը առանց նշանի ամբողջ թվերը որոշող տիպի չափն է։

Փոփոխականին կարելի է արժեք վերագրել հենց անմիջապես հայտարարության ժամանակ։ Ընդհանրապես, ծրագրավորման «լավ ոճ» է համարվում, երբ փոփոխականին արժեք է վերագրվում հայտարարության հետ միասին։ Օրինակ․

int count = 0;       /* հայտարարել count ամբողջաթիվ փոփոխականը՝ 0 արժեքով */
double pi = 3.1415;  /* հայտարարել pi իրական փոփոխականը՝ 3.1415 արժեքով */
char c = 'A';        /* հայտարարել c նիշային փոփոխականը՝ «A» արժեքով */

Փոփոխականին նոր արժեք է տրվում = վերագրման գործողությամբ։ Օրինակ,

double a0, a1, b0;  /* a0-ն, a1-ը և b0-ն իրական թվեր են */
a0 = 2.36;          /* a0-ին վերագրել 2.36 */
a1 = 4.1;           /* a1-ին վերագրել 4.1 */
b0 = a0 + a1;       /* b0-ին վերագրել a0-ի և a1-ի գումարը */

Ծրագրի կատարման ժամանակ ամեն մի հայտարարված փոփոխականի հատկացվում է հիշողության մի որոշակի տիրույթ։ Այդ տիրույթի առաջին բայթի համարը փոփոխականի հասցեն է, որը կարող ենք ստանալ & միտեղանի (unary) գործողությամբ։ Օրինակ, &pi արտհայտության արժեքը pi փոփոխականի հասցեն է։

Ես ուզում եմ ցույց տալ ու մեկնաբանել մի ծրագիր, որն օգտագործողից պահանջում է ներմուծել հարթության մի որևէ կետի դեկարտյան կոորդինատները և արտածում է նույն այդ կետի բևեռային կոորդինատները։

Պարզ է, որ կետի դեկարտյան կոորդինատները ներկայացնելու համար պետք է հայտարարել x և y իրական փոփոխականները, ապա դրանց արժեքները կարդալ ստեղնաշարից։

double x = 0.0, y = 0.0;
scanf( "%lf", &x );  /* կարդալ իրական թիվ և վերագրել x-ին */
scanf( "%lf", &y );  /* նույնը՝ y-ի համար */

C լեզվի ստանդարտ գրադարանի scanf (scan formated ― ֆորմատավորված ընթերցում) ֆունկցիան ներմուծման ստանդարտ հոսքից (stdin) կարդում է նիշերի հաջորդականություն և այն ձևափոխում է ըստ տրված ֆորմատի։ scanf ֆունկցիայի առաջին արգումենտը ֆորմատավորման տողն է, որ կարող է պարունակել % նիշով սկսվող ֆորմատավորման հրահանգներ։ % նիշին հաջորդող նիշերով որոշվում է, թե ինչպես պետք է մեկնաբանվեն կարդացած տվյալները։ Օրինակ, եթե գրված է %d, ապա սա նշանակում է, որ հոսքից կարդացած նիշերը պետք է դիտարկել որպես տասական (decimal) թիվ։ Եթե գրված է %f, ապա՝ սովորական ճշտության իրական (float) թիվ։ Ֆորմատավորման հրահանգներին համապատասխան ձևափոխված տվյալները գրվում են scanf ֆունկցիայի երկրորդ և հաջորդ արգումենտներով տրված հասցեներում։ Օրինակ.

scanf( "%lf", &x );

արտահայտության մեջ տրված "%lf" ֆորմատը նշում է, որ պետք է կարդալ երկար իրական թիվ (l - long, f - float) և կարդացած արժեքը գրել x փոփոխականի զբաղեցրած հասցեում։ scanf ֆունկցիային փոխանցվում է ոչ թե փոփոխականը, այլ նրա հասցեն՝ այն տեղը, որտեղ պետք է գրել կարդացած և ֆորմատավորած տվյալները։

scanf և printf ֆունկցիաների ֆորմատավորման հրահանգների մասին ես դեռ պատմելու շատ առիթներ կունենամ։ Առայժմ բավական է իմանալ, որ "%lf" ֆորմատը կարդում է double արժեք։

x և y փոփոխականների արժեքները կարդացող scanf ֆունկցիայի երկու կանչերը կարելի է միավորել մեկի մեջ՝ "%lf" ֆորմատը փոխարինելով "%lf,%lf" ֆորմատով։ Սա նշանակում է, որ scanf ֆունկցիան ստեղնաշարից կարդալու է իրարից ստորակետով բաժանված երկու double արժեք։

double x = 0.0, y = 0.0;
scanf( "%lf,%lf", &x, &y );  /* կարդալ երկու իրական թվեր և վերագրել x-ին ու y-ին */

Դե քանի որ scanf ֆունկցիայի ֆորմատավորման տողը պարունակում է երկու ֆորմատավորման հրահանգ, ապա պետք է տալ կարդացած արժեքները գրելու երկու տեղ՝ &x և &y։

Եթե պետք լինի օգտագործողին ստիպել, որ նա կետի կոորդինատները ներմուծի ( և ) փակագծերի մեջ վերցրած, ապա կարելի է ֆորմատավորման տողը գրել "(%lf,%lf)" տեսքով։ Բառացիորեն սա կարելի է կարդալ հետևյալ կերպ. «կարդալ ( նիշը, կարդալ double թիվ, կարդալ , նիշը, կարդալ double թիվ, կարդալ ) նիշը»։

Կետի x աբսցիսի և y օրդինատի արժեքները կարդալուց հետո պետք է հաշվել նրա բևեռային կոորդինատները՝ ρ շառավիղը և φ ազիմուտը։ Կոորդինատների բևեռային համակարգում ρ-ն որոշվում է որպես կետի հեռավորությունն բևեռից, իսկ φ-ն՝ որպես շառավղի և բևեռային առանցքի կազմած անկյուն։ Բևեռային շառավիղը որոշվում է Պյութագորասի թեորեմով՝ քանի որ այն իրենից ներկայացնում է ուղղանկյուն եռանկյան ներքնաձիգ։ Բևեռային անկյունը որոշելու համար էլ օգտվում ենք նույն ուղղանկյուն եռանկյունից և այն փաստից, որ որոնելի անկյան տանգենսը հավասար է նրա դիմացի էջի երկարության հարաբերությանը կից էջի երկարությանը՝ y/x, դե իսկ անկյան մեծությունն էլ հավասար կլինի այդ վերջին հարաբերության արկտանգենսին։

կոորդինատներ

Ասվածը C լեզվով կարող եմ գրել հետևյալ արտահայտություններով․

double rho = sqrt( x * x + y * y );
double phi = atan2( y, x );

sqrt ֆունկցիան վերադարձնում է արգումենտի երկրորդ աստիճանի արմատը, իսկ atan2 ֆունկցիան վերադարձնում է 0 սկզբնակետ և (x,y) վերջնակետ ունեցող վեկտորի և աբսցիցների առանցքի կազմած անկյունը ռադիաններով։ Այս երկու ֆունկցիաներն էլ սահմանված են C լեզվի ստանդարտ գրադարանի math.h ֆայլում։

rho և phi արժեքների հաշվարկից հետո պարզապես պետք է արտածել դրանք․

puts( "Բևեռային կոորդնատներն են․ " );
printf( "ρ = %lf, φ = %lf\n", rho, phi );

printf ֆունկցիայում նույնպես օգտագործվել են "%lf" ֆորմատավորման հրահանգները։ Քանի որ printf ֆունկցիան իր արգումենտները չի փոխում, այստեղ նրան փոխանցում ենք x և y փոփոխականների արժեքները, այլ ոչ թե հասցեները։

Վերջ։ Մնում է այս ամենը հավաքել main ֆունկցիայի մեջ, կոմպիլյացնել, գործարկել ու տեսնել արդյունքները։ Ես ամբողջ ծրագիրը գրել եմ prog02.c ֆայլի մեջ։

#include <stdio.h>
#include <math.h>

int main()
{
  double x = 0.0, y = 0.0;
  puts( "Ներմուծիր դեկարտյան կոորդինատները x,y․ " );
  scanf( "%lf,%lf", &x, &y );

  double rho = sqrt( x * x + y * y );
  double phi = atan2( y, x );

  puts( "Բևեռային կոորդնատներն են․ " );
  printf( "ρ = %lf, φ = %lf\n", rho, phi );

  return 0;
}

#include դիրեկտիվով կցվել են stdio.h և math.h ֆայլերը։ Առաջինից օգտագործվում են puts, scanf և printf ֆունկցիաները, իսկ երկրորդից՝ sqrt և atan2 ֆունկցիաները։

Երբ ես փորձում եմ prog02.c ֆայլը կոմպիլյացնել այնպես, ինչպես դա արեցի առաջին զրույցում նկարագրված prog01.c ֆայլի հետ.

$ clang prog02.c -o prog2

ապա ստանում եմ մի հաղորդագրություն, որն ասում է, թե linker ծրագիրը՝ կապերի խմբագրիչը, չի գտել sqrt և atan2 ֆունկցիաները։

/tmp/prog02-80b543.o: In function `main':
prog02.c:(.text+0x6a): undefined reference to `sqrt'
prog02.c:(.text+0x91): undefined reference to `atan2'
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Բանն այն է, որ math.h ֆայլը պարունակում է մաթեմատիկական գրադարանի հայտարարությունները, բայց ոչ սահմանումները։ Սահմանումները, այսինքն մաթեմատիկական գրադարանի կոմպիլյացված օբյեկտային կոդը, գտնվում է libm.a (ստատիկ) և libm.so (դինամիկ) օբյեկտային ֆայլերում։ ՈՒրեմն, երբ ծրագրի ֆայլում #include դիրեկտիվով կցում ենք math.h ֆայլը, դա անում ենք, պարզպես, որ կոմպլյատորն իմանա օգտագործվող ֆունկցիաների հայտարարությունները։ Իսկ կապերի խմբագրման (link) ժամանակ, երբ պետք է ստացվի կատարվող ֆայլ, կոմպիլյատորի -l պարամետրով պիտի տալ m գրադարանը (գրադարանների անունները սկսվում են lib նախածանցով, բայց -l պարամետրով գրադարանը նշելիս lib-ը չի գրվում)։

prog02.c ֆայլի կոմպիլյացիայի՝ վերը բերված հրամանն իրականում կատարվում է չորս քայլով՝ նախամշակում (preprocessing), կոմպիլյացիա (compilation), ասեմբլացում (assembly) և կապակցում (linking)։ Նախամշակման փուլում մշակվում են # նիշով սկսվող հրահանգները. #include հրահանգով կցվում են ֆայլեր, #define հրահանգով սահմանվում են մակրոսներ և այլն (նախամշակման քայլի մասին մանրամասն կխոսեմ քիչ ավելի ուշ)։ Կոմպիլյացիայի փուլում C լեզվով գրված ծրագիրը թարգմանվում է ասեմբլերի լեզվով գրված ծրագրի։ Ասեմբլացման փուլում ասեմբլերի լեզվով ծրագիրը թարգմանվում է կոնկրետ ապարատային պլատորմի վրա աշխատող կոնկրետ օպերացիոն համակարգի օբյեկտային կոդի։ Կապակցման փուլում իրար են կցվում առանձին-առանձին կոմպիլյացված օբյեկտային մոդուլները, ստեղծվում է կատարվող մոդուլ։ Եթե ուզում ես տեսնել, թե այդ միջանկյալ քայլերում ինչ ֆայլեր են գեներացվում, ապա կարող ես կոմպիլյացիայի հրամանին տալ --save-temps պարամետրը.

$ clang --save-temps -o prog02 prog02.c -lm

Այս հրամանի կատարումից հետո ստեղծվում են prog02.i, prog02.s, prog02.o, prog02 ֆայլերը։ Դրանք համապատասխանաբար նախամշակման, կոմպիլյացիայի և ասեմբլացման փուլերում ստեղծված ֆայլերն են։ Կապակցման արդյունքում ստացված ֆայլը ստանում է կոմպիլյատորի ՝-օ՝ պարամետրով տրված անունը։

Կոմպիլյացիայի քայլերը ես էլ կարող եմ առանձնացնել։

  1. clang -E -o prog02.i prog02.c ― Նախամշակել, բայց չկոմպիլյացնել։ Ստեղծվում է prog02.i ֆայլը։
  2. clang -S -o prog02.s prog02.i ― Կոմպիլյացնել նախամշակված ֆայլը, բայց չասեմբլացնել։ Ստեղծվում է prog02.s ֆայլը։
  3. clang-3.5 -c -o prog02.o prog02.s ― Ասեմբլերային ֆայլը թարգմանել օբյեկտային ֆայլի։ Ստեղծվում է prog02.o ֆայլը։
  4. clang -o prog02 prog02.o -lm ― Օբյեկտային ֆայլը կապակցել գրադարանների հետ և ստեղծել prog02 կատարվող մոդուլը։

Հիմա արդեն ամեն ինչ կարգին է․ ֆայլը թարգմանվել է և կառուցվել է prog02 կատարվող ֆայլը։ Գործարկեմ այն ու տեսնեմ, թե ինչ է ստացվում․

$ ./prog02
Ներածիր դեկարտյան կոորդինատները x,y․
3,2
Բևեռային կոորդնատներն են․
ρ = 3.605551, φ = 0.588003

Հենց որ ծրագիրն առաջարկում է ներածել x և y դեկարտյան կոորդինատները, ես ներածում եմ 3,2 թվերը։ Դրան ի պատասխան ծրագիրն արտածել է ρ = 3.605551 և φ = 0.588003 արժեքները։

Այս զրույցի համար էլ այսքանը։ Հաջորդ զրույցում կպատմեմ ֆունկցիաների սահմանման, դրանց արգումենտների ու վերադարձրած արժեքի մասին։