Це досить давнє запитання, але я збираюся поставити свої 2 центи, оскільки відповіді дуже багато, але жоден не показує всі можливі методи чітко і лаконічно (не впевнений у лаконічному шматочку, оскільки це отримало трохи від руки. TL; DR 😉).
Я припускаю, що ОП хотів повернути масив, який передали без копіювання, як деякий засіб прямої передачі цього виклику, щоб передати його іншій функції, щоб зробити вигляд красивішим.
Однак використовувати такий масив - це дозволити йому розкладатись на покажчик і компілятор розглянути його як масив. Це може призвести до тонких помилок, якщо ви переходите в масив, як функція, очікуючи, що у нього буде 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), але вони не працюють у цьому контексті.