Тип массива – правила для назначения / использования в качестве параметра функции

когда мне нужно передать массив функции, кажется, все последующие объявления функции будут работать

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 

Итак, почему массив, переданный в качестве аргумента функции, в порядке, но неправильное использование в rhs простого назначения?

    Чтобы понять разницу, нам нужно понять два разных контекста .

    • В контексте значений имя массива типа 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 используется в контексте объекта и не сводится к указателю. (Кстати, здесь находится в контексте значений и сводится к указателю.)

    Теперь int b[4]; присваивает значение хранилища 4 int s и дает ему имя b . a также было назначено аналогичное хранилище. Таким образом, вышеупомянутое назначение означает: «Я хочу, чтобы хранилище было таким же, как и предыдущее местоположение». Это не имеет смысла.

    Если вы хотите скопировать содержимое a в b , вы можете сделать следующее:

     #include  int b[4]; memcpy(b, a, sizeof b); 

    Или, если вам нужен указатель b , указывающий на:

     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.

     void f(int arr[]); void f(int arr[4]); 

    Синтаксис вводит в заблуждение. Они одинаковы:

     void f(int *arr); 

    т.е. вы передаете указатель на начало массива. Вы не копируете массив.

    C не поддерживает назначение массивов. В случае вызова функции массив распадается на указатель. C поддерживает назначение указателей. Это задается здесь примерно каждый день – какой учебник C вы читаете, ребята, это не объясняет?

    Попробуйте memcpy.

     int a[]={1,2,3,4}; int b[4]; memcpy(b, a, sizeof(b)); 

    Спасибо, что указали, что Стив, прошло какое-то время, так как я использовал C.

    Чтобы понять свою интуицию, вы должны понять, что происходит на уровне машины.

    Семантика инициализации (= {1,2,3,4}) означает «поставить ее на двоичное изображение именно таким образом», чтобы ее можно было скомпилировать.

    Назначение массива было бы иным: компилятор должен был бы перевести его в цикл, который фактически перебирал элементы. C-компилятор (или C ++, если на то пошло) никогда не делает такого. Он по праву ожидает, что вы сделаете это сами. Зачем? Потому что ты можешь. Итак, это должна быть подпрограмма, написанная на C (memcpy). Это все о простоте и близости к вашему оружию, о котором все говорят C и C ++.

    Обратите внимание, что тип 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 } 

    Я хочу уточнить. В ответах есть некоторые вводящие в заблуждение советы … Все следующие функции могут принимать целые массивы:

     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 () ничего не знает о размере.

    Давайте будем гением компьютера.