2014-10-24 4 views
0

Я пытаюсь понять различные способы объявления массива (одного или двух измерений) в C++ и что именно они возвращаются (указатели, указатели на указатели и т.д.)Как объявление массива работает на C++?

Вот некоторые примеры:

int A[2][2] = {0,1,2,3}; 
int A[2][2] = {{0,1},{2,3}}; 
int **A = new int*[2]; 
int *A = new int[2][2]; 

В каждом случае, что именно есть A? Это указатель, двойной указатель? Что происходит, когда я делаю A+1? Являются ли эти все допустимыми способами объявления матриц?

Кроме того, почему для первого варианта не требуется второй набор фигурных скобок для определения «столбцов»?

+0

Где вы нашли примеры? –

+0

@ Cheersandhth.-Alf, в основном из вопросов, которые мне дал мой профессор. Он намеренно пытается иногда сбивать с толку. – n0pe

+0

@clcto, да, но я пытаюсь понять, что именно такое A, поэтому я могу нарисовать небольшую диаграмму памяти и работать со структурой. – n0pe

ответ

-2
int A[2][2] = {0,1,2,3}; 

А является массив из 4 целых чисел. Для удобства кодера он решил объявить его как 2-мерный массив, поэтому компилятор позволит кодеру получить к нему доступ в виде двухмерного массива. Coder инициализировал все элементы линейно, поскольку они заложены в память. Как обычно, поскольку A является массивом, A сам является адресом массива, поэтому A + 1 (после применения математики указателя) смещает A на размер 2 int указателя. Поскольку адрес массива указывает на первый элемент этого массива, A будет указывать на первый элемент второй строки массива, значение 2.

Редактировать: Доступ к двумерному массиву с использованием оператора одиночной матрицы будет работать по первому размеру, обрабатывая второе как 0. Таким образом, A [1] эквивалентно A [1] [0]. A + 1 приводит к эквивалентному добавлению указателя.

int A[2][2] = {{0,1},{2,3}}; 

А представляет собой массив из 4 цепей. Для удобства кодера он решил объявить его как 2-мерный массив, поэтому компилятор позволит кодеру получить к нему доступ в виде двухмерного массива. Кодер инициализирует элементы по строкам. По тем же причинам выше, A + 1 точек, чтобы оценить 2.

int **A = new int*[2]; 

А указатель на Int указатель, который был инициализирован, чтобы указывать на массив 2 указателей на INT указатели. Поскольку A является указателем, A + 1 принимает значение из A, которое является адресом массива указателей (и, следовательно, первым элементом массива) и добавляет 1 (указатель math), где теперь будет указывать на второй элемент массива. Поскольку массив не был инициализирован, фактическое выполнение чего-либо с A + 1 (например, чтение или запись на него) будет опасным (кто знает, какое значение есть и что на самом деле указывает, если это даже действительный адрес).

int *A = new int[2][2]; 

Редактировать: как указал Jarod42, это недействительно. Я думаю, что это может быть ближе к тому, что вы имели в виду. Если нет, мы можем уточнить в комментариях.

int *A = new int[4]; 

A - указатель на int, который был инициализирован, чтобы указать на анонимный массив из 4 целых чисел. Поскольку A является указателем, A + 1 принимает значение из A, которое является адресом массива указателей (и, следовательно, первым элементом массива) и добавляет 1 (указатель math), где теперь будет указывать на второй элемент массива.

Некоторые вынос:

  1. В первых двух случаях, является адрес массива, а в двух последних, A является значение указателя, которое произошло инициализируется в адрес массива ,
  2. В первых двух случаях A нельзя изменить после инициализации. В последних двух A можно изменить после инициализации и указать на другую память.
  3. Сказанное: вам нужно быть осторожным с тем, как вы можете использовать указатели с элементом массива. Рассмотрим следующий пример:

    int *a = new int(5); 
    int *b = new int(6); 
    int c[2] = {*a, *b}; 
    int *d = a; 
    

c+1 не то же самое, как d+1. Фактически, доступ к d+1 очень опасен. Зачем? Поскольку c представляет собой массив int, который был инициализирован разыменованием a и b. это означает, что c, является адресом a фрагмента памяти, где в этой ячейке памяти указано значение, которое было установлено на значение, указанное tovariable a, и в следующей ячейке памяти, которая является значением, закрепленным переменной b. С другой стороны, d - это только адрес a. Итак, вы можете видеть, c != d поэтому, нет причин, чтобы c + 1 == d + 1.

+0

'int * A = новый int [2] [2];' недействителен. и ваш 'int a = new int (5);' также недействителен, вы, вероятно, имеете в виду 'int * a = new int (5);' (но тогда 'd' тоже неправильного типа ...) – Jarod42

+0

@ Jarod42 Спасибо, я изначально написал, что с a и b в стеке (нет новых) и забыли обновление. Что касается другой ошибки, я полностью замалчиваю ее. – iheanyi

+0

@ Jarod42 дерьмо, забыли тоже. Это то, что происходит, когда вы делаете последние изменения из стека в кучу! – iheanyi

2
int A[2][2] = {0,1,2,3}; 
int A[2][2] = {{0,1},{2,3}}; 

Это объявить A как array of size 2 of array of size 2 of int. Декларации абсолютно идентичны.

int **A = new int*[2]; 

Объявляет pointer to pointer to int инициализированную массив из двух указателей. Вы также должны выделить память для этих двух указателей, если вы хотите использовать ее в качестве двумерного массива.

int *A = new int[2][2]; 

И это не компилируется, так как тип правой части является pointer to array of size 2 of int, которые не могут быть преобразованы в pointer to int.

Во всех действительных случаях A + 1 такое же, как &A[1], это означает, что она указывает на второй элемент массива, то есть, в случае int A[2][2] ко второй массив из двух целых чисел, а в случае int **A ко второму указатель в массиве.

+0

Удивительный! Несколько разъяснений. Я думал, что массив - это то же самое, что и указатель. Другими словами, 'A [1] = * (A + 1)' правильно? Кроме того, в случае первого примера, что бы я получил от '* (A + 2)'? Это «вне границ» «строк» ​​этого массива, нет? То же самое со вторым примером: '* (A)' будет '{0,1}' и '* (A + 1)' будет '{2,3}' - но что бы '* (A + 2) 'давать? – n0pe

+0

@MaxMackie yes '* (A + 2)' вне пределов всех ваших случаев. Первый и второй примеры идентичны, не смотрите на «отсутствующие» фигурные скобки, что важно для типа. –

+0

@AntonSavin это будет работать? int * A = новый int [2] [2]; – Stasik

2

Для объявления массива первое указанное измерение является самым внешним, массивом, который содержит другие массивы.

Для объявлений указателей каждый * добавляет еще один уровень косвенности.

Синтаксис был разработан для C, чтобы декларации имитировали использование. Создатели C и создатель C++ (Bjarne Stroustrup) описали синтаксис как неудачный эксперимент. Основная проблема заключается в том, что она не соответствует обычным правилам замены в математике.

В C++ 11 вы можете использовать std::array вместо объявления квадратных скобок.

Также вы можете определить аналогичный строитель типа ptr, например.

template< class T > 
using ptr = T*; 

, а затем написать

ptr<int> p; 
ptr<ptr<int>> q; 
0

Хорошо, я постараюсь это объяснить вам:

  1. Это инициализация.Вы можете создать двумерный массив со значениями:
    • A [0] [0] -> 0
    • A [0] [1] -> 1
    • A [1] [0] -> 2
    • A [1] [1] -> 3
  2. Это точно так же, как и выше, но здесь вы используете фигурные скобки. Всегда ли это так лучше для чтения.
  3. int ** A означает, что у вас есть указатель на указатель ints. Когда вы делаете новый int * [2], вы зарезервируете память для 2 указателей целых чисел.
  4. Это не будет скомпилировано.
+1

typo in 3: doubles вместо ints – Stasik

1

Другие ответы охватывают другие объявления, но я объясню, почему вам не нужны скобки в первых двух инициализациях. Причина, почему эти два инициализацию идентичны:

int A[2][2] = {0,1,2,3}; 
int A[2][2] = {{0,1},{2,3}}; 

потому, что она покрыта aggregate initialization. В этом случае фигурные скобки разрешены «исключены» (опущены).

стандарт C++ представляет собой пример в § 8.5.1:

[...]

float y[4][3] = { 
    { 1, 3, 5 }, 
    { 2, 4, 6 }, 
    { 3, 5, 7 }, 
}; 

[...]

В следующем примере, в фигурных скобках список инициализаторов отменен; однако инициализатор-лист имеет тот же эффект, что и полностью-рамно инициализатор-лист приведенный выше пример,

float y[4][3] = { 
    1, 3, 5, 2, 4, 6, 3, 5, 7 
}; 

Инициализатор у начинается с левой фигурной скобкой, но один для у [0] не используется, поэтому используются три элемента из списка. Аналогично следующие три берутся последовательно для y [1] и y [2].

0
int A[2][2] = {0,1,2,3}; 
int A[2][2] = {{0,1},{2,3}}; 

Эти два эквивалентны.
Оба означают: «Я объявляю двухмерный массив целых чисел. Массив имеет размер 2 на 2».

Память, однако, не является двумерной, она не выложена в сетках, но (концептуально) в одной длинной строке. В многомерном массиве каждая строка просто выделяется в памяти сразу после предыдущего. Из-за этого мы можем перейти к адресу памяти, указанному A, и либо сохранить две строки длиной 2, либо одну строку длины 4, а конечный результат в памяти будет таким же.

int **A = new int*[2]; 

Объявляет указатель на указатель называется А.
хранит адрес указателя на массив размером 2, содержащего int с. Этот массив выделяется в куче.

int *A = new int[2][2]; 

А является указателем на int.
Это int - начало массива 2x2 int, выделенного в куче.

Aparrently это недействительно:

prog.cpp:5:23: error: cannot convert ‘int (*)[2]’ to ‘int*’ in initialization 
    int *A = new int[2][2]; 

Но из-за того, что мы видели в первых двух, это будет работать (и 100% эквивалент):

int *A new int[4]; 
+0

Последняя часть неверна: 'int * A = new int [2] [2];' недействителен. – Jarod42

+0

@ Jarod42 обновлен. Благодарю. – Baldrickk

3

Похоже, вы получили множество ответов, пока я писал мой, но я мог бы также опубликовать мой ответ в любом случае, так что я не чувствую, что все это было зря ...

(все sizeof результаты взяты из VC2012 - 32 бит сборки, размеры указателей будет, конечно, дважды с 64 битным построить)

size_t f0(int* I); 
size_t f1(int I[]); 
size_t f2(int I[2]); 

int main(int argc, char** argv) 
{ 
    // A0, A1, and A2 are local (on the stack) two-by-two integer arrays 
    // (they are technically not pointers) 

    // nested braces not needed because the array dimensions are explicit [2][2] 
    int A0[2][2] = {0,1,2,3}; 

    // nested braces needed because the array dimensions are not explicit, 
    //so the braces let the compiler deduce that the missing dimension is 2 
    int A1[][2] = {{0,1},{2,3}}; 

    // this still works, of course. Very explicit. 
    int A2[2][2] = {{0,1},{2,3}}; 

    // A3 is a pointer to an integer pointer. New constructs an array of two 
    // integer pointers (on the heap) and returns a pointer to the first one. 
    int **A3 = new int*[2]; 
    // if you wanted to access A3 with a double subscript, you would have to 
    // make the 2 int pointers in the array point to something valid as well 
    A3[0] = new int[2]; 
    A3[1] = new int[2]; 
    A3[0][0] = 7; 

    // this one doesn't compile because new doesn't return "pointer to int" 
    // when it is called like this 
    int *A4_1 = new int[2][2]; 

    // this edit of the above works but can be confusing 
    int (*A4_2)[2] = new int[2][2]; 
    // it allocates a two-by-two array of integers and returns a pointer to 
    // where the first integer is, however the type of the pointer that it 
    // returns is "pointer to integer array" 

    // now it works like the 2by2 arrays from earlier, 
    // but A4_2 is a pointer to the **heap** 
    A4_2[0][0] = 6; 
    A4_2[0][1] = 7; 
    A4_2[1][0] = 8; 
    A4_2[1][1] = 9; 


    // looking at the sizes can shed some light on subtle differences here 
    // between pointers and arrays 
    A0[0][0] = sizeof(A0);  // 16 // typeof(A0) is int[2][2] (2by2 int array, 4 ints total, 16 bytes) 
    A0[0][1] = sizeof(A0[0]);  // 8 // typeof(A0[0]) is int[2] (array of 2 ints) 

    A1[0][0] = sizeof(A1);  // 16 // typeof(A1) is int[2][2] 
    A1[0][1] = sizeof(A1[0]);  // 8 // typeof(A1[0]) is int[2] 

    A2[0][0] = sizeof(A2);  // 16 // typeof(A2) is int[2][2] 
    A2[0][1] = sizeof(A2[0]);  // 8 // typeof(A1[0]) is int[2] 

    A3[0][0] = sizeof(A3);  // 4 // typeof(A3) is int** 
    A3[0][1] = sizeof(A3[0]);  // 4 // typeof(A3[0]) is int* 

    A4_2[0][0] = sizeof(A4_2); // 4 // typeof(A4_2) is int(*)[2] (pointer to array of 2 ints) 
    A4_2[0][1] = sizeof(A4_2[0]); // 8 // typeof(A4_2[0]) is int[2] (the first array of 2 ints) 
    A4_2[1][0] = sizeof(A4_2[1]); // 8 // typeof(A4_2[1]) is int[2] (the second array of 2 ints) 
    A4_2[1][1] = sizeof(*A4_2); // 8 // typeof(*A4_2) is int[2] (different way to reference the first array of 2 ints) 

// confusion between pointers and arrays often arises from the common practice of 
// allowing arrays to transparently decay (implicitly convert) to pointers 

    A0[1][0] = f0(A0[0]); // f0 returns 4. 
    // Not surprising because declaration of f0 demands int* 

    A0[1][1] = f1(A0[0]); // f1 returns 4. 
    // Still not too surprising because declaration of f1 doesn't 
    // explicitly specify array size 

    A2[1][0] = f2(A2[0]); // f2 returns 4. 
    // Much more surprising because declaration of f2 explicitly says 
    // it takes "int I[2]" 

    int B0[25]; 
    B0[0] = sizeof(B0); // 100 == (sizeof(int)*25) 
    B0[1] = f2(B0); // also compiles and returns 4. 
    // Don't do this! just be aware that this kind of thing can 
    // happen when arrays decay. 

    return 0; 
} 

// these are always returning 4 above because, when compiled, 
// all of these functions actually take int* as an argument 
size_t f0(int* I) 
{ 
    return sizeof(I); 
} 

size_t f1(int I[]) 
{ 
    return sizeof(I); 
} 

size_t f2(int I[2]) 
{ 
    return sizeof(I); 
} 

// indeed, if I try to overload f0 like this, it will not compile. 
// it will complain that, "function 'size_t f0(int *)' already has a body" 
size_t f0(int I[2]) 
{ 
    return sizeof(I); 
} 

да, этот образец имеет тонны подписанного/без знака Int рассогласования, но эта часть не относится к вопрос. Кроме того, не забудьте delete все созданные с new и delete[] все созданные с new[]

EDIT:

«Что происходит, когда я A+1?» - Я пропустил это раньше.

Операции, подобные этому, будут называться «арифметикой указателей» (хотя я обратился к вершине своего ответа, что некоторые из них не являются указателями, но они могут превращаться в указатели).

Если у меня есть указатель P на массив someType, то индекс доступа P[n] точно так же, как с помощью этого синтаксиса *(P + n). Компилятор учитывает размер указателя, указываемого в обоих случаях. Таким образом, полученный код операции действительно сделает что-то вроде этого для вас *(P + n*sizeof(someType)) или эквивалентно *(P + n*sizeof(*P)), потому что физический процессор не знает и не заботится обо всех наших «типах». В конце концов, все смещения указателя должны быть байтом. Для согласованности имена массивов, такие как указатели, работают одинаково.

Возвращаясь к выше образцов: A0, A1, A2 и A4_2 ведут себя одинаково с указателем арифметики.

A0[0] такое же, как *(A0+0), который ссылается на первый int[2] из A0

аналогично:

A0[1] такое же, как *(A0+1), который компенсирует «указатель» на sizeof(A0[0]) (т.е.8, см. Выше), и он заканчивается ссылкой на второй int[2] от A0

A3 действует несколько иначе. Это связано с тем, что A3 является единственным, который не хранит все 4 типа массива 2 на 2 смежно. В моем примере A3 указывает на массив из 2 указателей int, каждый из которых указывает на полностью отделяет массивы двух ints. Использование A3[1] или *(A3+1) по-прежнему будет направлять вас ко второму из двух массивов int, но это будет сделано путем смещения только 4 байта с начала A3 (с использованием 32-битных указателей для моих целей), который дает вам указатель, который сообщает вам где можно найти второй массив с двумя int. Я надеюсь, что в этом есть смысл.

Смежные вопросы