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

Զրույց տասներորդ

Նախորդ զրույցներում ներկայացված օրինակներում ծրագիրն իր տվյալները կարդում էր ներմուծման ստանդարտ հոսքից և իր աշխատանքի արդյունքներն ուղարկում էր արտածման ստանդարտ հոսքին՝ այդ նպատակների համար օգտագործելով stdio գրադարանի, օրինակ, scanf, puts և printf ֆունկցիաները։ Բայց «իսկական» ծրագրերում տվյալները հաճախ պահպանվում են ֆայլերում (կամ տվյալների բազաներում), և «իսկական» ծրագրավորողը պետք է տիրապետի ծրագրավորման լեզուների այն միջոցներին, որոնք հնարավորություն են տալիս կարդալ գոյություն ունեցող ֆայլի պարունակությունը և ստեղծել նոր ֆայլեր՝ դրանց մեջ գրելով պահպանման ենթակա տվյալները։

C ծրագրավորման լեզվում ֆայլի հետ աշխատանքը կազմված է երեք հիմնական քայլերից․ ա) բացել ֆայլը և ստանալ նրա դեսկրիպտորը, բոլոր այլ գործողությունները կատարվելու են ֆայլային դեսկրիպտորի հետ բ) ստացված գեսկրիպտորն օգտագործելով որպես ֆայլի հետ աշխատելու «կապ»՝ գրել ֆայլի մեջ կամ կարդալ նրանից, գ) փակել ֆայլի դեսկրիպտորը՝ ազատելով դրա զբաղեցրած ռեսուրսները։

Ստանդարտ գրադարանի stdio.h վերնագրային ֆայլում սահմանված է FILE տիպը։ Ֆայլերի հետ աշխատող ֆունկցիաներն օգտագործում են FILE տիպի ցուցիչը որպես ֆայլերի դեսկրիպտոր։ Օրինակ, եթե ուզում եմ inp անունը հայտարարել որպես ֆայլի դեսկրիպտոր, պետք է գրեմ․

FILE* inp = NULL;

Տրված անունով ֆայլի դեսկրիպտորը ստանալու համար պետք է օգտագործել ֆայլը բացելու fopen ֆունկցիան։ Այս ֆունկցիայի առաջին արգումենտը բացվելիք ֆայլի անունն է, իսկ երկրորդը՝ ֆայլի հետ աշխատելու ռեժիմը։ Ահա նրա հայտարարությունը․

FILE* fopen(const char*, const char*);

Եթե ֆայլը բացելը ձախողվել է, ապա fopen ֆունկցիան վերադարձնում է NULL արժեքը։

Ֆայլը կարելի է բացել կարդալու, գրելու կամ լրացնելու ռեժիմներում։ Կարդալու ռեժիմում բացելու համար fopen ֆունկցիայի երկրորդ արգումենտը պետք է տալ "r" (read բառից)։ Օրինակ, ուզում եմ example0.txt անունով ֆայլը բացել կարդալու համար․

const char* name = "example0.txt";
FILE* inp = fopen(name, "r");
if( inp == NULL )
    printf("ՍԽԱԼ։ ՝%s՝ ֆայլի բացելը ձախողվեց։\n", name);

Գրելու և լրացնելու ռեժիմներով ֆայլը բացելու համար պետք է openf ֆունկցիայի երկրորդ արգումենտում տալ համապատասխանաբար "w" (write) և "a" (append)։

Ֆայլի դեսկրիպտորը փակելու համար ստանդարտ գրադարանը տրամադրում է fclose ֆունկցիան։ Ահա հայտարարությունը․

int fclose(FILE*);

Հիմա այն մասին, թե ինչպես կարդալ ֆայլային հոսքից և գրել դրա մեջ։ Եթե հոսքը բացված է կարդալու համար, ապա fgetc ֆունկցիան հնարավորություն է տալիս դրանից կարդալ մեկ նիշ։ Այս ֆունկցիան արգումենտում ստանում է հոսքի դեսկրիպտորը և վերադարձնում է դրանից կարդացած հերթական նիշը։

int fgetc(FILE*);

Եթե ֆայլում էլ կարդալու բան չկա՝ կարդալու գործողությունը հասել է ֆայլի վերջին, ապա fgetc ֆունկցիան վերադարձնում է EOF (end of file ― ֆայլի վերջը) հաստատուն արժեքը։

Օրինակ, inp փոփոխականին կապված հոսքից բոլոր նիշերը կարդալու է արտածման հոսքին ուղարկելու համար կարող եմ գրել հետևյալ ցիկլը։

char c = '\0';
while( EOF != (c = fgetc(inp)) )
    putchar(c);

Ֆայլի վերջը ստուգելու համար ստանդարտ գրադարանն ունի feof ֆունկցիան։ Դրա օգտագործմամբ վեր բերված ցիկլը կարող եմ գրել հետևյալ կերպ։

char c = fgetc(inp);
while( !feof(inp) ) {
    putchar(c);
    c = fgetc(inp);
}

Գրելու կամ լրացնելու համար բացված ֆայլային հոսքում մեկ նիշ գրելու համար է նախատեսված ստանդարտ գրադարանի fputc ֆունկցիան։ Սրա առաջին արգումենտը նիշն է, իսկ երկրորդը՝ դեսկրիպտորը։ Եթե գրելու գործողությունը հաջողվում է, ապա fputc ֆունկցիան վերադարձնում է գրած նիշը, հակառակ դեպքում՝ EOF արժեքը։

Եթե, օրինակ, inp դեսկրիպտորը կապված է կարդալու հոսքի հետ, իսկ out դեսկրիպտորը՝ գրելու, ապա inp-ի պարունակությունը out-ի մեջ պատճենելու համար պարզապես պետք է գրել․

char c = fgetc(inp);
while( !feof(inp) ) {
    fputc(c, out);
    c = fgetc(inp);
}

Այսպիսով, ես արդեն բավականաչափ նյութ պատմեցի, որպեսզի ցույց տամ, թե ինչպես կարելի է գրել Linux համակարգերում ֆայլը պատճենող cp հրամանի «նմանակ» ծրագիրը։ Ստորև ներկայացնում եմ ամբողջական ծրագիրը՝ համապատասխան մեկնաբանություններով։

/* ներմուծման/արտածման ստանդարտ գրադարան */
#include <stdio.h>

/**/
int main(int argc, char** argv)
{
  /* ծրագիրը սպասում է ճիշտ երկու արգումենտ */
  if( argc != 3 ) {
    printf("Սխալ արգումենտների քանակ։\nՊետք է գրել․\n");
    printf("  %s <ֆայլ> <պատճեն>\n", argv[0]);
    return 1;
  }

  /* պատճենվող ֆայլը */
  FILE* inp = fopen(argv[1], "r");
  if( inp == NULL ) {
    printf("ՍԽԱԼ։ `%s` ֆայլի բացելը ձախողվեց։\n", argv[1]);
    return 2;
  }

  /* պատճենի ֆայլը */
  FILE* out = fopen(argv[2], "w");
  if( out == NULL ) {
    printf("ՍԽԱԼ։ `%s` ֆայլի ստեղծումը ձախողվեց։\n", argv[2]);
    fclose(inp); /* փակել արդեն բացված դեսկրիպտորը */
    return 3;
  }

  /* պատճենելու ցիկլը */
  char c = fgetc(inp);  /* կարդալ առաջին նիշը*/
  /* քանի դեռ ֆայլի վերջը չէ */
  while( !feof(inp) ) {
    fputc(c, out); /* գրել նիշը */
    c = fgetc(inp); /* կարդալ հերթական նիշը */
  }

  /* փակել դեսկրիպտորները */
  fclose(out);
  fclose(inp);

  return 0;
}

Ֆայլի պարունակության հետ նիշ առ նիշ աշխատելու համար, իհարկե, բավարար են fgetc և fputc ֆունկցիաները։ Դրանց հաջորդական կանչերով նույնիսկ կարելի է ֆայլից ամբողջական տողեր կարդալ, կամ ֆայլում ամբողջական տողեր գրել։ Բայց ստանդարտ գրադարանում սահմանված fgets և fputs ֆունկցիաները հենց նախատեսված են այդ գործը հեշտացնելու համար։

fgets ֆունկցիան stream հոսքից կարդում է առավելագույնը count-1 քնակությամբ նիշեր ու դրանք գրում buffer զանգվածում։ Հաջողության դեպքում վերադարձնում է buffer-ը, իսկ ձախողման դեպքում՝ NULL։

char* fgets(char* buffer, int count, FILE* stream);

fputs ֆունկցիան շատ նման է արդեն հիշատակված puts ֆունկցիային, որը տրված տողը դուրս էր բերում արտածման ստանդարտ հոսքին։ fputs ֆունկցիան str տողն արտածում է տրված stream հոսքին։ Ձախողման դեպքում այս ֆունկցիան վերադարձնում է EOF արժեքը, իսկ հաջողության դեպքում՝ ոչ-բացասական թիվ։

int fputs(const char* str, FILE* stream);

Ֆորմատավորված ներմուծման ու արտածման համար նույնպես նախատեսված են scanf և printf ֆունկցիաների ֆայլային տարբերակները՝ fscanf և fprintf ֆունկցիաները։

int fscanf(FILE* stream, ...);
int fprintf(FILE* stream, const char* format, ...);

Ի դեպ, ավելի ճիշտ է ասել, որ հենց scanf, printf և puts ֆունկցիաներն են fscanf, fprintf և fputs ֆունկցիաների մասնավոր դեպքերը։ Բանն այն է, որ stdio գրադարանում սահմանված են երեք ստանդարտ ֆայլային դեսկրիպտորներ՝ stdin ― ներմուծման, stdout― արտածման և stderr ― սխալների հոսքերի համար։ Եվ scanf ֆունկցիան կարդում է stdin հոսքից, իսկ printf ու puts ֆունկցիաները գրում են stdout հոսքում։