Làm cách nào để sử dụng mảng trong C ++?


480

C ++ thừa hưởng mảng từ C nơi chúng được sử dụng hầu như ở mọi nơi. C ++ cung cấp các tóm tắt dễ sử dụng hơn và ít bị lỗi hơn ( std::vector<T>kể từ C ++ 98 và std::array<T, n>từ C ++ 11 ), do đó, nhu cầu về mảng không phát sinh thường xuyên như trong C. Tuy nhiên, khi bạn đọc di sản mã hoặc tương tác với một thư viện được viết bằng C, bạn nên nắm vững cách thức hoạt động của mảng.

Câu hỏi thường gặp này được chia thành năm phần:

  1. mảng ở cấp độ loại và các phần tử truy cập
  2. tạo và khởi tạo mảng
  3. gán và tham số truyền
  4. mảng đa chiều và mảng con trỏ
  5. cạm bẫy phổ biến khi sử dụng mảng

Nếu bạn cảm thấy thiếu một cái gì đó quan trọng trong Câu hỏi thường gặp này, hãy viết câu trả lời và liên kết nó ở đây như một phần bổ sung.

Trong văn bản sau đây, "mảng" có nghĩa là "mảng C", không phải mẫu lớp std::array. Kiến thức cơ bản về cú pháp khai báo C được giả định. Lưu ý rằng việc sử dụng thủ công newdeletenhư được trình bày dưới đây là cực kỳ nguy hiểm khi đối mặt với các ngoại lệ, nhưng đó là chủ đề của Câu hỏi thường gặp khác .

(Lưu ý: Đây có nghĩa là một mục trong Câu hỏi thường gặp về C ++ của Stack Overflow . Nếu bạn muốn phê bình ý tưởng cung cấp Câu hỏi thường gặp trong biểu mẫu này, thì bài đăng trên meta bắt đầu tất cả điều này sẽ là nơi để thực hiện điều đó. câu hỏi đó được theo dõi trong phòng chat C ++ , nơi ý tưởng FAQ bắt đầu ngay từ đầu, vì vậy câu trả lời của bạn rất có thể được đọc bởi những người nghĩ ra ý tưởng.)


Họ thậm chí sẽ tốt hơn nếu con trỏ luôn chỉ vào điểm bắt đầu thay vì ở đâu đó ở giữa mục tiêu của họ mặc dù ...
Ded repeatator

Bạn nên sử dụng Vector STL vì nó cung cấp cho bạn sự linh hoạt cao hơn.
Moiz Sajid ngày 30/8/2015

2
Với sự sẵn có kết hợp của std::arrays, std::vectors và gsl::spans - tôi thực sự mong đợi một Câu hỏi thường gặp về cách sử dụng mảng trong C ++ để nói "Đến bây giờ, bạn có thể bắt đầu xem xét chỉ, tốt, không sử dụng chúng."
einpoklum

Câu trả lời:


302

Mảng trên cấp độ loại

Một kiểu mảng được ký hiệu là T[n]nơi Tloại phần tửnkích thước dương , số lượng phần tử trong mảng. Kiểu mảng là loại sản phẩm của loại phần tử và kích thước. Nếu một hoặc cả hai thành phần đó khác nhau, bạn sẽ có một loại khác biệt:

#include <type_traits>

static_assert(!std::is_same<int[8], float[8]>::value, "distinct element type");
static_assert(!std::is_same<int[8],   int[9]>::value, "distinct size");

Lưu ý rằng kích thước là một phần của loại, nghĩa là, các loại mảng có kích thước khác nhau là các loại không tương thích hoàn toàn không liên quan gì đến nhau. sizeof(T[n])tương đương với n * sizeof(T).

Phân rã mảng-con trỏ

Các chỉ "kết nối" giữa T[n]T[m]là cả hai loại ngầm có thể được chuyển đổi để T*, và kết quả của việc chuyển đổi này là một con trỏ đến phần tử đầu tiên của mảng. Nghĩa là, bất cứ nơi nào T*được yêu cầu, bạn có thể cung cấp a T[n]và trình biên dịch sẽ âm thầm cung cấp con trỏ đó:

                  +---+---+---+---+---+---+---+---+
the_actual_array: |   |   |   |   |   |   |   |   |   int[8]
                  +---+---+---+---+---+---+---+---+
                    ^
                    |
                    |
                    |
                    |  pointer_to_the_first_element   int*

Chuyển đổi này được gọi là "phân rã mảng-con trỏ" và nó là một nguồn gây nhầm lẫn chính. Kích thước của mảng bị mất trong quá trình này, vì nó không còn là một phần của kiểu ( T*). Pro: Việc quên kích thước của một mảng ở cấp độ cho phép một con trỏ trỏ đến phần tử đầu tiên của một mảng kích thước bất kỳ . Con: Đưa ra một con trỏ tới phần tử đầu tiên (hoặc bất kỳ phần tử nào khác) của một mảng, không có cách nào để phát hiện mảng đó lớn đến mức nào hoặc nơi chính xác con trỏ trỏ đến giới hạn của mảng. Con trỏ cực kỳ ngu ngốc .

Mảng không phải là con trỏ

Trình biên dịch sẽ âm thầm tạo một con trỏ tới phần tử đầu tiên của một mảng bất cứ khi nào nó được coi là hữu ích, nghĩa là, bất cứ khi nào một hoạt động sẽ thất bại trên một mảng nhưng thành công trên một con trỏ. Việc chuyển đổi từ mảng này sang con trỏ là không đáng kể, vì giá trị con trỏ kết quả chỉ đơn giản là địa chỉ của mảng. Lưu ý rằng con trỏ không được lưu trữ như một phần của chính mảng (hoặc bất kỳ nơi nào khác trong bộ nhớ). Một mảng không phải là một con trỏ.

static_assert(!std::is_same<int[8], int*>::value, "an array is not a pointer");

Một bối cảnh quan trọng trong đó một mảng không phân rã thành một con trỏ đến phần tử đầu tiên của nó là khi &toán tử được áp dụng cho nó. Trong trường hợp đó, &toán tử mang lại một con trỏ cho toàn bộ mảng, không chỉ là một con trỏ tới phần tử đầu tiên của nó. Mặc dù trong trường hợp đó, các giá trị (địa chỉ) là như nhau, một con trỏ tới phần tử đầu tiên của một mảng và một con trỏ tới toàn bộ mảng là các loại hoàn toàn khác nhau:

static_assert(!std::is_same<int*, int(*)[8]>::value, "distinct element type");

Nghệ thuật ASCII sau đây giải thích sự khác biệt này:

      +-----------------------------------+
      | +---+---+---+---+---+---+---+---+ |
+---> | |   |   |   |   |   |   |   |   | | int[8]
|     | +---+---+---+---+---+---+---+---+ |
|     +---^-------------------------------+
|         |
|         |
|         |
|         |  pointer_to_the_first_element   int*
|
|  pointer_to_the_entire_array              int(*)[8]

Lưu ý cách con trỏ tới phần tử đầu tiên chỉ trỏ đến một số nguyên duy nhất (được mô tả dưới dạng một hộp nhỏ), trong khi con trỏ tới toàn bộ mảng trỏ đến một mảng gồm 8 số nguyên (được mô tả dưới dạng một hộp lớn).

Tình trạng tương tự phát sinh trong các lớp học và có thể rõ ràng hơn. Một con trỏ tới một đối tượng và một con trỏ tới thành viên dữ liệu đầu tiên của nó có cùng một giá trị (cùng một địa chỉ), nhưng chúng là các kiểu hoàn toàn khác nhau.

Nếu bạn không quen với cú pháp khai báo C, dấu ngoặc đơn trong loại int(*)[8]là điều cần thiết:

  • int(*)[8] là một con trỏ tới một mảng gồm 8 số nguyên.
  • int*[8]là một mảng gồm 8 con trỏ, mỗi phần tử của loại int*.

Các yếu tố truy cập

C ++ cung cấp hai biến thể cú pháp để truy cập các phần tử riêng lẻ của một mảng. Không ai trong số họ vượt trội so với người kia, và bạn nên làm quen với cả hai.

Số học con trỏ

Cho một con trỏ ptới phần tử đầu tiên của một mảng, biểu thức p+imang lại một con trỏ tới phần tử thứ i của mảng. Bằng cách hủy bỏ hội thảo mà con trỏ sau đó, người ta có thể truy cập các yếu tố riêng lẻ:

std::cout << *(x+3) << ", " << *(x+7) << std::endl;

Nếu xbiểu thị một mảng , thì phân rã mảng-con trỏ sẽ khởi động, bởi vì việc thêm một mảng và một số nguyên là vô nghĩa (không có thao tác cộng trên các mảng), nhưng thêm một con trỏ và một số nguyên có ý nghĩa:

   +---+---+---+---+---+---+---+---+
x: |   |   |   |   |   |   |   |   |   int[8]
   +---+---+---+---+---+---+---+---+
     ^           ^               ^
     |           |               |
     |           |               |
     |           |               |
x+0  |      x+3  |          x+7  |     int*

(Lưu ý rằng con trỏ được tạo ngầm không có tên, vì vậy tôi đã viết x+0để xác định nó.)

Mặt khác, nếu xbiểu thị một con trỏ đến phần tử đầu tiên (hoặc bất kỳ phần nào khác) của một mảng, thì việc phân rã mảng thành con trỏ là không cần thiết, bởi vì con trỏ isẽ được thêm vào đã tồn tại:

   +---+---+---+---+---+---+---+---+
   |   |   |   |   |   |   |   |   |   int[8]
   +---+---+---+---+---+---+---+---+
     ^           ^               ^
     |           |               |
     |           |               |
   +-|-+         |               |
x: | | |    x+3  |          x+7  |     int*
   +---+

Lưu ý rằng trong trường hợp được mô tả, xlà một biến con trỏ (có thể thấy rõ bằng ô nhỏ bên cạnh x), nhưng nó cũng có thể là kết quả của một hàm trả về một con trỏ (hoặc bất kỳ biểu thức nào khác của loại T*).

Toán tử lập chỉ mục

Vì cú pháp *(x+i)hơi vụng về, C ++ cung cấp cú pháp thay thế x[i]:

std::cout << x[3] << ", " << x[7] << std::endl;

Do thực tế là bổ sung là giao hoán, đoạn mã sau thực hiện chính xác như sau:

std::cout << 3[x] << ", " << 7[x] << std::endl;

Định nghĩa của toán tử lập chỉ mục dẫn đến sự tương đương thú vị sau:

&x[i]  ==  &*(x+i)  ==  x+i

Tuy nhiên, &x[0]nói chung là không tương đương với x. Cái trước là một con trỏ, cái sau là một mảng. Chỉ khi bối cảnh kích hoạt phân rã mảng-con trỏ mới có thể x&x[0]được sử dụng thay thế cho nhau. Ví dụ:

T* p = &array[0];  // rewritten as &*(array+0), decay happens due to the addition
T* q = array;      // decay happens due to the assignment

Trên dòng đầu tiên, trình biên dịch phát hiện một phép gán từ một con trỏ đến một con trỏ, thành công không đáng kể. Trên dòng thứ hai, nó phát hiện một phép gán từ một mảng đến một con trỏ. Do đây là vô nghĩa (nhưng con trỏ để giao con trỏ làm cho tinh thần), mảng-to-con trỏ đá sâu trong như bình thường.

Các dãy

Một mảng kiểu T[n]ncác phần tử, được lập chỉ mục từ 0đến n-1; không có yếu tố n. Tuy nhiên, để hỗ trợ các phạm vi nửa mở (trong đó bắt đầu là bao gồm và kết thúc là độc quyền ), C ++ cho phép tính toán một con trỏ đến phần tử thứ n (không tồn tại), nhưng việc vi phạm con trỏ đó là bất hợp pháp:

   +---+---+---+---+---+---+---+---+....
x: |   |   |   |   |   |   |   |   |   .   int[8]
   +---+---+---+---+---+---+---+---+....
     ^                               ^
     |                               |
     |                               |
     |                               |
x+0  |                          x+8  |     int*

Ví dụ: nếu bạn muốn sắp xếp một mảng, cả hai điều sau đây sẽ hoạt động tốt như nhau:

std::sort(x + 0, x + n);
std::sort(&x[0], &x[0] + n);

Lưu ý rằng việc cung cấp &x[n]làm đối số thứ hai là bất hợp pháp vì điều này tương đương với &*(x+n)và biểu thức phụ *(x+n)gọi về mặt kỹ thuật hành vi không xác định trong C ++ (nhưng không phải trong C99).

Cũng lưu ý rằng bạn chỉ có thể cung cấp xnhư là đối số đầu tiên. Đó là một chút quá ngắn gọn đối với sở thích của tôi và nó cũng làm cho việc suy luận đối số khuôn mẫu khó hơn một chút đối với trình biên dịch, bởi vì trong trường hợp đó, đối số thứ nhất là một mảng nhưng đối số thứ hai là một con trỏ. (Một lần nữa, phân rã mảng-con trỏ khởi động.)


Các trường hợp mảng không phân rã thành một con trỏ được minh họa ở đây để tham khảo.
huyền thoại2k

@fredoverflow Trong phần Access hoặc Ranges, có thể đáng nói rằng các mảng C hoạt động với các vòng lặp dựa trên phạm vi C ++ 11 cho các vòng lặp.
gnzlbg

135

Các lập trình viên thường nhầm lẫn các mảng đa chiều với các mảng con trỏ.

Mảng nhiều chiều

Hầu hết các lập trình viên đều quen thuộc với các mảng đa chiều có tên, nhưng nhiều người không biết thực tế là mảng đa chiều cũng có thể được tạo ẩn danh. Mảng nhiều chiều thường được gọi là "mảng của mảng" hoặc " mảng đa chiều thực sự ".

Được đặt tên là mảng đa chiều

Khi sử dụng mảng đa chiều có tên, tất cả các kích thước phải được biết tại thời điểm biên dịch:

int H = read_int();
int W = read_int();

int connect_four[6][7];   // okay

int connect_four[H][7];   // ISO C++ forbids variable length array
int connect_four[6][W];   // ISO C++ forbids variable length array
int connect_four[H][W];   // ISO C++ forbids variable length array

Đây là cách một mảng đa chiều được đặt tên trông giống như trong bộ nhớ:

              +---+---+---+---+---+---+---+
connect_four: |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+

Lưu ý rằng các lưới 2D như ở trên chỉ là trực quan hữu ích. Theo quan điểm của C ++, bộ nhớ là một chuỗi byte "phẳng". Các phần tử của một mảng nhiều chiều được lưu trữ theo thứ tự hàng lớn. Đó là, connect_four[0][6]connect_four[1][0]là hàng xóm trong ký ức. Trong thực tế, connect_four[0][7]connect_four[1][0]biểu thị cùng một yếu tố! Điều này có nghĩa là bạn có thể lấy mảng nhiều chiều và coi chúng là mảng lớn, một chiều:

int* p = &connect_four[0][0];
int* q = p + 42;
some_int_sequence_algorithm(p, q);

Mảng đa chiều vô danh

Với các mảng đa chiều ẩn danh, tất cả các kích thước ngoại trừ đầu tiên phải được biết tại thời điểm biên dịch:

int (*p)[7] = new int[6][7];   // okay
int (*p)[7] = new int[H][7];   // okay

int (*p)[W] = new int[6][W];   // ISO C++ forbids variable length array
int (*p)[W] = new int[H][W];   // ISO C++ forbids variable length array

Đây là cách một mảng đa chiều ẩn danh trông giống như trong bộ nhớ:

              +---+---+---+---+---+---+---+
        +---> |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |
      +-|-+
   p: | | |
      +---+

Lưu ý rằng bản thân mảng vẫn được phân bổ dưới dạng một khối trong bộ nhớ.

Mảng con trỏ

Bạn có thể khắc phục hạn chế về chiều rộng cố định bằng cách đưa ra một mức độ gián tiếp khác.

Mảng con trỏ được đặt tên

Dưới đây là một mảng có tên gồm năm con trỏ được khởi tạo với các mảng ẩn danh có độ dài khác nhau:

int* triangle[5];
for (int i = 0; i < 5; ++i)
{
    triangle[i] = new int[5 - i];
}

// ...

for (int i = 0; i < 5; ++i)
{
    delete[] triangle[i];
}

Và đây là cách nó trông giống như trong bộ nhớ:

          +---+---+---+---+---+
          |   |   |   |   |   |
          +---+---+---+---+---+
            ^
            | +---+---+---+---+
            | |   |   |   |   |
            | +---+---+---+---+
            |   ^
            |   | +---+---+---+
            |   | |   |   |   |
            |   | +---+---+---+
            |   |   ^
            |   |   | +---+---+
            |   |   | |   |   |
            |   |   | +---+---+
            |   |   |   ^
            |   |   |   | +---+
            |   |   |   | |   |
            |   |   |   | +---+
            |   |   |   |   ^
            |   |   |   |   |
            |   |   |   |   |
          +-|-+-|-+-|-+-|-+-|-+
triangle: | | | | | | | | | | |
          +---+---+---+---+---+

Vì hiện tại mỗi dòng được phân bổ riêng lẻ, nên xem mảng 2D dưới dạng mảng 1D không hoạt động nữa.

Mảng ẩn danh của con trỏ

Dưới đây là một mảng ẩn danh gồm 5 (hoặc bất kỳ số lượng con trỏ) nào khác được khởi tạo với các mảng ẩn danh có độ dài khác nhau:

int n = calculate_five();   // or any other number
int** p = new int*[n];
for (int i = 0; i < n; ++i)
{
    p[i] = new int[n - i];
}

// ...

for (int i = 0; i < n; ++i)
{
    delete[] p[i];
}
delete[] p;   // note the extra delete[] !

Và đây là cách nó trông giống như trong bộ nhớ:

          +---+---+---+---+---+
          |   |   |   |   |   |
          +---+---+---+---+---+
            ^
            | +---+---+---+---+
            | |   |   |   |   |
            | +---+---+---+---+
            |   ^
            |   | +---+---+---+
            |   | |   |   |   |
            |   | +---+---+---+
            |   |   ^
            |   |   | +---+---+
            |   |   | |   |   |
            |   |   | +---+---+
            |   |   |   ^
            |   |   |   | +---+
            |   |   |   | |   |
            |   |   |   | +---+
            |   |   |   |   ^
            |   |   |   |   |
            |   |   |   |   |
          +-|-+-|-+-|-+-|-+-|-+
          | | | | | | | | | | |
          +---+---+---+---+---+
            ^
            |
            |
          +-|-+
       p: | | |
          +---+

Chuyển đổi

Phân rã mảng-con trỏ tự nhiên mở rộng thành các mảng của mảng và mảng của con trỏ:

int array_of_arrays[6][7];
int (*pointer_to_array)[7] = array_of_arrays;

int* array_of_pointers[6];
int** pointer_to_pointer = array_of_pointers;

Tuy nhiên, không có chuyển đổi ngầm định từ T[h][w]sang T**. Nếu một chuyển đổi ngầm như vậy đã tồn tại, kết quả sẽ là một con trỏ tới phần tử đầu tiên của một mảng các hcon trỏ tới T(mỗi trỏ đến phần tử đầu tiên của một dòng trong mảng 2D gốc), nhưng mảng con trỏ đó không tồn tại ở bất kỳ đâu trong bộ nhớ chưa. Nếu bạn muốn chuyển đổi như vậy, bạn phải tạo và điền vào mảng con trỏ yêu cầu theo cách thủ công:

int connect_four[6][7];

int** p = new int*[6];
for (int i = 0; i < 6; ++i)
{
    p[i] = connect_four[i];
}

// ...

delete[] p;

Lưu ý rằng điều này tạo ra một khung nhìn của mảng đa chiều ban đầu. Nếu bạn cần một bản sao thay thế, bạn phải tạo thêm mảng và tự sao chép dữ liệu:

int connect_four[6][7];

int** p = new int*[6];
for (int i = 0; i < 6; ++i)
{
    p[i] = new int[7];
    std::copy(connect_four[i], connect_four[i + 1], p[i]);
}

// ...

for (int i = 0; i < 6; ++i)
{
    delete[] p[i];
}
delete[] p;

Theo gợi ý: Bạn nên chỉ ra rằng int connect_four[H][7];, int connect_four[6][W]; int connect_four[H][W];cũng như int (*p)[W] = new int[6][W];int (*p)[W] = new int[H][W];là các câu lệnh hợp lệ, khi HWđược biết tại thời điểm biên dịch.
RobertS hỗ trợ Monica Cellio

88

Bài tập

Không có lý do cụ thể, các mảng không thể được gán cho nhau. Sử dụng std::copythay thế:

#include <algorithm>

// ...

int a[8] = {2, 3, 5, 7, 11, 13, 17, 19};
int b[8];
std::copy(a + 0, a + 8, b);

Điều này linh hoạt hơn những gì gán mảng thực sự có thể cung cấp vì có thể sao chép các lát của mảng lớn hơn thành mảng nhỏ hơn. std::copythường là chuyên biệt cho các loại nguyên thủy để cung cấp hiệu suất tối đa. Nó không chắc là std::memcpythực hiện tốt hơn. Nếu nghi ngờ, hãy đo.

Mặc dù bạn không thể gán các mảng trực tiếp, bạn có thể gán các cấu trúc và các lớp có chứa các thành viên mảng. Đó là bởi vì các thành viên mảng được sao chép thành viên bởi toán tử gán được cung cấp làm mặc định bởi trình biên dịch. Nếu bạn xác định toán tử gán theo cách thủ công cho các kiểu cấu trúc hoặc lớp của riêng bạn, bạn phải quay lại sao chép thủ công cho các thành viên mảng.

Thông số đi qua

Mảng không thể được thông qua bởi giá trị. Bạn có thể vượt qua chúng bằng con trỏ hoặc bằng cách tham chiếu.

Đi qua con trỏ

Vì bản thân các mảng không thể được truyền theo giá trị, nên thường một con trỏ đến phần tử đầu tiên của chúng được truyền theo giá trị thay thế. Điều này thường được gọi là "vượt qua con trỏ". Vì kích thước của mảng không thể truy xuất được thông qua con trỏ đó, bạn phải truyền tham số thứ hai cho biết kích thước của mảng (giải pháp C cổ điển) hoặc con trỏ thứ hai chỉ sau phần tử cuối cùng của mảng (giải pháp lặp C ++) :

#include <numeric>
#include <cstddef>

int sum(const int* p, std::size_t n)
{
    return std::accumulate(p, p + n, 0);
}

int sum(const int* p, const int* q)
{
    return std::accumulate(p, q, 0);
}

Là một thay thế cú pháp, bạn cũng có thể khai báo các tham số là T p[]và nó có nghĩa chính xác giống như T* p trong ngữ cảnh của danh sách tham số :

int sum(const int p[], std::size_t n)
{
    return std::accumulate(p, p + n, 0);
}

Bạn có thể nghĩ về trình biên dịch như viết lại T p[]để T *p trong bối cảnh chỉ liệt kê các tham số . Quy tắc đặc biệt này chịu trách nhiệm một phần cho toàn bộ sự nhầm lẫn về mảng và con trỏ. Trong mọi bối cảnh khác, việc khai báo một cái gì đó là một mảng hoặc như một con trỏ tạo ra sự khác biệt rất lớn .

Thật không may, bạn cũng có thể cung cấp một kích thước trong một tham số mảng mà trình biên dịch âm thầm bỏ qua. Đó là, ba chữ ký sau đây là tương đương chính xác, như được chỉ ra bởi các lỗi biên dịch:

int sum(const int* p, std::size_t n)

// error: redefinition of 'int sum(const int*, size_t)'
int sum(const int p[], std::size_t n)

// error: redefinition of 'int sum(const int*, size_t)'
int sum(const int p[8], std::size_t n)   // the 8 has no meaning here

Đi qua tham khảo

Mảng cũng có thể được thông qua tham chiếu:

int sum(const int (&a)[8])
{
    return std::accumulate(a + 0, a + 8, 0);
}

Trong trường hợp này, kích thước mảng là đáng kể. Do việc viết một hàm chỉ chấp nhận các mảng có đúng 8 phần tử nên ít được sử dụng, các lập trình viên thường viết các hàm như mẫu:

template <std::size_t n>
int sum(const int (&a)[n])
{
    return std::accumulate(a + 0, a + n, 0);
}

Lưu ý rằng bạn chỉ có thể gọi một mẫu hàm như vậy với một mảng số nguyên thực tế, không phải bằng một con trỏ tới một số nguyên. Kích thước của mảng được tự động suy ra và với mỗi kích thước n, một chức năng khác nhau được khởi tạo từ mẫu. Bạn cũng có thể viết các mẫu hàm khá hữu ích trừu tượng từ cả loại phần tử và kích thước.


2
Có thể đáng để thêm một lưu ý rằng ngay cả tho trong void foo(int a[3]) atrông giống như một người đang truyền mảng theo giá trị, sửa đổi abên trong foosẽ sửa đổi mảng ban đầu. Điều này cần rõ ràng vì các mảng không thể được sao chép, nhưng nó có thể đáng để củng cố điều đó.
gnzlbg

C ++ 20 córanges::copy(a, b)
LF

int sum( int size_, int a[size_]);- từ (tôi nghĩ) C99 trở đi
Đầu bếp Gladiator

73

5. Cạm bẫy thường gặp khi sử dụng mảng.

5.1 Cạm bẫy: Liên kết loại không an toàn.

OK, bạn đã được thông báo hoặc đã tự mình tìm ra rằng các quả cầu (biến phạm vi không gian tên có thể được truy cập bên ngoài đơn vị dịch) là Evil ™. Nhưng bạn có biết họ thật sự như thế nào không? Hãy xem xét chương trình bên dưới, bao gồm hai tệp [main.cpp] và [Numbers.cpp]:

// [main.cpp]
#include <iostream>

extern int* numbers;

int main()
{
    using namespace std;
    for( int i = 0;  i < 42;  ++i )
    {
        cout << (i > 0? ", " : "") << numbers[i];
    }
    cout << endl;
}

// [numbers.cpp]
int numbers[42] = {1, 2, 3, 4, 5, 6, 7, 8, 9};

Trong Windows 7, phần này biên dịch và liên kết tốt với cả MinGW g ++ 4.4.1 và Visual C ++ 10.0.

Vì các loại không khớp, chương trình gặp sự cố khi bạn chạy nó.

Hộp thoại Windows 7 crash

Giải thích không chính thức: chương trình có Hành vi không xác định (UB), và thay vì bị sập, do đó, nó có thể bị treo, hoặc có thể không làm gì, hoặc có thể gửi e-mail đe dọa tới các tổng thống của Hoa Kỳ, Nga, Ấn Độ, Trung Quốc và Thụy Sĩ, và làm cho Da mũi bay ra khỏi mũi của bạn.

Giải thích trong thực tế: trong main.cppmảng được coi là một con trỏ, được đặt tại cùng địa chỉ với mảng. Đối với thực thi 32 bit, điều này có nghĩa là intgiá trị đầu tiên trong mảng, được coi là một con trỏ. Ví dụ, trong main.cppcác numbersbiến chứa, hoặc xuất hiện để chứa, (int*)1. Điều này làm cho chương trình truy cập bộ nhớ xuống dưới cùng của không gian địa chỉ, được bảo lưu theo quy ước và gây ra bẫy. Kết quả: bạn gặp sự cố.

Các trình biên dịch hoàn toàn nằm trong quyền của họ để không chẩn đoán lỗi này, vì C ++ 11 §3,5 / 10 nói, về yêu cầu của các loại tương thích cho các khai báo,

[N3290 §3.5 / 10]
Việc vi phạm quy tắc này đối với nhận dạng loại không yêu cầu chẩn đoán.

Đoạn văn tương tự chi tiết biến thể được phép:

Khai báo của một đối tượng mảng có thể chỉ định các kiểu mảng khác nhau bởi sự hiện diện hoặc vắng mặt của một mảng chính bị ràng buộc (8.3.4).

Biến thể được phép này không bao gồm khai báo tên dưới dạng một mảng trong một đơn vị dịch thuật và như một con trỏ trong một đơn vị dịch thuật khác.

5.2 Cạm bẫy: Thực hiện tối ưu hóa sớm ( memset& bạn bè).

Chưa viết

5.3 Cạm bẫy: Sử dụng thành ngữ C để lấy số phần tử.

Với kinh nghiệm sâu sắc C, viết tự nhiên

#define N_ITEMS( array )   (sizeof( array )/sizeof( array[0] ))

Vì một arrayphân rã thành con trỏ đến phần tử đầu tiên khi cần, biểu thức sizeof(a)/sizeof(a[0])cũng có thể được viết là sizeof(a)/sizeof(*a). Nó có nghĩa tương tự, và cho dù nó được viết như thế nào thì đó là thành ngữ C để tìm các phần tử số của mảng.

Cạm bẫy chính: thành ngữ C không an toàn. Ví dụ, mã số

#include <stdio.h>

#define N_ITEMS( array ) (sizeof( array )/sizeof( *array ))

void display( int const a[7] )
{
    int const   n = N_ITEMS( a );          // Oops.
    printf( "%d elements.\n", n );
}

int main()
{
    int const   moohaha[]   = {1, 2, 3, 4, 5, 6, 7};

    printf( "%d elements, calling display...\n", N_ITEMS( moohaha ) );
    display( moohaha );
}

chuyển một con trỏ tới N_ITEMS, và do đó rất có thể tạo ra kết quả sai. Được biên dịch dưới dạng thực thi 32 bit trong Windows 7, nó tạo ra lỗi

7 yếu tố, hiển thị cuộc gọi ...
1 yếu tố.

  1. Trình biên dịch viết lại int const a[7]thành chỉ int const a[].
  2. Trình biên dịch viết lại int const a[]thành int const* a.
  3. N_ITEMS do đó được gọi với một con trỏ.
  4. Đối với thực thi 32 bit sizeof(array)(kích thước của một con trỏ) thì 4.
  5. sizeof(*array)tương đương với sizeof(int), đối với thực thi 32 bit cũng là 4.

Để phát hiện lỗi này trong thời gian chạy, bạn có thể thực hiện

#include <assert.h>
#include <typeinfo>

#define N_ITEMS( array )       (                               \
    assert((                                                    \
        "N_ITEMS requires an actual array as argument",        \
        typeid( array ) != typeid( &*array )                    \
        )),                                                     \
    sizeof( array )/sizeof( *array )                            \
    )

7 phần tử, hiển thị cuộc gọi ...
Xác nhận không thành công: ("N_ITEMS yêu cầu một mảng thực tế làm đối số", typeid (a)! = Typeid (& * a)), tệp runtime_detect ion.cpp, dòng 16

Ứng dụng này đã yêu cầu Runtime chấm dứt nó theo một cách khác thường.
Vui lòng liên hệ với nhóm hỗ trợ của ứng dụng để biết thêm thông tin.

Phát hiện lỗi thời gian chạy tốt hơn là không phát hiện, nhưng nó lãng phí một ít thời gian xử lý và có lẽ nhiều thời gian lập trình hơn. Tốt hơn với phát hiện tại thời gian biên dịch! Và nếu bạn không vui khi không hỗ trợ mảng các kiểu cục bộ với C ++ 98, thì bạn có thể làm điều đó:

#include <stddef.h>

typedef ptrdiff_t   Size;

template< class Type, Size n >
Size n_items( Type (&)[n] ) { return n; }

#define N_ITEMS( array )       n_items( array )

Biên dịch định nghĩa này được thay thế vào chương trình hoàn chỉnh đầu tiên, với g ++, tôi đã nhận được

M: \ Count> g ++ compile_time_detection.cpp
compile_time_detection.cpp: Trong hàm 'void display (const int *)':
compile_time_detection.cpp: 14: error: không có chức năng khớp nào để gọi tới 'n_items (const int * &)'

M: \ đếm> _

Cách thức hoạt động: mảng được truyền bằng tham chiếu đến n_items, và do đó nó không phân rã thành con trỏ đến phần tử đầu tiên và hàm chỉ có thể trả về số lượng phần tử được chỉ định bởi loại.

Với C ++ 11, bạn cũng có thể sử dụng điều này cho các mảng kiểu cục bộ và đó là thành ngữ C ++ an toàn để tìm số lượng phần tử của một mảng.

Cạm bẫy 5,4 C ++ 11 & C ++ 14: Sử dụng constexprhàm kích thước mảng.

Với C ++ 11 trở lên, điều đó là tự nhiên, nhưng như bạn sẽ thấy nguy hiểm!, Để thay thế chức năng C ++ 03

typedef ptrdiff_t   Size;

template< class Type, Size n >
Size n_items( Type (&)[n] ) { return n; }

với

using Size = ptrdiff_t;

template< class Type, Size n >
constexpr auto n_items( Type (&)[n] ) -> Size { return n; }

trong đó sự thay đổi đáng kể là việc sử dụng constexpr, cho phép hàm này tạo ra hằng số thời gian biên dịch .

Ví dụ, trái ngược với hàm C ++ 03, hằng số thời gian biên dịch như vậy có thể được sử dụng để khai báo một mảng có cùng kích thước với một mảng khác:

// Example 1
void foo()
{
    int const x[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 4};
    constexpr Size n = n_items( x );
    int y[n] = {};
    // Using y here.
}

Nhưng hãy xem xét mã này bằng constexprphiên bản:

// Example 2
template< class Collection >
void foo( Collection const& c )
{
    constexpr int n = n_items( c );     // Not in C++14!
    // Use c here
}

auto main() -> int
{
    int x[42];
    foo( x );
}

Cạm bẫy: kể từ tháng 7 năm 2015, các biên dịch trên với MinGW-64 5.1.0 với -pedantic-errors, và, thử nghiệm với các trình biên dịch trực tuyến tại gcc.godbolt.org/ , cũng với clang 3.0 và clang 3.2, nhưng không phải với clang 3.3, 3.4. 1, 3.5.0, 3.5.1, 3.6 (RC1) hoặc 3.7 (thử nghiệm). Và quan trọng đối với nền tảng Windows, nó không biên dịch với Visual C ++ 2015. Lý do là một tuyên bố C ++ 11 / C ++ 14 về việc sử dụng các tham chiếu trong các constexprbiểu thức:

C ++ 11 C ++ 14 $ 5,19 / 2 dấu gạch ngang thứ chín

Một điều kiện thể hiện e là một biểu thức hằng lõi trừ khi việc thẩm định e, theo các quy tắc của máy trừu tượng (1.9), sẽ đánh giá một trong những biểu hiện sau:
        ⋮

  • một biểu thức id đề cập đến một biến hoặc thành viên dữ liệu của kiểu tham chiếu trừ khi tham chiếu có khởi tạo trước và
    • nó được khởi tạo với một biểu thức không đổi hoặc
    • nó là một thành viên dữ liệu không tĩnh của một đối tượng có tuổi thọ bắt đầu trong quá trình đánh giá e;

Người ta luôn có thể viết dài dòng hơn

// Example 3  --  limited

using Size = ptrdiff_t;

template< class Collection >
void foo( Collection const& c )
{
    constexpr Size n = std::extent< decltype( c ) >::value;
    // Use c here
}

Sầu nhưng điều này thất bại khi Collectionkhông phải là một mảng thô.

Để đối phó với các tập hợp có thể không phải là mảng, người ta cần tính quá tải của n_itemshàm, nhưng đối với thời gian biên dịch, người ta cần một biểu diễn thời gian biên dịch của kích thước mảng. Và giải pháp C ++ 03 cổ điển, cũng hoạt động tốt trong C ++ 11 và C ++ 14, là để cho hàm báo cáo kết quả của nó không phải là một giá trị mà thông qua loại kết quả chức năng của nó . Ví dụ như thế này:

// Example 4 - OK (not ideal, but portable and safe)

#include <array>
#include <stddef.h>

using Size = ptrdiff_t;

template< Size n >
struct Size_carrier
{
    char sizer[n];
};

template< class Type, Size n >
auto static_n_items( Type (&)[n] )
    -> Size_carrier<n>;
// No implementation, is used only at compile time.

template< class Type, size_t n >        // size_t for g++
auto static_n_items( std::array<Type, n> const& )
    -> Size_carrier<n>;
// No implementation, is used only at compile time.

#define STATIC_N_ITEMS( c ) \
    static_cast<Size>( sizeof( static_n_items( c ).sizer ) )

template< class Collection >
void foo( Collection const& c )
{
    constexpr Size n = STATIC_N_ITEMS( c );
    // Use c here
    (void) c;
}

auto main() -> int
{
    int x[42];
    std::array<int, 43> y;
    foo( x );
    foo( y );
}

Về lựa chọn loại trả về cho static_n_items: mã này không sử dụng std::integral_constant vì với std::integral_constantkết quả được biểu thị trực tiếp dưới dạng constexprgiá trị, giới thiệu lại vấn đề ban đầu. Thay vì một Size_carrierlớp, người ta có thể để hàm trực tiếp trả về một tham chiếu đến một mảng. Tuy nhiên, không phải ai cũng quen thuộc với cú pháp đó.

Về cách đặt tên: một phần của giải pháp này cho constexprvấn đề -invalid-do-tham chiếu là làm cho sự lựa chọn của thời gian biên dịch không đổi rõ ràng.

Hy vọng rằng constexprvấn đề liên quan đến vấn đề của bạn sẽ được khắc phục với C ++ 17, nhưng cho đến khi đó, một macro như các STATIC_N_ITEMStrình biên dịch trên mang lại tính di động, ví dụ như trình biên dịch clang và Visual C ++, kiểu giữ lại sự an toàn.

Liên quan: macro không tôn trọng phạm vi, vì vậy để tránh xung đột tên nó có thể là một ý tưởng tốt để sử dụng một tiền tố tên, ví dụ MYLIB_STATIC_N_ITEMS.


1
+1 Thử nghiệm mã hóa Great C: Tôi đã dành 15 phút cho VC ++ 10.0 và GCC 4.1.2 để cố gắng khắc phục Segmentation fault... Cuối cùng tôi đã tìm thấy / hiểu được sau khi đọc các giải thích của bạn! Vui lòng viết phần §5.2 của bạn :-) Chúc mừng
olibre

Tốt Một nit - loại trả về cho CountOf phải là size_t thay vì ptrdiff_t. Có lẽ đáng nói đến là trong C ++ 11/14, nó phải là constexpr và noexception.
Ricky65

@ Ricky65: Cảm ơn bạn đã đề cập đến C ++ 11 cân nhắc. Hỗ trợ cho các tính năng này đã bị trễ trong Visual C ++. Về size_tđiều đó, không có lợi thế mà tôi biết đối với các nền tảng hiện đại, nhưng nó có một số vấn đề do các quy tắc chuyển đổi kiểu ngầm của C và C ++. Đó là, ptrdiff_tđược sử dụng rất có chủ ý, để tránh các vấn đề với size_t. Tuy nhiên, ta nên biết rằng g ++ có vấn đề với việc khớp kích thước mảng với tham số mẫu trừ khi nó size_t(tôi không nghĩ vấn đề cụ thể của trình biên dịch này với không size_tquan trọng, nhưng YMMV).
Chúc mừng và hth. - Alf

@Alf. Trong Dự thảo làm việc tiêu chuẩn (N3936) 8.3.4 Tôi đã đọc - Giới hạn của một mảng là ... "một biểu thức hằng được chuyển đổi của loại std :: size_t và giá trị của nó sẽ lớn hơn 0".
Ricky65

@Ricky: Nếu bạn đang đề cập đến sự không nhất quán, câu lệnh này không có trong tiêu chuẩn C ++ 11 hiện tại nên rất khó đoán ngữ cảnh, nhưng mâu thuẫn (một mảng được phân bổ động thể bị ràng buộc 0, mỗi C + +11 §5.3.4 / 7) có thể sẽ không kết thúc trong C ++ 14. Dự thảo chỉ là: dự thảo. Thay vào đó, nếu bạn hỏi về "cái" của nó đề cập đến cái gì, thì nó đề cập đến biểu thức ban đầu, không phải là biểu thức được chuyển đổi. Nếu trên mặt thứ ba, bạn đề cập đến điều này bởi vì bạn nghĩ rằng có thể một câu như vậy có nghĩa là người ta nên sử dụng size_tđể biểu thị kích thước của mảng, thì tất nhiên là không.
Chúc mừng và hth. - Alf

72

Tạo và khởi tạo mảng

Giống như bất kỳ loại đối tượng C ++ nào khác, các mảng có thể được lưu trữ trực tiếp trong các biến được đặt tên (khi đó kích thước phải là hằng số thời gian biên dịch; C ++ không hỗ trợ VLAs ) hoặc chúng có thể được lưu trữ ẩn danh trên heap và truy cập gián tiếp qua con trỏ (chỉ sau đó kích thước có thể được tính khi chạy).

Mảng tự động

Mảng tự động (mảng sống "trên ngăn xếp") được tạo ra mỗi khi luồng điều khiển đi qua định nghĩa của biến mảng cục bộ không tĩnh:

void foo()
{
    int automatic_array[8];
}

Khởi tạo được thực hiện theo thứ tự tăng dần. Lưu ý rằng các giá trị ban đầu phụ thuộc vào loại phần tử T:

  • Nếu Tlà một POD (như inttrong ví dụ trên), không có khởi tạo nào diễn ra.
  • Mặt khác, hàm tạo mặc định của Tkhởi tạo tất cả các phần tử.
  • Nếu Tkhông cung cấp hàm tạo mặc định có thể truy cập, chương trình không biên dịch.

Ngoài ra, các giá trị ban đầu có thể được chỉ định rõ ràng trong trình khởi tạo mảng , danh sách được phân tách bằng dấu phẩy được bao quanh bởi dấu ngoặc nhọn:

    int primes[8] = {2, 3, 5, 7, 11, 13, 17, 19};

Vì trong trường hợp này, số lượng phần tử trong trình khởi tạo mảng bằng với kích thước của mảng, việc chỉ định kích thước thủ công là không cần thiết. Nó có thể tự động được suy ra bởi trình biên dịch:

    int primes[] = {2, 3, 5, 7, 11, 13, 17, 19};   // size 8 is deduced

Cũng có thể chỉ định kích thước và cung cấp trình khởi tạo mảng ngắn hơn:

    int fibonacci[50] = {0, 1, 1};   // 47 trailing zeros are deduced

Trong trường hợp đó, các phần tử còn lại được khởi tạo bằng không . Lưu ý rằng C ++ cho phép một trình khởi tạo mảng trống (tất cả các phần tử đều được khởi tạo bằng 0), trong khi C89 thì không (ít nhất một giá trị là bắt buộc). Cũng lưu ý rằng bộ khởi tạo mảng chỉ có thể được sử dụng để khởi tạo mảng; sau này chúng không thể được sử dụng trong các bài tập.

Mảng tĩnh

Mảng tĩnh (mảng sống "trong phân đoạn dữ liệu") là các biến mảng cục bộ được xác định bằng các biến statictừ khóa và mảng ở phạm vi không gian tên ("biến toàn cục"):

int global_static_array[8];

void foo()
{
    static int local_static_array[8];
}

(Lưu ý rằng các biến ở phạm vi không gian tên là hoàn toàn tĩnh. Thêm statictừ khóa vào định nghĩa của chúng có ý nghĩa hoàn toàn khác, không dùng nữa .)

Đây là cách các mảng tĩnh hoạt động khác với các mảng tự động:

  • Mảng tĩnh không có bộ khởi tạo mảng được khởi tạo bằng 0 trước bất kỳ khởi tạo tiềm năng nào khác.
  • Các mảng POD tĩnh được khởi tạo chính xác một lần và các giá trị ban đầu thường được đưa vào tệp thực thi, trong trường hợp đó không có chi phí khởi tạo khi chạy. Tuy nhiên, đây không phải luôn là giải pháp tiết kiệm không gian nhất, và nó không được yêu cầu bởi tiêu chuẩn.
  • Các mảng không POD tĩnh được khởi tạo lần đầu tiên khi luồng điều khiển đi qua định nghĩa của chúng. Trong trường hợp mảng tĩnh cục bộ, điều đó có thể không bao giờ xảy ra nếu hàm không bao giờ được gọi.

(Không có điều nào ở trên là đặc trưng cho mảng. Các quy tắc này áp dụng tốt như nhau cho các loại đối tượng tĩnh khác.)

Thành viên dữ liệu mảng

Các thành viên dữ liệu mảng được tạo khi đối tượng sở hữu của chúng được tạo. Thật không may, C ++ 03 không cung cấp phương tiện để khởi tạo các mảng trong danh sách khởi tạo thành viên , vì vậy việc khởi tạo phải được làm giả bằng các bài tập:

class Foo
{
    int primes[8];

public:

    Foo()
    {
        primes[0] = 2;
        primes[1] = 3;
        primes[2] = 5;
        // ...
    }
};

Ngoài ra, bạn có thể xác định một mảng tự động trong phần thân của hàm tạo và sao chép các phần tử qua:

class Foo
{
    int primes[8];

public:

    Foo()
    {
        int local_array[] = {2, 3, 5, 7, 11, 13, 17, 19};
        std::copy(local_array + 0, local_array + 8, primes + 0);
    }
};

Trong C ++ 0x, các mảng có thể được khởi tạo trong danh sách khởi tạo thành viên nhờ khởi tạo thống nhất :

class Foo
{
    int primes[8];

public:

    Foo() : primes { 2, 3, 5, 7, 11, 13, 17, 19 }
    {
    }
};

Đây là giải pháp duy nhất hoạt động với các loại phần tử không có hàm tạo mặc định.

Mảng động

Mảng động không có tên, do đó phương tiện duy nhất để truy cập chúng là thông qua con trỏ. Bởi vì chúng không có tên, tôi sẽ gọi chúng là "mảng ẩn danh" kể từ bây giờ.

Trong C, mảng ẩn danh được tạo thông qua mallocvà bạn bè. Trong C ++, các mảng ẩn danh được tạo bằng new T[size]cú pháp trả về một con trỏ tới phần tử đầu tiên của một mảng ẩn danh:

std::size_t size = compute_size_at_runtime();
int* p = new int[size];

Nghệ thuật ASCII sau đây mô tả bố cục bộ nhớ nếu kích thước được tính là 8 khi chạy:

             +---+---+---+---+---+---+---+---+
(anonymous)  |   |   |   |   |   |   |   |   |
             +---+---+---+---+---+---+---+---+
               ^
               |
               |
             +-|-+
          p: | | |                               int*
             +---+

Rõ ràng, các mảng ẩn danh đòi hỏi nhiều bộ nhớ hơn các mảng được đặt tên do con trỏ phụ phải được lưu trữ riêng. (Ngoài ra còn có một số chi phí bổ sung trên cửa hàng miễn phí.)

Lưu ý rằng có không phân rã mảng-to-con trỏ xảy ra ở đây. Mặc dù đánh giá new int[size]nào trên thực tế tạo ra một mảng các số nguyên, kết quả của biểu thức new int[size]đã một con trỏ đến một số nguyên duy nhất (phần tử đầu tiên), không một mảng các số nguyên hoặc một con trỏ đến một mảng các số nguyên kích thước rõ. Điều đó là không thể, bởi vì hệ thống kiểu tĩnh yêu cầu kích thước mảng là hằng số thời gian biên dịch. (Do đó, tôi không chú thích mảng ẩn danh với thông tin kiểu tĩnh trong ảnh.)

Liên quan đến các giá trị mặc định cho các phần tử, mảng ẩn danh hoạt động tương tự như mảng tự động. Thông thường, các mảng POD ẩn danh không được khởi tạo, nhưng có một cú pháp đặc biệt kích hoạt khởi tạo giá trị:

int* p = new int[some_computed_size]();

(Lưu ý cặp dấu ngoặc đơn ngay trước dấu chấm phẩy.) Một lần nữa, C ++ 0x đơn giản hóa các quy tắc và cho phép chỉ định giá trị ban đầu cho mảng ẩn danh nhờ khởi tạo thống nhất:

int* p = new int[8] { 2, 3, 5, 7, 11, 13, 17, 19 };

Nếu bạn hoàn thành việc sử dụng một mảng ẩn danh, bạn phải giải phóng nó trở lại hệ thống:

delete[] p;

Bạn phải phát hành từng mảng ẩn danh chính xác một lần và sau đó không bao giờ chạm vào nó một lần nữa. Không phát hành nó ở tất cả các kết quả trong rò rỉ bộ nhớ (hay nói chung hơn, tùy thuộc vào loại phần tử, rò rỉ tài nguyên) và cố gắng giải phóng nó nhiều lần dẫn đến hành vi không xác định. Sử dụng dạng không phải mảng delete(hoặc free) thay vì delete[]để giải phóng mảng cũng là hành vi không xác định .


2
Sự phản đối của staticviệc sử dụng trong phạm vi không gian tên đã được loại bỏ trong C ++ 11.
huyền thoại2k

Bởi vì newlà toán tử am, nó chắc chắn có thể trả về mảng được phân bổ theo tham chiếu. Không có điểm nào cho nó ...
Ded repeatator

@Ded repeatator Không, không thể, bởi vì trong lịch sử, newnó cũ hơn rất nhiều so với tài liệu tham khảo.
dòng chảy

@FredOverflow: Vì vậy, có một lý do nó không thể trả về một tham chiếu, nó hoàn toàn khác với lời giải thích bằng văn bản.
Ded repeatator

2
@Ded repeatator Tôi không nghĩ rằng một tham chiếu đến một loạt các giới hạn chưa biết tồn tại. Ít nhất g ++ từ chối biên dịchint a[10]; int (&r)[] = a;
fredoverflow
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.