C-keeles failikirjutamine: täielik juhend koos näidete ja vigade käsitlemisega

1. Sissejuhatus

Programmeerimises on failide lugemine ja kirjutamine üks väga olulisi toiminguid. C-keeles on oluline mõista faili avamise, andmete kirjutamise ja faili sulgemise põhiprintsiipe. Selles artiklis selgitame põhjalikult C-keeles failikirjutamise põhilisi meetodeid ja toome konkreetseid näiteid.

Faili kirjutamist kasutatakse andmete püsivaks salvestamiseks ja andmete jagamiseks teiste programmidega, mistõttu on see oskus väga oluline paljudes programmides. Failikäitlust C-keeles õppides muutub failikäitlus ka teistes programmeerimiskeeltes kergemini arusaadavaks. Selle artikli abil õpid nii põhilisi kirjutamisviise kui ka edasijõudnud vigade käsitlemist ning süvendad oma arusaamist failikäitlusest.

Järgmises peatükis selgitame faili avamise ja sulgemise toiminguid ning kirjutamisrežiime alates põhitasemest.

2. Faili kirjutamise põhitõed

Faili kirjutamiseks C-keeles tuleb esmalt fail avada. Faili avamisel on vaja määrata „mis eesmärgil” fail avatakse. C-keeles kasutatakse faili avamiseks funktsiooni fopen ja sulgemiseks funktsiooni fclose. Siin selgitame faili avamise ja sulgemise põhitööd ning kirjutamisrežiime.

fopen funktsiooni kasutamine

Faili avamiseks kasutatakse funktsiooni fopen, mis võtab argumentideks failinime ja režiimi (failikäitluse tüüp). fopen-i põhivorm on järgmine:

FILE *fopen(const char *filename, const char *mode);
  • filename: avatava faili nimi (tee)
  • mode: faili avamise viis (kirjutamine, lugemine, lisamine jms)

Kirjutamisrežiimide tüübid

Faili avamiseks on mitu režiimi. Siin on toodud peamised kirjutamisega seotud režiimid:

  • "w": Ainult kirjutamiseks. Kui fail juba eksisteerib, kustutatakse selle sisu. Kui fail puudub, luuakse uus.
  • "a": Ainult lisamiseks. Kui fail eksisteerib, lisatakse andmed lõppu. Kui fail puudub, luuakse uus.
  • "wb": Binaarse kirjutamise režiim. Kui fail eksisteerib, kustutatakse sisu ja kirjutatakse binaarvormingus. Kui fail puudub, luuakse uus.

Näide kirjutamisest

Allpool on toodud näide, kuidas luua uus fail ja sinna kirjutada. Kui fail juba eksisteerib, kustutatakse selle sisu ja kirjutatakse „w”-režiimis.

#include <stdio.h>

int main() {
    FILE *file = fopen("example.txt", "w"); // Ava fail "w"-režiimis
    if (file == NULL) {
        printf("Faili ei saanud avada.\n");
        return 1;
    }

    fprintf(file, "Tere, see on failikirjutamine C-keeles!\n"); // Kirjuta faili
    fclose(file); // Sulge fail
    printf("Faili kirjutamine on lõpetatud.\n");

    return 0;
}

Selles näites luuakse funktsiooniga fopen fail „example.txt” ja kirjutatakse fprintf-iga tekstiandmed. Pärast kirjutamist tuleb alati sulgeda fail fclose-iga, vastasel juhul võib juhtuda, et andmeid ei salvestata õigesti.

fclose funktsiooni olulisus

fclose on funktsioon, mida tuleb alati kutsuda pärast faili avamist. Faili sulgemine vabastab süsteemi ressursid ja tagab andmete salvestamise. Kui faili ei suleta ja programm lõpeb, võib kirjutamine katkeda. Harjuta alati faili sulgema pärast kasutamist.

Järgmises peatükis vaatleme üksikasjalikumalt, kuidas kirjutada teksti faili.

3. Tekstifaili kirjutamise meetodid

C-keeles saab teksti faili kirjutada kolmel viisil: märkhaaval, stringhaaval ja vormindatud andmete kaupa. Iga meetodi jaoks on olemas vastav funktsioon, mida saab kasutada vastavalt vajadusele. Selles jaotises selgitame funktsioonide fputc, fputs ja fprintf kasutamist.

fputc – ühe märgi kirjutamine

fputc kirjutab faili ühe märgi korraga. See on lihtne meetod, mida kasutatakse peamiselt siis, kui on vaja tegeleda üksikute märkidega. Süntaks on järgmine:

int fputc(int character, FILE *stream);
  • character: kirjutatav märk
  • stream: failipointer

fputc kasutamise näide

Allpool on näide, kus faili kirjutatakse üksikuid märke:

#include <stdio.h>

int main() {
    FILE *file = fopen("example.txt", "w");
    if (file == NULL) {
        printf("Faili ei saanud avada.\n");
        return 1;
    }

    fputc('A', file);
    fputc('B', file);
    fputc('\n', file);

    fclose(file);
    printf("Märkide kirjutamine on lõpetatud.\n");

    return 0;
}

Selles näites kirjutatakse faili tähed 'A' ja 'B' ükshaaval. Sobib väikeste andmete salvestamiseks.

fputs – stringi kirjutamine

fputs kirjutab faili terve stringi korraga. See on tõhus viis teksti salvestamiseks, kui ei ole vaja töötada märkhaaval. Süntaks:

int fputs(const char *str, FILE *stream);
  • str: kirjutatav string
  • stream: failipointer

fputs kasutamise näide

#include <stdio.h>

int main() {
    FILE *file = fopen("example.txt", "w");
    if (file == NULL) {
        printf("Faili ei saanud avada.\n");
        return 1;
    }

    fputs("See on näide fputs-funktsiooni kasutamisest.\n", file);

    fclose(file);
    printf("Stringi kirjutamine on lõpetatud.\n");

    return 0;
}

See meetod sobib, kui soovitakse kiiresti ja tõhusalt stringi faili salvestada.

fprintf – vormindatud andmete kirjutamine

fprintf on printf-i failiversioon, mis võimaldab kirjutada vormindatud andmeid. See sobib, kui on vaja salvestada nii numbreid kui ka teksti kindlas vormingus.

int fprintf(FILE *stream, const char *format, ...);
  • stream: failipointer
  • format: vormingustring koos formaadimäärangutega

fprintf kasutamise näide

#include <stdio.h>

int main() {
    FILE *file = fopen("example.txt", "w");
    if (file == NULL) {
        printf("Faili ei saanud avada.\n");
        return 1;
    }

    int number = 123;
    float decimal = 45.67;
    fprintf(file, "Täisarv: %d, Komaarv: %.2f\n", number, decimal);

    fclose(file);
    printf("Vormindatud andmete kirjutamine on lõpetatud.\n");

    return 0;
}

Siin salvestatakse nii täisarv kui ka komaarv etteantud vormingus.

Kokkuvõte

fputc, fputs ja fprintf on peamised funktsioonid tekstifaili kirjutamiseks C-keeles. Õige meetodi valimine sõltub andmete tüübist ja salvestusviisist.

4. Binaarfaili kirjutamise meetod

C-keeles saab lisaks tekstile salvestada ka binaarandmeid, näiteks pilte, helifaile või struktuure. Selleks kasutatakse fwrite-funktsiooni.

fwrite kasutamine

size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
  • ptr: viit kirjutatavale andmele
  • size: ühe elemendi suurus baitides
  • count: kirjutatavate elementide arv
  • stream: failipointer

Binaarrežiimis avamine

Binaarfaili avamiseks kasuta režiime nagu "wb" (kirjutamine) või "ab" (lisamine), et andmed salvestataks muutmata kujul.

fwrite näide

#include <stdio.h>

int main() {
    FILE *file = fopen("example.bin", "wb");
    if (file == NULL) {
        printf("Faili ei saanud avada.\n");
        return 1;
    }

    int data[] = {10, 20, 30, 40, 50};
    size_t dataSize = sizeof(data) / sizeof(data[0]);

    fwrite(data, sizeof(int), dataSize, file);

    fclose(file);
    printf("Binaarandmete kirjutamine on lõpetatud.\n");

    return 0;
}

Olulised tähelepanekud

  • Andmete ühilduvus: binaarandmete lugemine teises süsteemis võib põhjustada probleeme, kui arhitektuur erineb.
  • Endianness: erinevates süsteemides võib baitide järjestus olla erinev. Vajadusel tee teisendused.
  • Reavahetused: binaarrežiimis ei muudeta reavahetusi automaatselt.

Kokkuvõte

Binaarsete andmete kirjutamine on efektiivne viis salvestada keerulisi andmestruktuure. fwrite võimaldab seda teha kiiresti ja paindlikult.

5. Vigade käsitlemine

Failidega töötades võivad tekkida vead, näiteks kui fail puudub või puuduvad juurdepääsuõigused. Vigade korrektne käsitlemine aitab vältida ootamatut programmi käitumist ja parandab töökindlust. Selles jaotises selgitame C-keele failikäitluse veahaldust.

Faili avamise vea kontroll

Kui faili avamine fopen-iga ebaõnnestub, tagastab see NULL. Seda saab kasutada vea tuvastamiseks ja sobiva teate kuvamiseks.

Näide faili avamise vea kontrollist

#include <stdio.h>

int main() {
    FILE *file = fopen("example.txt", "r");
    if (file == NULL) {
        perror("Faili ei saanud avada");
        return 1;
    }

    // Failiga töötlemine
    fclose(file);

    return 0;
}

perror kuvab automaatselt süsteemipõhise vea kirjelduse, mis aitab põhjust tuvastada.

perror ja strerror kasutamine

  • perror: kuvab teate koos süsteemi veakirjeldusega standardväljundis stderr.
  • strerror: tagastab veakoodi tekstikirjelduse.

strerror näide

#include <stdio.h>
#include <string.h>
#include <errno.h>

int main() {
    FILE *file = fopen("nonexistent.txt", "r");
    if (file == NULL) {
        printf("Viga: %s\n", strerror(errno));
        return 1;
    }

    fclose(file);
    return 0;
}

Siin kasutatakse errno ja strerror, et saada ja kuvada vea kirjeldus.

Kirjutamisvigade tuvastamine ja käsitlemine

Faili kirjutamise ajal võib tekkida vigu, näiteks kettaruumipuuduse tõttu. ferror abil saab kontrollida, kas kirjutamisel tekkis viga.

Näide kirjutamisvea tuvastamisest

#include <stdio.h>

int main() {
    FILE *file = fopen("example.txt", "w");
    if (file == NULL) {
        perror("Faili ei saanud avada");
        return 1;
    }

    if (fprintf(file, "Andmete kirjutamine") < 0) {
        perror("Kirjutamisviga");
        fclose(file);
        return 1;
    }

    fclose(file);
    printf("Kirjutamine lõpetatud.\n");

    return 0;
}

Kui fprintf tagastab negatiivse väärtuse, käsitletakse seda veana ja väljastatakse teade.

Kokkuvõte

Veakäsitlus on oluline töökindlate programmide loomisel. Faili avamise või kirjutamise ajal ilmnenud probleemidele tuleb reageerida asjakohaste teadetega.

6. Rakendusnäited

Kui oled failikirjutamise põhialused omandanud, saad seda kasutada erinevates praktilistes olukordades, näiteks logifailide pidamisel, seadistusfailide loomisel ja andmete serialiseerimisel.

Logifaili kirjutamine

Logifailide abil saab jälgida programmi tööd ja vigu.

Näide logifaili kirjutamisest

#include <stdio.h>
#include <time.h>

void log_message(const char *message) {
    FILE *file = fopen("log.txt", "a");
    if (file == NULL) {
        perror("Logifaili ei saanud avada");
        return;
    }

    time_t now = time(NULL);
    struct tm *t = localtime(&now);

    fprintf(file, "[%04d-%02d-%02d %02d:%02d:%02d] %s\n",
            t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
            t->tm_hour, t->tm_min, t->tm_sec, message);

    fclose(file);
}

int main() {
    log_message("Programm käivitati.");
    log_message("Tekkis viga.");

    return 0;
}

Seadistusfaili loomine

Seadistusfailid salvestavad rakenduse seaded ja võimaldavad neid programmi käivitamisel laadida.

Näide seadistusfaili loomisest

#include <stdio.h>

void save_settings(const char *filename, int volume, int brightness) {
    FILE *file = fopen(filename, "w");
    if (file == NULL) {
        perror("Seadistusfaili ei saanud avada");
        return;
    }

    fprintf(file, "volume=%d\n", volume);
    fprintf(file, "brightness=%d\n", brightness);

    fclose(file);
}

int main() {
    save_settings("settings.conf", 75, 50);
    printf("Seadistusfail salvestatud.\n");

    return 0;
}

Andmete serialiseerimine ja deserialiseerimine

Serialiseerimine tähendab andmestruktuuri salvestamist faili kujul, et seda hiljem taastada.

Näide struktuuri salvestamisest ja laadimisest

#include <stdio.h>

typedef struct {
    int id;
    char name[50];
    float score;
} Student;

void save_student(const char *filename, Student *student) {
    FILE *file = fopen(filename, "wb");
    if (file == NULL) {
        perror("Faili ei saanud avada");
        return;
    }

    fwrite(student, sizeof(Student), 1, file);
    fclose(file);
}

void load_student(const char *filename, Student *student) {
    FILE *file = fopen(filename, "rb");
    if (file == NULL) {
        perror("Faili ei saanud avada");
        return;
    }

    fread(student, sizeof(Student), 1, file);
    fclose(file);
}

int main() {
    Student s1 = {1, "Taro Sato", 89.5};
    save_student("student.dat", &s1);

    Student s2;
    load_student("student.dat", &s2);
    printf("ID: %d, Nimi: %s, Tulemus: %.2f\n", s2.id, s2.name, s2.score);

    return 0;
}

Kokkuvõte

Logifailid, seadistusfailid ja serialiseerimine on tavalised failikirjutuse rakendused. Nende oskamine muudab programmeerija töö palju paindlikumaks.

7. Korduma kippuvad küsimused (KKK)

Faili ei saa avada

K: fopen tagastab NULL. Mida teha?
V: Kontrolli faili teed, õigusi, kettaruumi ja kasuta perror või strerror vea täpsustamiseks.

Kirjutamine ei kajastu failis

K: Faili kirjutatud andmed ei ilmu koheselt.
V: Sulge fail fclose-ga või kasuta fflush, et puhverdus koheselt tühjendada.

Teksti- ja binaarfailide erinevus

K: Mis vahe on?
V: Tekstifail salvestab märgid ja teeb võimalikud formaadimuutused (nt reavahetused), binaarfail salvestab andmed muutmata kujul.

Endianness probleemid

K: Erinevad süsteemid tõlgendavad binaarandmeid valesti.
V: Tee baitide järjekorra teisendus (htonl, htons) või lisa failiformaati endianness info.

8. Kokkuvõte

Selles artiklis käsitlesime C-keele failikirjutamist algtasemest kuni rakendusnäideteni. Õppisime failide avamist ja sulgemist (fopen, fclose), teksti- ja binaarfailide kirjutamist (fputc, fputs, fprintf, fwrite), vigade käsitlemist (perror, strerror) ning praktilisi kasutusviise (logid, seaded, serialiseerimine). Failitöötlus on oskus, mis on vajalik pea igas programmeerimisprojektis ja mille tundmine aitab kirjutada töökindlat koodi.