• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Zadanie Domowe w kursie C++ P.M.Zelenta odc11. - potrzebuję mądrego wyjaśnienia :D

Object Storage Arubacloud
0 głosów
748 wizyt
pytanie zadane 12 sierpnia 2016 w C i C++ przez Wojciech Trojanek Obywatel (1,230 p.)

Witam wszystkich :)

Z racji że na urlopie postanowiłem nauczyć się w końcu programowania C++ i mam z tym niesamowitą przyjemność mam pytanie odnośnie zadania domowego kursu C++ P. Zelenta z odcinka 11 dotyczącego odnalezienia najbliższej liczby do średniej wyliczonej spośród 5 liczb. Poleciałem ambitnie po bandzie dodając wprowadzenie nie 5ciu a dowolnej liczby argumentów do wyliczenia średniej i tak jak w zadaniu możliwość ujęcia dwóch możliwości najbliższych liczb (np 3 i 5 dla wyliczonej średniej 4) do tego zadanie rozwiązałem z użyciem wskaźników opisanych w poprzednich odcinkach. Ogólnie program działa i ma się dobrze ale jednej drobnej rzeczy nie rozumiem.

Kod:

#include <iostream>

using namespace std;


//zadanie domowe kursu C++ P.M.Zelenta odc. 11
//znajdowanie najbliższej liczby do średniej
// (z zastosowaniem wskaznikow)


int ile;
float suma=0,srednia,naj1=0,naj2=0,roznica1=0,roznica2=0;   //naj1-2 wartosci najblizej sredniej, roznica1-2 wartosci sprawdzajace najnizsza mozliwa roznice od sredniej

int main()
{
    cout <<"Podaj z ilu liczb chcesz policzyc srednia: ";

    cin >> ile;

    float *wsk;                     //tablica dynamiczna
    wsk = new float[ile];

    cout <<"Podaj po enterze wartosci z klawiatury: "<<endl;

    for (int i=1; i<=ile; i++)
    {
        cin>>*wsk;
        suma+=*wsk;
        if(*wsk>naj1)
        naj1=*wsk;               //naj1 najpierw sluzy jako wykrycie najwiekszej liczby podanej przez uzytkownika
        wsk++;
    }
    wsk-=ile;                       //cofniecie wskaznika do poczatkowego adresu, żeby móc raz jeszcze użyć go do liczenia najbliższej liczby do wyliczonej średniej

    srednia=suma/ile;

    cout << "Srednia: "<<srednia<<endl;

    roznica2=naj1;                  //nadaje maksymalna roznice z jaka moze miec do czynienia czyli max wartosc podana przez uzytkownika

    for(int i=1; i<=ile; i++)

    {
        roznica1=srednia-*wsk;
        if(srednia-*wsk<0)
        {
            roznica1*=-1;                   //jesli roznica pomiedzy srednia a wartoscia pisana z klawiatury jest ujemna to zwraca wartosc bezwzledna
        }
        if(roznica1==roznica2)      // sprawdza istnienie dwoch wartosci spelniajacych zadanie w razie czego druga wartosc zapisuje do zmiennej naj2
        {
            naj2=*wsk;
        }
        if(roznica1<roznica2)         // sprawdza jaka wartosc jest najmniejsza do spelnienia warunku
        {
            roznica2=roznica1;         //podstawia wartosc jesli jest mniejsza od poprzedniej zapisanej
            naj1=*wsk;
        }
        wsk++;
    }
    wsk-=ile;             //znow cofniecie wskaznika - bez tej linii program co prawda liczy ale wywala się np jesli podam "ile" na 8 liczb lub 10 nie wiem dlaczego
    delete [] wsk;     //tutaj tablica i tak zostaje usunieta nie wiem czemu program wysypuje sie na wartosciach "ile" np 8 i 10 bez cofniecia wskaznika


    if((naj2!=0)&&(naj1!=naj2))                                                                      //wypisanie wartosci na ekran zaleznie od tego czy istnieje jedna czy dwie spelniajace warunki
    cout << "Liczby najblizsze sredniej: " << naj2 <<" i "<<naj1;
    else cout <<"Liczba nablizsza sredniej: "<<naj1;

    return 0;
}

Całość pracuje na wskaźnikach i działa na wszystkich testach zadanych przez Pana Mirosława. Nie rozumiem tylko jednej rzeczy. Co prawda o ile wiem dlaczego trzeba cofać wskaźnik przed ponownym liczeniem na nim najmniejszej różnicy do średniej, o tyle nie mam pojęcia dlaczego trzeba go cofać przed usunięciem tablicy (2 ostatnie linijki kodu przed wypisaniem wyników na ekran) bez linii cofającej wskaźnik też wszystko działa dla testów z kursu ale z uporem maniaka testowałem program dla większej liczby argumentów niż 5 i o dziwo program bez tej linii wywala się np gdy podam 8 argumentów i liczby np (1,2,3,4,5,6,7,8) tak samo dla 10 (1,2,3,4,5,6,7,8,9,10) program owszem wyliczy srednia 5.5 i dwie wartości spełniające warunek czyli 5 i 6, ale po tym następuje krytyczne zamknięcie aplikacji. Problem znika po dodaniu przedostatniej linijki dotyczącej cofnięcia wskaźnika dodałem ją że tak powiem z myślą "a spróbujmy co się stanie jak to dopiszę" a teraz ni w ząb nie rozumiem dlaczego to tak musi być. :D tablica i tak jest usuwana przed returnem 0; nie rozumiem dlaczego miałbym cofać wskaźnik żeby nic się nie wykrzaczało ;] proszę o wyrozumiałość dopiero raczkuję w temacie mimo moich 27 wiosen :P. A wskaźniki stały się dla mnie w miarę zrozumiałe właśnie dopiero po kursie P. M. Zelenta.

2 odpowiedzi

+2 głosów
odpowiedź 12 sierpnia 2016 przez MetRiko Nałogowiec (37,110 p.)
wybrane 12 sierpnia 2016 przez Wojciech Trojanek
 
Najlepsza

Na pierwszy rzut oka mogę powiedzieć jedno.. Robisz jeden dość istotny błąd..
Wszystko powinno być bez problemu jeżeli zamienisz ten fragment kodu:
float *wsk; //tablica dynamiczna
wsk = new float[ile];

Na taki:
float *tab; //tablica dynamiczna
tab = new float[ile];
float *wsk = tab;

Różnica jest mała, ale znacząca, mianowicie.. zamiast majstrować przy wskaźniku, który jest bezpośrednim odwołaniem do tablicy (traktujemy go bardziej jako konkretną tablicę, niż wskaźnik).. utworzony zostaje nowy wskaźnik, który można już bez problemu modyfikować/przesuwać. Teraz niezależnie od wskaźnika wsk, zwolnienie pamięci zawierającej tablicę nie będzie problemem.. ponieważ wskaźnik tab zawsze będzie pokazywał na to samo miejsce:
delete[] tab;

komentarz 12 sierpnia 2016 przez Wojciech Trojanek Obywatel (1,230 p.)
Hmm jeśli dobrze rozumiem to po prostu chodzi o stworzenie po prostu drugiego wskaźnika tzn - na podstawie jednego wskaznika dokonujemy obliczen a na podstawie drugiego daną tablicę usuwamy?   Z tego co widzę nawet jeśli przestawimy wskaznik *wsk np o 1 w gore wpisujac kolejne argumenty do tablicy to wskaznik *tab i tak bedzie wskazywal na poczatek tablicy i to za jego pomocą ja usuwamy? A jak to się ma w przypadku pierwszego cofnięcia wskaźnika *wsk w moim kodzie tzn kiedy to zaczynam porównywać różnice pomiędzy średnią a kolejnymi argumentami ? Czy w tym miejscu słusznie jest cofnięty wskaźnik czy również powinienem w jakiś sposób skorzystać ze wskaźnika *tab np zamiast użyć linijki "*wsk-=ile" (która "cofnie" działanie pętli do wpisywania danych do tablicy) użyć zapisu "*wsk=*tab" ustawi to wskaźnik znowu na 0 element tablicy? O to w tym wszystkim chodzi? Nie wiem czy dobrze się wyraziłem ;]
komentarz 12 sierpnia 2016 przez Wojciech Trojanek Obywatel (1,230 p.)

Kod:

 

#include <iostream>

using namespace std;


//zadanie domowe kursu C++ P.M.Zelenta odc. 11
//znajdowanie najbli¿szej liczby do œredniej
// (z zastosowaniem wskaznikow)


int ile;
float suma=0,srednia,naj1=0,naj2=0,roznica1=0,roznica2=0;   //naj1-2 wartosci najblizej sredniej, roznica1-2 wartosci sprawdzajace najnizsza mozliwa roznice od sredniej

int main()
{
    cout <<"Podaj z ilu liczb chcesz policzyc srednia: ";

    cin >> ile;

    float *tab;                     //tablica dynamiczna
    tab = new float[ile];
    float *wsk = tab;

    cout <<"Podaj po enterze wartosci z klawiatury: "<<endl;

    for (int i=1; i<=ile; i++)
    {
        cin>>*wsk;
        suma+=*wsk;
        if(*wsk>naj1)
        naj1=*wsk;               //naj1 najpierw sluzy jako wykrycie najwiekszej liczby podanej przez uzytkownika
        wsk++;
    }
   wsk=tab;                       //cofniecie wskaznika do poczatkowego adresu, zeby móc raz jeszcze u¿yæ go do liczenia najbli¿szej liczby do wyliczonej œredniej

    srednia=suma/ile;

    cout << "Srednia: "<<srednia<<endl;

    roznica2=naj1;                  //nadaje maksymalna roznice z jaka moze miec do czynienia czyli max wartosc podana przez uzytkownika

    for(int i=1; i<=ile; i++)

    {
        roznica1=srednia-*wsk;
        if(srednia-*wsk<0)
        {
            roznica1*=-1;                   //jesli roznica pomiedzy srednia a wartoscia pisana z klawiatury jest ujemna to zwraca wartosc bezwzledna
        }
        if(roznica1==roznica2)      // sprawdza istnienie dwoch wartosci spelniajacych zadanie w razie czego druga wartosc zapisuje do zmiennej naj2
        {
            naj2=*wsk;
        }
        if(roznica1<roznica2)         // sprawdza jaka wartosc jest najmniejsza do spelnienia warunku
        {
            roznica2=roznica1;         //podstawia wartosc jesli jest mniejsza od poprzedniej zapisanej
            naj1=*wsk;
        }
        wsk++;
    }

    delete [] tab;


    if((naj2!=0)&&(naj1!=naj2))                                                                      //wypisanie wartosci na ekran zaleznie od tego czy istnieje jedna czy dwie spelniajace warunki
    cout << "Liczby najblizsze sredniej: " << naj2 <<" i "<<naj1;
    else cout <<"Liczba nablizsza sredniej: "<<naj1;

    return 0;
}

Czy teraz ma to ręce i nogi ? Problem faktycznie zniknął. Natomiast przed liczeniem różnić linijka "wsk=tab;" musi zostać żeby przywrócić wskaźnik na 0 element tablicy. :)

1
komentarz 12 sierpnia 2016 przez MetRiko Nałogowiec (37,110 p.)

"na podstawie jednego wskaznika dokonujemy obliczen a na podstawie drugiego daną tablicę usuwamy?"
Dokładnie mówiąc to przechowujemy i usuwamy x) Ale tak.. dokładnie o to chodzi.

"Czy w tym miejscu słusznie jest cofnięty wskaźnik czy również powinienem w jakiś sposób skorzystać ze wskaźnika *tab"
Jeżeli dobrze zrozumiałem twój kod to zapis:
wsk-=ile; 
byłby równoznaczny zapisowi:
wsk=tab;
czyli dokładnie tak jak myślisz.. ale to jakiego zapisu użyjesz zależy od ciebie.. osobiście bym jednak wybrał wsk=tab..
1. unikasz niepotrzebnych działań (w tym wypadku odejmowania). 
2. taki zapis jest bezpieczniejszy i stosując go masz większą pewność, że wskaźnik pokazuje dokładnie na początek tablicy.

2
komentarz 12 sierpnia 2016 przez Wojciech Trojanek Obywatel (1,230 p.)
Mocarne :D No to jestem mądrzejszy :) dzięki bardzo za pomoc ;]
+2 głosów
odpowiedź 12 sierpnia 2016 przez criss Mędrzec (172,590 p.)

Musiałbyś poczytać jak działa operator delete[]. Konkretnie skąd wie jakiej wielkości jest tablica jaką ma usunąć. Z tego, co wiem, to jest zależne od kompilatora, ale przynajmniej niektóre kompilatory robią coś takiego:

Rezerwujesz tablice (załóżmy) 5 intów: int* ptr = new int[5]; A kompilator poza samą tablicą zapisuje sobie jej rozmiar tuż przed pierwszym elementem. Także w pamięci to wygląda tak:

[rozmiar][0][1][2][3][4]. Wskaźnik ptr po alokacji wskazuje na [0] (na pierwszy element). Kluczową informacją do zrozumienia tego, jest, że nazwa tablicy jest wskaźnikiem do jej pierwszego (0) elementu, ale już to pewnie zauważyłeś przy dynamicznej alokacji.

Zatem przy zwalnianiu pamięci operator delete[] może sobie pobrać wielkość tablicy z *(ptr - 1) i na tej podstawie zwalniać w tym przypadku 5 * sizeof(int) bajtów pamięci.

Ale jeżeli przestawisz wskaźnik ptr i (załóżmy) że teraz wskazuje na [3]  to delete[] szuka rozmiaru w [2]. A jeśli (załóżmy) znajduje sie tam liczba 17 (losowa liczba jaka mi przyszła do głowy), to nie dosyć, że delete[] nie próbuje zwolnić całej zaalokowanej pamięci, to jeszcze najprawdopobniej spróbuje zwolnić pamięć ze stosu programu (nie zaalokowanej dynamicznie), albo w ogóle do niego nie należącej, więc crash jest pewny.

Być może wygląda to inaczej, ale na pewno można sporo namieszać.

Btw. od kiedy od c++11 wprowadzono std::shared_ptr i std::unique_ptr, powinno się mieć dobry powód żeby alokować pamięć "na czysto".

komentarz 12 sierpnia 2016 przez Wojciech Trojanek Obywatel (1,230 p.)
Tutaj faktycznie zapewne dochodziło do próby usunięcia tablicy w momencie kiedy wskaźnik tak naprawdę był ustawiony na element o +1 poza tablice. Stąd spokój po cofnięciu wskaźnika o ilość iteracji czyli "ile". Nie rozumiem tylko do końca czemu zawsze dobrze liczy w ilościach argumentów od 1 do 7 potem na 8 się wykrzacza na 9 znowu ok i na 10 znowu błąd. Dalej nie sprawdzałem, a i chyba dalej zastanawiać się nie będę ;] Wiem natomiast, że kod jest błędny bez tego zapisu zresztą w ogóle inaczej powinienem potraktować wskaźnik podobnie jak kolega wyżej napisał ale to wynika jeszcze z niedostatecznej wiedzy :D po prostu opierałem się na tym co do tej pory z 11 odcinków kursu C++. Cóż będę dalej zgłębiał wiedzę :) Dzięki za odpowiedź ;]
komentarz 12 sierpnia 2016 przez criss Mędrzec (172,590 p.)

Nie rozumiem tylko do końca czemu zawsze dobrze liczy w ilościach argumentów od 1 do 7 potem na 8 się wykrzacza na 9 znowu ok i na 10 znowu błąd.

Nie bardzo rozumiem, co tu napisałeś :P 

komentarz 12 sierpnia 2016 przez Wojciech Trojanek Obywatel (1,230 p.)
Program pyta o ilość liczb do przeliczenia sredniej

kiedy podamy powiedzmy 5 to wpisujemy np 1,2,3,4,5 przelicza srednia i podaje najwyzsze badz najwyzszą wartosc. Problem z crashem programu dziwnym trafem dopadal tylko wtedy gdy podalismy 8 albo 10 argumentów (czyli wartości "ile" na start do przeliczenia sredniej. (Wiecej pewnie tez by wystepowalo ale juz nie sprawdzalem, Jak podawalem 2 argumenty do sredniej - liczylo, 3 argumenty takze, itd liczylo spokojnie dla 2,3,4,5,6,7 wartosci. jak chcialem obliczyc srednia np z 8miu liczb to owszem przeliczalo a zaraz potem wykrzaczalo program. Dla 9ciu argumentow bylo dobrze liczone bez wykrzaczenia a dla 10ciu liczb znowu to samo. dalej nie sprawdzalem. Wiadomo gdzie lezal problem ze wskaznikiem. Po prostu uporem maniaka nie rozumiem dlaczego akurat zawsze na 8miu argumentach i 10ciu się wykrzaczało. Może wynikalo to po prostu z aktualnie zajetej pamieci ram i beztroskiego wyskoku poza tablice wskaznika.
1
komentarz 12 sierpnia 2016 przez criss Mędrzec (172,590 p.)
W "glownej" odpowiedzi dałem ci przykład jak to może wyglądać. Kiedy wychodzisz poza tablice, program moze się wykrzaczyc ale nie musi, zależnie na jaką pamięć trafi. Nikt ci nie powie dlaczego dla takich liczb działa, a dla innych nie. Tak się po prostu pofarciło.
komentarz 12 sierpnia 2016 przez Wojciech Trojanek Obywatel (1,230 p.)
Ok myślę że rozumiem w czym rzecz :)

Podobne pytania

0 głosów
1 odpowiedź 477 wizyt
0 głosów
1 odpowiedź 371 wizyt
0 głosów
4 odpowiedzi 696 wizyt
pytanie zadane 20 marca 2016 w C i C++ przez Eliro Stary wyjadacz (12,160 p.)

92,551 zapytań

141,393 odpowiedzi

319,524 komentarzy

61,936 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Kolejna edycja największej imprezy hakerskiej w Polsce, czyli Mega Sekurak Hacking Party odbędzie się już 20 maja 2024r. Z tej okazji mamy dla Was kod: pasjamshp - jeżeli wpiszecie go w koszyku, to wówczas otrzymacie 40% zniżki na bilet w wersji standard!

Więcej informacji na temat imprezy znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...