C - Tablica statyczna dwuwymiarowa w funkcji

0

Cześć, mam problem z działaniem na tablicy statycznej dwuwymiarowej w funkcji.
W poniższym kodzie próbuje zrozumieć działanie funkcji i wskaźników, docelowo mam napisany inny kod ale cały w main i chcę go poprawić. Zależy mi na umieszczeniu wzoru w funkcji, wzór ten wykorzystuje dwie tablice dynamiczne, tablice statyczną i kilka zmiennych. Wynikiem są zmienione wartości tylko w jednej tablicy dynamicznej. Nic nie zwracam bo wyniki z tej tablicy są potrzebne w dalszych obliczeniach.
Rozumiem jak wrzucić do funkcji zmienną jako kopie, zmienną jako wskaźnik, tablice dynamiczną dwuwymiarową jako wskaźnik, ale nie wiem jak umieścić i korzystać z:

  • tablicy statycznej dwuwymiarowej jako kopia
  • tablicy statycznej dwuwymiarowej jako wskaźnik
  • tablicy dynamicznej dwuwymiarowej jako kopia

Jako kopia rozumiem działanie na kopiach wartości, czyli po za funkcją nic się nie zmienia, natomiast jako wskaźnik rozumiem działanie na adresach wartości, czyli po wyjściu z funkcji i nie zwracaniu niczego zmiany są widoczne bo bazuje na adresach.

W poniższym kodzie mogę działać na tablicy dynamicznej, jednak nie wiem jak zaimplementować tablicę statyczną w funkcji. Ma ktoś czas pomóc?

Jestem samoukiem więc wszelkie uwagi odnośnie sposobu pisania kodu są bardzo mile widziane, dopiero zaczynam więc warto porzucić złe nawyki.

#include <stdio.h>
#include <stdlib.h>

void test_dynamiczna(int *p, int **tablica_dyn);
void test_statyczna(int *p, int **tablica_stat);

int main(){
    int p = 5;      // do mnożenia
    int w1 = 3;     // liczba wierszy
    int k = 2;      // liczba kolumn
    int i, j;       // do for
    int o = 1;      // do zapisywania

    // Alokacja pamięci
    int **tablica_dyn;
    tablica_dyn = (int **)malloc(w1 * sizeof(int *));
    for (i = 0; i < w1; i++){
        tablica_dyn[i] = (int *)malloc(k * sizeof(int));
    }
    // Zapisanie wartości do tablica_dyn
    for (i = 0; i < w1; i++){
        for (j = 0; j < k; j++){
            tablica_dyn[i][j] = o;
            o++;
        }
    }
    // Zapisanie wartości do tablica_stat
    int tablica_stat[3][2] = {
        {1 ,2},
        {3, 4},
        {5, 6}
    };

    
    // Wyświetlenie tablica_dyn przed zmianą
    for (i = 0; i < w1; i++){
       for (j = 0; j < k; j++){
          printf("%d  ", tablica_dyn[i][j]);
       }
       printf("\n");
    }
    printf("\n");
    // Wyświetlenie tablica_stat przed zmianą
    for (i = 0; i < w1; i++){
       for (j = 0; j < k; j++){
          printf("%d  ", tablica_stat[i][j]);
       }
       printf("\n");
    } 
    printf("\n\n");

    test_dynamiczna(&p, tablica_dyn);
    test_statyczna(&p, tablica_stat);


    // Wyświetlenie tablica_dyn po zmianach
    for (i = 0; i < w1; i++){
       for (j = 0; j < k; j++){
          printf("%d  ", tablica_dyn[i][j]);
       }
       printf("\n");
    }
    printf("\n");
    // Wyświetlenie tablica_stat po zmianach
    for (i = 0; i < w1; i++){
       for (j = 0; j < k; j++){
          printf("%d  ", tablica_stat[i][j]);
       }
       printf("\n");
    } 


    // Zwalnianie pamięci
    for (i = 0; i < w1; i++){
        free(tablica_dyn[i]);
    }
    free(tablica_dyn);
    tablica_dyn = NULL;

    return 0;
}


void test_dynamiczna(int *p, int **tablica_dyn){
    tablica_dyn[1][1] *= *p;
}

void test_statyczna(int *p, int **tablica_stat){
    tablica_stat[1][1] *= *p;
}
1

W języku C, nie da się przekazać tablicy przez wartość (a tym bardziej tablicy zagnieżdżonej).

Możesz przez wartości przekazywać natomiast struktury. Np.

struct MojaTablica {
    int tab[3][2];
};

Ale w ten sposób nie poradzisz sobie z tablicami tworzonymi dynamicznie, gdyż one tak naprawdę nie są tablicami (tylko wskaźnikami na ileś elementów).
To co możesz zrobić, to np. funkcję klonująca daną tablicę dynamiczną (tutaj zwracamy int**), albo funkcję która w dane miejsce w pamięci kopiuje tablicę zablokowaną automatycznie (którą ty nazywasz alokacją statyczną, co nie jest poprawne).

Nitpicks: malloc może się nie powieść. ZAWSZE należy sprawdzać czy wynik malloca to NULL, i zareagować na to jakoś. Wyświetlanie (obu tablic) można zrealizować za pomocą dedykowanej (jednej) funkcji.

0

Jak się przekazuje tablicę jednowymiarową do funkcji to następuje degradacja do wskaźnika. W przypadku tablic dwu i więcej wymiarowych degradacja do wskaźnika następuje tylko dla jednego wymiaru. Dla funkcji statycznej musisz więc podać jeden wymiar i musi się on zgadzać deklarację tablicy w funkcji wywołującej:

void test_statyczna(int *p, int (*tablica_stat)[2]){
    tablica_stat[1][1] *= *p;
}

tablica statyczna wielowymiarowa ma w pamięci po kolei ułożone wartości więc przekaż jako wskaźnik, jej wymiary i licz offset:

void test_statyczna(int* p, int* tablica_stat, int xdim, int ydim, int x, int y) {
    int offset = xdim*y + x; // <- coś w tym stylu, ale nie testowane z powodu chwilowego spadku IQ :-)
    tablica_stat[offset] *= *p;
}
1

Uważaj na terminologię, bo słowo "statyczne zmienne" ma znaczenie inne niż ci się wydaje.

Niestety w C tablice kopiowane są wyłącznie jawnie.
Tablice przekazywane są jako wskaźnik, albo wręcz ulegają degradacji do wskaźnika (decay).

Każdy komentarz jak np ten:

// Wyświetlenie tablica_dyn przed zmianą

tak naprawdę jest zaproszeniem do wydzielenia funkcji z kodu.
Komentarze lubią się przedawniać (ktoś poprawi kod, ale nie komentarz), więc lepiej wyrazić coś w kodzie zamiast w komentarzu.

wyswietl_tablic_dyn(tablica_dyn, w1, k);

powyższe mówi co komentarz, a zstępuje nie tylko komentarz, ale też kod.

1

Najlepiej pokazuje się jak powinien wyglądać kod przez przykład:
https://godbolt.org/z/sa1z77TYn

Zwróć uwagę, że:

  1. wszystko jest po angielsku (zostawiłem parę komentarzy po polsku)
  2. nazwy symboli mówią co się dzieje, więc większość komentarzy jest zbędna
  3. Wydzielone są małe funkcje k†óre łątwo ogarnąć
  4. zmienne dotyczące macierzy zostały zamknięte w jednej strukturze, w przez co łatwiej się ogarnia kod (mniej argumentów do przekazania
  5. niepotrebnie dodajesz cast wartości zwracanej z malloc. TO jest potrzeben w C++, w C void* domyślnie konwertuje się do wskaźnika na dowolny typ

Jeszcze można to poprawiać (np ten niezrozumiały wskaźnik przy testowaniu mnożenia).

Użyłem też VLA z C99 (jako argument funkcji).

#include <stdio.h>
#include <stdlib.h>

typedef struct MatrixInt {
    int rows;
    int columns;
    int **items;
} MatrixInt;

MatrixInt MatrixIntMake(int r, int c) {
    int i;
    MatrixInt result = {};
    result.items = malloc(r * sizeof(result.items[0]));
    if (!result.items) return result;

    for (i = 0; i < r; i++) {
        result.items[i] = malloc(c * sizeof(result.items[0][0]));
    }
    result.rows = r;
    result.columns = c;

    return result;
}

void MatrixIntFree(MatrixInt m) {
    int i;
    for (i = 0; i < m.rows; i++) {
        free(m.items[i]);
    }
    free(m.items);
}

void MatrixIntFillIncresing(MatrixInt m, int start) {
    int i, j;
    for (i = 0; i < m.rows; i++) {
        for (j = 0; j < m.columns; j++) {
            m.items[i][j] = start;
            start++;
        }
    }
}

void MatrixIntPrintInto(FILE *f, MatrixInt m) {
    int i, j;
    for (i = 0; i < m.rows; i++) {
        for (j = 0; j < m.columns; j++) {
            fprintf(f, "%d  ", m.items[i][j]);
        }
        fprintf(f, "\n");
    }
    fprintf(f, "\n");
}

void MatrixIntTest(MatrixInt m, int *multiplicationFactor) {
    if (m.rows > 1 && m.columns > 1) m.items[1][1] *= *multiplicationFactor;
}

void testIntVLA(int columns, int tablica_stat[][columns],
                int *multiplicationFactor) {
    tablica_stat[1][1] *= *multiplicationFactor;
}

void MatrixIntPrint(MatrixInt m) { MatrixIntPrintInto(stdout, m); }

void printIntVLA(int rows, int columns, int m[][columns]) {
    int i, j;
    for (i = 0; i < rows; i++) {
        for (j = 0; j < columns; j++) {
            printf("%d  ", m[i][j]);
        }
        printf("\n");
    }
    printf("\n");
}

int main() {
    int multiplicationFactor = 5; 
    int rowsCount = 3;
    int columnsCount = 2;

    MatrixInt tablica_dyn;
    tablica_dyn = MatrixIntMake(rowsCount, columnsCount);

    MatrixIntFillIncresing(tablica_dyn, 1);

    int tablica_stat[3][2] = {{1, 2}, {3, 4}, {5, 6}};

    MatrixIntPrint(tablica_dyn);

    printIntVLA(3, 2, tablica_stat);

    MatrixIntTest(tablica_dyn, &multiplicationFactor);
    testIntVLA(2, tablica_stat, &multiplicationFactor);

    // Wyświetlenie tablica_dyn po zmianach
    MatrixIntPrint(tablica_dyn);
    printIntVLA(3, 2, tablica_stat);

    MatrixIntFree(tablica_dyn);

    return 0;
}
0

Wielkie dzięki za pomoc, struktury znam tylko z nazwy więc zabieram się za ich naukę a potem przerobienie kodów od Was. Dzięki też za ogólne uwagi odnośnie pisanie kodu.
Chciałbym jeszcze zapytać o Twój kod @MarekR22, piszę w VSC i zapisuje wszystko jako .c jednak po skopiowaniu kodu i odpaleniu wskazuje mi 3 problemy:

  • oczekiwano wyrażenia C/C++ (29) [12, 25]
  • parametr jest niedozwolony C/C++ (411) [58, 49]
  • parametr jest niedozwolony C/C++ (411) [65, 49]

Czym to jest spowodowane?

0
B4rte napisał(a):

Wielkie dzięki za pomoc, struktury znam tylko z nazwy więc zabieram się za ich naukę a potem przerobienie kodów od Was. Dzięki też za ogólne uwagi odnośnie pisanie kodu.

Chciałbym jeszcze zapytać o Twój kod @MarekR22, piszę w VSC i zapisuje wszystko jako .c jednak po skopiowaniu kodu i odpaleniu wskazuje mi 3 problemy:

  • oczekiwano wyrażenia C/C++ (29) [12, 25]
  • parametr jest niedozwolony C/C++ (411) [58, 49]
  • parametr jest niedozwolony C/C++ (411) [65, 49]

Czym to jest spowodowane?

Dobra już wiem o co chodzi. Twój kompilator to MSVC.
Z powodu wstecznej kompatybilności Microsoft zdecydował, że będzie wspierał tylko C94, więc nie ma VLA wprowadzonych w C99.

Bez C99 nie ma prostej metody zaimplementowania: testIntVLA i printIntVLA :(.
W twoim przypadku trzeba sztywno wprowadzić wielkość tablic.

// było testIntVLA ale VLA jest nidostępne
void testIntConstSizeArray(int tablica_stat[][2],
                int *multiplicationFactor) {
    tablica_stat[1][1] *= *multiplicationFactor;
}

A błąd dla linii 12 łatwo naprawić (wywal = {} i jawnie wszystko ustaw na zero).

1

Sytuacja staje się prostsza, gdy uświadomisz sobie, że tak na prawdę nie potrzebujesz dwuwymiarowej tablicy, bo jednowymiarowa pokryje wszystkie potrzeby. Sporo problemów odpada, do tego jeden malloc jeden free. Pozowliłem sobie lekko zmodyfikować kod @MarekR22

typedef struct MatrixInt {
    int rows;
    int columns;
    int *items;
} MatrixInt;

MatrixInt MatrixIntMake(int r, int c) {
    MatrixInt result = {
       .rows = r,
       .columns = c,
    };

    result.items = (int*)malloc(r * c * sizeof *result.items);

    return result;
}

void MatrixIntFree(MatrixInt m) {
    free(m.items);
}

int MatrixGetValue(MatrixInt m, int x, int y)
{
   return m.items[y * m.columns + x);
}

void MatrixIntPrint(MatrixInt m)
{
   int row = 0, col = 0;
   for(; row < m.rows; ++row)
   {
      for(;col < m.columns; ++col)
         printf("%i ", MatrixGetValue(m, col, row));
   
      printf("\n");
      col = 0;
   }
}

1 użytkowników online, w tym zalogowanych: 0, gości: 1