Тип массива - правила для назначения / использования в качестве параметра функции - PullRequest
6 голосов
/ 10 января 2010

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

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

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

Ответы [ 7 ]

12 голосов
/ 10 января 2010

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

  • В значение контекстов имя массива типа 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.

7 голосов
/ 10 января 2010
void f(int arr[]);
void f(int arr[4]);

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

void f(int *arr);

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

6 голосов
/ 10 января 2010

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

3 голосов
/ 10 января 2010

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

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

Спасибо, что указали на это, Стив, давно я не использовал C.

1 голос
/ 10 января 2010

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

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

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

0 голосов
/ 14 октября 2016

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

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

0 голосов
/ 05 апреля 2010

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