Przekazywanie tablicy 2D do funkcji C ++


324

Mam funkcję, którą chcę przyjąć jako parametr tablicę 2D o zmiennej wielkości.

Do tej pory mam to:

void myFunction(double** myArray){
     myArray[x][y] = 5;
     etc...
}

I zadeklarowałem tablicę gdzie indziej w moim kodzie:

double anArray[10][10];

Jednak wywołanie myFunction(anArray)powoduje błąd.

Nie chcę kopiować tablicy po jej przekazaniu. Wszelkie wprowadzone zmiany myFunctionpowinny zmienić stan anArray. Jeśli dobrze rozumiem, chcę przekazać jako argument wskaźnik do tablicy 2D. Funkcja musi także akceptować tablice o różnych rozmiarach. Na przykład [10][10]i [5][5]. W jaki sposób mogę to zrobić?


1
nie można przekonwertować parametru 3 z „podwójnego [10] [10]” na „podwójnego **”
RogerDarwin

3
Do akceptowanych odpowiedź przedstawia tylko dwie techniki [produkty (2) i (3) są takie same], ale nie są 4 unikalne sposobów przechodząc tablicy 2D do funkcji .
legends2k

Ściśle mówiąc, tak, nie są to tablice 2D, ale ta konwencja (aczkolwiek prowadząca do UB) posiadania tablicy wskaźników, z których każda wskazuje na tablicę (1D), wydaje się dominująca :( Posiadanie spłaszczonej tablicy 1D mxn długość z funkcjami pomocniczymi / klasą do emulacji tablicy 2D jest być może lepsza
legends2k

ŁATWIEJSZY - func(int* mat, int r, int c){ for(int i=0; i<r; i++) for(int j=0; j<c; j++) printf("%d ", *(mat+i*c+j)); }. Nazwij to jakint mat[3][5]; func(mat[0], 3, 5);
Minhas Kamal

Odpowiedzi:


413

Istnieją trzy sposoby przekazania tablicy 2D do funkcji:

  1. Ten parametr to tablica 2D

    int array[10][10];
    void passFunc(int a[][10])
    {
        // ...
    }
    passFunc(array);
    
  2. Parametr jest tablicą zawierającą wskaźniki

    int *array[10];
    for(int i = 0; i < 10; i++)
        array[i] = new int[10];
    void passFunc(int *a[10]) //Array containing pointers
    {
        // ...
    }
    passFunc(array);
    
  3. Ten parametr jest wskaźnikiem do wskaźnika

    int **array;
    array = new int *[10];
    for(int i = 0; i <10; i++)
        array[i] = new int[10];
    void passFunc(int **a)
    {
        // ...
    }
    passFunc(array);
    

4
@Overflowh Możesz dostać elementy arrayz array[i][j]:)
shengy

14
W pierwszym przypadku parametr można zadeklarować jako int (*a)[10].
Zachary

9
W drugim przypadku parametr można zadeklarować jako int **.
Zachary

1
@Zack: Masz rację, tak naprawdę są tylko dwie sprawy; jeden jest wskaźnikiem do wskaźnika, a drugi jest pojedynczym wskaźnikiem do tablicy liczb całkowitych o rozmiarze n tj int (*a) [10].
legends2k

3
Przypadki 2 i 3 nie są tablicami 2D, więc ta odpowiedź jest myląca. Zobacz to .
Lundin

178

Naprawiono rozmiar

1. Przekaż przez odniesienie

template <size_t rows, size_t cols>
void process_2d_array_template(int (&array)[rows][cols])
{
    std::cout << __func__ << std::endl;
    for (size_t i = 0; i < rows; ++i)
    {
        std::cout << i << ": ";
        for (size_t j = 0; j < cols; ++j)
            std::cout << array[i][j] << '\t';
        std::cout << std::endl;
    }
}

W C ++ przekazywanie tablicy przez referencję bez utraty informacji o wymiarze jest prawdopodobnie najbezpieczniejsze, ponieważ nie trzeba się martwić, że osoba dzwoniąca przekaże niepoprawny wymiar (flagi kompilatora podczas niedopasowania). Nie jest to jednak możliwe w przypadku tablic dynamicznych (Freestore); działa tylko w przypadku tablic automatycznych ( zwykle stosowych ), tzn. wymiary powinny być znane w czasie kompilacji.

2. Przejdź obok wskaźnika

void process_2d_array_pointer(int (*array)[5][10])
{
    std::cout << __func__ << std::endl;
    for (size_t i = 0; i < 5; ++i)
    {
        std::cout << i << ": ";
        for (size_t j = 0; j < 10; ++j)
            std::cout << (*array)[i][j] << '\t';
        std::cout << std::endl;
    }    
}

Odpowiednikiem C poprzedniej metody jest przekazanie tablicy przez wskaźnik. Nie należy tego mylić z przekazywaniem zepsutego typu wskaźnika tablicy (3) , który jest powszechną, popularną metodą, choć mniej bezpieczną niż ta, ale bardziej elastyczną. Podobnie jak (1) , użyj tej metody, gdy wszystkie wymiary tablicy są ustalone i znane w czasie kompilacji. Zauważ, że podczas wywoływania funkcji należy podać adres tablicy, process_2d_array_pointer(&a)a nie adres pierwszego elementu przez zanik process_2d_array_pointer(a).

Zmienny rozmiar

Są one dziedziczone z C, ale są mniej bezpieczne, kompilator nie ma możliwości sprawdzenia, gwarantując, że osoba dzwoniąca przekazuje wymagane wymiary. Funkcja opiera się tylko na tym, co przekazujący wywołuje jako wymiar (wymiary). Są one bardziej elastyczne niż powyższe, ponieważ tablice o różnych długościach mogą być do nich przekazywane niezmiennie.

Należy pamiętać, że nie ma czegoś takiego jak przekazywanie tablicy bezpośrednio do funkcji w C [podczas gdy w C ++ można je przekazywać jako odwołanie (1) ]; (2) przekazuje wskaźnik do tablicy, a nie do samej tablicy. Zawsze przekazywanie tablicy bez zmian staje się operacją kopiowania wskaźnika, która jest ułatwiona przez naturę rozkładu tablicy na wskaźnik .

3. Przekaż (wartość) wskaźnik do zepsutego typu

// int array[][10] is just fancy notation for the same thing
void process_2d_array(int (*array)[10], size_t rows)
{
    std::cout << __func__ << std::endl;
    for (size_t i = 0; i < rows; ++i)
    {
        std::cout << i << ": ";
        for (size_t j = 0; j < 10; ++j)
            std::cout << array[i][j] << '\t';
        std::cout << std::endl;
    }
}

Chociaż int array[][10]jest to dozwolone, nie poleciłbym go powyżej powyższej składni, ponieważ powyższa składnia wyjaśnia, że ​​identyfikator arrayjest pojedynczym wskaźnikiem do tablicy 10 liczb całkowitych, podczas gdy ta składnia wygląda na tablicę 2D, ale jest tym samym wskaźnikiem tablica 10 liczb całkowitych. Znamy tutaj liczbę elementów w jednym wierszu (tj. Rozmiar kolumny, tutaj 10), ale liczba wierszy jest nieznana i dlatego należy ją przekazać jako argument. W tym przypadku istnieje pewne bezpieczeństwo, ponieważ kompilator może oflagować się, gdy zostanie przekazany wskaźnik do tablicy o drugim wymiarze innym niż 10. Pierwszy wymiar jest częścią zmienną i można go pominąć. Zobacz tutaj uzasadnienie, dlaczego można pominąć tylko pierwszy wymiar.

4. Przekaż wskaźnik do wskaźnika

// int *array[10] is just fancy notation for the same thing
void process_pointer_2_pointer(int **array, size_t rows, size_t cols)
{
    std::cout << __func__ << std::endl;
    for (size_t i = 0; i < rows; ++i)
    {
        std::cout << i << ": ";
        for (size_t j = 0; j < cols; ++j)
            std::cout << array[i][j] << '\t';
        std::cout << std::endl;
    }
}

Znowu istnieje alternatywna składnia, int *array[10]która jest taka sama jak int **array. W tej składni [10]znak ignorowany jest, gdy rozpada się na wskaźnik, stając się w ten sposób int **array. Być może jest to tylko wskazówka dla wywołującego, że przekazywana tablica powinna mieć co najmniej 10 kolumn, nawet wtedy wymagana jest liczba wierszy. W każdym razie kompilator nie zgłasza żadnych naruszeń długości / rozmiaru (sprawdza tylko, czy przekazany typ jest wskaźnikiem do wskaźnika), dlatego wymaga liczenia wierszy i kolumn, ponieważ parametr ma tutaj sens.

Uwaga: (4) jest najmniej bezpieczną opcją, ponieważ prawie nie ma żadnej kontroli typu i jest najbardziej niewygodna. Do tej funkcji nie można legalnie przekazać tablicy 2D; C-FAQ potępia zwykłe obejście, int x[5][10]; process_pointer_2_pointer((int**)&x[0][0], 5, 10);ponieważ może potencjalnie prowadzić do nieokreślonego zachowania z powodu spłaszczenia tablicy. Właściwy sposób przekazywania tablicy w tej metodzie prowadzi nas do niewygodnej części, tzn. Potrzebujemy dodatkowej (zastępczej) tablicy wskaźników, z których każdy jej element wskazuje na odpowiedni rząd rzeczywistej tablicy, która ma być przekazana; ten surogat jest następnie przekazywany do funkcji (patrz poniżej); wszystko po to, aby wykonać tę samą pracę, co powyższe metody, które są bezpieczniejsze, czystsze i być może szybsze.

Oto program sterownika do testowania powyższych funkcji:

#include <iostream>

// copy above functions here

int main()
{
    int a[5][10] = { { } };
    process_2d_array_template(a);
    process_2d_array_pointer(&a);    // <-- notice the unusual usage of addressof (&) operator on an array
    process_2d_array(a, 5);
    // works since a's first dimension decays into a pointer thereby becoming int (*)[10]

    int *b[5];  // surrogate
    for (size_t i = 0; i < 5; ++i)
    {
        b[i] = a[i];
    }
    // another popular way to define b: here the 2D arrays dims may be non-const, runtime var
    // int **b = new int*[5];
    // for (size_t i = 0; i < 5; ++i) b[i] = new int[10];
    process_pointer_2_pointer(b, 5, 10);
    // process_2d_array(b, 5);
    // doesn't work since b's first dimension decays into a pointer thereby becoming int**
}

Co powiesz na przekazywanie dynamicznie przydzielanych tablic do funkcji w C ++? W standardzie C11 można to zrobić dla tablic alokowanych statycznie i dynamicznie, takich jak ten fn (int col, int row, int array [col] [row]): stackoverflow.com/questions/16004668 /... Zadałem pytanie dotyczące tego problemu : stackoverflow.com/questions/27457076/…
42n4

@ 42n4 Przypadek 4 obejmuje (również dla C ++) to. W przypadku dynamicznie przydzielanych tablic tylko linia wewnątrz pętli zmieniłaby się z b[i] = a[i];, powiedzmy, na b[i] = new int[10];. Można także bprzypisać dynamicznie int **b = int *[5];i nadal będzie działał bez zmian.
legends2k

1
Jak adresowanie array[i][j]działa w funkcji w 4) ? Ponieważ otrzymał ptr do ptr i nie zna wartości ostatniego wymiaru, co jest konieczne do wykonania przesunięcia dla poprawnego adresowania?
user1234567,

2
array[i][j]jest po prostu arytmetyką wskaźnika, tj. do wartości wskaźnika array, idodawałby i wyłapywał wynik jako int*, do którego jdodawałby i wyłapywał to położenie, czytając int. Więc nie, nie musi to mieć żadnego wymiaru. Ale o to chodzi! Kompilator bierze słowo programisty z wiarą, a jeśli programista był niepoprawny, powstaje niezdefiniowane zachowanie. Właśnie dlatego wspomniałem, że przypadek 4 jest najmniej bezpieczną opcją.
legends2k

W takich przypadkach struktura może ci dobrze służyć.
Xofo

40

Modyfikacja pierwszej sugestii shengy, możesz użyć szablonów, aby funkcja zaakceptowała wielowymiarową zmienną tablicową (zamiast przechowywać tablicę wskaźników, którymi trzeba zarządzać i usuwać):

template <size_t size_x, size_t size_y>
void func(double (&arr)[size_x][size_y])
{
    printf("%p\n", &arr);
}

int main()
{
    double a1[10][10];
    double a2[5][5];

    printf("%p\n%p\n\n", &a1, &a2);
    func(a1);
    func(a2);

    return 0;
}

Instrukcje print mają na celu pokazanie, że tablice są przekazywane przez referencję (poprzez wyświetlanie adresów zmiennych)


2
Powinieneś użyć %pdo wydrukowania wskaźnika, a nawet wtedy musisz go rzucić void *, w przeciwnym razie printf()wywoła niezdefiniowane zachowanie. Ponadto nie powinieneś używać &operatora addressof ( ) podczas wywoływania funkcji, ponieważ funkcje oczekują argumentu typu double (*)[size_y], podczas gdy obecnie przekazujesz je double (*)[10][10]i double (*)[5][5].

Jeśli używasz szablonów, tworząc oba wymiary jako argumenty szablonów, jest bardziej odpowiednie i lepsze, ponieważ można całkowicie uniknąć dostępu do wskaźnika niskiego poziomu.
legends2k

3
Działa to tylko wtedy, gdy rozmiar tablicy jest znany w czasie kompilacji.
jeb_is_a_mess

@Georg Kod powyżej w odpowiedzi jest dokładnie tym, co zasugerowałem. Działa w GCC 6.3 - demo online . Czy zapomniałeś ustawić parametr jako odniesienie?
legends2k

21

Zaskoczony, że nikt jeszcze o tym nie wspominał, ale możesz po prostu szablonować na dowolnej 2D obsługującej [] [] semantykę.

template <typename TwoD>
void myFunction(TwoD& myArray){
     myArray[x][y] = 5;
     etc...
}

// call with
double anArray[10][10];
myFunction(anArray);

Działa z dowolną std::vector<std::vector<T>>strukturą danych typu „tablicowego” 2D, taką jak typ zdefiniowany przez użytkownika, aby zmaksymalizować ponowne użycie kodu.


1
To powinna być właściwa odpowiedź. Rozwiązuje wszystkie wymienione problemy i niektóre nie wymienione tutaj. Bezpieczeństwo typu, niekompatybilność tablic w czasie kompilacji, brak arytmetyki wskaźnika, brak rzutowania typu, brak kopiowania danych. Działa dla C i C ++.
OpalApps

To działa dla C ++; C nie obsługuje szablonów. Wykonanie tego w C wymagałoby makr.
Gunnar

20

Możesz utworzyć szablon funkcji w ten sposób:

template<int R, int C>
void myFunction(double (&myArray)[R][C])
{
    myArray[x][y] = 5;
    etc...
}

Następnie masz oba rozmiary wymiarów za pośrednictwem R i C. Dla każdego rozmiaru tablicy zostanie utworzona inna funkcja, więc jeśli twoja funkcja jest duża i wywołujesz ją z różnymi rozmiarami tablicy, może to być kosztowne. Możesz jednak użyć go jako opakowania funkcji takiej jak ta:

void myFunction(double * arr, int R, int C)
{
    arr[x * C + y] = 5;
    etc...
}

Traktuje tablicę jako jednowymiarową i wykorzystuje arytmetykę do ustalenia przesunięć indeksów. W takim przypadku należy zdefiniować szablon w następujący sposób:

template<int C, int R>
void myFunction(double (&myArray)[R][C])
{
    myFunction(*myArray, R, C);
}

2
size_tjest lepszym typem dla indeksów tablic niż int.
Andrew Tomazos,

13

anArray[10][10]nie jest wskaźnikiem do wskaźnika, to ciągły fragment pamięci odpowiedni do przechowywania 100 wartości typu double, który kompilator wie jak zaadresować, ponieważ określono wymiary. Musisz przekazać go do funkcji jako tablicy. Możesz pominąć rozmiar wymiaru początkowego w następujący sposób:

void f(double p[][10]) {
}

Nie pozwoli to jednak na przekazywanie tablic o ostatnim wymiarze innym niż dziesięć.

Najlepszym rozwiązaniem w C ++ jest użycie std::vector<std::vector<double> >: jest prawie tak samo wydajny i znacznie wygodniejszy.


1
Wolę to rozwiązanie, ponieważ biblioteka std jest bardzo wydajna - tak przy okazji lubię dasblinkenlight; Kiedyś korzystałem z dasblikenlicht
mozillanerd

Prawie tak samo wydajny? Tak, jasne. Pogoń za wskaźnikiem jest zawsze droższa niż pogoń za wskaźnikiem.
Thomas Eding,

8

Tablica jednowymiarowa rozpada się na wskaźnik wskazujący na pierwszy element w tablicy. Podczas gdy tablica 2D rozpada się na wskaźnik wskazujący pierwszy rząd. Prototypem funkcji powinno być -

void myFunction(double (*myArray) [10]);

Wolałbym std::vectorsurowe tablice.


8

Możesz zrobić coś takiego ...

#include<iostream>

using namespace std;

//for changing values in 2D array
void myFunc(double *a,int rows,int cols){
    for(int i=0;i<rows;i++){
        for(int j=0;j<cols;j++){
            *(a+ i*rows + j)+=10.0;
        }
    }
}

//for printing 2D array,similar to myFunc
void printArray(double *a,int rows,int cols){
    cout<<"Printing your array...\n";
    for(int i=0;i<rows;i++){
        for(int j=0;j<cols;j++){
            cout<<*(a+ i*rows + j)<<"  ";
        }
    cout<<"\n";
    }
}

int main(){
    //declare and initialize your array
    double a[2][2]={{1.5 , 2.5},{3.5 , 4.5}};

    //the 1st argument is the address of the first row i.e
    //the first 1D array
    //the 2nd argument is the no of rows of your array
    //the 3rd argument is the no of columns of your array
    myFunc(a[0],2,2);

    //same way as myFunc
    printArray(a[0],2,2);

    return 0;
}

Twój wynik będzie następujący ...

11.5  12.5
13.5  14.5

1
Jedynym powodem, dla którego mogę wymyślić powód, dla którego w tym przypadku miałaby zostać zmieniona tablica, jest brak wiedzy na temat działania wskaźników tablicy.
Lundin

3
zmienną i należy pomnożyć przez kolumny, a nie przez wiersze, chyba że kolumny i wiersze są takie same, jak w tym przypadku
Andrey Chernukha

4

Oto przykład wektora macierzy wektorów

#include <iostream>
#include <vector>
using namespace std;

typedef vector< vector<int> > Matrix;

void print(Matrix& m)
{
   int M=m.size();
   int N=m[0].size();
   for(int i=0; i<M; i++) {
      for(int j=0; j<N; j++)
         cout << m[i][j] << " ";
      cout << endl;
   }
   cout << endl;
}


int main()
{
    Matrix m = { {1,2,3,4},
                 {5,6,7,8},
                 {9,1,2,3} };
    print(m);

    //To initialize a 3 x 4 matrix with 0:
    Matrix n( 3,vector<int>(4,0));
    print(n);
    return 0;
}

wynik:

1 2 3 4
5 6 7 8
9 1 2 3

0 0 0 0
0 0 0 0
0 0 0 0

2

Możemy użyć kilku sposobów przekazania tablicy 2D do funkcji:

  • Za pomocą pojedynczego wskaźnika musimy typecastować tablicę 2D.

    #include<bits/stdc++.h>
    using namespace std;
    
    
    void func(int *arr, int m, int n)
    {
        for (int i=0; i<m; i++)
        {
           for (int j=0; j<n; j++)
           {
              cout<<*((arr+i*n) + j)<<" ";
           }
           cout<<endl;
        }
    }
    
    int main()
    {
        int m = 3, n = 3;
        int arr[m][n] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
        func((int *)arr, m, n);
        return 0;
    }
  • Korzystanie z podwójnego wskaźnika W ten sposób również rzutujemy tablicę 2d

    #include<bits/stdc++.h>
    using namespace std;

   void func(int **arr, int row, int col)
   {
      for (int i=0; i<row; i++)
      {
         for(int j=0 ; j<col; j++)
         {
           cout<<arr[i][j]<<" ";
         }
         printf("\n");
      }
   }

  int main()
  {
     int row, colum;
     cin>>row>>colum;
     int** arr = new int*[row];

     for(int i=0; i<row; i++)
     {
        arr[i] = new int[colum];
     }

     for(int i=0; i<row; i++)
     {
         for(int j=0; j<colum; j++)
         {
            cin>>arr[i][j];
         }
     }
     func(arr, row, colum);

     return 0;
   }

1

Jedną ważną rzeczą do przekazywania tablic wielowymiarowych jest:

  • First array dimension nie trzeba podawać.
  • Second(any any further)dimension musi zostać określony.

1.Gdy tylko drugi wymiar jest dostępny globalnie (jako makro lub jako stała globalna)

`const int N = 3;

`void print(int arr[][N], int m)
{
int i, j;
for (i = 0; i < m; i++)
  for (j = 0; j < N; j++)
    printf("%d ", arr[i][j]);
}`

int main()
{
int arr[][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
print(arr, 3);
return 0;
}`

2.Korzystając z jednego wskaźnika : w tej metodzie musimy przerzucić tablicę 2D podczas przekazywania do funkcji.

`void print(int *arr, int m, int n)
{
int i, j;
for (i = 0; i < m; i++)
  for (j = 0; j < n; j++)
    printf("%d ", *((arr+i*n) + j));
 }

`int main()
{
int arr[][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
int m = 3, n = 3;

// We can also use "print(&arr[0][0], m, n);"
print((int *)arr, m, n);
return 0;
}`

0

Aby to zrobić, możesz skorzystać z narzędzia szablonów w C ++. Zrobiłem coś takiego:

template<typename T, size_t col>
T process(T a[][col], size_t row) {
...
}

Problem z tym podejściem polega na tym, że dla każdej wartości podanej kolumny, tworzona jest nowa definicja funkcji przy użyciu szablonu. więc,

int some_mat[3][3], another_mat[4,5];
process(some_mat, 3);
process(another_mat, 4);

tworzy szablon dwa razy, aby utworzyć 2 definicje funkcji (jedną, gdzie col = 3 i drugą, gdzie col = 5).


0

Jeśli chcesz przejść , musisz int a[2][3]wykonać void func(int** pp)następujące kroki pomocnicze.

int a[2][3];
int* p[2] = {a[0],a[1]};
int** pp = p;

func(pp);

Ponieważ pierwszy [2]można niejawnie określić, można go dodatkowo uprościć jako.

int a[][3];
int* p[] = {a[0],a[1]};
int** pp = p;

func(pp);

0

W przypadku, gdy chcesz przekazać do funkcji tablicę 2D o dynamicznym rozmiarze, użycie niektórych wskaźników może być dla Ciebie przydatne.

void func1(int *arr, int n, int m){
    ...
    int i_j_the_element = arr[i * m + j];  // use the idiom of i * m + j for arr[i][j] 
    ...
}

void func2(){
    ...
    int arr[n][m];
    ...
    func1(&(arr[0][0]), n, m);
}

0

Możesz pominąć skrajny lewy wymiar, więc otrzymujesz dwie opcje:

void f1(double a[][2][3]) { ... }

void f2(double (*a)[2][3]) { ... }

double a[1][2][3];

f1(a); // ok
f2(a); // ok 

To samo dotyczy wskaźników:

// compilation error: cannot convert ‘double (*)[2][3]’ to ‘double***’ 
// double ***p1 = a;

// compilation error: cannot convert ‘double (*)[2][3]’ to ‘double (**)[3]’
// double (**p2)[3] = a;

double (*p3)[2][3] = a; // ok

// compilation error: array of pointers != pointer to array
// double *p4[2][3] = a;

double (*p5)[3] = a[0]; // ok

double *p6 = a[0][1]; // ok

Rozpad N-wymiarowej tablicy na wskaźnik na tablicę wymiarową N-1 jest dozwolony przez standard C ++ , ponieważ możesz stracić skrajny lewy wymiar i nadal być w stanie poprawnie uzyskać dostęp do elementów tablicy z informacjami o wymiarach N-1.

Szczegóły tutaj

Chociaż tablice i wskaźniki nie są takie same : tablica może rozpaść się na wskaźnik, ale wskaźnik nie ma stanu dotyczącego rozmiaru / konfiguracji danych, na które wskazuje.

A char **jest wskaźnikiem do bloku pamięci zawierającego wskaźniki znaków , które same wskazują na bloki pamięci znaków. A char [][]to pojedynczy blok pamięci, który zawiera znaki. Ma to wpływ na to, jak kompilator przetłumaczy kod i jak będzie wyglądać końcowa wydajność.

Źródło

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.