C ++ 함수에 2D 배열 전달


324

변수 크기의 2D 배열을 매개 변수로 사용하려는 함수가 있습니다.

지금까지 나는 이것을 가지고있다 :

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

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

double anArray[10][10];

그러나 전화 myFunction(anArray)하면 오류가 발생합니다.

전달할 때 배열을 복사하고 싶지 않습니다. 변경 사항은 myFunction의 상태를 변경해야합니다 anArray. 올바르게 이해하면 2D 배열에 대한 포인터 만 인수로 전달하고 싶습니다. 이 함수는 다른 크기의 배열도 수용해야합니다. 그래서 예를 들면, [10][10]하고 [5][5]. 어떻게해야합니까?


1
파라미터 3을 'double [10] [10]'에서 'double **'로 변환 할 수 없음
RogerDarwin

3
수락 응답 만을 도시 기술 2 [의 (2)(3) 와 동일하다]하지만 거기 함수에 대한 2 차원 어레이를 전달하는 4 개 독특한 방법 .
legends2k

엄밀히 말하면, 그들은 2D 배열이 아니지만 각각 (1D) 배열을 가리키는 포인터 배열을 갖는이 규칙은 (UB로 이어지지 만) 널리 퍼져있는 것처럼 보입니다. 2D 배열을 에뮬레이트하는 도우미 함수 / 클래스가있는 길이가 더 낫습니다
legends2k

가장 쉬운 - 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)); }. 처럼 부릅니다 –int mat[3][5]; func(mat[0], 3, 5);
Minhas Kamal

답변:


413

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

4
당신의 요소를 얻을 수 있습니다 @Overflowh arrayarray[i][j]:)
shengy

14
첫 번째 경우 매개 변수는로 선언 될 수 있습니다 int (*a)[10].
Zachary

9
두 번째 경우에는 매개 변수를로 선언 할 수 있습니다 int **.
Zachary

1
@Zack : 네 말이 맞아, 실제로 두 가지 경우가있다; 하나는 포인터 대 포인터이고 다른 하나는 n 크기의 정수 배열에 대한 단일 포인터 int (*a) [10]입니다.
legends2k

3
사례 2와 3은 2D 배열이 아니므로이 답변은 잘못된 것입니다. 이것을보십시오 .
Lundin

178

고정 크기

1. 참조로 전달

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 ++에서는 차원 정보를 잃지 않고 참조로 배열을 전달하는 것이 가장 안전합니다. 호출자가 잘못된 차원을 전달하는 것에 대해 걱정할 필요가 없으므로 (일치하지 않을 때 컴파일러 플래그). 그러나 동적 (프리 스토어) 배열에서는 불가능합니다. 자동 ( 보통 스택 리빙 ) 배열에서만 작동합니다. 즉, 차원은 컴파일 타임에 알려야합니다.

2. 포인터로 전달

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)첫 번째 요소의 주소가 아닌 배열의 주소가 decay 로 전달되어야합니다 process_2d_array_pointer(a).

가변 크기

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

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

3. 소멸 된 유형으로 포인터를 전달 (값)

// 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)를 알고 있지만 행 수는 알 수 없으므로 인수로 전달됩니다. 이 경우 2 차원이 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**
}

C ++에서 동적으로 할당 된 배열을 함수에 전달하는 것은 어떻습니까? : C11 표준에서는 정적 및 동적으로 할당 된 그 FN 같은 배열 (INT의 COL, INT 행, int 배열 [COL] [행])에 대해 수행 할 수 있습니다 stackoverflow.com/questions/16004668/... 나는이 문제에 대한 질문을 만들었습니다 : stackoverflow.com/questions/27457076/…
42n4

@ 42n4 사례 4는 (C ++의 경우에도) 그 내용을 다룹니다. 동적으로 할당 된 배열의 경우 루프 내부의 줄만에서 b[i] = a[i];로 변경됩니다 b[i] = new int[10];. 또한 b동적으로 할당 될 수 있으며 int **b = int *[5];여전히 그대로 작동합니다.
legends2k

1
4)array[i][j] 에서 어드레싱 기능 이 어떻게 작동 합니까? 그것은 ptr을 ptr로 받았으며 마지막 차원의 값을 알지 못하기 때문에 올바른 주소 지정을 위해 시프트를 수행해야합니까?
user1234567

2
array[i][j]는 포인터 산술, 즉 포인터 값에 대한 array것입니다 i. 결과를로 int*추가 j하고 역 참조하여 해당 위치를 추가 하고 역 참조하여을 읽습니다 int. 따라서 아닙니다. 이에 대한 차원을 알 필요는 없습니다. 그러나 그것은 요점입니다! 컴파일러는 프로그래머의 말을 믿으며 프로그래머가 틀렸다면 정의되지 않은 동작이 발생합니다. 이것이 사례 4가 가장 안전한 옵션이라고 언급 한 이유입니다.
legends2k

그러한 경우 구조체가 잘 제공 될 수 있습니다.
Xofo

40

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

print 문은 배열이 참조로 전달되고 있음을 보여줍니다 (변수의 주소를 표시하여)


2
%p포인터를 인쇄하는 데 사용해야 하며조차로 캐스팅해야합니다 . void *그렇지 않으면 printf()정의되지 않은 동작 이 호출됩니다. 또한, 당신은 AddressOf를 (사용하지 말아야 &함수를 호출 할 때 함수 타입의 인수를 기대하기 때문에,) 연산자를 double (*)[size_y]현재 전달할 반면, double (*)[10][10]double (*)[5][5].

두 차원을 템플릿 인수로 만드는 템플릿을 사용하는 경우 저수준 포인터 액세스를 완전히 피할 수 있으므로 더 적합하고 좋습니다.
legends2k

3
이것은 컴파일 시간에 배열의 크기가 알려진 경우에만 작동합니다.
jeb_is_a_mess

위의 @Georg 코드는 내가 제안한 것입니다. GCC 6.3- 온라인 데모 에서 작동합니다 . 매개 변수를 참조로 만드는 것을 잊었습니까?
legends2k

21

아무도 이것을 언급하지 않은 것에 놀랐지 만 [] [] 시맨틱을 지원하는 2D로 템플릿을 작성할 수 있습니다.

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>>코드 재사용을 극대화하기 위해, 또는 사용자 정의 유형 과 같은 2D "배열 형"데이터 구조와 함께 작동합니다 .


1
이것이 정답이어야합니다. 언급 된 모든 문제와 여기에 언급되지 않은 일부 문제를 해결합니다. 타입 안전, 배열의 컴파일 시간 비 호환성, 포인터 산술 없음, 타입 캐스팅 없음, 데이터 복사 없음. C 및 C ++에서 작동합니다.
OpalApps

자, 이것은 C ++에서 작동합니다. C는 템플릿을 지원하지 않습니다. C에서 그렇게하려면 매크로가 필요합니다.
Gunnar

20

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

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

2
size_t보다 배열 인덱스 유형이 더 좋습니다 int.
Andrew Tomazos

13

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

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

그러나 이렇게하면 마지막 차원이 10이 아닌 배열을 전달할 수 없습니다.

C ++에서 가장 좋은 솔루션은 사용하는 std::vector<std::vector<double> >것입니다. 거의 효율적이고 훨씬 더 편리합니다.


1
std 라이브러리가 매우 효율적이므로 dasblinkenlight를 좋아 하므로이 솔루션을 선호합니다. 나는 dasblikenlicht를 사용했었다
mozillanerd

거의 효율적입니까? 그래 맞아. 포인터 추적은 비 포인터 추적보다 항상 비쌉니다.
Thomas Eding

8

1 차원 배열은 배열의 첫 번째 요소를 가리키는 포인터 포인터로 감소합니다. 2D 배열은 첫 번째 행을 가리키는 포인터로 붕괴합니다. 따라서 함수 프로토 타입은-

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

std::vector원시 배열보다 선호 합니다.


8

이런 식으로 할 수 있습니다 ...

#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

1
이 경우 배열을 엉망으로 만드는 이유를 생각해 낼 수있는 유일한 이유는 배열 포인터의 작동 방식에 대한 지식이 부족하기 때문입니다.
Lundin

3
i 변수에 열과 행이 같은 경우가 아니라면 행이 아니라 열을 곱해야합니다.
Andrey Chernukha

4

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

#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

2

여러 가지 방법으로 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;
   }

1

다차원 배열을 전달할 때 중요한 것은 다음과 같습니다.

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

1. 두 번째 차원 만 전체적으로 사용할 수있는 경우 (매크로 또는 전역 상수로)

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

단일 포인터 사용 :이 방법에서는 함수에 전달할 때 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;
}`

0

이를 위해 C ++에서 템플릿 기능을 사용할 수 있습니다. 나는 이런 식으로했다 :

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

이 방법의 문제점은 제공하는 모든 col 값에 대해 새 함수 정의가 템플리트를 사용하여 인스턴스화된다는 것입니다. 그래서,

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

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


0

통과 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);

0

동적 크기의 2 차원 배열을 함수에 전달하려는 경우 일부 포인터를 사용하면 효과가 있습니다.

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

가장 왼쪽의 치수를 생략 할 수 있으므로 두 가지 옵션이 있습니다.

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

C ++ 표준에 의해 N-1 차원 배열에 대한 포인터에 대한 N 차원 배열의 붕괴가 허용됩니다.가장 왼쪽의 차원을 잃어도 여전히 N-1 차원 정보로 배열 요소에 올바르게 액세스 할 수 있기 때문에 .

자세한 내용은 여기

비록, 배열과 포인터는 동일하지 않습니다 배열 포인터로 붕괴 할 수 있지만, 포인터가 가리키는에 데이터의 크기 / 구성에 대한 상태를 수행하지 않습니다.

A char **는 문자 포인터를 포함하는 메모리 블록에 대한 포인터 이며, 이는 자체적으로 문자의 메모리 블록을 가리 킵니다. A char [][] 문자를 포함 하는 단일 메모리 블록 입니다. 이는 컴파일러가 코드를 변환하는 방법과 최종 성능이 어떻게 달라지는 지에 영향을줍니다.

출처

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.