Один из вопросов интервью попросил меня "написать прототип для функции C, которая принимает массив ровно 16 целых чисел", и мне было интересно, что это может быть? Возможно, это объявление функции:
Ответ 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 лучше, но вещество одно и то же.
Ответ 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%.