wskaźniki i tablice wielowymiarowe

0

Witam wszystkich.
Mam problem z opanowaniem tablicy pokazywanej wskaźnikiem. Moja tablica wygląda następująco:

int iA[2][3][4] = {{{50,1,2,3},{4,5,6,7},{8,9,10,11}},{{12,13,14,15},{16,17,18,19},{20,21,22,23}}};

teraz definiuję wskaźnik do pokazywania na taką tablicę i przypisuję mu adres zerowego elementu iA:

int (*p1)[2][3][4] = &iA;

Moje pytanie jest następujące: jak odnieść się do indexu w tablicy w którym jest liczba 4.
Sam się trochę tym bawiłem i przy takiej definicji wskaźnika (wskaźnik do int):

int *p = &iA[0][0][0];

odniesienie się do indexu z liczbą 4 wygląda następująco:
*p[0][1][0] i także poruszanie się po tablicy jest proste. Natomiast ciekaw jestem czy ten pierwszy sposób pozwala odczytać dowolny element w tablicy. Z góry dziękuję za pomocne odpowiedzi.

0

Mi się coś zdaje, że to nie jest 1 wskaźnik tylko tablica wskaźników :)

0
int *p1[2][3][4] // deklaracja na tablice wskaźników 2x3x4
int (*p1)[2][3][4]; //deklaracja wskaźnika na tablicę 2x3x4
int *p; //deklaracja wskaźnika na int (lub tablicy jednowymiarowej int)

wynika to z kolejności operatorów.

Tablica = wskaźnik. Chyba najważniejsze i najbardziej mylące prawo C ;-)

Podana przez ciebie tablica jest statyczna - program zrobił z niej w pamięci jeden ciurek (tablicę jednowymiarową) dokładnie taki jak to napisałeś w deklaracji tutaj na forum ;-)

Pytasz o sposoby odnoszenia się do poszczególnych elementów w tablicy.

a) poprzez indeksowanie czyli:

int elem_rowny_4 = iA[0][1][0];
int elem_rowny_4 = (*p1)[2][3][4];
int elem_rowny_4 = *p[0][1][0]; //blad

b) korzystając z arytmetyki wskaźników:

int elem_rowny_4 = *(iA+4);
int elem_rowny_4 = *(***p1+4);
int elem_rowny_4 = *(p+4);

Albo ogólniej

int elem_rowny_4 = *(iA+0*(4*3)+1*4+0);
int elem_rowny_4 = *(***p1+0*(4*3)+1*4+0);
int elem_rowny_4 = *(p+0*(4*3)+1*4+0);

tab[n] to tak jakby nakładka na *(tab+n).

Mam nadzieję, że o to Ci chodziło.

Na forum uzywaj bezwzględnie BBCode !!

``

Moja rada taka - wziąść Grębosza do reki i przestudiować od góry do dołu.
0
Mammoth napisał(a)

A propos winerfresh:

int *p1[2][3][4] // deklaracja na tablice wskaźników 2x3x4
int (*p1)[2][3][4]; //deklaracja wskaźnika na tablicę 2x3x4

wynika to z kolejności operatorów, ale nie o tym mowa w tym poście.

Jakiej kolejności operatorów? Tutaj nie ma operatorów.

Mammoth napisał(a)

Tablica = wskaźnik. Chyba najważniejsze i najbardziej mylące prawo C ;-)

Tablica to zupełnie inny typ niż wskaźnik, może co najwyżej ulegać niejawnej konwersji do niego. Tak samo sprawa ma się zresztą i w C++.

Mammoth napisał(a)

Podana przez ciebie tablica jest statyczna - program zrobił z niej w pamięci jeden ciurek (tablicę jednowymiarową) dokładnie taki jak to napisałeś w deklaracji tutaj na forum ;-)

Znowu bzdura - tablica nie jest statyczna, zapoznaj się z pojęciem zmiennej statycznej. Tablica jest wielowymiarowa chociaż jej elementy ułożone są kolejno za sobą, indeksowanie jej zwraca wskaźnik na n-1 wymiarową tablicę lub na najniższym poziomie wartość elementu. 'Arytmetyka wskaźników' nie ma tutaj nic do rzeczy, poza tym, że zachowuje się podobnie.

Mammoth napisał(a)

Na forum uzywaj bezwzględnie BBCode !!

Tak się składa, że na tym forum BBCode nie obowiązuje.

Podsumowując - nauczanie innych zacznij od siebie.

Mammoth napisał(a)

Moja rada taka - wziąć Grębosza do reki i przestudiować od góry do dołu.

Wziąć to powinieneś książkę, którą zrozumiesz i ew. wesprzeć się standardem panie Mammoth, bo jak widać wspomniana lektura okazała się za trudna.

0
LV napisał(a)
Mammoth napisał(a)

A propos winerfresh:

int *p1[2][3][4] // deklaracja na tablice wskaźników 2x3x4
int (*p1)[2][3][4]; //deklaracja wskaźnika na tablicę 2x3x4

wynika to z kolejności operatorów, ale nie o tym mowa w tym poście.

Jakiej kolejności operatorów? Tutaj nie ma operatorów.

O, to czym jest *, () i []?

LV napisał(a)
Mammoth napisał(a)

Tablica = wskaźnik. Chyba najważniejsze i najbardziej mylące prawo C ;-)

Tablica to zupełnie inny typ niż wskaźnik, może co najwyżej ulegać niejawnej konwersji do niego. Tak samo sprawa ma się zresztą i w C++.

Hm... Nazwa tablicy jest iteratorem na jej pierwszy element - tak najbezpieczniej. Ale racja - there's no pointer.

Mammoth napisał(a)

tab[n] to tak jakby nakładka na *(tab+n).

Nie. Jeżeli przeciążę operator[] to nie oznacza że mam także operator+ (który zwraca obiekt tego samego typu co this/pierwszy arg) i operator* (i vice versa).

0
manfredek napisał(a)

O, to czym jest *, () i []?

Od kiedy symbole w deklaracjach są nazywane operatorami?

manfredek napisał(a)

Hm... Nazwa tablicy jest iteratorem na jej pierwszy element - tak najbezpieczniej. Ale racja - there's no pointer.

Nie chrzań. Iterator to wzorzec projektowy będący swego rodzaju uogólnionym wskaźnikiem na abstrakcyjne dane. Tablica to zupełnie inny typ danych niż wskaźnik\iterator, czy Ty w ogóle odróżniasz agregator od wartości skalarnej? Tylko mi nie pisz znowu o nazwie, nazwa reprezentuje wyłącznie powiązany z nią obiekt, całą tablicę.

Żeby chociaż jeden wiedział o czym pisze...

0

Nazwa tablicy jest jednocześnie wskaźnikiem na jej pierwszy element co można zobaczyć na przykład tak:

int tab[2] = { 1, 2 };
cout << *tab;

//EDIT

0

@LV: Niejawnie rzutowalna. Gr, konwertowalna. Pasi?
@winerfresh: Bzdura.

0

winerfresh, nie, nie jest, zachodzi niejawna konwersja. Ilu jeszcze durniów bez oparcia w faktach będzie się wypowiadać? Aż sięgnę po standardy C i C++ i poszukam kilku cytatów na ten temat, powinny się zgadzać...

ISO 9898 (C):

An array type describes a contiguously allocated nonempty set of objects with a particular member object type, called the element type. Array types are characterized by their element type and by the number of elements in the array. An array type is said to be derived from its element type, and if its element type is T, the array type is sometimes called 'array of T'. The construction of an array type from an element type is called 'array type derivation

Arithmetic types and pointer types are collectively called scalar types. Array and structure types are collectively called aggregate types.

I z rozdziału o konwersjach:

Except when it is the operand of the sizeof operator or the unary & operator, or is a string literal used to initialize an array, an expression that has type 'array of type' is converted to an expression with type 'pointer to type' that points to the initial element of the array object and is not an lvalue. If the array object has register storage class, the behavior is undefined.

A teraz winerfreshowy C++ - ISO 14882:

4 Standard conversions [conv]
Standard conversions are implicit conversions defined for built-in types. Clause 4 enumerates the full set of such conversions. A standard conversion sequence is a sequence of standard conversions in the following order:
— Zero or one conversion from the following set: lvalue-to-rvalue conversion, array-to-pointer conversion,
and function-to-pointer conversion.
— Zero or one conversion from the following set: integral promotions, floating point promotion, integral
conversions, floating point conversions, floating-integral conversions, pointer conversions, pointer to
member conversions, and boolean conversions.
— Zero or one qualification conversion.

[conv.array] 4.2 Array-to-pointer conversion

An lvalue or rvalue of type “array of N T” or “array of unknown bound of T” can be converted to an rvalue of type “pointer to T.” The result is a pointer to the first element of the array.
A string literal (2.13.4) that is not a wide string literal can be converted to an rvalue of type “pointer to char”; a wide string literal can be converted to an rvalue of type “pointer to wchar_t”. In either case, the result is a pointer to the first element of the array. This conversion is considered only when there is an explicit appropriate pointer target type, and not when there is a general need to convert from an lvalue to an rvalue. [Note: this conversion is deprecated. See Annex D. ] For the purpose of ranking in overload resolution (13.3.3.1.1), this conversion is considered an array-to-pointer conversion followed by a qualification conversion (4.4). [Example: "abc" is converted to “pointer to const char” as an array-to-pointer conversion, and then to “pointer to char” as a qualification conversion. ]

Więc 'jest' a 'może zostać skonwertowany' to jednak różnica. Są miejsca gdzie takie rozróżnienie faktycznie ma znaczenie, tak samo jak w przypadku nazw funkcji. Cóż, jeżeli nigdy nie wyrosło się z etapu pisania hello-world... Zabawne, że każdy kolejny osobnik tylko dorzuca kolejne bzdury. Fakt faktem C i C++ to najbardziej niespójne i skomplikowane języki będące w powszechnym użyciu.

@manfred, rzutowanie a konwersja to nie to samo.

0

Wiedziałem, że zaraz mnie znaczną ciągnąć za słówka, a najbardziej Ci którzy się najmniej znają.

LV:

Ja zajmuje się praktyką C++, a nie jakimiś tam regułkami.

Czy na poziomie kodu asemblera i ślaczków w pamięci powiesz mi że coś jest tablicą, a coć wskaźnikiem ?? Dokumentacja stara się obrazowo objaśnić działanie C++, a ja myślę o c++ jako o algorytmie przetwarzającym tekst na kod.

Najwyraźniej bardziej znasz się na Delphi (jakimś wysokopoziomowym języku) niż na C++ ;-) Wnioskuje to na tym jaki masz tok myślenia w stosunku do C++.

int (*p1)[2][3][4];

mówiąc obrazowo najpierw robione jest int [2][3][4], a potem na to wskaźnik o nazwie p1. Wynika to stąd gdzie jest (), a gdzie [] na karcie kolejności operatorów. Nawet new jest operatorem.

  1. wskaźniki to są tablice, przynajmniej tak mogą być interpretowane:
int *xxx;
*xxx = 6;
if (xxx[0] == 6)
{
     // i wejdzie nam program tutaj
}

Poza tym jak mówię o tablicy tutaj to nie myślę o takim typie danych, ale konstrukcji/elemencie języka C++. Zmienna xxx będzie przechowywała ADRES na pierwszy element, więc jak pomyślimy o tym na wspak to ze wskaźnika wyciągniemy tablice.

  1. "Tablica jest wielowymiarowa chociaż..." chyba z tego co napisałem jasno wynika, że chodzi mi o to co jest w pamięci i dlaczego można użyć konstrukcji *(tab+n).

  2. "Podana przez ciebie tablica jest statyczna" a nie słyszałeś na przykład o tablicach dynamicznych. Statyczna tj. statycznie zadeklarowana, a nie żadne tam static. A to z arytmetyka wskaźników to już padam - chyba nawet o czymś takim nie słyszałeś.

int tab[5] = {1,2,3,4,5};
*(tab+1); //wydrukuje 2! to nie jest tab+1bajt tylko tab+4bajty

"tab[n] to tak jakby nakładka na *(tab+n)." chodziło mi tu o to, że to samo mozna osiągnąć obydwiema metodami - i co najważniejsze działaja one dla obojętnie jakich danych, czy to liczb czy klas bo żadne przeciążanie operatorów tutaj nie gra roli itp. To zwykła arytmetyka wskaźników.

A to wszystko wynika z tego, że C++ jest nie bez powodu nazywany językiem którego nie da się nauczyć - trzeba go zrozumieć dlatego jest dla prawdziwych programistów.

atch sie ucieszy, że aż 9 palących odpowiedzi otrzymal ;-) [rotfl]

0
Mammoth napisał(a)

Wiedziałem, że zaraz mnie znaczną ciągnąć za słówka, a najbardziej Ci którzy się najmniej znają.

Ja zajmuje się praktyką C++, a nie jakimiś tam regułkami.

Najwyraźniej bardziej znasz się na Delphi (jakimś wysokopoziomowym języku) niż na C++ ;-) Wnioskuje to na tym jaki masz tok myślenia w stosunku do C++.

Nigdy w Delphi nie programowałem. Najmniej na C++ znają się ci, którzy znają standard i możliwe, że mają wieloletnie doświadczenie w projektowaniu i implementowaniu oprogramowania w tym języku. Zajmujesz się praktyką... po takich praktykach, którzy nie znają regułek potem trzeba poprawiać, kod działa cudem, po minimalnej zmianie sypie się ze względu na efekty uboczne lub\i zachowania nieokreślone.

Składnię deklaracji sobie daruję, musiałbym znowu się babrać z papierami.

Mammoth napisał(a)
  1. wskaźniki to są tablice, przynajmniej tak mogą być interpretowane

Jak już to odwrotnie, przy dostępie tablica ulega konwersji na pointer.

Co do statyczności - jak widzę nie zaznajomiłeś się z tym pojęciem, tak samo pewnie jak nie znasz pojęcia zmiennej automatycznej i kilku innych. A o variable-length array słyszałeś? Taki element C99, Twoim zdaniem jest statyczna czy nie?

Uwierz mi, że arytmetykę wskaźników znam doskonale,

Mammoth napisał(a)

A to wszystko wynika z tego, że C++ jest nie bez powodu nazywany językiem którego nie da się nauczyć - trzeba go zrozumieć dlatego jest dla prawdziwych programistów.

Nie da się nauczyć? Język po prostu jest skomplikowany i niespójny - to nie jest czarna skrzynka, jest doskonale udokumentowany, spokojnie można się go nauczyć. Nie da się rozumieć bez znajomości... Powiem Ci jedno, 'prawdziwy programista' nie musi w życiu C++ na oczy zobaczyć. Swoją drogą do 'prawdziwych programistów' lepiej pasuje Prolog czy Haskell niż C++, programowanie to nie proces klepania kodu.

Faktycznie taki 'praktyk' nie może się C++ nauczyć, bo wymaga to znajomości głupich regułek... Ilość odpowiedzi zaś nie świadczy o ich jakości.

Nie twierdzę, że jestem programistą C/C++ i znam język idealnie, w każdym razie spędziłem sporo czasu na pracy z tym językiem oraz faktycznym poznaniu zasad nim rządzących. Znów robi się flame teoria vs. praktyka... Jedno bez drugiego nie ma racji bytu.1

0

dlatego dopoki jeszce nikt nie odpalil zasady Godwin'a i nie musze zamykac watku :) proponuje skonczyc z przekomarzaniem sie i odpowiedziec na pytanie postawione przez autora.. szkoda ze silnik forum wciaz nie obsluguje splitowania watkow..

po pierwsze, operator a definicja typu danych to inne rzeczy. nie mozna mowic ze 'najpierw sa wykonywane [][][] potem (*)'.. mozna to tak widziec, ale nie pisac na forum -- bo to pozniej ludzi w blad wprowadzi.. rzeczywiscie, jezyk jest tak skonstruowany, ze przy definicji typow tablicowych/wskanzikowych uzywa tych samych symboli co dla operatorow odwolania czy indeksu, ale apeluje o rozroznianie tego..

skrotowy opis jak wygladaja nieproste wskazniki/tablice-na-wskazniki/tablice: Tablice i wskaźniki, skomplikowana składnia
mam nadzieje ze wyjasni roznice w stawianiu [] () i * w definicjach typow danych..

po drugie:

int iA[2][3][4] = {..... ;
int (*p1)[2][3][4] = &iA; //3

Moje pytanie jest następujące: jak odnieść się do indexu w tablicy w którym jest liczba 4.
Sam się trochę tym bawiłem i przy takiej definicji wskaźnika (wskaźnik do int):

int *p = &iA[0][0][0];    //1

odniesienie się do indexu z liczbą 4 wygląda następująco:

*p[0][1][0]  //2 

zle. skoro (1) p jest typu int, to (*2) nie ma prawa sie skompilowac. kropka. (2) jest prawidlowe tylko przy definicji zmiennej jak w (3)
jelsi chcesz wykorzystac fakt, ze cala ta tablica 3D jest jednym duzym blokiem pamieci, i chcesz po niej przebiegac wskaznikiem typu int
(tzn. bez wykorzystania wiedzy kompilatora o jej wymiarach), musisz zajac sie przeliczaniem indeksow i offsetow w 100% recznie, czyli, aby dostac sie do [1][2][3] majac int
p:

int elem010 = *(p + 1*(wymiar2*wymiar3) + 2*(wymiar3) + 3*(1) );
  1. tablica a wskaznik to co innego. tak mowi standard. jesli ktos sie z tym kloci, patrz tekst standardu.
    tablica a wskaznik to co innego. tak mowi praktyka. jelsi ktos sie z tym kloci, patrz operator sizeof:
 int tab[300];
int *ptr = tab;
cout << sizeof(tab) << " " << sizeof(ptr);

i jesli jeszcze ktos sie z tym chce sprzeczac, to laduje w dziale newbie, ktore probuja z'sizeof'owac dynamicznie zaalokowana char[] zeby dowiedziec sie ile ma elementow..

  1. miedzy tablica a wskaznikiem zachodzi niejawna konwersja. dlatego pare linii powyzej, ptr ustawi sie na zgodnie z tab i nawet nie bedzie warninga. i dlatego masa ludzi mowi, ze 'nazwa tablicy to wskaznik na jej pierwszy element'. coz. niech tak mowia, jesli im latwiej w ten sposob to zapamietac. nie daje im to jednak prawa do najezdzania na osoby ktore mowia jak to jest faktycznie. jednak, na chlopski rozum: jesli tak by bylo, jesli TAB byloby wskanzikiem, skad lewy SIZEOF wiedzialby ze TAB ma (32bit) 1200bajtow a nie 4 ??? w kompilatorze nie ma magii, wszystko co kompilator jest w stanie osiagnac, jest deterministyczne i wynika wprost z kodu (plus bugi, plus niezgodnosc ze standardem)

  2. a teraz ad persona..

Mammoth napisał(a)

Wiedziałem, że zaraz mnie znaczną ciągnąć za słówka, a najbardziej Ci którzy się najmniej znają

Mammoth napisał(a)

Najwyraźniej bardziej znasz się na Delphi (jakimś wysokopoziomowym języku) niż na C++

nie masz prawa tak mowic. egzaminowales te osoby? znasz je? moze mialy zly dzien albo kaca albo sasiad im rozwalil samochod i byly wkurwione?
btw. C++ zalicza sie do jezykow wysokopoziomowych:)
btw2. Delphi i C++ sa na tym samym poziomie..

Mammoth napisał(a)

Na forum uzywaj bezwzględnie BBCode !!

nie wszystko co jest tagiem <code> czy [code] nazywa sie BBCode'em.. LV slusznie Ci zwrocil uwage

nie odmawiam Ci prawa do pisania opisowego, wyobrazeniowego. to jest dobre i skutkuje u delikwenta szybszym zalapaniem 'o co moze chodzic'. ale, jeli ktos sie pojawia i zarzuca Ci niescislosc, wystarczy odpowiedziec "tak, racja, pisalem obrazowo zeby gość zrozumial, nie chcialem pisac zbyt scisle". kropka. jesli ktos Cie poprawia/dopowiada ze faktycznie jest inaczej - niech mu bedzie. wie wiecej, chce mu sie pisac, niech pisze! nie mozna go objezdzac ze probuje szerzyc wiedze formalna o jezyku, bo wtedy rownie dobrze mozna bedzie objezdzac Ciebie ze pisales obrazowo..

LV napisał(a)

Znowu bzdura

slowo 'znowu' jest zbedne i skierowne do osoby wrazliwej na punkcie swojego ego, stanowi jawna zachete do natychmiastowego rozpoczecia flame'a.. ludzie sie myla, piszmy bezosobowo.. po co sie klocic ze soba skoro i tak wszyscy wiemy ze w standardzie jest dosc zawinietych tematow zeby przylapac kazdego na ich nieznajomosci :)))

LV napisał(a)

Podsumowując - nauczanie innych zacznij od siebie.

to juz jawny atak na przedmowce, sorry, ale IMHO ta linijka odpowiedzialna jest za w/w klotnie. nikt nikomu nie zabrania komentownaia, wysmiewania, zwracania uwagi, sam czesto opierdalam w ten czy inny sposob, ale jak zaczyna sie pyskowka to jej potem nie ciagne.. pohamuj sie z tym troche.. jesli w odpowiedzi na Twoj post ktos w Twoim mniemaniu wali same bzdury i sie osmiesza --- juz tego nie komentuj. sam to zrobil, sam sie osmieszyl, po co ciagnac? wskaz bledy, wystarczy. swietny przyklad - Twoje wklejenie wycinkow standardu gdy Mammoth zaczal sie zaperzac. chce dyskutowac z std? spox, ma prawo. 1+1 moze sie kiedys okazac =4..

Mammoth napisał(a)

Wynika to stąd gdzie jest (), a gdzie [] na karcie kolejności operatorów. Nawet new jest operatorem.

to nie sa operatory i ich prio nie ma to znaczenia. znaczenie ma jedynie ich pozycja. inaczej moglbys typ danych zapisac jako np

int*[]*[]& zmienna

i ... zgodnie z prio byloby to ref-na-wsk-na-wsk-na-tab-na-tab ? istnieje zasada 'srodek - lewo - prawo' w skrocie 'lewo-prawo' mowiaca jak nalezy czytac typy danych i tylko ona obowiazuje. plus nawiasy. tak - new jest operatorem, delete tez, sizeof tez, typoef tez. ale nie pozwala Ci to napisac int *new& zmienna

. w takim zapisie, * i & nie sa operatorami.

 > ##### Mammoth napisał(a)
> wspak to ze wskaźnika wyciągniemy tablice

nie nie i jeszce raz nie.. nie uda Ci sie wyciagnac tablicy w sensie C++. nigdy sizeof nie zwroci rozmiaru prawdziwego, nigdy template nie wybierze wlasciwej specjalizacji. to co masz to wskaznik na blok pamieci o ktorym Ty wiesz ze jest ciaglym blokiem wartosci typu int i mozesz go sobie sekwencyjnie przegladac tym wskaznikiem. z punktu widzenia 'klientow' tego wskaznika to juz _nie_ jest tablica sensu stricte, chocby dlatego ze straciles automatyczna informacje o jej wymiarach. logicznie zas -- kazdy ciag elementow mozesz nazwac tablica, nawet std::list ktory bedzie mial operator [int] mozesz nazywac tablica, bo sie go da tak uzyc jakby nia jakby byl...

dyskusji nie zamykam, bo mam nadzieje ze wiecie-co-wiecie i wiecie-co-ten-drugi-wie i biorac pod uwage ze juz padl standard i dyskusja przeszla na jakos C++ wobec innych jezykow, to moze skonczycie albo przejdziecie z tym do takiegożesz wątku. a i moze oryginalny autor sie pojawi..
0

Dodam coś od siebie.
C++ wywodzi się z C (ma wszystkie jego cechy), a ten jest językiem niskiego poziomu, którego zalicza się również do języków wysokopoziomowych. Chodzi o to, że można w C++ pisać na poziomie nie odbiegającym właściwie od assemblera. Można wykonać w tym języku kod, który będzie komunikował się za pomocą wywołań API, BIOS lub wprost za pomocą instrukcji maszynowych. W tym celu zawiera choćby wstawki asemblerowe pozwalające na komunikowanie się za pomocą niskopoziomowych rozkazów procesora (np. IN i OUT). W C++ nie zlikwidowano tego mechanizmu, a więc C++ jest również językiem niskopoziomowym. Finito.

Co do tablic.
Tablica, to byt wirtualny istniejący wyłącznie na poziomie kodu źródłowego. Oznacza to praktycznie tylko obszar w pamięci od adresu x, do y. Nazwa tablicy oznacza zawsze to samo co wskazanie na jej pierwszy element. Nie ma po prostu innej możliwości. W zasięgu deklaracji np. int x[10], nazwa x jest równoważnikiem &x[0]. (Jeżeli ktoś nie wierzy, to niech sobie przeanalizuje kod dowolnego kompilatora. Żeby było śmieszniej działa to również w zasięgu przeciążonego operatora [], co polecam sobie sprawdzić.)

Informacja ta i tak może zostać zignorowana ponieważ instrukcja wyrażenia x[15]; wykona się mimo, że określa ona formalnie element poza zadeklarowaną tablicą x. Dla kompilatora jest to przecież *(x+15), a z przemienności dodawania *(15+x);, a stąd to samo co 15[x]; Tak więc tablica, to wytwór naszej wyobraźni. Dla kompilatora C/C++ tablica jako to co rozumiemy pod jej określeniem nigdy nie istnieje (inaczej niż struktura/klasa).
Gdyby tablica nie była bytem wirtualnym, a jej nazwa jednocześnie równoważnikiem wskazania na pierwszy element (typu takiego jak wskazanie tego elementu), to instrukcja deklaracji: typedef char(*Code)[];
nie miałaby prawa skompilować się ponieważ w specyfikacji tej nie podano rozmiaru tablicy.

Druga sprawa: sizeof nie liczy rozmiaru tablic jako takich. Jeżeli argumentem tego operatora jest wyrażenie, to wynikiem jest rozmiar agregatu, który jest potrzebny do przechowania wyrażenia, które reprezentuje. Wynik jest w bajtach - nie w ilości elementów. Wobec tego (sizeof x) może dać wynik 20, 40, 80, albo nawet zupełnie inny na maszynach z egzotyczną długością słowa maszynowego. Druga postać sizeof też nie liczy formalnie długości tablic, a jedynie wyrażenie deklaracyjne np. sizeof (int[10]). I liczy ono rozmiar pamięci jaki przydzieliłby kompilator dla definicji takiego wyrażenia. Dla kompilatora nie ma czegoś takiego jak długość tablicy bo musiałaby to być informacja rozpatrywana w ilości elementów. A tak nie jest.

Trzecia sprawa. Skoro tablica to byt wirtualny, to można ją sobie wirtualnie wytworzyć przez konwersję czy to nazw zmiennych (obiektów) lub funkcji na wskazania i przekształcenie ich w twór będący funkcjonalnie tablicą. Na przykład w zasięgu deklaracji dwóch następujących po sobie funkcji f1(), f2() i f3() można stworzyć coś takiego:

int f1(int x)
{ //instrukcje do zamazania
	(void)(x /= 10, --x); //cokolwiek byle zajęło trochę miejsca
	return 20;
}

int f2(int x)
{
	return ++x * 10;
}

int f3(void) {}

typedef char(*Code)[];

int main(void)
{
	unsigned char NOP = '\0';
	Code kod_f1 = (Code)&f1;
	long rozmiar_f1 = ((char*)&f2 - (char*)&f1) - 1;
	Code kod_f2 = (Code)&f1;
	long rozmiar_f2 = ((char*)&f3 - (char*)&f2) - 1;

	printf("Wynik: %d\n", f1(5));
	assert (rozmiar_f1 >= rozmiar_f2);
	int i;
	for(i = 0; i < rozmiar_f2; i++)
		(*kod_f1)[i] = (*kod_f2)[i];
	printf("Wynik: %d\n", f1(5));
}

Programik ten przenosi kod f2 do f1. Kod obu funkcji f1() jak i f2() został potraktowany jako tablica znaków (bajtów). Można tak zrobić ponieważ tablica de facto nie istnieje. To tylko wysokopoziomowy skrót dla arytmetyki wskaźników. Wynikiem tego programiku jest 20 i 60.

Mało kto po prostu rozumie, że w C i C++ są dwa, zupełnie różne rodzaje wyrażeń - te służące do obliczeń oraz wyrażenia deklaracyjne. Te pierwsze podają konkretny wynik (i dodatkowo wydziela się z nich specjalny ich rodzaj czyli L-wyrażenia, które mogą być pierwszym argumentem dla operator=()).
Natomiast wyrażenia deklaracyjne pozwalają obliczyć typ wyniku.
W obu wypadkach operator taki jak [] reprezentuje tak naprawdę dwa zupełnie różne operatory o tym samym wyglądzie i pozornie podobnym zastosowaniu. W obu wypadkach stosuje się do nich podzbiór tej samej tablicy priorytetów. Najwyraźniej nie załapał tego również szanowny (bez podtekstów) moderator.

Wszystkie operatory mają ściśle zdefiniowane priorytety i wiązania. Nie są one w specyfikacji języka ot tak - dla jaj. A to, że nie wszystkie operatory są dopuszczone do tworzenia wyrażeń deklaracyjnych, to zupełnie inna bajka. Po to jest specyfikacja języka.

I tak na koniec. Tablice i ich fatalna specyfikacja jest jednym z powodów dla których ludzie powoli odchodzą od używania C i C++. W obecnych czasach trzeba być wariatem, żeby bawić się wielowymiarowymi tablicami typu C. Nie dość, że jest to piekielnie błędotwórcze, to jeszcze wybitnie nieczytelne, a wiele interpretacji kontrowersyjnych.
Znacznie lepiej do tego nadają się kolekcje zawarte w STL - choć i one są bardzo nieczytelne w użyciu.
Oczywiście kolejne aktualizacje języka dążą do lepszego sformalizowania tablic i tym samym zabezpieczenia się przed niewłaściwym (nieprzewidzianym) ich użyciem. Stąd kolejne interpretacje, które mają jakoby wyjaśnić nowe konstrukcje. Nie zmieni to jednak faktu, że jest to robione "na siłę". Nie polepszy to projektu języka C++. Język C miał spójną koncepcję prostego niskopoziomowego języka z wieloma aspektami języka wysokopoziomowego doskonale zastępującego assembler. Jednak C++, to potworek w którym Stroustrup próbował pożenić tę dość spójną koncepcję C z zupełnie inną koncepcją. Język odniósł sukces, to fakt - ale dużym kosztem i moim zdaniem z rozpędu - głównie jako wciąż wydajne rozwinięcie C. W C++ mieszają się interpretacje niskopoziomowe wywodzące się z C z interpretacjami wysokopoziomowymi, które próbują z wsuwanej ze smakiem acz marnej mordoklejki zrobić belgijskie czekoladki. ;-)

Jakiś czas temu stwierdziłem, że obecny C++ jest tak paskudnie nieczytelny, że najlepiej przerzucić się na pisanie w innym języku. Pisanie w C++, to teraz moim zdaniem zło konieczne.
Myślę iż przykład sporów w tym temacie jest charakterystyczny dla problemów z tym językiem.

Obecnie sądzę, że do pisania wydajnych i czytelnych programów znacznie lepiej używać tandemu C + Java niż C++.

Na koniec bo prawie zapomniałem o samym temacie:

	int iA[2][3][4] =
	{
		{
			{50,1,2,3},
			{4,5,6,7},
			{8,9,10,11}
		},
		{
			{12,13,14,15},
			{16,17,18,19},
			{20,21,22,23}
		}
	};
	typedef int T1[4];
	typedef T1 T2[3];
	typedef T2 T3[2];

Typ T3, to to samo co iA. Jest to dwuelementowa tablica (tablic T2). T2, to z kolei trójelementowa tablica (tablic T1), a ta ostatnia jest dopiero czteroelementową tablicą liczb całkowitych.

Dostać się do tej tablicy można zarówno korzystając ze wskazania do samej tablicy:
int (ptr_tab)[2][3][4] = &iA;
jak i korzystając z faktu, że iA jest wskazaniem do pierwszego elementu, którym jest int(
)[3][4].
int (*ptr_el)[3][4] = iA; //równoważne z przypisaniem &iA[0]
Różnica polega na użyciu operatora adresu w przypisaniu.
Dostęp do elementu o wartości 4:
(ptr_tab)[0][1][0];
lub (ptr_el)[1][0];
Do załapania wyrażeń tego typu trzeba pamiętać, że priorytet operatora[] jest wyższy niż operatora (
), wiązanie [] jest lewe, a (
) prawe.
Można to oczywiście rozwijać wg zasady a[b] == *(a + b) == b[a], ale czytelność takiego rozwinięcia jest zerowa.

0

plynnie napisane, ale..

również językiem niskopoziomowym. Finito

wiesz, tak juz powiem personalnie, skonfrontuj to swoje finito np. z http://en.wikipedia.org/wiki/High-level_programming_language i przemysl swoja definicje high/low level z formalnymi definicjami tych zwrotow..

co do przykladow i przekazu o tablicach --- trudno sie nie zgodzic, ze 'tablice' jako byt sa wirtualne i istnieja tylko na podczas kompilacji. nie pisalem tego wprost, bo wydawalo mi sie to oczywiste.. w ten sam sposob, wirtualnym bytem sa takie rzeczy jak niewirtualna klasa albo struktura, ba - albo wszystkie template'y.. niezmienia to jednak faktu, ze typ tablicowy to nie to samo co typ wskaznikowy, tak jak typ wskaznik-na-metode-klasy to nie to samo co typ wskaznik-na-funkcje-o-jednym-parametrze-wiecej. to, ze cos istnieje tylko w skladni/drzewie wyrazen/strukturze danych w czasie kompilacji, nie znaczy ze to tak naprawde nie istnieje i jest czyms innym:) nie odbierzesz chyba memberptr albo template'owi jego osobowosci tak jak probujesz to uczynic z typem tablicowym?

wszystkie Twoje przyklady korzystaja z wymienionego przez Twoich przedmowcow, w tym mnie, mechanizmu niejawnego rzutowania tablica->wskaznik albo wprost z arytmetyki wskaznikow.. to, ze jestes w stanie przerzutowac dwa wskazniki na funkcje na char* nie oznacza ze gdziekolwiek tam pojawia sie typ tablicowy.. to tylko arytmetyka wskaznikow.

typ tablicowy pojawilby sie gdybys funkcje przerzutowal na, dajmy na to, char[500], wtedy bylby sens mowienia o tablicy..

typedef char(*Code)[]; nie miałaby prawa skompilować się ponieważ w specyfikacji tej nie podano rozmiaru tablicy

mieszasz przypadki. [] to nie to samo co [5], i jeszcze bardziej cos innego niz wskaznik.. int[] jest zapisem typu tablicowego, ale .. jest on specjalnym przypadkiem (tzw. niekompletnym) i jest traktowany w inny sposob niz normalne (kompletne) typy tablicowe. sprawdz sobie chocby sizeof'em na przypadku: struct wielobajtowa{ int x[30]; } i teraz porownaj wynik sizeofa dla:

  • sizeof(wielobajtowa)
  • sizeof (wielobajtowa*)
  • sizeof (wielobajtowa[])
  • sizeof (welobajtpwa[0])
    .. i co wiecej - modyfikator niekompletny [] jest dozwolony tylko jako OSTATNI MODYFIKATOR TYPU.. sprobuj zrobic tablice 50 elementow typu int[]:

int a[][50] -- kompilator powie Ci dokladnie to co wygdybales: storage size of 'a' is not known! podczas gdy zamiana na
int a*[50], czyli piszac prawidlowo: int (a)[50] -- sprawi, ze deklaracja sie skompiluje (przy czym jej sizeof bedize oczywiscie rowny sizeof void, a nie tablicy..)
[btw: heh, znow prosty przyklad ze tablica to nie wskanzik i vice versa]

kolejny przyklad: napisz sobie template'a po T oraz jego dwie specjalizacje, jedna po T[] a druga po T* i sie nimi pobaw:

template<typename T> class X{};
template<typename T> class X<T*>{};  // specjaliz. ok
template<typename T> class X<T[]>{}; // specjaliz. ok //czemu nie ma bledu redefinicji? bo typ [] != typ *
template<typename T> class X<T[5]>{}; // specjaliz. ok //czemu nie ma bledu redefinicji? bo typ [5] != typ [] i != typ *
template<typename T> class X<T[55]>{}; // specjaliz. ok //czemu nie ma bledu redefinicji? bo typ [55] != ....

//template<typename T> class X<T*>{}; // error: redefinition, specjalizacja po takim typie juz byla

ujmujac w skrocie, probujac kiedykolwiek prezkonac kogokolwiek ze tablica to to samo co wskaznik na jej pierwszy element polemizuj z dwoma roznymi rozmiarami wypisywanymi przez:

 cout << sizeof(int[5][2]) << " " << sizeof(int(*)[2]) << endl

zwracajac przy tym uwage na fakt, ze lewy typ to tablica, prawy typ to wskaznik na jej pierwszy element, oraz ze oba te sizeof'y sa dokladnie tego samego "rodzaju"

Mało kto po prostu rozumie, że w C i C++ są dwa, zupełnie różne rodzaje wyrażeń (...) Natomiast wyrażenia deklaracyjne pozwalają obliczyć typ wyniku

obliczyc?? typ jest zawsze zapisany wprost, o ile pojawia sie jako wyrazenie. co najwyzej mozna obliczyc rozmiar tablic, w stylu int[50/10] - ale w tym przypadku musi byc to wyrazenie const czasu kompilacji i ciezko tutaj mowic o jakichs sensownych obliczeniach

W obu wypadkach operator taki jak [] reprezentuje tak naprawdę dwa zupełnie różne operatory o tym samym wyglądzie i pozornie podobnym zastosowaniu. W obu wypadkach stosuje się do nich podzbiór tej samej tablicy priorytetów. Najwyraźniej nie załapał tego również szanowny (bez podtekstów) moderator

wybacz, bede sie upieral ze w przypadku deklaracji typow, [] nie jest operatorem i jego prio nie ma znaczenia. powtorze sie, ale:
zapis definicji typow ma swoje (odrebne!) zasady parsowania, kompletnie inne od reszty wyrazen i nie uzywajace tabeli priorytetow operatorow (btw patrz http://www.cplusplus.com/doc/tutorial/operators.html)

nie lubie byc goloslownym:

wyrazenia
tab3 - wez element 3ci, wywolaj tab
(tab[3])() - to samo
tab()[3] - wywolaj tab, wez element 3ci
(tab())[3] - to samo
typy
(int (*x)(void)) [3] - tablica trzech 'funkcji' (dokladniej: wskaznikow-na-fkcje)
int (*x)(void) [3] - funkcja zwracajaca tablice trzech (bledny typ, ale rozumiany przez kompilator: function returning an array)

co pokazuje sprzecznosc tezy jakoby priorytety "operatrow" w deklaracjach typow:
zakladajac, ze tak samo jak w wyrazenach, operator () [wywolanie] oraz [] [indeksowanie] maja ten sam priorytet, wiec o kolejnosci decyduje tylko i wylacznie pozycja, czemu pierwsza deklaracja typu jest inna od deklaracji drugiej ? przeciez wstawilem tylko nawiasy wokol deklaracji typu "wskna-funkcje-zwracajacaint" ?

zerknij chocby do doc. kompilatora visuala: http://msdn.microsoft.com/en-us/library/tb971bed(VS.80).aspx na dole strony masz konkretne reguly gramatyki dla wyrazen deklaracyjnych. & * [] :: etc sa symbolami gramatyki, nie operatorami 'do wykonania' i do wykonania sobie wg. priorytetow. jak juz rozmawiamy o gramatyce, jest powazna roznica pomiedzy "expression" a "declaration" i slowo "operator" ma sens tylko w tym pierwszym 'czyms'
przyjemniejsze wyjasnienie niz formalny zapis gramatyki, to regula lewo-prawo: http://www.codeproject.com/KB/cpp/complex_declarations.aspx#right_left_rule

i ostatnia sprawa.. co do slynnego zapisu 15[tablica], czy jak kto woli 3["mama"] - dzieje sie tak nie dlatego, ze "z definicji" x[y] to to samo co y[x] i ze to tylko takie opakowanie dla przeliczania indeksow, tylko dlatego, ze tak zostaly skonstruowane wbudowane operatory[] dla wartosci calkowitych i wskaznikow/tablic. tak, mowa o tych automatycznych, nieprzeciazalnych, wzietych z epoki kamienia lupanego czyli z C. w C++ zas oba wyrazenia sa rozne

sprobuj na przyklad wykonac taka 'zamiane indeksow' dla typu niewbudowanego:

#include <iostream>

class Xx
{
public:
    int operator[] (int idx)
    {   int tab[] = {1,2,3,4,5};
        return tab[idx];
    }
};

int main()
{   int tablica[] = {1,2,3,4,5};
    Xx cosTypuNiewbudowanego;
    std::cout << 2[tablica] << std::endl;
    std::cout << 2[cosTypuNiewbudowanego] << std::endl;
}

acz bylo by to lepiej widac na przykladzie dwoch klas, obu implementujacych swoje operatory[], i probie porownania wywolan obktA[obktB] i obktB[obktA]

0

Ok dzięki chłopaki.

0

Słuchajcie 10 razy czytałem ten temat i dojść nie mogę jak rozwiązać swój problem. Każdy każdego poprawia a mnie zależy na rozwiązaniu problemu. A oto i on.

Mam tablicę tab[x][y][z] która jest zainicjowana na tab[100][100][100]
Do każdego elementu przypisałem wartości 1,2,3 czyli np tab[2][40][30] może mieć wartość 2.
Pytanie brzmi: Jak znaleźć "współrzędne" pola tablicy w którym znajduje wartość 3 ?
Czyli cout<<podaj wartosc komórki <<endl;
cin >> a;
for (z=0; z<100; z++) {
for (y=0; y<100; y++){
for (x=0; x<100; x ++){
tab[x][y][z] = a;}
i żeby wypisał mi wspórzędne z, y, x ?
Co źle robię...nie jestem programistą więc jakoś łopatologiczne poproszę :-)</cpp>

0
inspirion83 napisał(a)

Pytanie brzmi: Jak znaleźć "współrzędne" pola tablicy w którym znajduje wartość 3?

Na chłopski rozum...
Jeśli się czegoś szuka, to zwykle dokonuje się wyboru: czy to co teraz mam to to czego szukam, więc bez instrukcji warunkowej się nie obejdzie

//i żeby wypisał mi wspórzędne z, y, x
const int v = 3; //szukana wartość

for(int x=0; x<100; x++) for(int y=0; y<100; y++) for(int z=0; z<100; z++)
//punkt kulminacyjny: <instrukcja warunkowa>
if(tab[x][y][z] == v) cout<<"x: "<<x<<"y: "<<y<<"z: "<<z<<endl;

a wymiarowość tablicy nie ma tu żadnego znaczenia, tak samo byś to zrobił na tablicy liniowej

inspirion83 napisał(a)

[...]...nie jestem programistą więc jakoś łopatologiczne poproszę :-)

ja też nie, ale wcale nie trzeba być programistą, żeby to rozumieć, wystarczy ci ciut myślenia i matematyka taka z podstawówki, w większości na liczbach całkowitych

1 użytkowników online, w tym zalogowanych: 0, gości: 1