Тип массива - правила для назначения/использования в качестве параметра функции
когда мне нужно передать массив функции, кажется, все следующие объявления функции будут работать
void f(int arr[])
void f(int arr[4]) // is this one correct?
для этого:
int a[]={1,2,3,4};
f(a);
Но когда я назначаю массив другому массиву, он терпит неудачу
int a[]={1,2,3,4};
int b[4] = a; // error: array must be initialized with a brace-enclosed initializer
Итак, почему массив, переданный как аргумент функции, в порядке, но используется для определения простого простого назначения?
Ответы
Ответ 1
Чтобы понять разницу, нам нужно понять два разных контекста.
- В контексте значений имя массива типа
T
эквивалентно указателю на тип T
и равно указателю на первый элемент массива.
- В объектных контекстах имя массива типа
T
не сводится к указателю.
Что такое контекст объекта?
В a = b;
, a
находится в контексте объекта. Когда вы берете адрес переменной, он используется в контексте объекта. Наконец, когда вы используете оператор sizeof
для переменной, он используется в контексте объекта. Во всех других случаях переменная используется в контексте значений.
Теперь, когда мы имеем это знание, когда делаем:
void f(int arr[4]);
Это точно эквивалентно
void f(int *arr);
Как вы выяснили, мы можем опустить размер (4 выше) из объявления функции. Это означает, что вы не можете узнать размер "массива", переданный в f()
. Позже, когда вы это сделаете:
int a[]={1,2,3,4};
f(a);
В вызове функции имя a
находится в контексте значения, поэтому оно сводится к указателю на int
. Это хорошо, потому что f
ожидает указатель на int
, поэтому определение функции и использование совпадают. То, что передается в f()
, является указателем на первый элемент a
(&a[0]
).
В случае
int a[]={1,2,3,4};
int b[4] = a;
Имя b
используется в контексте объекта и не сводится к указателю. (Кстати, a
здесь находится в контексте значений и сводится к указателю.)
Теперь int b[4];
присваивает значение хранения 4 int
и дает ему имя b
. a
также было назначено аналогичное хранилище. Таким образом, вышеупомянутое назначение означает: "Я хочу, чтобы хранилище было таким же, как и предыдущее местоположение". Это не имеет смысла.
Если вы хотите скопировать содержимое a
в b
, вы можете сделать следующее:
#include <string.h>
int b[4];
memcpy(b, a, sizeof b);
Или, если вам нужен указатель b
, указывающий на a
:
int *b = a;
Здесь a
находится в контексте значения и сводится к указателю на int
, поэтому мы можем назначить a
на int *
.
Наконец, при инициализации массива вы можете назначить ему явные значения:
int a[] = {1, 2, 3, 4};
Здесь a имеет 4 элемента, инициализированных 1, 2, 3 и 4. Вы также можете сделать:
int a[4] = {1, 2, 3, 4};
Если в списке меньше элементов, чем количество элементов в массиве, остальные значения принимаются равными 0:
int a[4] = {1, 2};
устанавливает a[2]
и a[3]
в 0.
Ответ 2
void f(int arr[]);
void f(int arr[4]);
Синтаксис вводит в заблуждение. Они одинаковы:
void f(int *arr);
i.e, вы передаете указатель на начало массива. Вы не копируете массив.
Ответ 3
C не поддерживает назначение массивов. В случае вызова функции массив распадается на указатель. C поддерживает назначение указателей. Это задается здесь примерно каждый день - какой текст вы читаете в C-тексте, который не объясняет это?
Ответ 4
Попробуйте memcpy.
int a[]={1,2,3,4};
int b[4];
memcpy(b, a, sizeof(b));
Спасибо, что указали это, Стив, прошло некоторое время, так как я использовал C.
Ответ 5
Чтобы понять свою интуицию, вы должны понять, что происходит на уровне машины.
Семантика инициализации (= {1,2,3,4}) означает "поместить ее на двоичное изображение именно таким образом", чтобы ее можно было скомпилировать.
Назначение массива будет другим: компилятор должен будет перевести его в цикл, который на самом деле итерации над элементами. C-компилятор (или С++, если на то пошло) никогда не делает такого. Он по праву ожидает, что вы сделаете это сами. Зачем? Потому что ты можешь. Итак, это должна быть подпрограмма, написанная на C (memcpy). Это все о простоте и близости к вашему оружию, о котором все говорят C и С++.
Ответ 6
Обратите внимание, что тип a
в int a[4]
равен int [4]
.
Но TypeOf (&a
) == int (*)[4]
!= int [4]
.
Также обратите внимание, что тип значения a
равен int *
, который отличается от всего вышеперечисленного!
Вот пример программы, которую вы можете попробовать:
int main() {
// All of these are different, incompatible types!
printf("%d\n", sizeof (int[4])); // 16
// These two may be the same size, but are *not* interchangeable!
printf("%d\n", sizeof (int (*)[4])); // 4
printf("%d\n", sizeof (int *)); // 4
}
Ответ 7
Я хочу уточнить. В ответах есть некоторые вводящие в заблуждение советы... Все следующие функции могут принимать целые массивы:
void f(int arr[])
void f(int arr[4])
void f(int *arr)
Но формальные аргументы не совпадают. Таким образом, компилятор может обрабатывать их по-разному. В смысле управления внутренней памятью все аргументы приводят к указателям.
void f(int arr[])
... f() принимает массив любого размера.
void f(int arr[4])
... Формальный аргумент указывает размер массива.
void f(int *arr)
... Вы также можете передать целочисленный указатель. f() ничего не знает о размере.