Նախորդ զրույցներում ներկայացված օրինակներում ծրագիրն իր տվյալները կարդում էր ներմուծման ստանդարտ հոսքից և իր աշխատանքի արդյունքներն ուղարկում էր արտածման ստանդարտ հոսքին՝ այդ նպատակների համար օգտագործելով 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
հոսքում։