programing

2D 배열을 C++ 함수로 전달

linuxpc 2023. 5. 11. 21:10
반응형

2D 배열을 C++ 함수로 전달

변수 크기의 2D 배열을 매개 변수로 사용하고 싶은 기능이 있습니다.

지금까지 저는 다음과 같습니다.

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

그리고 나는 내 코드의 다른 곳에 배열을 선언했습니다.

double anArray[10][10];

, 하만지, 전는것하를 부르는 것은myFunction(anArray)나에게 오류를 줍니다.

전달할 때 배열을 복사하고 싶지 않습니다.에서 myFunction.anArray제가 올바르게 이해했다면, 저는 단지 2D 배열에 대한 포인터를 인수로 전달하고 싶습니다.이 기능은 크기가 다른 배열도 수용해야 합니다.예를 들면, 를예들어,,[10][10]그리고.[5][5]어떻게 해야 하나요?

2D 배열을 함수로 전달하는 세 가지 방법이 있습니다.

  1. 매개 변수는 2D 배열입니다.

    int array[10][10];
    void passFunc(int a[][10])
    {
        // ...
    }
    passFunc(array);
    
  2. 매개 변수가 포인터를 포함하는 배열입니다.

    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. 매개 변수는 포인터에 대한 포인터입니다.

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

고정 크기

참조로 전달

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++에서는 차원 정보를 잃지 않고 참조로 배열을 전달하는 것이 가장 안전합니다. 왜냐하면 호출자가 잘못된 차원(불일치할 때 컴파일러 플래그)을 전달할 걱정이 없기 때문입니다.그러나 동적(자유 저장소) 어레이에서는 불가능합니다. 자동(일반적으로 스택-리빙) 어레이에서만 작동합니다. 즉, 컴파일 시 치수를 알아야 합니다.

포인터로 전달

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

이전 방법의 C에 해당하는 것은 포인터로 배열을 전달하는 것입니다.이는 어레이의 쇠퇴한 포인터 유형(3)을 통과하는 것과 혼동되어서는 안 됩니다. 이 방법은 일반적인 일반적인 방법입니다. 비록 이 방법보다 안전하지는 않지만 더 유연합니다.(1)과 마찬가지로 배열의 모든 차원이 컴파일 시 고정되고 알려진 경우 이 방법을 사용합니다.함수를 호출할 때 어레이의 주소를 전달해야 합니다.process_2d_array_pointer(&a) 붕괴에 첫 .process_2d_array_pointer(a).

가변 크기

이것들은 C에서 상속되었지만 덜 안전합니다. 컴파일러는 호출자가 필요한 치수를 전달하고 있는지 확인할 방법이 없습니다.함수는 호출자가 차원으로 전달하는 내용에만 의존합니다.길이가 다른 배열이 위의 배열보다 더 유연합니다.

기억해야 할 것은 C의 함수에 배열을 직접 전달하는 것과 같은 것은 없다는 것입니다. [C++에서는 (1) 참조로 전달할 수 있지만, (2) 배열 자체가 아니라 배열에 포인터를 전달하는 것입니다.배열을 항상 있는 그대로 전달하는 것은 배열이 포인터로 감쇠하는 특성에 의해 용이한 포인터 복사 작업이 됩니다.

포인터를 감쇠 유형으로 전달(값)

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

비록 ~일지라도int array[][10]됩니다. 위 위 array는 10개의 정수 배열에 대한 단일 포인터인 반면, 구문은 2D 배열처럼 보이지만 10개의 정수 배열에 대한 동일한 포인터입니다.여기서는 단일 행의 요소 수(예: 열 크기, 여기서는 10)를 알고 있지만 행 수는 알 수 없으므로 인수로 전달해야 합니다.이 경우 컴파일러는 두 번째 차원이 10이 아닌 배열에 대한 포인터가 전달될 때 플래그를 지정할 수 있으므로 안전합니다.첫 번째 차원은 가변 부분이므로 생략할 수 있습니다. 번째 차원만 생략할 수 있는 이유는 여기를 참조하십시오.

포인터로 포인터 전달

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

또 다른 구문은 다음과 같습니다.int *array[10]은 와같은것과 .int **array에서 이에서는은[10]그것이 포인터로 붕괴하여 그것이 되기 때문에 무시됩니다.int **array행 수가 필요하더라도 전달된 배열에 최소 10개의 열이 있어야 한다는 것은 호출자에게 보내는 신호일 뿐입니다.어떤 경우에도 컴파일러는 길이/크기 위반에 플래그를 지정하지 않습니다(전달된 유형이 포인터인지 여부만 확인함). 따라서 매개 변수에 따라 행 수와 열 수가 모두 필요합니다.

참고: (4)는 유형 검사가 거의 없고 가장 불편하기 때문에 가장 안전하지 않은 옵션입니다.이 기능에 2D 배열을 합법적으로 전달할 수 없습니다. C-FAQ는 일반적인 해결 방법을 비난합니다.int x[5][10]; process_pointer_2_pointer((int**)&x[0][0], 5, 10);어레이 평탄화로 인해 잠재적으로 정의되지 않은 동작이 발생할 수 있기 때문입니다.이 방법에서 배열을 전달하는 올바른 방법은 불편한 부분으로 우리를 데려갑니다. 즉, 각 요소가 실제의 각 행을 가리키는 추가적인 (대칭) 포인터 배열이 필요합니다.통과할 배열, 이 대리는 함수로 전달됩니다(아래 참조). 이 모든 것은 위의 방법과 동일한 작업을 수행하기 위해 더 안전하고, 더 깨끗하고, 더 빠를 수 있습니다.

다음은 위의 기능을 테스트하는 드라이버 프로그램입니다.

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

shengy의 첫 번째 제안에 대한 수정 사항으로, 관리 및 삭제해야 하는 포인터 배열을 저장하는 대신 템플릿을 사용하여 함수가 다차원 배열 변수를 허용하도록 할 수 있습니다.

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

변수의 주소를 표시하여 배열이 참조를 통해 전달되고 있음을 보여주는 인쇄 문입니다.

아직 아무도 이것을 언급하지 않은 것에 놀랐지만, [][] 의미론을 지원하는 모든 2D 템플릿을 만들 수 있습니다.

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

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

같은어 2D "와이유사한구작데서다동이합니조에터음"와 같은 모든 유사한" 데이터 합니다.std::vector<std::vector<T>>또는 코드 재사용을 최대화하는 사용자 정의 유형입니다.

다음과 같은 기능 템플릿을 만들 수 있습니다.

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

그러면 R과 C를 통해 두 치수 크기를 모두 가지게 됩니다.각 어레이 크기에 대해 다른 기능이 생성되므로, 기능이 크고 다양한 어레이 크기로 호출할 경우 비용이 많이 들 수 있습니다.그러나 다음과 같은 기능 위에 래퍼로 사용할 수 있습니다.

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

배열을 1차원으로 처리하고 산술을 사용하여 인덱스의 오프셋을 파악합니다.이 경우 템플릿을 다음과 같이 정의합니다.

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

anArray[10][10]포인터에 대한 포인터가 아니라, 사용자가 차원을 지정했기 때문에 컴파일러가 주소 지정 방법을 알고 있는 더블 유형의 100개 값을 저장하기에 적합한 연속 메모리 청크입니다.배열로 함수에 전달해야 합니다.다음과 같이 초기 치수의 크기를 생략할 수 있습니다.

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

그러나 마지막 차원이 10개가 아닌 배열은 통과할 수 없습니다.

C++ 가에해은다같다습니음과결은책장좋서▁c▁in▁the다를 사용하는 것입니다.std::vector<std::vector<double> >거의 효율적이고 훨씬 더 편리합니다.

다음은 벡터 행렬의 벡터 예제입니다.

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

출력:

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

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

단일 차원 배열은 배열의 첫 번째 요소를 가리키는 포인터 포인터로 감쇠합니다.2D 배열이 첫 번째 행을 가리키는 포인터로 감쇠하는 동안.따라서 기능 프로토타입은 -이어야 합니다.

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

나는 더 좋습니다std::vector오버로우 어레이.

기능에 2D 배열을 전달하는 몇 가지 방법을 사용할 수 있습니다.

  • 단일 포인터를 사용하여 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;
     }
    
  • 이중 포인터를 사용하는 방법으로 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;
    }
    

당신은 이런 일을 할 수 있어요.

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

출력은 다음과 같습니다.

11.5  12.5
13.5  14.5

다차원 어레이를 통과할 때 한 가지 중요한 사항은 다음과 같습니다.

  • First array dimension지정할 필요가 없습니다.
  • Second(any any further)dimension지정해야 합니다.

1.2차원만 전역적으로 사용할 수 있는 경우(매크로 또는 전역 상수)

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. 단일 포인터 사용:이 방법에서는 기능으로 전달할 때 2D 배열을 타이프캐스트해야 합니다.

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

/**
 * Prints out the elements of a 2D array row by row.
 *
 * @param arr The 2D array whose elements will be printed.
 */
template <typename T, size_t rows, size_t cols>
void Print2DArray(T (&arr)[rows][cols]) {
    std::cout << '\n';
    for (size_t row = 0; row < rows; row++) {
        for (size_t col = 0; col < cols; col++) {
            std::cout << arr[row][col] << ' ';
        }
        std::cout << '\n';
    }    
}

int main()
{
    int i[2][5] = { {0, 1, 2, 3, 4},
                    {5, 6, 7, 8, 9} };
    char c[3][9] = { {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'},
                     {'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R'},
                     {'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '&'} };
    std::string s[4][4] = { {"Amelia", "Edward", "Israel", "Maddox"},
                            {"Brandi", "Fabian", "Jordan", "Norman"},
                            {"Carmen", "George", "Kelvin", "Oliver"},
                            {"Deanna", "Harvey", "Ludwig", "Philip"} };
    Print2DArray(i);
    Print2DArray(c);
    Print2DArray(s);
    std::cout <<'\n';
}

동적 크기의 2-D 배열을 함수에 전달하려는 경우 일부 포인터를 사용하는 것이 좋습니다.

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

C++의 템플릿 기능을 사용하여 이 작업을 수행할 수 있습니다.저는 다음과 같은 일을 했습니다.

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

이 접근 방식의 문제는 제공하는 모든 콜 값에 대해 새 함수 정의가 템플릿을 사용하여 인스턴스화된다는 것입니다.그렇게,

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

템플릿을 두 번 인스턴스화하여 두 개의 함수 정의를 생성합니다(하나는 col = 3, 다른 하나는 col = 5).

,에 합격하고 싶다면,int a[2][3]void func(int** pp)당신은 다음과 같은 보조 단계가 필요합니다.

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

func(pp);

번째 첫째로번으로서.[2]은 암시적으로 지정할 수 있으며, 다음과 같이 더 단순화할 수 있습니다.

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

func(pp);

맨 왼쪽 치수를 생략할 수 있으므로 두 가지 옵션이 나타납니다.

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

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

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

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

포인터도 마찬가지입니다.

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

N차원 배열을 N-1 차원 배열에 대한 포인터로 감쇠시키는 것은 C++ 표준에 의해 허용됩니다. 왜냐하면 가장 왼쪽 차원을 잃을 수 있지만 N-1 차원 정보를 가진 배열 요소에 여전히 올바르게 액세스할 수 있기 때문입니다.

자세한 내용은 여기에 있습니다.

그러나 배열과 포인터는 같지 않습니다. 배열은 포인터로 붕괴될 수 있지만 포인터는 가리키는 데이터의 크기/구성에 대한 상태를 전달하지 않습니다.

A는 문자 포인터를 포함하는 메모리 블록에 대한 포인터로, 문자 자체가 문자의 메모리 블록을 가리킵니다.A은(는) 문자가 포함된 단일 메모리 블록입니다.이것은 컴파일러가 코드를 변환하는 방법과 최종 성능에 영향을 미칩니다.

원천

외에도불데구이다같암다니시됩이음과조는관구터고가 하는 데이터 구조.double**의 경우와 호환되지 않습니다.double[][]문제는 둘 다 C(또는 C++)의 배열을 처리하는 잘못된 방법으로 널리 사용된다는 것입니다.https://www.fftw.org/fftw3_doc/Dynamic-Arrays-in-C_002dThe-Wrong-Way.html 참조하십시오.

만약 당신이 코드의 어느 부분도 제어할 수 없다면 당신은 번역 계층이 필요합니다.adapt여기), 여기에 설명된 바와 같이, https://c-faq.com/aryptr/dynmuldimary.html

C 배열의 각 행을 가리키는 보조 포인터 배열을 생성해야 합니다.

#include<algorithm>
#include<cassert>
#include<vector>

void myFunction(double** myArray) {
    myArray[2][3] = 5;
}

template<std::size_t N, std::size_t M>
auto adapt(double(&Carr2D)[N][M]) {
    std::array<double*, N> ret;
    std::transform(
        std::begin(Carr2D), std::end(Carr2D),
        ret.begin(),
        [](auto&& row) { return &row[0];}
    );
    return ret;
}

int main() {
    double anArray[10][10];

    myFunction( adapt(anArray).data() );

    assert(anArray[2][3] == 5);
}

(여기서 작업 코드 참조: https://godbolt.org/z/7M7KPzbWY)

재난의 원인으로 보이는 것은 앞서 말했듯이 두 데이터 구조가 근본적으로 호환되지 않기 때문입니다.


코드의 양 끝을 제어할 수 있다면 요즘에는 Boost와 같은 최신(또는 반현대) 배열 라이브러리를 사용하는 것이 좋습니다.멀티 어레이, Boost.uBLAS, 고유 또는 다중.어레이가 작을 경우 "작은" 어레이 라이브러리가 있습니다. 예를 들어, Eigen 내부에 있거나 종속성을 감당할 수 없는 경우에는 단순히std::array<std::array<double, N>, M>.

Multi를 사용하면 다음 작업을 간단히 수행할 수 있습니다.

#include<multi/array.hpp>

#include<cassert>

namespace multi = boost::multi;

template<class Array2D>
void myFunction(Array2D&& myArray) {
    myArray[2][3] = 5;
}

int main() {
    multi::array<double, 2> anArray({10, 10});

    myFunction(anArray);

    assert(anArray[2][3] == 5);
}

(작업 코드: https://godbolt.org/z/7M7KPzbWY)

임의 개수의 차원 배열을 참조하여 한 번에 하나의 레이어를 반복적으로 벗겨낼 수 있습니다.

여기에 다은예입다의 .print시연용 기능:

#include <cstddef>
#include <iostream>
#include <iterator>
#include <string>
#include <type_traits>

template <class T, std::size_t N>
void print(const T (&arr)[N], unsigned indent = 0) {
    if constexpr (std::rank_v<T> == 0) {
        // inner layer - print the values:
        std::cout << std::string(indent, ' ') << '{';
        auto it = std::begin(arr);
        std::cout << *it;
        for (++it; it != std::end(arr); ++it) {
            std::cout << ", " << *it;
        }
        std::cout << '}';
    } else {
        // still more layers to peel off:
        std::cout << std::string(indent, ' ') << "{\n";
        auto it = std::begin(arr);
        print(*it, indent + 1);
        for (++it; it != std::end(arr); ++it) {
            std::cout << ",\n";
            print(*it, indent + 1);
        }
        std::cout << '\n' << std::string(indent, ' ') << '}';
    }
}

다음은 3차원 배열의 사용 예입니다.

int main() {
    int array[2][3][5]
    {
        {
            {1, 2, 9, -5, 3},
            {6, 7, 8, -45, -7},
            {11, 12, 13, 14, 25}
        },
        {
            {4, 5, 0, 33, 34},
            {8, 9, 99, 54, 44},
            {14, 15, 16, 19, 20}
        }
    };

    print(array);
}

다음과 같은 출력을 생성합니다.

{
 {
  {1, 2, 9, -5, 3},
  {6, 7, 8, -45, -7},
  {11, 12, 13, 14, 25}
 },
 {
  {4, 5, 0, 33, 34},
  {8, 9, 99, 54, 44},
  {14, 15, 16, 19, 20}
 }
}

언급URL : https://stackoverflow.com/questions/8767166/passing-a-2d-array-to-a-c-function

반응형