Jestem nowy w programowaniu w C ++, ale mam doświadczenie w Javie. Potrzebuję wskazówek, jak przekazywać obiekty do funkcji w C ++.
Czy muszę przekazywać wskaźniki, referencje lub wartości inne niż wskaźnik i inne? Pamiętam, że w Javie nie ma takich problemów, ponieważ przekazujemy tylko zmienną, która zawiera odniesienie do obiektów.
Byłoby wspaniale, gdybyś mógł również wyjaśnić, gdzie użyć każdej z tych opcji.
c++
pointers
pass-by-reference
pass-by-value
c++-faq
Rakesh K
źródło
źródło
Odpowiedzi:
Praktyczne zasady dla C ++ 11:
Przekaż wartość , z wyjątkiem kiedy
const
odniesienie ,const
odwołanie inne niż wartość ,const
odniesienie, czy nie.)Ominięcie wskaźnika praktycznie nigdy nie jest zalecane. Parametry opcjonalne najlepiej wyrażać jako
std::optional
(boost::optional
dla starszych bibliotek std), a aliasing jest wykonywany dobrze przez odniesienie.Semantyka ruchów w C ++ 11 sprawia, że przekazywanie i zwracanie wartości jest o wiele bardziej atrakcyjne, nawet w przypadku złożonych obiektów.
Praktyczne zasady dla C ++ 03:
Przekaż argumenty przez
const
odniesienie , z wyjątkiem kiedyconst
odniesieniaNULL
/0
/nullptr
zamiast tego; zastosuj poprzednią regułę, aby ustalić, czy powinieneś przekazać wskaźnik doconst
argumentu(tutaj „przekazanie przez wartość” nazywa się „przekazanie przez kopię”, ponieważ przekazywanie przez wartość zawsze tworzy kopię w C ++ 03)
Jest coś więcej, ale te zasady dla kilku początkujących zaprowadzą cię dość daleko.
źródło
Istnieją pewne różnice w konwencjach wywoływania w C ++ i Javie. W języku C ++ technicznie rzecz biorąc istnieją tylko dwie konwencje: pass-by-value i pass-by-reference, z pewną literaturą zawierającą trzecią konwencję pass-by-point (czyli faktycznie pass-by-value typu wskaźnika). Ponadto możesz dodać ciąg do typu argumentu, poprawiając semantykę.
Przekaż przez odniesienie
Przekazywanie przez referencję oznacza, że funkcja odbierze koncepcyjnie instancję obiektu, a nie jej kopię. Odwołanie jest koncepcyjnie aliasem obiektu użytego w kontekście wywołującym i nie może mieć wartości null. Wszystkie operacje wykonywane wewnątrz funkcji dotyczą obiektu poza funkcją. Ta konwencja nie jest dostępna w Javie ani C.
Przekaż według wartości (i wskaźnika po)
Kompilator wygeneruje kopię obiektu w kontekście wywołującym i użyje tej kopii wewnątrz funkcji. Wszystkie operacje wykonywane wewnątrz funkcji są wykonywane na kopii, a nie na elemencie zewnętrznym. Jest to konwencja dla pierwotnych typów w Javie.
Specjalną jego wersją jest przekazywanie wskaźnika (adresu obiektu) do funkcji. Funkcja odbiera wskaźnik, a wszelkie operacje zastosowane do samego wskaźnika są stosowane do kopiowania (wskaźnik), z drugiej strony operacje zastosowane do wyłuskowanego wskaźnika będą miały zastosowanie do instancji obiektu w tej lokalizacji pamięci, więc funkcja może powodować działania niepożądane. Efekt użycia parametru pass-by-value wskaźnika do obiektu pozwoli funkcji wewnętrznej zmodyfikować wartości zewnętrzne, tak jak w przypadku pass-by-referencji, a także pozwoli na opcjonalne wartości (pass wskaźnik zerowy).
Jest to konwencja stosowana w C, gdy funkcja musi zmodyfikować zmienną zewnętrzną, a konwencja stosowana w Javie z typami referencji: referencja jest kopiowana, ale obiekt referencyjny jest taki sam: zmiany referencji / wskaźnika nie są widoczne na zewnątrz funkcja, ale zmiany we wskazanej pamięci są.
Dodanie stałej do równania
W C ++ można przypisywać stałość obiektom podczas definiowania zmiennych, wskaźników i referencji na różnych poziomach. Możesz zadeklarować zmienną jako stałą, możesz zadeklarować odwołanie do stałej instancji i możesz zdefiniować wszystkie wskaźniki do stałych obiektów, stałe wskaźniki do zmiennych obiektów i stałe wskaźniki do stałych elementów. I odwrotnie, w Javie można zdefiniować tylko jeden poziom stałości (słowo kluczowe końcowe): poziom zmiennej (instancja dla typów prymitywnych, referencja dla typów referencyjnych), ale nie można zdefiniować referencji do niezmiennego elementu (chyba że sama klasa jest niezmienny).
Jest to szeroko stosowane w konwencjach wywoływania C ++. Gdy obiekty są małe, możesz przekazać obiekt według wartości. Kompilator wygeneruje kopię, ale ta kopia nie jest kosztowną operacją. W przypadku dowolnego innego typu, jeśli funkcja nie zmieni obiektu, możesz przekazać odwołanie do stałej instancji (zwykle nazywanej stałą referencją) typu. To nie skopiuje obiektu, ale przekaże go do funkcji. Ale jednocześnie kompilator zagwarantuje, że obiekt nie zostanie zmieniony wewnątrz funkcji.
Reguły kciuka
Oto kilka podstawowych zasad, których należy przestrzegać:
Istnieją inne niewielkie odstępstwa od tych zasad, z których pierwszą jest obsługa własności obiektu. Gdy obiekt jest dynamicznie przydzielany do nowego, należy go cofnąć przy pomocy delete (lub jego [] wersji). Obiekt lub funkcja odpowiedzialna za zniszczenie obiektu jest uważana za właściciela zasobu. Kiedy dynamicznie alokowany obiekt jest tworzony w kawałku kodu, ale własność jest przenoszona na inny element, zwykle odbywa się to za pomocą semantyki pass-by-point lub, jeśli to możliwe, za pomocą inteligentnych wskaźników.
Dygresja
Ważne jest podkreślenie znaczenia różnicy między odniesieniami do C ++ i Java. W C ++ referencje są koncepcyjnie wystąpieniem obiektu, a nie akcesorium do niego. Najprostszym przykładem jest implementacja funkcji wymiany:
Powyższa funkcja zamiany zmienia oba argumenty za pomocą referencji. Najbliższy kod w Javie:
Wersja kodu Java modyfikuje wewnętrznie kopie referencji, ale nie modyfikuje zewnętrznych obiektów zewnętrznie. Odwołania Java to wskaźniki C bez arytmetyki wskaźników, które są przekazywane wartościom do funkcji.
źródło
Jest kilka przypadków do rozważenia.
Zmodyfikowany parametr (parametry „out” i „in / out”)
Ten przypadek dotyczy głównie stylu: czy chcesz, aby kod wyglądał jak call (obj) lub call (& obj) ? Istnieją jednak dwa punkty, w których różnica ma znaczenie: przypadek opcjonalny poniżej i chcesz użyć odniesienia podczas przeciążania operatorów.
... i opcjonalnie
Parametr niezmodyfikowany
To ciekawy przypadek. Ogólna zasada jest taka, że typy „tanie do kopiowania” są przekazywane przez wartość - są to zazwyczaj małe typy (ale nie zawsze) - podczas gdy inne są przekazywane przez const ref. Jeśli jednak musisz wykonać kopię w ramach funkcji, powinieneś przekazać wartość . (Tak, to ujawnia trochę szczegółów implementacji. C'est le C ++. )
... i opcjonalnie
Jest tu najmniejsza różnica między wszystkimi sytuacjami, więc wybierz to, co czyni twoje życie najłatwiejszym.
Stała wartość jest szczegółem implementacji
Te deklaracje są dokładnie taką samą funkcją! Przekazując wartość, const jest jedynie szczegółem implementacji. Wypróbuj to:
źródło
const
że jestem implementacją, kiedy przekazuję wartość.Przekaż według wartości:
Przekazuj zmienne według wartości, gdy funkcja wymaga całkowitej izolacji od środowiska, tj. Aby zapobiec modyfikowaniu oryginalnej zmiennej przez funkcję, a także aby zapobiec modyfikowaniu jej wartości przez inne wątki podczas wykonywania funkcji.
Minusem są cykle procesora i dodatkowa pamięć poświęcona na skopiowanie obiektu.
Pomiń const const:
Ten formularz emuluje zachowanie typu „pass-by-value” podczas usuwania narzutu związanego z kopiowaniem. Funkcja uzyskuje dostęp do odczytu oryginalnego obiektu, ale nie może modyfikować jego wartości.
Minusem jest bezpieczeństwo wątków: wszelkie zmiany dokonane w oryginalnym obiekcie przez inny wątek pojawią się wewnątrz funkcji podczas jej wykonywania.
Przekaż odniesienie non-const:
Użyj tego, gdy funkcja musi zapisać pewną wartość do zmiennej, która ostatecznie zostanie wykorzystana przez program wywołujący.
Podobnie jak w przypadku stałej referencji, nie jest to bezpieczne dla wątków.
Pomiń wskaźnik const:
Funkcjonalnie taki sam, jak przekazanie przez const-referen, z wyjątkiem innej składni, plus fakt, że funkcja wywołująca może przekazać wskaźnik NULL, wskazując, że nie ma prawidłowych danych do przekazania.
Nie jest bezpieczny dla wątków.
Pomiń wskaźnik non-const:
Podobne do odwołania non-const. Program wywołujący zwykle ustawia zmienną na NULL, gdy funkcja nie powinna zapisywać wartości. Ta konwencja jest widoczna w wielu interfejsach API glibc. Przykład:
Podobnie jak wszystkie przekazywane przez referencję / wskaźnik, nie są bezpieczne dla wątków.
źródło
Ponieważ nikt o tym nie wspomniał, dodaję go, kiedy przekazujesz obiekt do funkcji w c ++, domyślny konstruktor kopiowania obiektu jest wywoływany, jeśli nie masz takiego, który tworzy klon obiektu, a następnie przekazujesz go do metody, więc kiedy zmieniasz wartości obiektu, które będą odzwierciedlać kopię obiektu zamiast oryginalnego obiektu, jest to problem w c ++, więc jeśli uczynisz wszystkie atrybuty klasy wskaźnikami, wówczas konstruktorzy kopiowania skopiują adresy atrybuty wskaźnika, więc gdy metoda wywołuje obiekt, który manipuluje wartościami przechowywanymi w adresach atrybutów wskaźnika, zmiany odzwierciedlają się również w oryginalnym obiekcie, który jest przekazywany jako parametr, dzięki czemu może zachowywać się tak samo jak Java, ale nie zapomnij, że cała twoja klasa atrybuty muszą być wskaźnikami, należy również zmienić wartości wskaźników,będzie wyjaśnione z wyjaśnieniem kodu.
Ale to nie jest dobry pomysł, ponieważ skończysz pisać dużo kodu zawierającego wskaźniki, które są podatne na wycieki pamięci i nie zapomnij wywołać destruktorów. Aby uniknąć tego c ++, mają konstruktory kopiowania, w których utworzysz nową pamięć, gdy obiekty zawierające wskaźniki zostaną przekazane do argumentów funkcji, które przestaną manipulować danymi innych obiektów, Java przekazuje wartość, a wartość jest referencją, więc nie wymaga konstruktorów kopiowania.
źródło
Istnieją trzy metody przekazywania obiektu do funkcji jako parametru:
Przejdź przez następujący przykład:
Wynik:
źródło
Poniżej przedstawiono sposoby przekazywania argumentów / parametrów do działania w C ++.
1. według wartości.
2. przez odniesienie.
3. według obiektu.
źródło