1. Sissejuhatus
Miks käsitleda massiive argumentidena C-keeles
C-keeles programme kirjutades on sageli vaja massiive teistes funktsioonides kasutada. Näiteks andmekogumite hulgitöötlus või suurte andmemahtude otsimine ja sorteerimine muutub tõhusamaks, kui massiive töödeldakse eraldi funktsioonides, parandades koodi taaskasutatavust.
Massiivi argumendina edastamine muudab koodi modulaarseks ja võimaldab funktsioonide kaupa eraldada konkreetseid funktsioone. See mitte ainult ei hõlbusta iga funktsiooni iseseisvat testimist ja silumist, vaid võimaldab ka mitmel arendajal samaaegselt tõhusalt töötada.
Käesolevas artiklis selgitatakse algajatele arusaadavalt, kuidas C-keeles massiive argumentidena edastada ja millistele punktidele tuleb seejuures tähelepanu pöörata. Käsitleme nii ühemõõtmelisi kui ka mitmemõõtmelisi massiive, tuues näiteid praktiliste koodilõikudega.
Sihtlugeja
See artikkel on mõeldud nii algajatele, kes on C-keelt alles õppimas, kui ka kesktasemel programmeerijatele, kes on juba omandanud põhikasutuse. Artikli kaudu õpid mitte ainult massiivide ja funktsioonide põhitõdesid, vaid saad teadmisi ka massiivide mäluhalduse ja osutite kohta, et koostada tõhusamaid programme.
2. Massiivide ja osutite põhiteadmised
Massiivi nime ja osuti seos
C-keeles on massiiv lihtsalt järjestikune mälupiirkond, kuid massiivi nimi ise viitab spetsiaalselt massiivi esimesele aadressile. Näiteks kui deklareerida int array[5];
, siis array
tähistab esimese elemendi aadressi (&array[0]
). Seetõttu piisab massiivi funktsioonile edastamiseks selle nimest, mis annab funktsioonile esimese elemendi aadressi.
Selle seose mõistmine on oluline, sest C-keeles on massiivi nimi ja osuti tihedalt seotud, eriti kui on vaja mõista osutiaritmeetikat.
Massiivi nime ja osuti erinevused
Kuigi massiividel ja osutitel on sarnasusi, on ka olulisi erinevusi. Massiivil on fikseeritud suurus, samas kui osuti saab viidata eri suurusega või erinevates mälukohtades asuvatele andmetele. Lisaks paiknevad massiivi elemendid mälus järjestikku, kuid osuti ei pruugi alati viidata järjestikusele mälule.
Järgmine koodinäide selgitab massiivi ja osuti seost:
#include <stdio.h>
void printArray(int *arr, int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
}
int main() {
int array[5] = {1, 2, 3, 4, 5};
printArray(array, 5); // Massiivi esimese elemendi aadressi edastamine
return 0;
}
Selles näites võtab printArray
funktsioon vastu massiivi esimese elemendi aadressi ja väljastab järjest kõik elemendid.
3. Ühemõõtmelise massiivi edastamine funktsioonile
Põhietapid esimese aadressi edastamiseks
C-keeles saab massiivi elemente funktsioonis töödelda, kui edastada massiivi esimese elemendi aadress. Kuna edastatakse ainult aadress, mitte massiivi koopia, on see mälu- ja ajakulu poolest tõhus meetod.
#include <stdio.h>
void modifyArray(int *arr, int size) {
for (int i = 0; i < size; i++) {
arr[i] *= 2; // Kahekordistab massiivi elemendid
}
}
int main() {
int array[5] = {1, 2, 3, 4, 5};
modifyArray(array, 5);
for (int i = 0; i < 5; i++) {
printf("%d ", array[i]);
}
return 0;
}
Selles koodis muudab modifyArray
funktsioon massiivi elementide väärtusi, kasutades edastatud esimese elemendi aadressi.
Massiivi elementide arvu edastamine
Massiivi edastamisel tuleb funktsioonile anda kaasa ka elementide arv, sest C-keeles puudub sisseehitatud viis massiivi pikkuse määramiseks. Ülaltoodud näites edastatakse size
muutujana.

4. Mitmemõõtmeliste massiivide edastamine funktsioonile
Kahemõõtmeliste massiivide eripärad
C-keeles saab ridade arvu funktsiooni parameetris jätta määramata, kuid veergude arvu tuleb alati täpsustada. Seetõttu tuleb kahemõõtmelise massiivi edastamisel funktsioonile määrata veergude arv.
#include <stdio.h>
void print2DArray(int arr[][3], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
int main() {
int array[2][3] = {{1, 2, 3}, {4, 5, 6}};
print2DArray(array, 2);
return 0;
}
Siin on funktsioonis määratud veergude arv 3
, mis võimaldab õigesti elemente lugeda.
Muudetava pikkusega massiivide kasutamine (C99 ja uuem)
C99 standardist alates saab funktsioonides kasutada muutuva pikkusega massiive (VLA), määrates rea- ja veeruarvu parameetritena.
#include <stdio.h>
void printFlexibleArray(int rows, int cols, int arr[rows][cols]) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
int main() {
int array[2][3] = {{1, 2, 3}, {4, 5, 6}};
printFlexibleArray(2, 3, array);
return 0;
}
VLA-de kasutamine võimaldab dünaamilisemalt määrata mitmemõõtmeliste massiivide mõõtmeid funktsiooni sees.
5. Massiivi argumendina edastamise tähelepanekud
Mõõtmete haldamise tähtsus
Kui massiiv edastatakse funktsioonile, võib vale mõõdu kasutamine põhjustada mälukasutuse raiskamist või puhvripiire ületavaid kirjutusi (buffer overflow). Seetõttu tuleb massiivi mõõtmeid hoolikalt hallata ja tsüklite tingimustes veenduda, et ei ületataks massiivi piire.
Dünaamilised massiivid ja mäluhaldus
Dünaamilise mälu eraldamiseks kasutatakse malloc
ja free
funktsioone, et määrata mälu kuhjapiirkonnas (heap). Funktsioonides dünaamilisi massiive kasutades tuleb alati meeles pidada mälu vabastada, et vältida mälulekkeid.
Massiivi muutmine funktsioonis ja kõrvaltoimed
Kui massiiv edastatakse funktsioonile, mõjutavad funktsioonis tehtavad muudatused ka väljaspool funktsiooni olevat massiivi. Kui see pole soovitav, tuleks luua massiivist koopia või kujundada lahendus nii, et muudatused ei leviks väljapoole funktsiooni.
6. Korduma kippuvad küsimused (KKK)
Kuidas saada massiivi suurust?
C-keeles ei ole võimalik funktsiooni sees massiivi suurust otse teada saada. Seetõttu tuleb massiivi suurus edastada funktsiooni argumendina. Näiteks: void myFunction(int *arr, int size)
, kus koos massiiviga antakse kaasa ka selle suurus.
Mõõtmete arvutamiseks saab massiivi definitsiooni juures kasutada valemit sizeof(array) / sizeof(array[0])
. Näide:
#include <stdio.h>
void printArray(int *arr, int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
}
int main() {
int array[] = {1, 2, 3, 4, 5};
int size = sizeof(array) / sizeof(array[0]); // Arvutab elementide arvu
printArray(array, size);
return 0;
}
Selles näites arvutatakse massiivi elementide arv ja edastatakse see funktsioonile koos massiiviga.
Dünaamiliste massiivide kasutamise küsimused
Dünaamilised massiivid on kasulikud, kui massiivi suurus pole ette teada või soovitakse mälu kasutada paindlikult. Selleks eraldatakse mälu funktsioonidega malloc
või calloc
ning vabastatakse see töö lõppedes funktsiooniga free
.
Näide dünaamilisest massiivist:
#include <stdio.h>
#include <stdlib.h>
void fillArray(int *arr, int size) {
for (int i = 0; i < size; i++) {
arr[i] = i * 2; // Omistab väärtused massiivi elementidele
}
}
int main() {
int size = 5;
int *array = (int *)malloc(size * sizeof(int)); // Dünaamiline mälueraldus
if (array == NULL) {
printf("Mälu eraldamine ebaõnnestus.\n");
return 1;
}
fillArray(array, size);
for (int i = 0; i < size; i++) {
printf("%d ", array[i]);
}
free(array); // Mälu vabastamine
return 0;
}
Siin kasutatakse malloc
funktsiooni mälu eraldamiseks ja free
funktsiooni selle vabastamiseks, et vältida mälulekkeid.
Kumb valida: massiiv või osuti?
Massiivid ja osutid on omavahel tihedalt seotud, kuid nende valik sõltub eesmärgist. Kui andmete suurus on fikseeritud ja teada, on massiiv sobivam. Kui aga on vaja töödelda muutuvat andmemahtu või hallata mälu dünaamiliselt, on osuti parem valik.
Suurte või muutuva suurusega andmestruktuuride puhul pakuvad osutid suuremat paindlikkust ja võimalust mälu otse hallata, mis on C-keele üks tugevusi.
7. Kokkuvõte
Põhipunktid massiivide argumendina edastamisel
Käesolevas artiklis käsitlesime, kuidas edastada massiive C-keeles funktsioonidele – alates ühemõõtmelistest kuni mitmemõõtmelisteni – koos praktiliste koodinäidetega. Massiivide edastamine argumentidena suurendab koodi modulaarust ja taaskasutatavust, kuid mitmemõõtmeliste ja dünaamiliste massiivide puhul on oluline mõista osutite ja mäluhalduse põhimõtteid.
Oluline on hoolikas mõõtmete haldamine ja mälu vabastamine, kui kasutatakse dünaamilist eraldust. Nende aspektide jälgimine aitab tõsta C-keeles kirjutatud programmide töökindlust ja jõudlust.