To dość stare pytanie, ale zamierzam włożyć moje 2 centy, ponieważ jest wiele odpowiedzi, ale żadne nie pokazuje wszystkich możliwych metod w jasny i zwięzły sposób (nie jestem pewien co do zwięzłego fragmentu, ponieważ otrzymałem bit poza kontrolą. TL; DR 😉).
Zakładam, że OP chciał zwrócić tablicę, która została przekazana bez kopiowania, jako pewien sposób bezpośredniego przekazania tego do programu wywołującego, który zostanie przekazany do innej funkcji, aby kod wyglądał ładniej.
Jednak użycie tablicy takiej jak ta pozwala jej rozpadnąć się na wskaźnik i pozwolić kompilatorowi traktować ją tak tablicę. Może to powodować subtelne błędy, jeśli przejdziesz przez tablicę podobną do, z funkcją oczekującą, że będzie ona miała 5 elementów, ale twój rozmówca faktycznie poda inną liczbę.
Jest kilka sposobów, aby lepiej sobie z tym poradzić. Przekaż std::vector
lub std::array
(nie jestem pewien, czy std::array
było w 2010 roku, kiedy zadano pytanie). Następnie możesz przekazać obiekt jako odniesienie bez kopiowania / przenoszenia obiektu.
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;
}
Jeśli jednak nalegasz na grę tablicami C, użyj szablonu, który zachowa informacje o tym, ile elementów w tablicy.
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;
}
Tyle że wygląda brzydko i jest bardzo trudny do odczytania. Teraz używam czegoś, aby pomóc w tym, czego nie było w 2010 roku, którego używam również do wskaźników funkcji:
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;
}
Zmienia to typ, w którym można się spodziewać, czyniąc to znacznie bardziej czytelnym. Oczywiście użycie szablonu jest zbędne, jeśli nie zamierzasz używać niczego poza 5 elementami, więc możesz oczywiście go na stałe zapisać:
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;
}
Jak powiedziałem, moja type_t<>
sztuczka nie zadziałałaby w chwili, gdy pytanie zostało zadane. Najlepszym, na co wtedy mogłeś mieć nadzieję, było użycie typu w strukturze:
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;
}
Które zaczyna znów wyglądać dość brzydko, ale przynajmniej jest jeszcze bardziej czytelne, chociaż wtedy typename
mogły być opcjonalne w zależności od kompilatora, w wyniku czego:
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;
}
I oczywiście mógłbyś podać konkretny typ, zamiast używać mojego pomocnika.
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;
}
Wówczas darmowe funkcje std::begin()
i std::end()
nie istniały, choć można je było łatwo wdrożyć. Pozwoliłoby to na iterację po tablicy w bezpieczniejszy sposób, ponieważ mają one sens w przypadku tablicy C, ale nie wskaźnika.
Jeśli chodzi o dostęp do tablicy, możesz albo przekazać ją do innej funkcji, która przyjmuje ten sam typ parametru, albo utworzyć alias do niej (co nie miałoby większego sensu, ponieważ masz już oryginał w tym zakresie). Dostęp do odwołania do tablicy jest jak dostęp do oryginalnej tablicy.
void other_function(type_t<int(&)[5]> x) { /* do something else */ }
void fn()
{
int array[5];
other_function(fillarr(array));
}
lub
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
}
Podsumowując, najlepiej nie dopuścić do rozpadu tablicy na wskaźnik, jeśli zamierzasz iterować po nim. To po prostu zły pomysł, ponieważ powstrzymuje kompilator przed ochroną przed postrzeleniem się w stopę i utrudnia odczytanie kodu. Zawsze staraj się pomóc kompilatorowi w utrzymaniu typów tak długo, jak to możliwe, chyba że masz bardzo dobry powód, aby tego nie robić.
Edytować
Aha, i dla kompletności, możesz pozwolić, aby zdegradowała się do wskaźnika, ale to oddziela tablicę od liczby elementów, które zawiera. Odbywa się to często w C / C ++ i zwykle jest łagodzone przez przekazywanie liczby elementów w tablicy. Jednak kompilator nie może ci pomóc, jeśli popełnisz błąd i podasz niewłaściwą wartość do liczby elementów.
// separate size value
int* fillarr(int* arr, size_t size)
{
for(int* it = arr; it != arr + size; ++it)
{ /* do stuff */ }
return arr;
}
Zamiast przekazać rozmiar, możesz przekazać wskaźnik końca, który wskaże jeden koniec końca tablicy. Jest to przydatne, ponieważ pozwala uzyskać coś, co jest bliższe algorytmom std, które przyjmują wskaźnik początku i końca, ale to, co powrócisz, jest teraz tylko czymś, o czym musisz pamiętać.
// separate end pointer
int* fillarr(int* arr, int* end)
{
for(int* it = arr; it != end; ++it)
{ /* do stuff */ }
return arr;
}
Alternatywnie możesz udokumentować, że ta funkcja zajmie tylko 5 elementów i masz nadzieję, że użytkownik tej funkcji nie zrobi nic głupiego.
// 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;
}
Zauważ, że zwracana wartość straciła swój pierwotny typ i jest zdegradowana do wskaźnika. Z tego powodu jesteś teraz sam, aby upewnić się, że nie zamierzasz przekroczyć tablicy.
Możesz przekazać std::pair<int*, int*>
, którego możesz użyć na początku i na końcu, i przekazać dalej, ale wtedy naprawdę przestaje wyglądać jak tablica.
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];
}
lub
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])));
}
Zabawne, jest to bardzo podobne do std::initializer_list
działania (c ++ 11), ale nie działają w tym kontekście.