Это довольно старый вопрос, но я собираюсь поставить свои 2 цента, так как есть много ответов, но ни один из них не показывает все возможные методы в четкой и сжатой форме (не уверен насчет краткого, так как это получило немного вышел из-под контроля. TL; DR 😉).
Я предполагаю, что OP хотел вернуть массив, который был передан без копирования, в качестве средства прямой передачи его вызывающей стороне для передачи в другую функцию, чтобы код выглядел красивее.
Однако использовать такой массив - это позволить ему распадаться на указатель, а компилятор обрабатывать его. как массив. Это может привести к незначительным ошибкам, если вы передадите массив, например, с функцией, ожидающей, что он будет иметь 5 элементов, но ваш вызывающий код фактически передает другое число.
Есть несколько способов справиться с этим лучше. Передайте в std::vector
или или std::array
(не уверен, что std::array
был где-то в 2010 году, когда был задан вопрос). Затем вы можете передать объект в качестве ссылки без какого-либо копирования / перемещения объекта.
std::array<int, 5>& fillarr(std::array<int, 5>& arr)
{
// (before c++11)
for(auto it = arr.begin(); it != arr.end(); ++it)
{ /* do stuff */ }
// Note the following are for c++11 and higher. They will work for all
// the other examples below except for the stuff after the Edit.
// (c++11 and up)
for(auto it = std::begin(arr); it != std::end(arr); ++it)
{ /* do stuff */ }
// range for loop (c++11 and up)
for(auto& element : arr)
{ /* do stuff */ }
return arr;
}
std::vector<int>& fillarr(std::vector<int>& arr)
{
for(auto it = arr.begin(); it != arr.end(); ++it)
{ /* do stuff */ }
return arr;
}
Однако, если вы настаиваете на игре с массивами Си, используйте шаблон, который будет хранить информацию о количестве элементов в массиве.
template <size_t N>
int(&fillarr(int(&arr)[N]))[N]
{
// N is easier and cleaner than specifying sizeof(arr)/sizeof(arr[0])
for(int* it = arr; it != arr + N; ++it)
{ /* do stuff */ }
return arr;
}
За исключением того, что выглядит ужасно, и супер трудно читать. Теперь я использую кое-что, чтобы помочь с тем, чего не было в 2010 году, что я также использую для указателей функций:
template <typename T>
using type_t = T;
template <size_t N>
type_t<int(&)[N]> fillarr(type_t<int(&)[N]> arr)
{
// N is easier and cleaner than specifying sizeof(arr)/sizeof(arr[0])
for(int* it = arr; it != arr + N; ++it)
{ /* do stuff */ }
return arr;
}
Это перемещает тип, в котором можно было бы ожидать, делая это намного более читабельным. Конечно, использование шаблона является излишним, если вы не собираетесь использовать ничего, кроме 5 элементов, поэтому вы, конечно, можете жестко его кодировать:
type_t<int(&)[5]> fillarr(type_t<int(&)[5]> arr)
{
// Prefer using the compiler to figure out how many elements there are
// as it reduces the number of locations where you have to change if needed.
for(int* it = arr; it != arr + sizeof(arr)/sizeof(arr[0]); ++it)
{ /* do stuff */ }
return arr;
}
Как я уже сказал, мой type_t<>
трюк не сработал бы в то время, когда был задан этот вопрос. Лучшее, на что вы могли надеяться тогда, - это использовать тип в структуре:
template<typename T>
struct type
{
typedef T type;
};
typename type<int(&)[5]>::type fillarr(typename type<int(&)[5]>::type arr)
{
// Prefer using the compiler to figure out how many elements there are
// as it reduces the number of locations where you have to change if needed.
for(int* it = arr; it != arr + sizeof(arr)/sizeof(arr[0]); ++it)
{ /* do stuff */ }
return arr;
}
Который снова начинает казаться довольно уродливым, но, по крайней мере, все еще более читабельным, хотя в то время он typename
мог быть необязательным в зависимости от компилятора, в результате чего:
type<int(&)[5]>::type fillarr(type<int(&)[5]>::type arr)
{
// Prefer using the compiler to figure out how many elements there are
// as it reduces the number of locations where you have to change if needed.
for(int* it = arr; it != arr + sizeof(arr)/sizeof(arr[0]); ++it)
{ /* do stuff */ }
return arr;
}
И тогда, конечно, вы могли бы указать конкретный тип, а не использовать мой помощник.
typedef int(&array5)[5];
array5 fillarr(array5 arr)
{
// Prefer using the compiler to figure out how many elements there are
// as it reduces the number of locations where you have to change if needed.
for(int* it = arr; it != arr + sizeof(arr)/sizeof(arr[0]); ++it)
{ /* do stuff */ }
return arr;
}
Тогда свободных функций std::begin()
и std::end()
не было, хотя их можно было легко реализовать. Это позволило бы выполнять итерации по массиву более безопасным способом, поскольку они имеют смысл для массива C, но не указателя.
Что касается доступа к массиву, вы можете либо передать его другой функции, которая принимает тот же тип параметра, либо создать псевдоним для него (что не имеет большого смысла, поскольку у вас уже есть оригинал в этой области). Доступ к ссылке на массив аналогичен доступу к исходному массиву.
void other_function(type_t<int(&)[5]> x) { /* do something else */ }
void fn()
{
int array[5];
other_function(fillarr(array));
}
или
void fn()
{
int array[5];
auto& array2 = fillarr(array); // alias. But why bother.
int forth_entry = array[4];
int forth_entry2 = array2[4]; // same value as forth_entry
}
Подводя итог, лучше не допускать распада массива в указатель, если вы собираетесь его перебирать. Это просто плохая идея, так как она не дает компилятору защитить вас от удара ногой и затрудняет чтение кода. Всегда старайтесь помочь компилятору помочь вам, сохраняя типы как можно дольше, если только у вас нет веских причин не делать этого.
редактировать
Да, и для полноты вы можете разрешить его снижение до указателя, но это отделяет массив от числа элементов, которые он содержит. Это много делается в C / C ++ и обычно смягчается передачей количества элементов в массиве. Однако компилятор не может помочь вам, если вы допустили ошибку и передали неправильное значение числу элементов.
// separate size value
int* fillarr(int* arr, size_t size)
{
for(int* it = arr; it != arr + size; ++it)
{ /* do stuff */ }
return arr;
}
Вместо того, чтобы передавать размер, вы можете передать указатель конца, который будет указывать на один за концом вашего массива. Это полезно, так как это делает что-то похожее на алгоритмы std, которые принимают указатель начала и конца, но то, что вы возвращаете, теперь только то, что вы должны помнить.
// separate end pointer
int* fillarr(int* arr, int* end)
{
for(int* it = arr; it != end; ++it)
{ /* do stuff */ }
return arr;
}
Кроме того, вы можете задокументировать, что эта функция займет всего 5 элементов, и надеяться, что пользователь вашей функции не сделает глупостей.
// I document that this function will ONLY take 5 elements and
// return the same array of 5 elements. If you pass in anything
// else, may nazal demons exit thine nose!
int* fillarr(int* arr)
{
for(int* it = arr; it != arr + 5; ++it)
{ /* do stuff */ }
return arr;
}
Обратите внимание, что возвращаемое значение потеряло свой первоначальный тип и ухудшено до указателя. Из-за этого вы теперь сами по себе гарантируете, что не собираетесь переполнять массив.
Вы можете передать a std::pair<int*, int*>
, который вы можете использовать для начала и конца и передать его, но тогда он действительно перестанет выглядеть как массив.
std::pair<int*, int*> fillarr(std::pair<int*, int*> arr)
{
for(int* it = arr.first; it != arr.second; ++it)
{ /* do stuff */ }
return arr; // if you change arr, then return the original arr value.
}
void fn()
{
int array[5];
auto array2 = fillarr(std::make_pair(&array[0], &array[5]));
// Can be done, but you have the original array in scope, so why bother.
int fourth_element = array2.first[4];
}
или
void other_function(std::pair<int*, int*> array)
{
// Can be done, but you have the original array in scope, so why bother.
int fourth_element = array2.first[4];
}
void fn()
{
int array[5];
other_function(fillarr(std::make_pair(&array[0], &array[5])));
}
Забавно, это очень похоже на std::initializer_list
работу (c ++ 11), но они не работают в этом контексте.