Напишите прототип для функции, которая принимает массив из ровно 16 целых чисел. - PullRequest
21 голосов
/ 17 января 2011

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

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

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

А что, если бы язык был C ++ вместо этого?

Ответы [ 5 ]

44 голосов
/ 17 января 2011

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

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

Это будет вызвано с:

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

В C ++ вы также можете использовать ссылку на массив, как показано в ответе Nawaz . (Вопрос касается C в заголовке и первоначально упоминал только 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 лучше, но суть та же.

11 голосов
/ 17 января 2011

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

void foo(int values[16]);

принимает любой указатель на 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 элементами, передавая их по значению.

6 голосов
/ 17 января 2011

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

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

2 голосов
/ 17 января 2011

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

struct Array16 {
  int elt[16];
};


void Foo(struct Array16* matrix);
1 голос
/ 15 октября 2015

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

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

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

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

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


Обратите внимание, что это будет наиболее полезно для нового кода, или если вы преобразуете свой ранее существующий код для использования массивов std :: arrays вместо массивов в стиле C. Поскольку std :: arrays являются агрегатными типами, им не хватает пользовательских конструкторов, и поэтому вы не можете напрямую переключаться с массива в стиле 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) Большинство функций стандартной библиотеки C ++, предназначенных для работы с контейнерами, не зависят от реализации; вместо того, чтобы быть разработанными для определенных контейнеров, они работают на диапазонах, используя итераторы. (Это также означает, что они работают для 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%.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...