Làm cách nào để khởi tạo bộ nhớ với toán tử mới trong C ++?


175

Tôi mới bắt đầu tham gia C ++ và tôi muốn tìm ra một số thói quen tốt. Nếu tôi vừa phân bổ một mảng kiểu intvới newtoán tử, làm thế nào tôi có thể tự khởi tạo tất cả chúng thành 0 mà không tự lặp qua tất cả chúng? Tôi chỉ nên sử dụng memset? Có cách nào để làm điều đó không?


19
Nếu bạn muốn chọn một thói quen C ++ tốt, thì hãy tránh sử dụng mảng trực tiếp và sử dụng vector thay thế. Vector sẽ khởi tạo tất cả các mục bất kể loại nào, và sau đó bạn không cần phải gọi toán tử xóa [].
brianegge

@brianegge: Nếu tôi cần truyền một mảng cho hàm C bên ngoài, tôi có thể chỉ cho nó vectơ không?
dreamlax

12
Bạn có thể vượt qua &vector[0].
jamesdlin

Tất nhiên, khi bạn truyền mảng cho các hàm C, bạn thường phải chỉ định con trỏ cho phần tử đầu tiên, & vector [0] như @jamesdlin đã nói và kích thước của mảng, được cung cấp trong trường hợp này bởi vector.size ().
Trebor Rude

Liên quan (yêu cầu các loại không phải mảng): stackoverflow.com/questions/7546620/,
Aconcagua

Câu trả lời:


392

Đây là một tính năng ít được biết đến của C ++ (bằng chứng là thực tế chưa có ai đưa ra câu trả lời này), nhưng nó thực sự có cú pháp đặc biệt để khởi tạo giá trị một mảng:

new int[10]();

Lưu ý rằng bạn phải sử dụng dấu ngoặc đơn trống - ví dụ: bạn không thể sử dụng (0)hoặc bất cứ thứ gì khác (đó là lý do tại sao điều này chỉ hữu ích cho việc khởi tạo giá trị).

Điều này được cho phép rõ ràng bởi ISO C ++ 03 5.3.4 [expr.new] / 15, cho biết:

Một biểu thức mới tạo ra một đối tượng kiểu Tkhởi tạo đối tượng đó như sau:

...

  • Nếu trình khởi tạo mới có dạng (), mục được khởi tạo giá trị (8,5);

và không hạn chế các loại mà điều này được cho phép, trong khi (expression-list)biểu mẫu bị hạn chế rõ ràng bởi các quy tắc tiếp theo trong cùng một phần sao cho nó không cho phép các kiểu mảng.


1
Mặc dù tôi đồng ý rằng điều này ít được biết đến, nhưng tôi không thể (hoàn toàn) đồng ý rằng nó thực sự rất đáng ngạc nhiên - nó đã được thêm vào trong C ++ 03, mà hầu hết mọi người dường như đã bỏ qua (vì đây là một trong số ít những điều mới nó đã thêm).
Jerry Coffin

2
@Jerry: Tôi phải thừa nhận rằng tôi chưa biết (có lẽ vì khi tôi đọc tiêu chuẩn, đó là C ++ 03 rồi). Điều đó nói rằng, đáng chú ý là tất cả các triển khai tôi biết đều hỗ trợ điều này (tôi đoán là vì nó quá tầm thường để thực hiện).
Pavel Minaev

2
Vâng, nó là khá nhỏ để thực hiện. Theo như mới, tất cả "khởi tạo giá trị" là mới trong C ++ 03.
Jerry Coffin

34
Trong C ++ 11, bạn cũng có thể sử dụng khởi tạo thống nhất : new int[10] {}. Bạn cũng có thể cung cấp các giá trị để khởi tạo với:new int[10] {1,2,3}
bames53

Xin đừng nhầm lẫn giữa khởi tạo mặc định với khởi tạo giá trị: Cả hai đều được xác định rõ ràng trong tiêu chuẩn và là các khởi tạo khác nhau.
Ded repeatator

25

Giả sử rằng bạn thực sự muốn một mảng chứ không phải std :: vector, "cách C ++" sẽ là thế này

#include <algorithm> 

int* array = new int[n]; // Assuming "n" is a pre-existing variable

std::fill_n(array, n, 0); 

Nhưng hãy lưu ý rằng dưới mui xe, đây thực sự vẫn chỉ là một vòng lặp gán mỗi phần tử thành 0 (thực sự không có cách nào khác để thực hiện, loại bỏ một kiến ​​trúc đặc biệt có hỗ trợ ở mức phần cứng).


Tôi không phiền nếu vòng lặp được triển khai bên dưới một hàm, tôi chỉ muốn biết liệu tôi có phải tự thực hiện một vòng lặp như vậy hay không. Cảm ơn vì tiền hỗ trợ.
dreamlax

4
Bạn có thể ngạc nhiên. Tôi đã. Trên STL của tôi (cả GCC và Dinkumware), std :: copy thực sự biến thành một memcpy nếu phát hiện ra nó đang được gọi với các loại tích hợp. Tôi sẽ không ngạc nhiên nếu std :: fill_n sử dụng bộ nhớ.
Brian Neal

2
Không. Sử dụng 'Giá trị khởi tạo' để đặt tất cả các thành viên về 0.
Martin York

24

Có một số phương thức để phân bổ một mảng kiểu nội tại và tất cả các phương thức này đều đúng, mặc dù chọn phương thức nào, tùy thuộc vào ...

Hướng dẫn khởi tạo tất cả các phần tử trong vòng lặp

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

Sử dụng std::memsetchức năng từ<cstring>

int* p = new int[10];
std::memset(p, 0, sizeof(int) * 10);

Sử dụng std::fill_nthuật toán từ<algorithm>

int* p = new int[10];
std::fill_n(p, 10, 0);

Sử dụng std::vectorcontainer

std::vector<int> v(10); // elements zero'ed

Nếu C ++ 0x khả dụng, sử dụng các tính năng của danh sách khởi tạo

int a[] = { 1, 2, 3 }; // 3-element static size array
vector<int> v = { 1, 2, 3 }; // 3-element array but vector is resizeable in runtime

1
nên là vector <int> Nếu bạn đã thêm p = new int [10] () bạn đã có một danh sách đầy đủ.
karsten

@mloskot, trong trường hợp đầu tiên bạn đã khởi tạo một mảng bằng cách sử dụng "mới", việc chuyển qua tham chiếu sẽ xảy ra như thế nào? Nếu tôi sử dụng int array[SIZE] ={1,2,3,4,5,6,7};ký hiệu, tôi có thể sử dụng void rotateArray(int (& input)[SIZE], unsigned int k);sẽ là khai báo hàm của tôi, điều gì sẽ xảy ra khi sử dụng quy ước đầu tiên? Bất kì lời đề nghị nào?
Anu

1
Tôi e rằng ví dụ std::memsetnày là sai - bạn vượt qua 10, có vẻ như sẽ mong đợi số byte - xem en.cppreference.com/w/cpp/opes/byte/memset . (Tôi nghĩ điều này cho thấy lý do tại sao người ta nên tránh xây dựng mức độ thấp như vậy khi có thể.)
Suma

@Suma Bắt tuyệt vời! Đã sửa. Đây dường như là một ứng cử viên cho một lỗi cũ trong một thập kỷ :-) Vâng, tôi đồng ý với nhận xét của bạn.
mloskot

7

Nếu bộ nhớ bạn đang phân bổ là một lớp với hàm tạo có tác dụng gì đó, toán tử mới sẽ gọi hàm tạo đó và để đối tượng của bạn khởi tạo.

Nhưng nếu bạn đang phân bổ POD hoặc thứ gì đó không có hàm tạo khởi tạo trạng thái của đối tượng, thì bạn không thể phân bổ bộ nhớ và khởi tạo bộ nhớ đó với toán tử mới trong một thao tác. Tuy nhiên, bạn có một số tùy chọn:

1) Sử dụng biến stack thay thế. Bạn có thể phân bổ và khởi tạo mặc định trong một bước, như thế này:

int vals[100] = {0};  // first element is a matter of style

2) sử dụng memset(). Lưu ý rằng nếu đối tượng bạn đang phân bổ không phải là POD , việc ghi nhớ nó là một ý tưởng tồi. Một ví dụ cụ thể là nếu bạn ghi nhớ một lớp có chức năng ảo, bạn sẽ thổi bay vtable và để đối tượng của bạn ở trạng thái không sử dụng được.

3) Nhiều hệ điều hành có các cuộc gọi thực hiện những gì bạn muốn - phân bổ trên một đống và khởi tạo dữ liệu thành một cái gì đó. Một ví dụ về Windows sẽ làVirtualAlloc()

4) Đây thường là lựa chọn tốt nhất. Tránh phải tự quản lý bộ nhớ. Bạn có thể sử dụng các bộ chứa STL để làm bất cứ điều gì bạn sẽ làm với bộ nhớ thô, bao gồm phân bổ và khởi tạo tất cả trong một cú trượt:

std::vector<int> myInts(100, 0);  // creates a vector of 100 ints, all set to zero

6

Có, có:

std::vector<int> vec(SIZE, 0);

Sử dụng một vectơ thay vì một mảng được phân bổ động. Lợi ích bao gồm không phải bận tâm với việc xóa rõ ràng mảng (nó bị xóa khi vectơ vượt quá phạm vi) và bộ nhớ cũng tự động bị xóa ngay cả khi có ngoại lệ bị ném.

Chỉnh sửa: Để tránh những lần tải xuống từ những người không bận tâm đọc các bình luận bên dưới, tôi nên nói rõ hơn rằng câu trả lời này không nói rằng vectơ luôn là câu trả lời đúng. Nhưng nó chắc chắn là một cách C ++ nhiều hơn là "thủ công" đảm bảo xóa một mảng.

Bây giờ với C ++ 11, cũng có std :: mảng mô hình hóa một mảng kích thước không đổi (so với vector có thể phát triển). Ngoài ra còn có std :: unique_ptr quản lý một mảng được phân bổ động (có thể được kết hợp với khởi tạo như đã trả lời trong các câu trả lời khác cho câu hỏi này). Bất kỳ cách nào trong số đó là một cách C ++ nhiều hơn là xử lý thủ công con trỏ tới mảng, IMHO.


11
Điều này không thực sự trả lời câu hỏi đã được hỏi.
John Knoeller

1
Tôi có nên luôn luôn sử dụng std::vector thay vì các mảng được phân bổ động? Những lợi ích của việc sử dụng một mảng trên một vectơ và ngược lại là gì?
dreamlax

1
@ John Knoller: OP đã hỏi về một cách C ++ để làm điều đó, tôi muốn nói rằng vector là cách c ++ để làm điều này. Tất nhiên, bạn đúng rằng có thể có những tình huống vẫn sẽ gọi cho một mảng đơn giản và không biết tình huống của OP có thể là một tình huống. Tôi đoán không, mặc dù có vẻ hợp lý rằng OP không biết về vectơ.
Villintehaspam

1
@villintehaspam: Mặc dù giải pháp này không trả lời câu hỏi của tôi, đó là con đường tôi sẽ đi. Tyler McHenry trả lời câu hỏi của tôi trực tiếp hơn, và sẽ giúp đặc biệt cho những người không thể sử dụng vì bất kỳ lý do gì mà sử dụng std::vector.
mơ mộng

2
@villintehaspam: Không, đó không phải là cách làm C ++. Đây là cách Java để làm điều đó. Bám sát vectormọi nơi bất kể bối cảnh được gọi là "Viết mã Java bằng C ++".
AnT

2

std::filllà một cách Có hai vòng lặp và một giá trị để điền vào vùng. Điều đó, hoặc vòng lặp for, (tôi cho rằng) sẽ là cách C ++ nhiều hơn.

Để thiết lập một mảng các kiểu số nguyên nguyên thành 0, cụ thể memsetlà tốt, mặc dù nó có thể làm tăng lông mày. Cũng nên xem xét calloc, mặc dù nó hơi bất tiện khi sử dụng từ C ++ vì dàn diễn viên.

Về phần tôi, tôi thường xuyên sử dụng một vòng lặp.

(Tôi không muốn đoán ý định của mọi người, nhưng sự thật std::vectorlà, tất cả mọi thứ đều bình đẳng, tốt hơn là sử dụng new[].)


1

bạn luôn có thể sử dụng bộ nhớ:

int myArray[10];
memset( myArray, 0, 10 * sizeof( int ));

Tôi hiểu rằng tôi có thể sử dụng memset, nhưng tôi không chắc đây có phải là cách C ++ để tiếp cận vấn đề hay không.
dreamlax

1
Nó không thực sự là 'cách C ++', nhưng sau đó không phải là mảng thô.
Pete Kirkham

1
@gbrandt: Điều đó có nghĩa là nó không hoạt động tốt trong cả C hoặc C ++. Nó hoạt động cho hầu hết các giá trị của loại là char hoặc unsign char. Nó hoạt động cho hầu hết các loại giá trị là 0 (ít nhất là trong hầu hết các triển khai). Mặt khác, nó thường vô dụng.
Jerry Coffin

1
10 * sizeof( *myArray )là tài liệu nhiều hơn và bằng chứng thay đổi hơn 10 * sizeof( int ).
Kevin Reid

1
Trong mọi trường hợp, OP có một mảng thô và bộ nhớ là cách nhanh nhất và dễ nhất để không mảng đó.
Gregor Brandt

-1

Thông thường cho danh sách các mục động, bạn sử dụng một std::vector .

Nói chung, tôi sử dụng bộ nhớ hoặc một vòng lặp để phân bổ động bộ nhớ thô, tùy thuộc vào cách tôi dự đoán khu vực mã đó sẽ có trong tương lai.

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.