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