2B diziyi C ++ işlevine geçirme


324

Parametre olarak değişken boyutlu bir 2D dizi almak istediğim bir fonksiyon var.

Şimdiye kadar bu var:

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

Ve benim kod başka bir yerde bir dizi ilan ettim:

double anArray[10][10];

Ancak, arama yapmak myFunction(anArray)bana bir hata veriyor.

Diziyi geçirdiğimde kopyalamak istemiyorum. Yapılan herhangi bir değişiklik myFunctiondurumunu değiştirmelidir anArray. Doğru anlarsam, sadece bir 2D diziye bir işaretçi argüman olarak geçmek istiyorum. Fonksiyonun farklı boyutlardaki dizileri de kabul etmesi gerekir. Örneğin, [10][10]ve [5][5]. Bunu nasıl yapabilirim?


1
parametre 3'ü 'double [10] [10]'
değerinden

3
Kabul edilen cevap sadece 2 tekniği gösterir ( onun (2) ve (3) aynıdır) ancak bir 2D diziyi bir işleve geçirmenin 4 benzersiz yolu vardır .
legends2k

Açıkçası, evet, 2D diziler değiller, ancak her biri (1D) dizisine işaret eden bir dizi işaretçi içeren bu kural (UB'ye yol açsa da) yaygın gibi görünüyor :( Düzleştirilmiş 1D mxn dizisine sahip olmak 2B diziyi taklit etmek için yardımcı işlevler / sınıfla uzunluk belki de daha iyidir
legends2k

KOLAY - 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)); }. Diyelim kiint mat[3][5]; func(mat[0], 3, 5);
Minhas Kamal

Yanıtlar:


413

Bir 2D diziyi bir işleve geçirmenin üç yolu vardır:

  1. Parametre bir 2D dizidir

    int array[10][10];
    void passFunc(int a[][10])
    {
        // ...
    }
    passFunc(array);
  2. Parametre işaretçiler içeren bir dizidir

    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. Parametre bir işaretçiye işaretçi

    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 Sen arrayile öğeleri alabilirsiniz array[i][j]:)
shengy

14
Birinci durum için parametre olarak bildirilebilir int (*a)[10].
Zachary

9
2. durum için, parametre olarak bildirilebilir int **.
Zachary

1
@Zack: Haklısın, sadece iki dava var; biri işaretçi-işaretçi, diğeri n boyutunda bir tamsayı dizisine tek bir işaretçi int (*a) [10].
legends2k

3
Durum 2 ve 3 2B diziler değildir, bu nedenle bu cevap yanıltıcıdır. Şuna bakın .
Lundin

178

Sabit Boyut

1. referans ile geçmek

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;
    }
}

C ++ 'da, boyut bilgisini kaybetmeden diziyi referans olarak geçirme, muhtemelen en güvenli olanıdır, çünkü arayanın yanlış bir boyut geçirmesi hakkında endişelenmenize gerek yoktur (uyuşmazken derleyici bayrakları). Ancak, dinamik (freestore) dizilerle bu mümkün değildir; yalnızca otomatik ( genellikle yığın-yaşam ) diziler için çalışır, yani boyutlandırma derleme zamanında bilinmelidir.

2. İşaretçi ile geçin

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;
    }    
}

Önceki yöntemin C eşdeğeri diziyi işaretçiyle geçiriyor. Bu, dizinin çürümüş işaretçi türünden (3) geçmemekle karıştırılmamalıdır , bu yaygın olan popüler yöntemdir, ancak bundan daha az güvenli ancak daha esnektir. Gibi (1) bir dizinin tüm boyutlar sabit ve derleme zamanında bilindiği zaman, bu yöntemi kullanır. İşlevi çağırırken, dizinin adresinin process_2d_array_pointer(&a)bozulma yoluyla ilk öğenin adresini değil, geçirilmesi gerektiğini unutmayınprocess_2d_array_pointer(a) .

Değişken Boyut

Bunlar C'den miras alınır, ancak daha az güvenlidir, derleyicinin kontrol etme yolu yoktur, arayanın gerekli boyutları geçtiğini garanti eder. İşlev yalnızca arayanın boyut (lar) olarak geçtiği şeyleri belirler. Bunlar, farklı uzunluklardaki diziler her zaman onlara geçirilebildiğinden, yukarıdakilerden daha esnektir.

Unutulmamalıdır ki, bir diziyi doğrudan C'deki bir işleve geçirmek gibi bir şey yoktur [C ++ 'da referans (1) olarak geçirilebilirler ]; (2) dizinin kendisine değil diziye bir işaretçi geçiriyor. Bir diziyi her zaman olduğu gibi iletmek, dizinin bir işaretçiye bozunmasının doğasıyla kolaylaştırılan bir işaretçi kopyalama işlemi haline gelir .

3. Çürümüş tipe bir işaretçi geçirin (değer verin)

// 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;
    }
}

Her ne kadar int array[][10]izin verilir Yukarıdaki söz dizimi o tanımlayıcı olduğunu temizlemek yapar çünkü, yukarıdaki sözdizimi üzerine bunu tavsiye ederim array10 sayı dizisi için tek işaretçisi bu sözdizimi ederken, bakışlar bir 2D dizi ama aynı işaretçisi gibi 10 tamsayıdan oluşan bir dizi. Burada tek bir satırdaki elemanların sayısını biliyoruz (yani sütun boyutu, burada 10), ancak satırların sayısı bilinmiyor ve bu nedenle bir argüman olarak iletilecek. Bu durumda, ikinci boyut 10'a eşit olmayan bir diziye bir işaretçi geçirildiğinde derleyici işaretleyebileceğinden, bazı güvenlikler vardır. İlk boyut değişen kısımdır ve atlanabilir. Sadece ilk boyutun neden atlanmasına izin verildiğinin mantığı için buraya bakın .

4. İşaretçiyle işaretçiye geçin

// 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;
    }
}

Yine bununla int *array[10]aynı olan alternatif bir sözdizimi vardır int **array. Bu sözdiziminde, [10]bir işaretçiye dönüştüğü için yok sayılır ve böylece olur int **array. Belki de geçilen dizinin en az 10 sütuna sahip olması, arayan için bir ipucudur, hatta satır sayısı gereklidir. Her durumda, derleyici herhangi bir uzunluk / boyut ihlali için işaretlemez (yalnızca geçirilen türün işaretçiye bir işaretçi olup olmadığını kontrol eder), bu nedenle parametre burada mantıklı olduğundan hem satır hem de sütun sayıları gerektirir.

Not: (4), herhangi bir tip kontrolü ve en rahatsız edici olması nedeniyle en güvenli seçenektir . Bu işleve yasal olarak bir 2D dizi geçirilemez; C-SSS kınadı yapmanın olağan geçici çözümü int x[5][10]; process_pointer_2_pointer((int**)&x[0][0], 5, 10);o kadar potansiyel istenmeyen davranışlara yol açabilir nedeniyle dizi düzleşme. Bu yöntemde bir diziyi geçirmenin doğru yolu bizi rahatsız edici kısma getirir; yani, her bir öğesinin gerçek, geçilecek dizinin ilgili satırına işaret ettiği ek bir (yedek) işaretçi dizisine ihtiyacımız vardır; bu vekil daha sonra fonksiyona aktarılır (aşağıya bakınız); tüm bunlar, daha güvenli, daha temiz ve belki de daha hızlı olan yukarıdaki yöntemlerle aynı işi yapmak için.

Yukarıdaki işlevleri test etmek için bir sürücü programı:

#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**
}

Dinamik olarak ayrılmış dizileri C ++ işlevlerine geçirmeye ne dersiniz? C11 standardında, fn (int col, int row, int array [col] [row]) gibi statik ve dinamik olarak ayrılmış diziler için yapılabilir: stackoverflow.com/questions/16004668/… Bu soruna soru sormuştum : stackoverflow.com/questions/27457076/…
42n4

@ 42n4 Durum 4 bunu kapsar (C ++ için de). Dinamik olarak tahsis edilen diziler için, yalnızca döngünün içindeki satır b[i] = a[i];, örneğin, olarak değişir b[i] = new int[10];. Ayrıca bdinamik olarak tahsis edilebilir int **b = int *[5];ve hala olduğu gibi çalışacaktır.
legends2k

1
Adresleme 4) 'array[i][j] deki işleve nasıl çalışır ? Ptr to ptr aldığından ve doğru adresleme için bir kaydırma yapmak için gerekli olan son boyutun değerini bilmediği için?
user1234567

2
array[i][j]işaretçi değeri sadece işaretçi aritmetik yani array, bu eklersiniz igibi bir sonuç KQUEUE int*o eklersiniz hangi, jbir okuma bu konumu ve KQUEUE int. Yani, hayır, bunun için herhangi bir boyut bilmesine gerek yok. Ama bütün mesele bu! Derleyici, programcının sözünü inançla alır ve programcı yanlışsa, tanımlanmamış davranış ortaya çıkar. Vaka 4'ün en güvenli seçenek olduğunu söylememin nedeni de budur.
legends2k

Bu gibi durumlarda bir yapı size iyi hizmet edebilir.
Xofo

40

Shengy'nin ilk önerisinde yapılan bir değişiklikle, işlevin çok boyutlu bir dizi değişkenini kabul etmesini sağlamak için şablonları kullanabilirsiniz (yönetilmesi ve silinmesi gereken bir dizi işaretçi saklamak yerine):

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;
}

Yazdırma ifadeleri, dizilerin referansla geçtiğini (değişkenlerin adreslerini görüntüleyerek) göstermek için vardır.


2
%pBir işaretçi yazdırmak için kullanmalısınız ve o zaman bile, onu tanımlamanız gerekir void *, aksi takdirde printf()tanımlanmamış davranışlar başlatır. Ayrıca, &işlevler çağrılırken addressof ( ) operatörünü kullanmamalısınız, çünkü işlevler bir tür argümanı beklerken double (*)[size_y], şu anda bunları iletiyor double (*)[10][10]ve double (*)[5][5].

Şablonları şablon argümanları olarak her iki boyutu da kullanıyorsanız, düşük düzeyli işaretçi erişiminden tamamen kaçınılabileceğinden daha uygundur ve daha iyidir.
legends2k

3
Bu yalnızca dizinin boyutu derleme zamanında biliniyorsa çalışır.
jeb_is_a_mess

Cevapta yukarıdaki @Georg Kodu tam olarak önerdiğim şey. Çevrimiçi demo - GCC 6.3 çalışır . Parametreyi referans yapmayı unuttunuz mu?
legends2k

21

Henüz kimsenin bundan bahsetmediği için şaşırttı, ancak [] [] semantiği destekleyen her şeyi şablonlayabilirsiniz.

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

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

std::vector<std::vector<T>>Kodun yeniden kullanımını en üst düzeye çıkarmak için herhangi bir 2D "dizi benzeri" veri yapısı veya kullanıcı tanımlı bir türle çalışır.


1
Bu doğru cevap olmalı. Bahsedilen tüm problemleri ve burada bahsedilmeyen bazı problemleri çözer. Tip güvenliği, dizilerin zaman uyumsuzluğu, işaretçi aritmetiği yok, tip dökümü yok, veri kopyalama yok. C ve C ++ için çalışır.
OpalApps

Peki, bu C ++ için çalışır; C, şablonları desteklemez. C ile yapmak makrolar gerektirir.
Gunnar

20

Bunun gibi bir işlev şablonu oluşturabilirsiniz:

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

Daha sonra R ve C üzerinden her iki boyut boyutuna sahip olursunuz. Her dizi boyutu için farklı bir işlev oluşturulur, bu nedenle işleviniz büyükse ve bunu çeşitli farklı dizi boyutlarıyla çağırırsanız, bu maliyetli olabilir. Yine de böyle bir işlev üzerinde bir sarıcı olarak kullanabilirsiniz:

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

Diziyi tek boyutlu olarak ele alır ve dizinlerin ofsetlerini bulmak için aritmetik kullanır. Bu durumda, şablonu şu şekilde tanımlarsınız:

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

2
size_tdizi dizinleri için olandan daha iyidir int.
Andrew Tomazos

13

anArray[10][10]bir işaretçiye bir işaretçi değildir, 100 tür çift değer depolamak için uygun bitişik bir bellek yığınıdır, derleyici boyutları belirttiğiniz için nasıl adresleneceğini bilir. Dizi olarak bir işleve geçirmeniz gerekir. İlk boyutun boyutunu aşağıdaki gibi göz ardı edebilirsiniz:

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

Ancak, bu son boyut ondan farklı diziler geçirmenize izin vermez.

C ++ 'daki en iyi çözüm kullanmaktır std::vector<std::vector<double> >: neredeyse aynı derecede verimli ve çok daha uygundur.


1
Ben std kütüphane çok verimli olduğu için bu çözümü tercih - bu arada dasblinkenlight gibi; Eskiden dasblikenlicht kullanıyordum
mozillanerd

Neredeyse verimli mi? Evet doğru. İşaretçi izlemesi, işaretçi olmayan izlemeden her zaman daha pahalıdır.
Thomas Eding

8

Tek boyutlu dizi, dizideki ilk öğeye işaret eden bir işaretçi işaretçisine bozulur. 2B bir dizi ise ilk satıra işaret eden bir işaretçiye bozulur. Bu nedenle, işlev prototipi -

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

std::vectorHam dizileri tercih ederim .


8

Böyle bir şey yapabilirsiniz ...

#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;
}

Çıktınız aşağıdaki gibi olacaktır ...

11.5  12.5
13.5  14.5

1
Biri neden bu durumda diziyi mangle yapmak için gelebilir tek nedeni, biri dizi işaretçilerin nasıl çalıştığı hakkında bilgi eksik olmasıdır.
Lundin

3
Bu durumda olduğu gibi sütunlar ve satırlar eşit olmadığı sürece i değişkeni sütunlarla çarpılmalıdır, satırlarla değil
Andrey Chernukha

4

İşte bir vektörler matrisi örneği vektörü

#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;
}

çıktı:

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

Bir 2D diziyi bir işleve geçirmek için birkaç yol kullanabiliriz:

  • Tek işaretçiyi kullanarak 2B diziyi yazmamız gerekir.

    #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;
    }
  • Çift işaretçiyi kullanma Bu şekilde, 2d dizisini de tahmin ederiz.

    #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

Çok boyutlu dizileri geçmek için önemli bir şey:

  • First array dimension belirtilmesi gerekmez.
  • Second(any any further)dimension belirtilmelidir.

1.Dünya çapında yalnızca ikinci boyut mevcut olduğunda (makro veya global sabit olarak)

`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.Tek bir işaretçi kullanma : Bu yöntemde, işleve geçerken 2D diziyi yazmamız gerekir.

`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

Bunu yapmak için C ++ 'da şablon özelliğini kullanabilirsiniz. Ben böyle bir şey yaptım:

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

bu yaklaşımla ilgili sorun, sağladığınız her col değeri için şablon kullanılarak yeni bir işlev tanımının başlatılmasıdır. yani,

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

2 işlev tanımı (biri col = 3, diğeri col = 5) üretmek için şablonu iki kez başlatır.


0

Eğer geçmek istiyorsanız int a[2][3]için void func(int** pp)aşağıdaki gibi yardımcı adımlar gerekiyor.

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

func(pp);

Birincisi [2]örtük olarak belirtilebildiğinden, daha da basitleştirilebilir.

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

func(pp);

0

Dinamik boyutlu 2 boyutlu diziyi bir işleve geçirmek istediğinizde, bazı işaretçileri kullanmak sizin için işe yarayabilir.

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

En soldaki boyutu atlamanıza izin verilir ve böylece iki seçenek sunulur:

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

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

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

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

Bu işaretçilerle aynıdır:

// 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

Bir N boyutlu dizinin N-1 boyutlu diziye bir işaretçiye dönüşmesine C ++ standardı tarafından izin verilir soldaki boyutu kaybedebileceğiniz ve yine de N-1 boyut bilgileri içeren dizi öğelerine doğru şekilde erişebildiğiniz için .

Ayrıntılar burada

Yine de, diziler ve işaretçiler aynı değildir : bir dizi bir işaretçiye bozulabilir, ancak bir işaretçi işaret ettiği verilerin boyutu / yapılandırması hakkında durum taşımaz.

A char **, karakter işaretleyicileri içeren bir bellek bloğunun, karakterlerin bellek bloklarına işaret ettiği bir göstericidir . A char [][], karakterler içeren tek bir bellek bloğudur . Bunun, derleyicinin kodu nasıl çevirdiği ve nihai performansın nasıl olacağı üzerinde etkisi vardır.

Kaynak

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.