Melewati array 2D ke fungsi C ++


324

Saya memiliki fungsi yang ingin saya ambil, sebagai parameter, array 2D ukuran variabel.

Sejauh ini saya punya ini:

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

Dan saya telah mendeklarasikan array di tempat lain dalam kode saya:

double anArray[10][10];

Namun, menelepon myFunction(anArray)memberi saya kesalahan.

Saya tidak ingin menyalin array ketika saya meneruskannya. Setiap perubahan yang dilakukan myFunctionharus mengubah keadaan anArray. Jika saya mengerti dengan benar, saya hanya ingin memberikan argumen sebagai pointer ke array 2D. Fungsi perlu menerima array dengan ukuran yang berbeda juga. Jadi misalnya, [10][10]dan [5][5]. Bagaimana saya bisa melakukan ini?


1
tidak dapat mengonversi parameter 3 dari 'double [10] [10]' ke 'double **'
RogerDarwin

3
The diterima jawaban menunjukkan hanya 2 teknik [nya (2) dan (3) adalah sama] tapi ada sudah 4 cara unik melewati array 2D ke fungsi .
legends2k

Sebenarnya, mereka bukan array 2D, tetapi konvensi ini (walaupun mengarah ke UB) untuk memiliki array pointer, masing-masing menunjuk ke array (1D), tampaknya lazim :( Memiliki array 1D yang rata dari mxn panjang, dengan fungsi pembantu / kelas untuk meniru array 2D mungkin lebih baik
legends2k

TERMUDAH - 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)); }. Sebut saja sepertiint mat[3][5]; func(mat[0], 3, 5);
Minhas Kamal

Jawaban:


413

Ada tiga cara untuk meneruskan array 2D ke suatu fungsi:

  1. Parameternya adalah array 2D

    int array[10][10];
    void passFunc(int a[][10])
    {
        // ...
    }
    passFunc(array);
  2. Parameter adalah array yang berisi pointer

    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. Parameternya adalah pointer ke pointer

    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 Anda bisa mendapatkan elemen-elemen arraydengan array[i][j]:)
shengy

14
Untuk kasus 1, parameternya dapat dinyatakan sebagai int (*a)[10].
Zachary

9
Untuk kasus ke-2, parameternya dapat dinyatakan sebagai int **.
Zachary

1
@Zack: Anda benar, hanya ada dua kasus; satu adalah pointer-to-pointer dan lainnya menjadi pointer tunggal ke array integer ukuran n yaitu int (*a) [10].
legends2k

3
Kasus 2 dan 3 bukan array 2D, jadi jawaban ini menyesatkan. Lihat ini .
Lundin

178

Ukuran tetap

1. Lulus dengan referensi

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

Dalam C ++ melewatkan array dengan referensi tanpa kehilangan informasi dimensi mungkin yang paling aman, karena orang tidak perlu khawatir pemanggil melewati dimensi yang salah (flag compiler ketika ketidakcocokan). Namun, ini tidak dimungkinkan dengan array dinamis (freestore); ini bekerja untuk otomatis ( array biasanya stack-living ) yaitu dimensi harus diketahui pada waktu kompilasi.

2. Lewati dengan pointer

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

Setara C dari metode sebelumnya melewati array dengan pointer. Ini tidak boleh disalahartikan dengan melewati tipe pointer yang rusak (3) , yang merupakan metode umum dan populer, meskipun kurang aman daripada yang satu ini tetapi lebih fleksibel. Seperti (1) , gunakan metode ini ketika semua dimensi array diperbaiki dan diketahui pada waktu kompilasi. Perhatikan bahwa saat memanggil fungsi, alamat array harus dikirimkan process_2d_array_pointer(&a)dan bukan alamat elemen pertama dengan pembusukan process_2d_array_pointer(a).

Ukuran Variabel

Ini diwarisi dari C tetapi kurang aman, kompiler tidak memiliki cara untuk memeriksa, menjamin bahwa pemanggil melewati dimensi yang diperlukan. Fungsi hanya bank pada apa yang penelepon masuk sebagai dimensi. Ini lebih fleksibel daripada yang di atas karena array dengan panjang yang berbeda dapat diteruskan ke mereka selalu.

Harus diingat bahwa tidak ada yang lewat array langsung ke fungsi dalam C [sementara di C + + mereka dapat dilewatkan sebagai referensi (1) ]; (2) meneruskan pointer ke array dan bukan array itu sendiri. Selalu melewati array apa adanya menjadi operasi pointer-copy yang difasilitasi oleh sifat array yang membusuk menjadi pointer .

3. Lewati (nilai) sebuah pointer ke tipe yang sudah membusuk

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

Meskipun int array[][10]diperbolehkan, saya tidak akan merekomendasikan ini di atas sintaks di atas karena sintaks di atas menjelaskan bahwa pengenal arrayadalah pointer tunggal ke array 10 integer, sementara sintaks ini terlihat seperti array 2D tetapi adalah pointer yang sama untuk sebuah array 10 bilangan bulat. Di sini kita tahu jumlah elemen dalam satu baris (yaitu ukuran kolom, 10 di sini) tetapi jumlah baris tidak diketahui dan karenanya dilewatkan sebagai argumen. Dalam hal ini ada beberapa keamanan karena kompiler dapat menandai ketika pointer ke array dengan dimensi kedua tidak sama dengan 10 dilewatkan. Dimensi pertama adalah bagian yang bervariasi dan dapat dihilangkan. Lihat di sini untuk alasan mengapa hanya dimensi pertama yang boleh dihilangkan.

4. Lewati pointer ke pointer

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

Sekali lagi ada sintaks alternatif int *array[10]yang sama dengan int **array. Dalam sintaksis [10]ini diabaikan karena meluruh menjadi pointer sehingga menjadi int **array. Mungkin itu hanya isyarat kepada pemanggil bahwa array yang lewat harus memiliki setidaknya 10 kolom, bahkan kemudian diperlukan jumlah baris. Dalam kasus apa pun kompilator tidak menandai pelanggaran panjang / ukuran (hanya memeriksa apakah tipe yang dilewati adalah pointer ke pointer), maka memerlukan jumlah baris dan kolom sebagai parameter masuk akal di sini.

Catatan: (4) adalah opsi yang paling tidak aman karena hampir tidak memiliki pemeriksaan tipe apa pun dan yang paling tidak nyaman. Seseorang tidak dapat secara sah melewatkan array 2D ke fungsi ini; C-FAQ mengutuk solusi yang biasa dilakukan int x[5][10]; process_pointer_2_pointer((int**)&x[0][0], 5, 10);karena berpotensi menyebabkan perilaku yang tidak terdefinisi karena perataan array. Cara yang benar untuk melewatkan array dalam metode ini membawa kita ke bagian yang tidak nyaman, yaitu kita membutuhkan array pointer tambahan (dengan pengganti) dengan masing-masing elemennya menunjuk ke masing-masing baris dari array aktual yang akan dilewati; pengganti ini kemudian diteruskan ke fungsi (lihat di bawah); semua ini untuk mendapatkan pekerjaan yang sama dilakukan sebagai metode di atas yang lebih aman, bersih dan mungkin lebih cepat.

Inilah program driver untuk menguji fungsi-fungsi di atas:

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

Bagaimana dengan meneruskan array yang dialokasikan secara dinamis ke fungsi di C ++? Dalam standar C11 dapat dilakukan untuk array yang dialokasikan secara statis dan dinamis seperti itu fn (int col, int row, int array [col] [row]): stackoverflow.com/questions/16004668/… Saya telah mengajukan pertanyaan untuk masalah ini : stackoverflow.com/questions/27457076/…
42n4

@ 42n4 Kasus 4 mencakup (untuk C ++ juga) itu. Untuk array yang dialokasikan secara dinamis, hanya garis di dalam loop akan berubah dari b[i] = a[i];menjadi, katakanlah b[i] = new int[10];,. Satu juga dapat membuat bdialokasikan secara dinamis int **b = int *[5];dan masih akan berfungsi apa adanya.
legends2k

1
Bagaimana cara array[i][j]kerja pengalamatan ke fungsi di 4) ? Karena telah menerima ptr ke ptr dan tidak tahu nilai dimensi terakhir, mana yang diperlukan untuk melakukan pergeseran untuk pengalamatan yang benar?
user1234567

2
array[i][j]hanya aritmatika pointer yaitu nilai pointer array, itu akan menambah idan merujuk hasilnya sebagai int*, yang akan menambah jdan referensi lokasi itu, membaca int. Jadi, tidak, tidak perlu tahu dimensi apa pun untuk ini. Tapi, itulah intinya! Kompilator mengambil kata-kata programmer dengan keyakinan dan jika programmer salah, perilaku yang tidak jelas terjadi kemudian. Inilah alasan saya menyebutkan bahwa case 4 adalah opsi yang paling tidak aman.
legends2k

Dalam kasus seperti itu, struct dapat melayani Anda dengan baik.
Xofo

40

Sebuah modifikasi terhadap saran pertama shengy, Anda dapat menggunakan templat untuk membuat fungsi menerima variabel array multi-dimensi (alih-alih menyimpan array pointer yang harus dikelola dan dihapus):

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

Pernyataan cetak ada di sana untuk menunjukkan bahwa array dilewatkan oleh referensi (dengan menampilkan alamat variabel)


2
Anda harus menggunakan %puntuk mencetak pointer, dan bahkan kemudian, Anda harus melemparkannya ke void *, jika printf()tidak memanggil perilaku yang tidak ditentukan. Selain itu, Anda tidak boleh menggunakan alamat ( &) operator saat memanggil fungsi, karena fungsi mengharapkan argumen tipe double (*)[size_y], sedangkan Anda saat ini meneruskannya double (*)[10][10]dan double (*)[5][5].

Jika Anda menggunakan templat yang membuat kedua dimensi sebagai argumen templat lebih tepat dan lebih baik karena akses pointer level rendah dapat sepenuhnya dihindari.
legends2k

3
Ini hanya berfungsi jika ukuran array diketahui pada waktu kompilasi.
jeb_is_a_mess

@ Georg Kode di atas dalam jawaban adalah persis apa yang saya sarankan. Ini bekerja di GCC 6.3 - demo online . Apakah Anda lupa menjadikan parameter sebagai referensi?
legends2k

21

Terkejut bahwa belum ada yang menyebutkan ini, tetapi Anda dapat dengan mudah mencetak pada apa pun 2D yang mendukung semantik [] [].

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

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

Ini bekerja dengan std::vector<std::vector<T>>struktur data 2D "seperti array", seperti , atau tipe yang ditentukan pengguna untuk memaksimalkan penggunaan kembali kode.


1
Ini harus menjadi jawaban yang tepat. Ini memecahkan semua masalah yang disebutkan dan beberapa yang tidak disebutkan di sini. Jenis keamanan, waktu kompilasi tidak kompatibel array, tidak ada aritmatika pointer, tidak ada tipe casting, tidak ada penyalinan data. Bekerja untuk C dan C ++.
OpalApps

Nah, ini berfungsi untuk C ++; C tidak mendukung templat. Melakukannya di C akan membutuhkan makro.
Gunnar

20

Anda dapat membuat templat fungsi seperti ini:

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

Kemudian Anda memiliki kedua ukuran dimensi melalui R dan C. Fungsi yang berbeda akan dibuat untuk setiap ukuran array, jadi jika fungsi Anda besar dan Anda menyebutnya dengan berbagai ukuran array yang berbeda, ini mungkin mahal. Anda bisa menggunakannya sebagai pembungkus fungsi seperti ini:

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

Ini memperlakukan array sebagai satu dimensi, dan menggunakan aritmatika untuk mencari tahu offset indeks. Dalam hal ini, Anda akan mendefinisikan template seperti ini:

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

2
size_tadalah tipe yang lebih baik untuk indeks array daripada int.
Andrew Tomazos

13

anArray[10][10]bukan penunjuk ke penunjuk, itu adalah potongan memori yang berdekatan yang cocok untuk menyimpan 100 nilai tipe ganda, yang dikompilasi oleh kompiler yang tahu cara mengatasinya karena Anda menentukan dimensi. Anda harus meneruskannya ke fungsi sebagai array. Anda dapat menghilangkan ukuran dimensi awal, sebagai berikut:

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

Namun, ini tidak akan membiarkan Anda melewati array dengan dimensi terakhir selain sepuluh.

Solusi terbaik dalam C ++ adalah menggunakan std::vector<std::vector<double> >: itu hampir seefisien, dan secara signifikan lebih nyaman.


1
Saya lebih suka solusi ini karena perpustakaan std sangat efisien - omong-omong saya suka dasblinkenlight; Saya menggunakan dasblikenlicht
mozillanerd

Hampir sama efisiennya? Ya benar. Pengejaran pointer selalu lebih mahal daripada pengejaran non-pointer.
Thomas Eding

8

Array dimensi tunggal meluruh menjadi penunjuk pointer yang menunjuk ke elemen pertama dalam array. Sementara array 2D meluruh ke pointer yang menunjuk ke baris pertama. Jadi, prototipe fungsi seharusnya -

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

Saya lebih suka std::vectordaripada array mentah.


8

Anda dapat melakukan sesuatu seperti ini ...

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

Output Anda akan sebagai berikut ...

11.5  12.5
13.5  14.5

1
Satu-satunya alasan saya dapat menemukan mengapa seseorang harus memotong-motong array dalam kasus ini, adalah karena seseorang kurang pengetahuan tentang cara kerja pointer array.
Lundin

3
variabel i harus dikalikan dengan kolom, bukan dengan baris kecuali kolom dan baris sama seperti dalam kasus ini
Andrey Chernukha

4

Berikut adalah contoh vektor vektor

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

keluaran:

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

Kita dapat menggunakan beberapa cara untuk meneruskan array 2D ke suatu fungsi:

  • Menggunakan pointer tunggal kita harus mengetikkan array 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;
    }
  • Menggunakan pointer ganda Dengan cara ini, kami juga mengetikkan array 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

Satu hal penting untuk melewati array multidimensi adalah:

  • First array dimension tidak perlu ditentukan.
  • Second(any any further)dimension harus ditentukan.

1.Ketika hanya dimensi kedua yang tersedia secara global (baik sebagai makro atau sebagai konstanta global)

`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.Menggunakan pointer tunggal : Dalam metode ini, kita harus mengetikkan larik 2D saat beralih ke fungsi.

`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

Anda dapat menggunakan fasilitas template di C ++ untuk melakukan ini. Saya melakukan sesuatu seperti ini:

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

masalah dengan pendekatan ini adalah bahwa untuk setiap nilai col yang Anda berikan, definisi fungsi baru dipakai menggunakan template. begitu,

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

instantiate templat dua kali untuk menghasilkan 2 definisi fungsi (satu di mana col = 3 dan satu di mana col = 5).


0

Jika Anda ingin menyampaikan int a[2][3]kepada void func(int** pp)Anda perlu langkah-langkah tambahan sebagai berikut.

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

func(pp);

Seperti yang pertama [2]dapat ditentukan secara implisit, itu dapat disederhanakan lebih lanjut sebagai.

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

func(pp);

0

Jika Anda ingin meneruskan array 2-d berukuran dinamis ke suatu fungsi, menggunakan beberapa pointer bisa bekerja untuk Anda.

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

Anda diizinkan menghilangkan dimensi paling kiri dan akhirnya Anda memiliki dua opsi:

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

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

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

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

Ini sama dengan pointer:

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

Peluruhan array dimensi N ke pointer ke array dimensi N-1 diizinkan oleh standar C ++ , karena Anda dapat kehilangan dimensi paling kiri dan masih dapat mengakses elemen array dengan benar dengan informasi dimensi N-1.

Detail di sini

Meskipun, array dan pointer tidak sama : sebuah array dapat meluruh menjadi sebuah pointer, tetapi sebuah pointer tidak membawa status tentang ukuran / konfigurasi data yang ditunjuknya.

A char **adalah penunjuk ke blok memori yang berisi pointer karakter , yang dengan sendirinya menunjuk ke blok memori karakter. A char [][]adalah blok memori tunggal yang berisi karakter. Ini berdampak pada bagaimana kompiler menerjemahkan kode dan bagaimana kinerja akhir akan.

Sumber

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.