Подтвердить что ты не робот

Напишите прототип для функции, которая принимает массив из ровно 16 целых чисел

Один из вопросов интервью попросил меня "написать прототип для функции C, которая принимает массив ровно 16 целых чисел", и мне было интересно, что это может быть? Возможно, это объявление функции:

void foo(int a[], int len);

Или что-то еще?

А что, если вместо этого был С++?

4b9b3361

Ответ 1

В C для этого требуется указатель на массив из 16 целых чисел:

void special_case(int (*array)[16]);

Он будет вызываться с помощью:

int array[16];
special_case(&array);

В С++ вы также можете использовать ссылку на массив, как показано в Nawaz. (Вопрос задает C в заголовке и первоначально упоминал только С++ в тегах.)


Любая версия, которая использует некоторый вариант:

void alternative(int array[16]);

заканчивается эквивалентом:

void alternative(int *array);

который на практике примет любой размер массива.


Задан вопрос - действительно ли special_case() предотвращает передачу другого размера массива. Ответ: "Да".

void special_case(int (*array)[16]);

void anon(void)
{

    int array16[16];
    int array18[18];
    special_case(&array16);
    special_case(&array18);
}

Компилятор (GCC 4.5.2 на MacOS X 10.6.6, как это бывает) жалуется (предупреждает):

$ gcc -c xx.c
xx.c: In function ‘anon’:
xx.c:9:5: warning: passing argument 1 of ‘special_case’ from incompatible pointer type
xx.c:1:6: note: expected ‘int (*)[16]’ but argument is of type ‘int (*)[18]’
$

Изменить на GCC 4.2.1 - как указано Apple - и предупреждение:

$ /usr/bin/gcc -c xx.c
xx.c: In function ‘anon’:
xx.c:9: warning: passing argument 1 of ‘special_case’ from incompatible pointer type
$

Предупреждение в 4.5.2 лучше, но вещество одно и то же.

Ответ 2

Существует несколько способов объявить параметры массива фиксированного размера:

void foo(int values[16]);

принимает любой указатель-to- int, но размер массива служит в качестве документации

void foo(int (*values)[16]);

принимает указатель на массив с ровно 16 элементами

void foo(int values[static 16]);

принимает указатель на первый элемент массива с не менее чем 16 элементами

struct bar { int values[16]; };
void foo(struct bar bar);

принимает структуру, боксирующую массив с ровно 16 элементами, передавая их по значению.

Ответ 3

& необходимо в С++:

void foo(int (&a)[16]); // & is necessary. (in C++)

Примечание: и это необходимо, иначе вы можете передать массив любого размера!


Для C:

void foo(int (*a)[16]) //one way
{
}

typedef int (*IntArr16)[16]; //other way
void bar(IntArr16 a)
{
}

int main(void) 
{
        int a[16];
        foo(&a); //call like this - otherwise you'll get warning!
        bar(&a); //call like this - otherwise you'll get warning!
        return 0;
}

Демо: http://www.ideone.com/fWva6

Ответ 4

Я думаю, что самым простым способом быть typeafe было бы объявить структуру, которая содержит массив, и передать это:

struct Array16 {
  int elt[16];
};


void Foo(struct Array16* matrix);

Ответ 5

У вас уже есть ответы для C и ответ для С++, но есть другой способ сделать это на С++.

Как сказал Наваз, чтобы передать массив из N размера, вы можете сделать это в С++:

const size_t N = 16; // For your question.

void foo(int (&arr)[N]) {
    // Do something with arr.
}

Однако, с С++ 11, вы также можете использовать контейнер std:: array, который может быть передан с более естественным синтаксисом (предполагая некоторое знакомство с синтаксисом шаблона).

#include <array>

const size_t N = 16;

void bar(std::array<int, N> arr) {
    // Do something with arr.
}

В качестве контейнера std:: array позволяет в основном использовать те же функции, что и обычный массив C-стиля, а также добавлять дополнительные функции.

std::array<int, 5> arr1 = { 1, 2, 3, 4, 5 };
int arr2[5] = { 1, 2, 3, 4, 5 };

// Operator[]:
for (int i = 0; i < 5; i++) {
    assert(arr1[i] == arr2[i]);
}

// Fill:
arr1.fill(0);
for (int i = 0; i < 5; i++) {
    arr2[i] = 0;
}

// Check size:
size_t arr1Size = arr1.size();
size_t arr2Size = sizeof(arr2) / sizeof(arr2[0]);

// Foreach (C++11 syntax):
for (int &i : arr1) {
    // Use i.
}
for (int &i : arr2) {
    // Use i.
}

Однако, насколько мне известно (что по общему признанию ограничено в то время), арифметика указателя небезопасна с std:: array, если вы не используете данные функции-члена(), чтобы получить фактический адрес массива в первую очередь. Это необходимо для предотвращения будущих изменений класса std:: array от взлома вашего кода, а также потому, что некоторые реализации STL могут хранить дополнительные данные в дополнение к фактическому массиву.


Обратите внимание, что это было бы наиболее полезно для нового кода, или если вы конвертируете свой ранее существующий код, чтобы использовать std:: arrays вместо массивов в стиле C. Поскольку std:: массивы являются агрегатными типами, им не нужны специальные конструкторы, и поэтому вы не можете напрямую переключиться с массива C-стиля на std:: array (не считая использования, но это уродливо и может потенциально вызвать проблемы в будущем). Чтобы преобразовать их, вам нужно будет использовать что-то вроде этого:

#include <array>
#include <algorithm>

const size_t N = 16;

std::array<int, N> cArrayConverter(int (&arr)[N]) {
    std::array<int, N> ret;

    std::copy(std::begin(arr), std::end(arr), std::begin(ret));

    return ret;
}

Следовательно, если ваш код использует массивы C-стиля, и было бы невозможно преобразовать его в использование std:: arrays, вместо этого вам лучше было бы придерживаться массивов C-стиля.

(Примечание. Я указал размеры как N, чтобы вы могли более легко использовать код везде, где он вам нужен.)


Редактирование: Есть несколько вещей, о которых я забыл упомянуть:

1) Большинство стандартных библиотечных функций С++, предназначенных для работы с контейнерами, являются неспециализированными; вместо того, чтобы разрабатываться для конкретных контейнеров, они работают на диапазонах, используя итераторы. (Это также означает, что они работают для std::basic_string и их экземпляров, таких как std::string.) Например, std::copy имеет следующий прототип:

template <class InputIterator, class OutputIterator>
OutputIterator copy(InputIterator first, InputIterator last,
                    OutputIterator result);
// first is the beginning of the first range.
// last is the end of the first range.
// result is the beginning of the second range.

Хотя это может показаться навязчивым, вам обычно не нужно указывать параметры шаблона и просто разрешить компилятору обрабатывать это для вас.

std::array<int, 5> arr1 = { 1, 2, 3, 4, 5 };
std::array<int, 5> arr2 = { 6, 7, 8, 9, 0 };
std::string str1 = ".dlrow ,olleH";
std::string str2 = "Overwrite me!";

std::copy(arr1.begin(), arr1.end(), arr2.begin());
// arr2 now stores { 1, 2, 3, 4, 5 }.

std::copy(str1.begin(), str1.end(), str2.begin());
// str2 now stores ".dlrow ,olleH".
// Not really necessary for full string copying, due to std::string.operator=(), but possible nonetheless.

Благодаря использованию итераторов эти функции также совместимы с массивами C-стиля (поскольку итераторы являются обобщением указателей, все указатели по определению являются итераторами (но не все итераторы обязательно являются указателями)). Это может быть полезно при работе с устаревшим кодом, поскольку это означает, что у вас есть полный доступ к функциям диапазона в стандартной библиотеке.

int arr1[5] = { 4, 3, 2, 1, 0 };
std::array<int, 5> arr2;

std::copy(std::begin(arr1), std::end(arr1), std::begin(arr2));

Возможно, вы заметили из этого примера, и последнее, что std::array.begin() и std::begin() могут быть взаимозаменяемы с std::array. Это связано с тем, что std::begin() и std::end() реализованы так, что для любого контейнера они имеют одинаковый тип возврата и возвращают одно и то же значение, как вызывающие функции-члены begin() и end() экземпляра этого контейнера.

// Prototype:
template <class Container>
auto begin (Container& cont) -> decltype (cont.begin());

// Examples:
std::array<int, 5> arr;
std::vector<char> vec;

std::begin(arr) == arr.begin();
std::end(arr) == arr.end();

std::begin(vec) == vec.begin();
std::end(vec) == vec.end();

// And so on...

Маски C-стиля не имеют функций-членов, что требует использования std::begin() и std::end() для них. В этом случае две функции перегружены, чтобы обеспечить применимые указатели, в зависимости от типа массива.

// Prototype:
template <class T, size_t N>
T* begin (T(&arr)[N]);

// Examples:
int arr[5];

std::begin(arr) == &arr[0];
std::end(arr) == &arr[4];

Как правило, если вы не уверены в том, нужно ли использовать какой-либо конкретный сегмент кода для использования массивов C-стиля, безопаснее использовать std::begin() и std::end().

[Обратите внимание, что, хотя я использовал std::copy() в качестве примера, использование диапазонов и итераторов очень распространено в стандартной библиотеке. Большинство, если не все, функции, предназначенные для работы с контейнерами (или, более конкретно, любая реализация Концепция контейнера, такие как std::array, std::vector и std::string) используют диапазоны, что делает их совместимыми с любыми текущими и будущими контейнерами, а также с массивами в стиле C. Могут быть исключения из этой широко распространенной совместимости, о которой я не знаю.]

2) При передаче значения std:: array по значению в зависимости от размера массива могут быть значительные накладные расходы. Таким образом, обычно лучше передать его по ссылке или использовать итераторы (например, стандартную библиотеку).

// Pass by reference.
const size_t N = 16;

void foo(std::array<int, N>& arr);

3) Все эти примеры предполагают, что все массивы в вашем коде будут иметь тот же размер, что и константа N. Чтобы сделать ваш код более независимым от реализации, вы можете сами использовать диапазоны и итераторы, или если вы хотите, чтобы ваш код фокусировался на массивах, используйте шаблонные функции. [На основе этот ответ на другой вопрос.]

template<size_t SZ> void foo(std::array<int, SZ>& arr);

...

std::array<int, 5> arr1;
std::array<int, 10> arr2;

foo(arr1); // Calls foo<5>(arr1).
foo(arr2); // Calls foo<10>(arr2).

Если вы это сделаете, вы можете даже дойти до шаблона типа элемента массива, если ваш код может работать с типами, отличными от int.

template<typename T, size_t SZ>
void foo(std::array<T, SZ>& arr);

...

std::array<int, 5> arr1;
std::array<float, 7> arr2;

foo(arr1); // Calls foo<int, 5>(arr1).
foo(arr2); // Calls foo<float, 7>(arr2).

Для примера этого в действии см. здесь.


Если кто-нибудь увидит какие-либо ошибки, которые я, возможно, пропустил, не стесняйтесь указывать им, чтобы я исправил или исправить их самостоятельно. Я думаю, что поймал их всех, но я не уверен на 100%.