tablica wskaźników, wskaźnik do tablicy wskaźników

0

Mam problem ze zrozumieniem wskaźników. Proszę o wyrozumiałość.

Na początek powiedzmy, że chcę zadeklarować statyczną tablicę wskaźników i odwołać się do jej pól, robię tak:

int main()
{
    int *tab[3];
    cin >> *tab[1];
    cout << *tab[1];
    return 0;
}

Powyższy kod sie kompiluje i program dziala. Jeśli natomiast chcę się odwołać to elementu tablicy o 1 mniejszej od jej rozmiaru to program się zawiesza:

int main()
{
    int *tab[3];
    cin >> *tab[2];
    cout << *tab[2];
    return 0;
}

Teraz jeśli chcę takiej samej tablicy wskaźników przydzielić pamięć dynamicznie i odwołać się do jej elementów, robię tak:

int main()
{
    int *tab = new int [3];
    tab[1] = 1;
    cout << tab[1];
    return 0;
}

Czy w takim razie sposób deklaracji statyczny bądź dynamiczny determinuje sposób odwoływania się do jej elementów? Dla statycznego z operatorem wyłuskania, dla dynamicznego bez?

Jak chcę zadeklarować statycznie wskaźnik do tablicy wskaźników to robię tak: (?)

int **tab[3];

A dynamicznie tak:

 int **tab = new int * [3];

Jak się odwołać do elementów tych tablic? Np mając wskaźnik do tablicy wskaźników i chcę pod pierwszy element tablicy wpisać konkretną wartość.

0
int *tab[3];
cin >> *tab[1];

Ten program jest niepoprawny z najgorszym typem błędu: undefined behaviour, dlatego nie wiesz że jest źle.
tab to tablica 3 wskaźników, żaden z tych wskaźników nie został zainicjalizowany
tab[1] to jeden z tych niezainicjalizowanych wskaźników
*tab[1] próbujesz wyłuskać niezainicjalizowany wskaźnik, co jest UB. Poza tym tab[1] wskazuje na jakieś miejsce w pamięci, które na pewno nie należy do Ciebie, więc zapisanie jakiejś wartości tam to też co najmniej segmentation fault. Czyli poprawnie to powinno być

tab[1] = new int;
cin >> *tab[1];

Analogicznie z pozostałymi komórkami tablicy.

Np mając wskaźnik do tablicy wskaźników i chcę pod pierwszy element tablicy wpisać konkretną wartość.

int* tab[3]   // statyczna tablica 3 wskaźników
int* tab = new int [3]  // "dynamiczna tablica" 3 int-ów

int** tab = new int* [3];   // "dynamiczna tablica" 3 wskaźników
tab[0] = new int;
tab[1] = new int;
tab[2] = new int;
*tab[0] = 13;
0

Ta nazwa wskaźnik do tablicy wskaźników mi przeszkadzała w zrozumieniu. Dzięki @twonek za przejrzyste wytłumaczenie.

0

Mam jeszcze jednak parę wątpliwości.

Mam taki program:

#include <iostream>

const int Cities = 3;
int main () {

using namespace std;
const char* cities[Cities];

cities[0] = new char;
cities[1] = new char;
cities[2] = new char;

cities[0] = "ABC";
cities[1] =	"DEF";
cities[2] =	"GHI";

for (int i=0; i < Cities; i++) {
	
	cout << cities[i] << endl;
}
	
return 0;
}

Dlaczego jak alokuję pamięć na trzy zmienne typu char leżące w pamięci obok siebie, a później wyświetlam nazwy miast, to nazwy te pokazują się poprawnie. Moim zdaniem, skoro wskaźnik na pierwszy element tablicy wskazują na A, drugi wskaźnik wskazuje na D a trzeci na G, to mamy konflikt adresów. Tzn adres D=B, a adres G=C, bo pamięć jest alokowana po kolei, a wyświetlanie pełnej nazwy miasta wiąże się też z odczytem trzech kolejnych adresów, począwszy od A. (obrazowo: adres A= 100, B=101, C=102, D=101, G=102).

Ponadto jak zamiast tablicy wskaźników char, zastosuję tablicę wskaźników string :

#include <iostream>

const int Cities = 3;
int main () {

using namespace std;
string* cities[Cities];

cities[0] = new string;
cities[1] = new string;
cities[2] = new string;

*cities[0] = "Jelenia Gora";
*cities[1] = "Jelenia Gora";
*cities[2] = "Jelenia Gora";

for (int i=0; i < Cities; i++) {
	
	cout << *cities[i] << endl;
}
	
return 0;
	
}

W przypadku char, do wskaźnika cities[0] przypisujemy adres pierwszego elementu łańcucha znakowego "Jelenia Gora". Dlaczego zatem w string nie przypisuje się adresu pierwszego elementu łańcucha znakowego do wskaźnika, tylko stosuje się operator wyłuskania. Jak zatem jest traktowany łańcuch znakowy. Na pewno nie jako char, bo do *cities[0] nie można przecież przypisać adresu.

0

Stringi ABC, DEF i GHI są zapisane w zupełnie różnych miejscach pamięci. Adres A może wynosić 100, wtedy adres B to 101, C 102, D z kolei może być pod 505, E pod 506 itd. To co robi kod cout << cities[1] to odczytanie adresu litery D, wyświetlenie jej i przechodzenie pod kolejne adresy, ale po kolei, a nie tak jak są zapisane w tablicy, czyli 506, 507 itd. aż do napotkania tzw. null terminatora czyli znaku o kodzie 0. Wskaźniki w tablicy są od siebie niezależne w tej sytuacji.

Używanie new w tym momencie jest zbyteczne, a nawet szkodliwe, bo string zapisany w cudzysłowie ma zarezerwowaną dla siebie pamięć przy starcie programu, a jego przypisanie to przypisanie samego wskaźnika, więc powodujesz wyciek pamięci.

Co do typu string to on ma przeładowany operator przypisania i on sobie automatycznie rezerwuje pamiec i kopiuje do niej ten ciąg który do niego przypisujemy, a po wyjściu z zakresu albo użyciu delete zwalnia.

0

Dlaczego jak alokuję pamięć na trzy zmienne typu char leżące w pamięci obok siebie

Nie, to wskaźniki leżą obok siebie, natomiast wskazują na zupełnie różne miejsca na stercie.
Poza tym

cities[0] = new char;

alokujesz pamięć na jeden znak. A to wszystko działa dlatego że literały znakowe są specjalnie zaalokowane i nie musisz rezerwować pamięci dla nich (wystarczy wskaźnikiem wskazać na początek literału, by móc go wyświetlić). Inaczej mówiąc

cities[0] = new char;
cities[0] = "ABC";

= new char jest w tym przypadku zbędne. Co więcej masz wyciek pamięci, bo przekierowałeś wskaźnik na początek literału, natomiast nie posprzątałeś tej pamięci zarezerwowanej na stercie.
I od razu zapamiętaj, że ciągu znakowego się nie kopiuje poprzez przypisanie. Takie coś

cities[0] = "ABC";

to jest jedynie zmiana wartości wskaźnika. Kopiować ciąg trzeba znak po znaku, albo korzystając z funkcji typu strcpy.
Chyba że piszesz w C++, to wtedy powinno się korzystać z klasy std::string, przy czym wskaźnik do obiektu typu string nie ma nic wspólnego z wskaźnikiem do ciągu znakowego. Typowy kod powinien mniej więcej tak wyglądać

std::string s = "ABC";

Zauważ, że mamy tutaj obiekt, a nie wskaźnik. Nie ma sensu używać wskaźnika bez potrzeby. No i na koniec: nie używaj new jak nie musisz, a gdy już używasz to (prawie) zawsze na jedno new/new[] powinien przypadać jeden delete/delete[]

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