Przekazywanie wskaźnika na pierwszy element listy

1

Dzień dobry,

Jestem laikiem programowania, właśnie stworzyłem swoją pierwszą jednokierunkową listę. Program działa, jednakże pisałem go w Code::Blocks, zauważyłem, że program pomija niektóre błędy i zaznacza je jako warningi. Staram się naprawić te błędy, gdyż np. Visual Studio, traktuje je bardziej poważnie, a także dlatego, że program oddaje na ocenę na studia, więc zależy mi na jak najwyższej, a wszystkie błędy "gramatyczne" w kodzie obniżają mi ocenę. Problem mój wygląda następująco: wysyłam zadeklarowany w main'ie wskaźnik do pierwszego elementu struktury do funkcji ("f"). Warning tego błędu brzmi następująco: "warning: passing argument 1 of 'dodaj; from incompatible pointer type [enabled by default]" oraz "note: expected 'struct osoba*' but argument is of type 'struct osoba **' ". Jak widać po zamieszczonym przeze mnie kodzie poniżej przesyłałam wskaźnik "f" (wskaźnik do pierwszego elementu) ze znakiem ampersand (z tego co wiem robi się to bez tego znaku, jednak program przestaje działać przy wywołaniu funkcji bez niego). Próbowałem naprawić błędy, jednakże za każdym razem dostaję ich coraz więcej, dlatego postanowiłem zasięgnąć pomocy i doświadczenia osób bardziej doświadczonych. Pozdrawiam i dziękuję za pomoc.
Poniżej zamieszczam kody źródłowe istotniejszych części programu (jako, że jest on duży a problem dotyczy, z tego co mi się wydaje jedynie wywołanie funkcji i przesłania wskaźnika "f", ew. alokacji pamięci wewnątrz tej funkcji):

//definicja struktury

typedef struct lista
{
    int wiek; // informacja o wieku
    char imie[50];
    char nazwisko[50];
    struct lista *polacz; // samoodwolujaca sie struktura

}osoba; // pojedynczy wezel listy

//definicja funkcji "dodaj", która dodaje nowy element do listy
void dodaj(osoba *f)
{
    osoba *nowy = (osoba*)malloc(sizeof(osoba));
    printf("Podaj imie osoby.\n");
    scanf("%s", &nowy -> imie);
    printf("Podaj nazwisko osoby.\n");
    scanf("%s", &nowy -> nazwisko);
    printf("Podaj wiek osoby.\n");
    scanf("%d", &nowy -> wiek);
    nowy -> polacz = NULL;

    while(f -> polacz != NULL)
    {
        f = f -> polacz;
    }
    f -> polacz = nowy;

}

//deklaracja wskaźnika "f" (w main'ie)

osoba *f;

//wywołanie funkcji "dodaj"
dodaj(&f);
 
1
while(f -> polacz != NULL)
{
        f = f -> polacz;
}

Taka mała uwaga. Nie musisz iterować przez całą liste, aby dodać nowy element. :)

1

Co to za bezsensowna nazwa zmiennej f? Zmienne powinny mówić czym są.

Powinno być

dodaj(f)

bo f jest wskaźnikiem "z jedną gwiazdką", czyli taki jaki funkcja chce przyjąć.
Natomiast program Ci się wywala, bo masz

while(f -> polacz != NULL)

Skoro f na początku jest wskaźnikiem, który nie wskazuje na nic sensownego, to próba wyciągania polacz z niego skończy się wywaleniem.

1

Jesli robisz listę, to nazywaj ja lista :) zamiast: struct lista
Napisz struct _lista
A zamiast "osoba" użyj "lista". Uwierz mi, ze takie rzeczy sa naprawde bardzo ważne. To dzięki nim analizując większe programy jesteś w stanie zrozumieć o co chodzi w każdym fragmencie kodu bez analizy całości. Pole "polacz" zwykle nazywa się raczej tym, na co wskazuje dany wskaźnik, czyli na "nastepnyElement".

Teraz może o wskaźnikach:
Warning który ci wyświetla się, jest niesamowicie istotny zarówno pod kątem działania programu jak i potencjalnych Twoich przyszłych bugow. Chodzi o to, ze w funkcji wymagasz wskaźnika na Twoja listę - i to jest ok, ale niepoprawnie ja wywolujesz. Musisz zrozumieć czym jest wskaźnik: (w uproszczeniu) jest to adres wskazujący na miejsce w pamięci RAM, gdzie przechowywane sa dane. To, jakie dane sa tam przechowywane i ile ich jest określasz poprzez podanie typu wskaźnika. Wiec, jesli napisalbys "int * Some = 0xFF" to oznacza, ze zmienna Some przechowuje adres w pamięci RAM, w którym przechowywane sa dane typu int. Adres ten w tym wypadku wynosi 0xFF. Czyli jesli byś mógł sobie obejrzę pamięć RAM i znalazłbyś w niej adres 0xFF to w nim jest wartość tego inta. Musisz zrozumieć, ze kazda kolejna gwiazdka w C to kolejny "poziom" wskaźnika, czyli jesli chciałbyś przechowywac informacje o tym, gdzie przechowujesz adres czegos, to musisz użyć dwóch gwiazdek: "int ** SomeAddressOfAddress = &Some" - w tym wypadku to będzie wskazywalo na komórkę w pamięci, gdzie jest przechowywane 0xFF które jest adresem gdzie przechowywamy wartość typu int. Wiem ze to na początku jest trochę skomplikowane, ale najlepiej jest to sobie rozrysować na kartce. Ja teraz jestem na telefonie wiec ciężko mi coś ladnego przygotować.

To co ty zrobiłeś w programie to: Zażądałeś adresu w pamięci, w ktorym sa przechowywane dane typu osoba, natomiast podales zamiast tego adres w pamięci w którym jest przechowywany adres na dane typu osoba. Rozumiesz roznice? To tak jak by Twój kolega poprosił Cie o Twój adres a zamiast tego Ty byś mu podal adres do wujka, który wie gdzie Ty mieszkasz. Niby spoko, ale skoro kolega myśli ze to Twój adres to dmuchana lale wyśle do Twojego wujka zamiast do Ciebie.

Kolejny blad to to, ze nie zarezerwowales miejsca w pamięci gdzie dane typu osoba maja być trzymane. Możesz to zrobić albo poprzez malloc albo statycznie czyli definiując zmienna jako "osoba f"

1
JohnAmadis napisał(a):

zamiast: struct lista
Napisz struct _lista
A zamiast "osoba" użyj "lista".

Można też typedef struct lista {...} lista (nazwy mogą być te same), szczególnie, że zaczynanie nazw od podkreśleń chyba nie jest wskazane w C/C++...

0

Nazwy zaczynające się od podkreslnika zwykle sa zarezerwowane dla definicji i typów, których przeciętny użytkownik nie powinien dotykać. Zwykle oznaczają, ze to jest cos, co trzeba użyć w odpowiedni sposób (np wewnętrze funkcje kernela). W wypadku tej struktury definicja _lista powinna być wlasnie taka według mnie, ale to jest raczej kwestia coding standardu. W każdym razie jak byś nie zrobił to na pewno nazwanie tego "osoba" nie jest najlepszym wyborem

0

Witam, chciałbym sprostować: nazwa wskaźnika "f" pochodzi od "first" w języku angielskim. Trochę to zawiłe, jednak wcześniej używałem angielskich nazw w programie, więc przepraszam. Program wywala kiedy wywołuję funkcję w taki sposób, jak powinno to się robić, czyli:

 dodaj(f); 

, momencie wywołania funkcji while w tejże funkcji. Cała lista, napisana przeze mnie w opisany w pierwszym poście sposób działa w Code::Blocks bez problemu (czyli kiedy wywołuję funkcje np. dodaj(&f);

). Podkreślam, że Code::Blocks jest mniej wyczulony na warning'i. Visual kompiluje, ale wywala program w momencie wywołania pętli while, dlatego chcę poprawić te warning'i, aby program był w pełni sprawny, a kod poprawny. Pisałem już programy ze wskaźnikami, jednakże wszelkie próby poprawienia kodu opartego na struktury, są dla mnie stosunkową nowością, zawodzą. Jeszcze jedno: wydaje mi się, że nie muszę rezerwować pamięci dla wskaźnika f, gdyż pamięć w funkcji "dodaj" jest alokowana dla wskaźnika "nowy" (który jest nowym elementem struktury, dla którego uzupełniane są dane (imię, nazwisko, wiek), a następnie jest on "wpinany" do listy).
0

Więc tak:

  1. f jest typu osoba*, więc &f jest typu osoba** i nie może być argumentem funkcji dodaj.
  2. zamiast &nowy->imie, &nowy->nazwisko i &nowy->wiek masz dwie opcje: albo piszesz (*nowy).imie, albo nowy->imie.
  3. domyślnie f jest NULL, więc nie ma czegoś takiego jak f->polacz.
    Ogólnie rzecz biorąc powinieneś się dokształcić ze wskaźników i przypomnieć, do czego służą operatory * oraz &. No i pamiętać, że w przypadku struktur dynamicznych trzeba osobno definiować dodawanie elementu do pustej struktury.
0
 
void dodaj(osoba *f)
{
    osoba *nowy = (osoba*)malloc(sizeof(osoba));
    printf("Podaj imie osoby.\n");
    scanf("%s", nowy -> imie); //imie jest wskaznikiem, nie dajemy wowczas & przed
    printf("Podaj nazwisko osoby.\n");
    scanf("%s", nowy -> nazwisko); //tutaj podobnie
    printf("Podaj wiek osoby.\n");
    scanf("%d", &nowy -> wiek);
    nowy -> polacz = NULL;
 
    while(f -> polacz != NULL)
    {
        f = f -> polacz;
    }
    f -> polacz = nowy;
 
}
int main()
{
    osoba root;
    dodaj(&root);
    //albo
    osoba* root2 = (osoba*) malloc(sizeof(osoba));
    dodaj(root2);

    return 0;
}

0

Dziękuję, za pomoc, udało mi się poprawić program. Chciałbym jeszcze spytać, czy w sposób podany w funkcji poniżej, poprawnie zwolnię pamięć:

void zakoncz(osoba *f)
{
    osoba *temp;
    while( f -> polacz != NULL)
    {
        temp = f;
        f = f -> polacz;
        free(temp);
    }
    free(f);
} 
0
tabularasa1234 napisał(a):

Dziękuję, za pomoc, udało mi się poprawić program. Chciałbym jeszcze spytać, czy w sposób podany w funkcji poniżej, poprawnie zwolnię pamięć:

void zakoncz(osoba *f)
{
    osoba *temp;
    while( f -> polacz != NULL)
    {
        temp = f;
        f = f -> polacz;
        free(temp);
    }
    free(f);
} 

Prawie, bo nie zadziała dla pustej listy (gdy f==NULL)... Może lepiej:

void zakoncz(osoba *f)
{
    osoba *temp;
    while( f != NULL )
    {
        temp = f;
        f = f -> polacz;
        free(temp);
    }
} 

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