Przekazywanie tablicy struktur do funkcji c++

Przepraszam za pytanie nooba, ale trochę się pogubiłem.
Jeśli mam tablicę struktur w main, którą chcę przekazać do funkcji:

struct MyStruct{
    int a;
    int b;
    char c;
    mayarray[5];
};  
MyStruct StructArray[10]; 

myFunction(StructArray[])

Przekazać do funkcji:

void myFunction(struct MyStruct PassedStruct[])
{
    PassedStruct[0].a = 1;
    PassedStruct[0].b = 2;
    // ... etc
}  

Moje pytanie brzmi, czy wywołanie takiej funkcji zmieni dane w StructArray? Potrzebuję tego. Czy to będzie wezwanie przez odniesienie? Jestem trochę zdezorientowany. Jak mogę to zmienić tak, że gdy przekażę tablicę struktur do funkcji, funkcja zmieni tablicę StructArray? Używam visual studio btw.
Dzięki.

Author: Philip Potter, 2010-09-01

5 answers

struct MyStruct PassedStruct[]

Jest w większości alternatywną składnią dla:

struct MyStruct * PassedStruct

Więc tak, uzyskasz dostęp i zmodyfikujesz oryginalną strukturę.

Tylko jeden szczegół do zmiany, poprawne wywołanie do funkcji nie jest

myFunction(StructArray[]);

Ale:

myFunction(StructArray);

Teraz postaram się wyjaśnić, dlaczego użyłem tego słowa przeważnie w powyższym zdaniu:

Podam jakąś podpowiedź na temat różnicy między tablicami a wskaźnikami, dlaczego nie należy ich mylić (nawet jeśli nie powiedziałbym, że są niepowiązane, wręcz przeciwnie), a problem z powyższym parametrem MyStruct PassedStruct[] przekazującym składnię.

To nie jest dla początkujących, a znawcy standardów C++ powinni również unikać czytania tego (ponieważ nie chcę się wdawać w jakąś wojnę-ISO Standard_, gdy wchodzę w ISO undefined behavior territory - vel forbidden territory).

Zacznijmy od tablic:

Wyobraź sobie prostą strukturę:

struct MyStruct{
    int a;
    int b;
    char c;
};  

MyStruct a1[3]; jest deklaracją tablicy których elementy są typu powyższej struktury. Najważniejszą rzeczą, jaką robi kompilator przy definiowaniu tablicy, jest przydzielenie dla niej miejsca. W naszym przykładzie zarezerwowano miejsce dla 3 struktur. Ta zarezerwowana przestrzeń może znajdować się na stosie lub z globalnych zasobów pamięci, w zależności od tego, gdzie znajduje się deklaracja deklaracji.

Możesz również zainicjalizować strukturę deklarując ją w następujący sposób:

struct MyStruct a1[3] = {{1, 2}, {3, 4}, {5, 6}};

Zauważ, że w tym przykładzie nie zainicjalizowałem pola c, ale tylko a i b. To jest dozwolone. Mogę również użyć składni desygnatora, jeśli mój kompilator obsługuje ją tak jak w:

struct MyStruct a1[3] = {{a:1, b:2}, {a:3, b:4}, {a:5, b:6}};

Teraz istnieje inna składnia do definiowania tablicy za pomocą pustych kwadratowych backetów, jak w:

struct MyStruct a2[] = {{1, 2}, {3, 4}, {5, 6}};

Chodzi o to, że a2 jest całkowicie normalną tablicą, tak jak a1. Rozmiar tablicy nie jest niejawny, jest podawany przez inicjalizator: mam trzy inicjalizatory, dlatego otrzymuję tablicę trzech struktur.

Mógłbym zdefiniować niezainicjowaną tablicę znanych rozmiarów za pomocą tego składnia. Dla niezaangażowanej tablicy o rozmiarze 3 miałbym:

struct MyStruct a2[] = {{},{},{}};

Spacja jest przydzielana, dokładnie tak jak w poprzedniej składni, nie ma tu żadnego wskaźnika.

Przedstawmy jeden wskaźnik:

MyStruct * p1;

Jest to prosty wskaźnik do struktury typu MyStruct. Mogę uzyskać dostęp do pól za pomocą zwykłej składni wskaźnika p1->a lub (*p1).a. Istnieje również składnia tablicowa, która robi to samo, co powyżej p1[0].a. Nadal to samo, co powyżej. Trzeba tylko pamiętać, że p1 [0] jest skrótem dla (*(p1+0)).

Zapamiętaj również regułę arytmetyki wskaźnika: dodanie 1 do wskaźnika oznacza dodanie sizeof wskazanego obiektu do bazowego adresu pamięci (co otrzymujesz, gdy używasz parametru % p printf format). Arytmetyka wskaźnika umożliwia dostęp do kolejnych identycznych struktur. Oznacza to, że możesz uzyskać dostęp do struktur według indeksu za pomocą p1[0], p1[2], itd.

Granice nie są sprawdzane. To, na co wskazuje pamięć, to odpowiedzialność programisty. Tak, Wiem, że ISO mówi inaczej, ale to jest to, co wszystkie Kompilatory, które kiedykolwiek próbowałem zrobić, więc jeśli wiesz o jednym, który nie, proszę powiedz mi.

Aby zrobić cokolwiek użytecznego z p1, musisz wskazać jakąś strukturę typu MyStruct. Jeśli masz dostępną tablicę takich struktur jak nasza a1, możesz po prostu zrobić p1=a1 i p1 wskaże początek tablicy. Innymi słowy, można również zrobić p1=&a1[0]. To naturalne, że dostępna jest prosta składnia, ponieważ jest to dokładnie to, co arytmetyka wskaźników jest zaprojektowana for: dostęp do tablic podobnych obiektów.

Piękno tej konwencji polega na tym, że pozwala ona całkowicie ujednolicić składnię dostępu do wskaźników i tablic. Różnica jest widoczna tylko przez kompilator:

  • Kiedy widzi p1[0], wie, że musi pobrać zawartość zmiennej o nazwie p1 i że będzie ona zawierać adres jakiejś struktury pamięci.

  • Kiedy widzi a1[0], wie a1 jest jakąś stałą, którą należy rozumieć jako adres (nie coś do pobrania w pamięci).

Ale gdy adres z p1 lub a1 jest dostępny, leczenie jest identyczne.

Częstym błędem jest pisanie p1 = &a1. Jeśli to zrobisz, kompilator da ci kilka czteroliterowych słów. Ok, &a1 jest również wskaźnikiem, ale to, co otrzymujesz, gdy przyjmujesz adres a1 jest wskaźnikiem do całej tablicy. Oznacza to, że jeśli dodasz 1 do wskaźnika tego typu, rzeczywisty adres będzie przesuwał się o kroki 3 w raz.

Rzeczywisty typ wskaźnika tego rodzaju (nazwijmy go p2) będzie MyStruct (*p2)[3];. Teraz możesz pisać p2 = &a1. Jeśli chcesz uzyskać dostęp do pierwszej struktury MyStruct na początku bloku pamięci wskazywanego przez p2, musisz napisać coś w rodzaju p2[0][0].a lub (*p2)[0].a lub (*(*p2)).a lub (*p2)->a lub p2[0]->a.

Dzięki systemowi typów i arytmetyce wskaźników wszystkie one robią dokładnie to samo: pobierają adres zawarty w p2, używają go jako tablicy (znanej stały adres) jak wyjaśniono powyżej.

Teraz możesz zrozumieć, dlaczego wskaźniki i tablice są zupełnie różnymi typami, które nie powinny być mylone, jak niektórzy mogą powiedzieć. W prostych słowach wskaźniki są zmienną zawierającą adres, tablice są stałymi adresami. Proszę nie strzelać do mnie Guru C++, tak Wiem, że to nie jest pełna historia i że Kompilatory przechowują wiele innych informacji wraz z adresem, rozmiarem wskazywanego (adresowanego ?) obiekt na przykład.

Teraz możesz zastanawiam się, dlaczego w kontekście przekazywania parametrów można użyć pustych nawiasów kwadratowych i tak naprawdę oznacza wskaźnik . ? Nie mam pojęcia. Ktoś pewnie pomyślał, że wygląda dobrze.

Nawiasem mówiąc, przynajmniej w gcc, możesz również umieścić pewną wartość między nawiasami zamiast trzymać je puste. Nie będzie to miało znaczenia, nadal otrzymasz wskaźnik, a nie tablicę, a granice lub sprawdzanie typu nie są wykonywane. Nie sprawdzałem w normie ISO powinno być zrobione i jeśli jest to wymagane przez normę lub jeśli jest to określone zachowanie.

Jeśli chcesz sprawdzić granice, użyj referencji. To może być zaskakujące, ale jest to obszar, w którym jeśli używasz referencji, rzeczywisty typ parametru jest zmieniany ze wskaźnika na tablicę(a nie ze wskaźnika na odniesienie do wskaźnika, jak można się spodziewać).

MyStruct StructArray[10]; 
  • header: void myFunction(struct MyStruct * PassedStruct)
  • rozmówca: myFunction(StructArray)
  • status: Działa, pracujesz ze wskaźnikiem w PassedStruct
  • nagłówek: void myFunction(struct MyStruct PassedStruct[])
  • rozmówca: myFunction(StructArray)
  • status: Działa, pracujesz ze wskaźnikiem w PassedStruct
  • header: void myFunction(struct MyStruct (& PassedStruct)[10])
  • rozmówca: myFunction(StructArray)
  • status: Działa, pracujesz z odniesieniem do tablicy o rozmiarze 10
  • header: void myFunction(struct MyStruct (& PassedStruct)[11])
  • rozmówca: myFunction(StructArray)
  • status: nie kompiluje, Typ niedopasowania tablicy między prototypem a rzeczywistym parametrem
  • nagłówek: void myFunction(struct MyStruct PassedStruct[10])
  • rozmówca: myFunction(StructArray)
  • status: Działa, PassedStruct jest wskaźnikiem, podany rozmiar jest ignorowany
  • header: void myFunction(struct MyStruct PassedStruct[11])
  • rozmówca: myFunction(StructArray)
  • status: Działa, PassedStruct jest wskaźnikiem, podany rozmiar jest ignorowany
 24
Author: kriss,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-06 11:21:29

Chociaż tablice i wskaźniki są koncepcyjnie różne, wody są bardzo mętne przez parametry funkcji. Nie można bezpośrednio przekazać tablicy do funkcji; można przekazać tylko wskaźnik. W rezultacie język "pomocnie" konwertuje prototyp taki jak ten:

void foo (char arr[])

Do tego:

void foo (char *arr)

I kiedy wywołujesz funkcję, nie przekazujesz jej pełnej tablicy, przekazujesz wskaźnik do pierwszego elementu. W rezultacie wewnątrz funkcji foo będzie ona miała wskaźnik do tablicy oryginalnej i przypisanie do elementów arr zmieni również tablicę w wywołującym.

W wszystkie inne sytuacje poza parametrami funkcji, składnia tablicy i wskaźnika w deklaracjach sąnie równoważne i odnoszą się do pojęciowo różnych rzeczy. Ale wewnątrz list parametrów funkcji, składnia tablicy tworzy typy wskaźników.

 6
Author: Philip Potter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-31 21:35:32

Uważam, że x [] oznacza to samo co x*, co oznacza "wskaźnik do typu". Ponieważ tak jest, będziesz modyfikować przekazywany obiekt (będziesz musiał wywołać go używając operatora & lub 'adres') i możesz myśleć o nim jako o referencji.

 1
Author: Aaron Anodide,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-31 21:23:49

Kiedy przekazujesz tablicę jako argument funkcji, tablica rozpada się na wskaźnik do pierwszego elementu tablicy.

Tak więc, gdy wewnątrz funkcji używasz [], aby uzyskać dostęp do elementów tablicy, tak naprawdę wykonujesz arytmetykę wskaźnika tylko z początkowym wskaźnikiem, aby uzyskać elementy oryginalnej tablicy.

Więc tak, modyfikujesz oryginalną tablicę. A ta odpowiedź jest w zasadzie niezależna od tego, jakiego kompilatora używasz (chociaż to dobra praktyka, IMHO, aby podać kompilator w pytaniu tak jak ty)

 1
Author: cake,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-31 21:29:20

Możesz zamiast tego użyć std::vector. Tablice są w dużej mierze konstrukcjami C, C++ ma klasy wrappera, aby zapobiec dokładnie takim niejednoznacznościom

 1
Author: Falmarri,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-09-01 00:18:14