Kích thước cố định
1. Đi qua tham khảo
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;
}
}
Trong C ++, truyền mảng bằng tham chiếu mà không làm mất thông tin kích thước có lẽ là an toàn nhất, vì người ta không cần lo lắng về việc người gọi chuyển một thứ nguyên không chính xác (cờ trình biên dịch khi không khớp). Tuy nhiên, điều này là không thể với các mảng động (freestore); nó chỉ hoạt động cho các mảng tự động ( thường là ngăn xếp ), tức là kích thước nên được biết đến tại thời điểm biên dịch.
2. Đi bằng con trỏ
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;
}
}
Tương đương C của phương thức trước đó là truyền mảng bằng con trỏ. Không nên nhầm lẫn với việc chuyển qua loại con trỏ bị phân rã của mảng (3) , đây là phương pháp phổ biến, phổ biến, mặc dù ít an toàn hơn phương pháp này nhưng linh hoạt hơn. Giống như (1) , sử dụng phương pháp này khi tất cả các kích thước của mảng được cố định và được biết tại thời gian biên dịch. Lưu ý rằng khi gọi hàm, địa chỉ của mảng phải được truyền process_2d_array_pointer(&a)
và không phải là địa chỉ của phần tử đầu tiên bằng cách phân rã process_2d_array_pointer(a)
.
Kích thước thay đổi
Chúng được kế thừa từ C nhưng kém an toàn hơn, trình biên dịch không có cách nào kiểm tra, đảm bảo rằng người gọi sẽ vượt qua các kích thước yêu cầu. Hàm chỉ ngân hàng về những gì người gọi chuyển qua dưới dạng thứ nguyên. Chúng linh hoạt hơn các loại trên vì các mảng có độ dài khác nhau có thể được truyền cho chúng một cách bất biến.
Cần nhớ rằng không có thứ gì truyền trực tiếp một mảng vào hàm trong C [trong khi ở C ++, chúng có thể được truyền dưới dạng tham chiếu (1) ]; (2) đang truyền một con trỏ tới mảng chứ không phải chính mảng đó. Luôn luôn truyền một mảng như là trở thành một hoạt động sao chép con trỏ, được tạo điều kiện thuận lợi bởi bản chất phân rã của mảng thành một con trỏ .
3. Chuyển qua (giá trị) một con trỏ đến loại đã phân rã
// 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;
}
}
Mặc dù int array[][10]
được cho phép, tôi không khuyến nghị sử dụng cú pháp trên vì cú pháp trên cho thấy rõ rằng định danh array
là một con trỏ duy nhất cho một mảng gồm 10 số nguyên, trong khi cú pháp này trông giống như một mảng 2D nhưng là cùng một con trỏ một mảng gồm 10 số nguyên. Ở đây chúng ta biết số lượng phần tử trong một hàng (tức là kích thước cột, 10 ở đây) nhưng số lượng hàng không xác định và do đó được chuyển qua làm đối số. Trong trường hợp này có một số an toàn vì trình biên dịch có thể gắn cờ khi con trỏ tới một mảng có kích thước thứ hai không bằng 10 được thông qua. Kích thước đầu tiên là phần khác nhau và có thể được bỏ qua. Xem ở đây để biết lý do tại sao chỉ có kích thước đầu tiên được phép bỏ qua.
4. Chuyển bằng con trỏ đến một con trỏ
// 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;
}
}
Một lần nữa, có một cú pháp thay thế int *array[10]
giống như int **array
. Trong cú pháp này, [10]
nó bị bỏ qua khi nó phân rã thành một con trỏ do đó trở thành int **array
. Có lẽ nó chỉ là một gợi ý cho người gọi rằng mảng đã qua phải có ít nhất 10 cột, thậm chí sau đó cần có số hàng. Trong mọi trường hợp, trình biên dịch không gắn cờ cho bất kỳ vi phạm chiều dài / kích thước nào (nó chỉ kiểm tra xem kiểu được truyền có phải là con trỏ tới con trỏ không), do đó yêu cầu cả số hàng và số cột là tham số có ý nghĩa ở đây.
Lưu ý: (4) là tùy chọn ít an toàn nhất vì hầu như không có bất kỳ loại kiểm tra nào và bất tiện nhất. Một cách hợp pháp không thể chuyển một mảng 2D cho chức năng này; C-FAQ lên án cách giải quyết thông thường khi thực hiện int x[5][10]; process_pointer_2_pointer((int**)&x[0][0], 5, 10);
vì nó có thể dẫn đến hành vi không xác định do làm phẳng mảng. Cách truyền đúng một mảng trong phương thức này đưa chúng ta đến phần bất tiện, tức là chúng ta cần một mảng con trỏ bổ sung (thay thế) với mỗi phần tử của nó trỏ đến hàng tương ứng của mảng thực tế được truyền; thay thế này sau đó được chuyển đến chức năng (xem bên dưới); tất cả điều này để có được cùng một công việc được thực hiện như các phương pháp trên an toàn hơn, sạch hơn và có lẽ nhanh hơn.
Đây là một chương trình trình điều khiển để kiểm tra các chức năng trên:
#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**
}