Forum Coders' city Strona Główna Coders' city
Nasza pasja to programowanie!
 

 PomocPomoc   SzukajSzukaj   UżytkownicyUżytkownicy   GrupyGrupy  RejestracjaRejestracja 
Archiwum starego forum + teoria    RSS & Panel/SideBar
 ProfilProfil   Zaloguj się, by sprawdzić wiadomościZaloguj się, by sprawdzić wiadomości   ZalogujZaloguj 

Potrzebuję szybkiej odpowiedzi na moje pytanie... Zasady

Często udzielane odpowiedzi

Idź do strony 1, 2  Następny

 
Odpowiedz do tematu    Forum Coders' city Strona Główna -> C i C++
Zobacz poprzedni temat :: Zobacz następny temat  
Autor Wiadomość
marcin_an



Dołączył: 26 Maj 2005
Posty: 18822

PostWysłany: Sro Gru 24, 2008 11:51 pm  OP    Temat postu: Często udzielane odpowiedzi Odpowiedz z cytatem Pisownia

Kiedyś mikmas stworzył wątek zatytułowany "nie!", w którym podawał informacje, co powinno być opisane w poście, żeby nie trzeba było wzywać wróżki. Robię właśnie podobną rzecz, ale w celu ułatwienia sobie odpowiedzi. Są odpowiedzi, których udzielam częściej niż innych. Nie ma wtedy czasu na opisanie problemu dokładnie. Czasami też celowo pomijam pewne kwestie lub przedstawiam je w trochę innym świetle. Sądzę, że wątek, w którym można rozwinąc te odpowiedzi może się przydać. Oto i on.

Zasady:
  1. Wątek będzie przyklejony, żeby nie spadał.
  2. Każdy może tuaj dopisywać swoje odpowiedzi, jeśli uzna, że udziela ich na tyle często, że opisanie ich w oddzielnym, większym poście może być potrzebne.
  3. To nie jest FAQ (przynajmniej nie wprost)! Chcę, by trafiały tutaj tylko odpowiedzi, które faktycznie się powtarzają (niezależnie od ilości pytań) i których pogłębienie może być potrzebne, a możliwość odesłania do takiego wyjaśnienia będzie przydatna.
  4. To nie jest oficjalne CC-FAQ. Jest to coś w rodzaju magazynu tekstów do dawania do nich odnośników. Tym samym każdy może sobie tutaj umieścić "paczkę" wg własnego uznania i jej zawartość nie będzie weryfikowana w tym wątku pod kątem merytorycznym. Możesz napisać, że Słońce krąży wokół Ziemi* i jeśli tak napiszesz, to tak będzie w twoim poście. Oczywiście poza tym posty są moderowane, więc nie oznacza to, że można wstawić gołą panienkę i post będzie wisiał na forum.
  5. Żadnych pytań i dyskusji w tym wątku. Każde pytanie lub próbę prowadzenia rozmowy usunę bez ostrzeżenia. Można w tym celu założyć oddzielny wątek i tam dyskutować. Tutaj umieszczamy tylko rzeczy do robienia do nich linków.
  6. Ponieważ osoby spoza zespołu moderatorów i administracji nie mają możliwości usunięcia swoich postów, można dać znać komuś z tych zespołów, by to zrobił. Jest to do zrobienia poprzez prywatną wiadomość lub dopisanie posta z taką prośbą. Proszę jednak o to, by prośby były wyraźne i dobrze opisywały, co ma być usunięte. Moderator może odmówić usunięcia wpisu, jeśli uzna, że naruszyłoby to treść postów do niego się odnoszących.
  7. Jeden post - jednen temat. Nie publikujemy tutaj artykułów o wszystkim i niczym. Poruszany temat -> odpowiedź/wyjaśnienie.
  8. Tym samym dozwolone jest umieszczanie kilku postów jeden pod drugim i jeżeli są to oddzielne tematy (tj. nie są to dopiski "bo zapomniałem napisać"), to moderatorzy nie będą zwracali na to uwagi.
  9. Nie jest to miejsce na publikację swoich artykułów, rozpraw etc. To można zrobić w serwisach do tego przeznaczonych.
  10. Jeżeli moderator stwierdzi, że wpis nie jest wykorzystywany lub też użytkownik wcale nie umieścił w nim niczego, na co musiał często odpowiadać, post może zostać usunięty.
  11. Goście nie mogą się tutaj wypowiadać. Przepraszam, kwestie organizacyjne ;).
____
* Co akurat jest prawdą...


Ostatnio zmieniony przez marcin_an dnia Czw Gru 25, 2008 12:02 am, w całości zmieniany 1 raz
Powrót do góry
Zobacz profil autora Wyślij prywatną wiadomość
marcin_an



Dołączył: 26 Maj 2005
Posty: 18822

PostWysłany: Sro Gru 24, 2008 11:51 pm  OP    Temat postu: [] Zwracanie wartości z main Odpowiedz z cytatem Pisownia

Zwracanie wartości z main
Tagi: C++
Q
Czy main musi zwracać wartość? Czy prawidłowy jest kod:
Kod:
int main() {}


A
Z punktu widzenia języka: tak, prawidłowy. Tak w sensie składni, jak i zwracanych wartości. C++ wymaga, by w przypadku braku istnienia jawnego zwracania wartości z main została zwrócona wartość 0. Powyższy kod jest zatem równoważny:
Kod:
int main()
{
    return 0;
}

W czym więc problem?
  • main nie zawsze będzie główną funkcją programu lub też to, co zdefiniujemy jako main, niekoniecznie musi być widziane przez kompiltor jako main. Przykłady: WinMain w Windowsie lub SDL_main podstawiany zamiast main w SDL.
  • Zasada o automatycznym zwróceniu 0 działa tylko, jeśli w main nie ma żadnego jawnego zwrócenia.
    Kod:
    int main()
    {
        if (warunek)
        {
            return 1;
        }
    }

    Powyższy kod nie zwróci automatycznie 0 w przypadku, gdy warunek da false, ponieważ gdzieś w kodzie main już coś jest zwracane (nie ma znaczenia, czy jest to osiągalne, czy nie). Problem? Wbrew pozorom przy wielu edycjach funkcji okazuje się, że nietrudno zgubić to return na końcu, jeśli od początku go tam nie było (nawet jeśli teraz wydaje się to niedorzeczne).
  • Początkujący: jeden z częstych błędów osób zaczynających przygodę z programowaniem jest nieświadome pomijanie wartości zwracanych przez funkcję np. przy wystąpieniu sytuacji alternatywnych. Uczenie, że można od tak sobie coś pominąć, chyba nie wpływa pozytywnie na zmianę tego - lepiej niech uczący się myśli, co ma zwrócić i niech to zwraca.


--- edit 27.12.2009 22:20 ---
Pojawiły się pewne niejasności, zatem wyjaśniam. Interpretacja z punktu 2 jest jedną z dwóch interpretacji. Druga mówi, że automatyczne zwrócenie zadziała zawsze. Problem z nią polega na tym, że przy takim ujęciu warunek "without encountering a return statement" jest co najmniej głupi. Możliwe, że jest to defekt standardu, ale narazie nie był jako taki zgłaszany, w najnowszym szkicu nadal występuje, a sama interpretacja jest bezpieczniejsza - w tą stronę nie zepsuje się żadnego programu, a w przeciwną już można. Z tych powodów oraz dlatego, że częściej się z nią spotykałem, podaję tą.

Bo profesor tak wymaga: jeśli profesor będzie wymagał, żeby podczas pisania kodu chodzić po ścianach, to student ma obowiązek chodzić po ścianach, bo inaczej nie zaliczy. Podane informacje dotyczą normalnych sytuacji. Profesorowi pisz jak profesor chce.


Ostatnio zmieniony przez marcin_an dnia Nie Gru 27, 2009 10:26 pm, w całości zmieniany 3 razy
Powrót do góry
Zobacz profil autora Wyślij prywatną wiadomość
marcin_an



Dołączył: 26 Maj 2005
Posty: 18822

PostWysłany: Sro Gru 24, 2008 11:53 pm  OP    Temat postu: Wypychanie danych ze strumieni Odpowiedz z cytatem Pisownia

Wypychanie danych ze strumieni
Tagi: C++
Q
Co robi std::endl i dlaczego jego stosowanie jest złe?
A
std::endl nie jest zły i jego stosowanie również złe nie jest. Jest wręcz bardzo przydatne. Jest jednak jeden istotny warunek: jest stosowane do tego, do czego służy. Dostałeś informację, że niepotrzebnie wypychasz dane ze strumienia lub w twoim programie pojawiają się konstrukcje podobne do tych?
Kod:
cout << "ala ma kota" << endl;
Kod:
cout << "x = " << x << endl;
cout << endl << endl << endl;
Jeżeli tak, to prawdopodobnie padłeś ofiarą nauczania, że endl służy do robienia znaków nowej linii w "stylu C++" (czymkolwiek to jest...). Nie będę się tutaj rozpisywał o źródłach takiego przekonania, ale jedno mogę powiedzieć natychmiast: jest to bzdura. Nie wierzysz? Zajrzyj do dobrej dokumentacji i sprawdź, jakie jest znaczenie tego manipulatora. Jest nim wypychanie danych ze strumienia, podobnie jak manipulatora std::flush. Różnica pomiędzy nimi polega na tym, że std::endl dodatkowo wyprowadza przed wypchnięciem danych znak nowej linii. Co powoduje wypchnięcie danych ze strumienia? 1001 rzeczy w zależności od rodzaju strumienia - generalnie zapewnia, że dane będące w wewnętrznych buforach implementacji zostaną w tym właśnie momencie wypchnięte z nich do miejsca, do którego prowadzi strumień. Np. do pliku na dysku, na terminal lub do sieci. Co robi w przypadku cout? Teoretycznie zapewnia, że... <blablabla>. Szczerze? W praktyce absolutnie nic. Na systemach uniksowych znak nowej linii i tak powoduje opróżnienie tych buforów, na Windowsie wyjście jest niebuforowane. Może istnieją inne implementacje, dla których to działa - nie mogę zaprzeczyć. Chętnie się dowiem jakie.
Zatem stosujesz konstrukcję, która:
  1. Jest użyta niezgodnie ze swoim przeznaczeniem i wykonuje inną czynność, niż ta, którą chcesz uzyskać.
  2. Wydłuża i komplikuje niepotrzebnie kod.
  3. Prawdopodobnie nic nie robi.
Równoważne kody będące nie mniej w "stylu C++":
Kod:
cout << "Hello world\n";
Kod:
cout << "x = " << x << "\n\n\n\n";
Jeśli chcesz coś, co bardziej pasuje do czegoś dającego się nazwać stylem C++, to taki kod będzie tego lepszym przykładem:
Kod:
cout << format("X = %1%\n") % x;
Tak, coś jak printf, ale dające się używać. Dostępne w pakiecie Boost.
Kiedy używać endl? Wtedy, kiedy jest potrzebne ;). Możesz użyć prostej zasady: jeżeli napisałbyś jeden z poniższych, wykonujących w zasadzie to samo, kodów...
Kod:
output.put('\n').flush();
output << '\n' << flush;
..., to należy użyć endl. W innych przypadkach nie należy.
Dlaczego widziałeś te endl w 9 na 10 książek, dlaczego używał ich sam Stroustrup i wiele innych znanych w środowisku C++ osób etc.? Moja teoria jest taka: ktoś kiedyś coś takiego naklepał w książce (może nawet faktycznie miał ku temu powód). Osoby uczące się nie mają zwyczaju starać się dogłębnie zrozumieć tematu, więc tego typu konstrukcje (np. static_const w srand+time) mają w swej naturze rozprzestrzenianie się gorzej niż wirusy komputerowe przez powtarzanie zaobserwowanego wzorca. Użycie takiej konstrukcji nie oznacza, że ktoś nie ma bladego pojęcia o C++. Najprawdopodobniej po prostu nigdy nie zastanawiał się nad tym, co pisze.
Bo profesor tak wymaga: jeśli profesor będzie wymagał, żeby podczas pisania kodu chodzić po ścianach, to student ma obowiązek chodzić po ścianach, bo inaczej nie zaliczy. Podane informacje dotyczą normalnych sytuacji. Profesorowi pisz jak profesor chce.


Ostatnio zmieniony przez marcin_an dnia Czw Cze 25, 2009 6:37 am, w całości zmieniany 3 razy
Powrót do góry
Zobacz profil autora Wyślij prywatną wiadomość
marcin_an



Dołączył: 26 Maj 2005
Posty: 18822

PostWysłany: Sro Gru 24, 2008 11:59 pm  OP    Temat postu: Odpowiedz z cytatem Pisownia

Interfejsy konsolowe w C++
Tagi: C++, C

Uwaga: po zamianie IOStreams na stdio, a cin/cout na odpowiednie funkcje stdio, poniższe jest prawdziwe także dla C.

Q
Używam IOStreams. Jak wyczyścić konsolę? Jak przenieść kursor na wybraną pozycję? Jak pokolorować litery? Jak pobierać klawisze bez naciskania entera? Jak sprawdzić dane i poprosić o ponowne ich wprowadzenie? Program mi pomija polecenia.
A
Nijak. Pradopodobnie ktoś powiedział ci, że cin i cout służą do pisania po ekranie i czytania z klawiatury. Nie jest to jednak prawda. IOStreams służy do operowania na strumieniach i jest to całkiem dobra biblioteka do budowania aplikacji strumieniowych. Aplikacja strumieniowa to pudełko z dwoma (lub więcej) rurami. Przez jedną wpada ciąg danych, przed drugą wypada ciąg danych (więcej). Koniec. Nie ma miejsca na jakąkolwiek interakcję z użytkownikiem, kolorowe interfejsy i klawiatury/ekrany. C++ nawet nie wie, co to klawiatura: sterownik konsoli, pod który prawdopodobnie został podpięty strumień danych, interpretuje dane z klawiatury i wysyła je w postaci tekstu do strumienia. Aplikacja strumieniowa tekst ten odbiera. Sam strumień nie ma zatem żadnego związku z klawiaturą bądź ekranem. Może nawet nie mieć ze sterownikiem konsoli, bo strumienie można zwykle przekierować z poziomu systemu (typowe przykłady: przekierowanie do pliku lub sieci). Owszem, można zrobić bardzo prosty interfejs na zasadzie "pytanie-odpowiedź". Taki, żeby wystarczył do pokazów działania kodu albo jego debugowania w czasie laboratoriów na studiach. Nie licz jednak na to, że zbudujesz cokolwiek więcej. Nie dlatego, że IoStreams jest złe, tylko dlatego, że to nie jest narzędzie do robienia tego typu rzeczy. Możesz oczywiście próbować i przy odpowiednim nakładzie pracy, współpracy sterownika konsoli (sterowanego odpowiednimi kodami) i ew. pomocy funkcji systemowych może nawet część z wymienionych wcześniej rzeczy uda ci się uzyskać. Będzie to jednak próba wkręcania śruby młotkiem i podobnie się natrudzisz. Zamiast tego weź po prostu bibliotekę do obsługi konsoli, która większość tego załatwi ci w jednej linijce kodu. Przykładem takiej biblioteki są różne implementacje curses: np. pdcurses pod Windowsa lub ncurses. Jeżeli korzystasz curses, to przeczytaj dobrze najpierw manual, żebyś za chwilę nie pytał, dlaczego jeśli wyprowadzasz coś przez cout, to to potem nagle znika albo jest wyprowadzane nie tam, gdzie ustawiłeś kursor. Wskazówka: dlatego, że używasz cout zamiast funkcji z biblioteki. W manualu są one opisane.
Dlaczego zostałeś okłamany? Po raz kolejny nie chcę się zagłębiać w historię. W skrócie: dawno, dawno temu świat wyglądał zupełnie inaczej, systemy były uboższe, ich API robione zupełnie inaczej itd. Sterowanie konsolą było realizowane właśnie przez wysyłanie danych na strumień* pomieszane z wywołaniami funkcji systemowych. Dzisiaj mamy do tego proste biblioteki i nie ma powodu, by robić dziwaczne konstrukcje, których efekty często są zupełnie inne, niż ich autor sobie wyobraża (często wystarczy trochę inna konfiguracja nawet tego samego systemu). Nie wszyscy nauczający mają jednak w zwyczaju aktualizować swoją wiedzię i nie dociera do nich, że informacje sprzed 20 lat mogą być z lekka nieświeże.
Bo profesor tak wymaga: jeśli profesor będzie wymagał, żeby podczas pisania kodu chodzić po ścianach, to student ma obowiązek chodzić po ścianach, bo inaczej nie zaliczy. Podane informacje dotyczą normalnych sytuacji. Profesorowi pisz jak profesor chce.

--- edit: uzupełnienie o uwagi dla C
--- edit: link do posta o aplikacjach strumieniowych
____
* Z technicznego punktu widzenia gdzieś na samym dole nadal część funkcjonalności jest tak realizowana. Jest to jednak poziom daleko poza tym, co leży w polu zainteresowań osoby, która chciałaby po prostu napisać użyteczną aplikację.


Ostatnio zmieniony przez marcin_an dnia Nie Lut 20, 2011 9:16 am, w całości zmieniany 2 razy
Powrót do góry
Zobacz profil autora Wyślij prywatną wiadomość
hobson



Dołączył: 13 Sie 2007
Posty: 439
Skąd: Trójmiasto

PostWysłany: Pią Mar 06, 2009 9:24 pm      Temat postu: Znikająca konsola Odpowiedz z cytatem Pisownia

Q: Napisałem swój pierwszy program typu "Hello World", ale on nie działa! Jedyne, co widzę, to migające przez chwilę czarne okienko konsoli. Co jest źle?

Najprawdopodobniej, nic nie jest źle. Po prostu twój program wykonuje się tak szybko, że zanim zdążysz cokolwiek zobaczyć na konsoli, funkcja main kończy swe działanie, a konsola znika. Problem ten można rozwiązać na kilka sposobów:

  • Jeśli uruchamiasz program pod kontrolą debuggera (a tak powinno być w przypadku 99% uruchomień podczas pisania programu), możesz ustawić breakpoint na ostatniej linijce funkcji main, lub na nawiasie klamrowym zamykającym tą funkcję. Gdy program dojdzie do tego miejsca zatrzyma się, a ty możesz zobaczyć wynik jego działania na konsoli.
  • Niektóre środowiska mają opcję uruchamiania programu i pozostawiania otwartej konsoli po zakończeniu. Inne pozwalają na podgląd wyjścia programu, gdy zakończy on swe działanie. W środowisku MS Visual Studio można skorzystać z opcji Run without debugging (skrót klawiszowy Ctrl + F5).
  • Gdy chcesz uruchomić swój program poza kontrolą środowiska, otwórz konsolę (np. następującym poleceniem systemu Windows: Start/Uruchom.../cmd), przejdź do katalogu, w którym znajduje się twój skompilowany program, i wpisz jego nazwę (oraz, w razie potrzeby, parametry). Gdy program zakończy się, konsola pozostanie otwarta, a ty będziesz mógł przeczytać wszystko, co program wypisał.
  • Jeśli chcesz uruchamiać program przy pomocy dwukrotnego kliknięcia na ikonkę, możesz stworzyć plik wsadowy uruchamiający twój program, i zatrzymujący się przy pomocy polecenia specyficznego dla Twojej powłoki. Albo możesz utworzyć skrót z ustawioną opcją pozostawiania otwartego okna konsoli po zakończeniu programu.


Q: Ale ktoś powiedział mi, że mogę zatrzymać program przy pomocy jakiejś linijki kodu. Czy jest to możliwe? Jak to zrobić?

Tak, jest to możliwe. Takich sposobów na zatrzymanie programu w celu powstrzymania konsoli przed znikaniem przy pomocy kodu jest kilka. Niestety, żaden z nich nie jest idealny. Przede wszystkim, powstrzymywanie konsoli przed zniknięciem jest w zdecydowanej większości przypadków zupełnie bez sensu, ponieważ nie jest to sposób, w jaki powinny działać aplikacje strumieniowe (a tego typu aplikacji zazwyczaj dotyczy ten problem). Popatrz sam: czy program ipconfig zatrzymuje się po wyświetleniu wszystkich informacji? A polecenie dir? Jeśli jednak powyższe argumenty nie przekonały cię, i nadal upierasz się przy tym, że powstrzymywanie konsoli przed znikaniem jest dobrym pomysłem, masz całkiem sporo sposobów do wyboru:


  • getch() z biblioteki conio.h - najgorszy chyba możliwy sposób. Wymaga dołączenia niestandardowej, przestarzałej biblioteki. Pochodzi z czasów DOSa, dostępna jest na przestarzałych kompilatorach Borlanda, jej obecność nie jest gwarantowana w żadnej współcześnie dostarczanej platformie.

  • system("PAUSE") - sposób jeszcze gorszy od poprzedniego. Jest mało efektywny, wymaga uruchomienia przez środowisko dodatkowych programów, działa tylko na systemach Windows (i to nie jest gwarantowane, że na każdym).

  • cin.get() z iostream oraz getchar() z stdio.h - trochę lepsze niż poprzednie, ale nie działają do końca tak, jak byśmy sobie tego życzyli. Przede wszystkim, są to funkcje do obsługi strumieni, nie mają zielonego pojęcia o istnieniu klawiatury i o wciskaniu klawiszy. Program, jeśli jest uruchomiony w konsoli, co prawda się zatrzyma, ale wcale nie ruszy dalej po wciśnięciu dowolnego przycisku. Najczęściej (ale nie zawsze, kolejny niepewny punkt!) dane z konsoli przesyłane są do strumienia dopiero po wciśnięciu klawisza ENTER, więc użytkownik może wpisać całkiem długi elaborat, zanim jego program ruszy dalej. Co gorsza, wszystko, co wpisał (nie tylko pierwszy znak), zostanie przesłane do strumienia wejściowego programu, więc przy kolejnych próbach komunikacji z użytkownikiem może on nie mieć możliwości wpisania czegokolwiek, bo w strumieniu już będą dane.

  • funkcje do obsługi konsoli, np. z biblioteki curses - chyba najlepszy sposób na wybrnięcie z problemu, bo operuje na konsoli, czyli robi dokładnie to, o co nam chodzi. Ale i tu tkwi haczyk: jeśli zainicjalizujemy bibliotekę curses, nie mamy pewności, co stanie się ze standardowymi strumieniami podpiętymi do konsoli. Może nie stanie się nic, a może przestaną działać. Może na twoim komputerze nie stanie się nic, a na komputerze profesora przestaną działać. Najlepiej, jeśli zdecydujesz się na użycie funkcji do obsługi konsoli, zrezygnować ze strumieni standardowych (iostream w C++ lub stdio.h w C).

  • Sposób jedyny słuszny: zrezygnować z aplikacji strumieniowej, i zastąpić ją aplikacją konsolową, korzystającą z odpowiednich funkcji w każdym miejscu programu.


Najważniejszą rzeczą jest jednak przyjęcie do wiadomości, że zatrzymanie aplikacji strumieniowej po jej wykonaniu to NIE JEST sposób, w jaki powinna ona działać.
Powrót do góry
Zobacz profil autora Wyślij prywatną wiadomość Numer GG
marcin_an



Dołączył: 26 Maj 2005
Posty: 18822

PostWysłany: Wto Kwi 14, 2009 6:44 pm  OP    Temat postu: Odpowiedz z cytatem Pisownia

Aplikacje strumieniowe
Tagi: C++
Q
Co to jest aplikacja strumieniowa?
A
Najkrócej, łopatologicznie i obrazowo: młynek z dwoma rurami. Jedną rurą znaki wpadają do młynka, drugą wypadają:
  .
.
.

A

C
,--------.
| |
X -----------| MŁYNEK |-----------
Q R T D > > R T A G
-----------| |----------- B
| |
`--------' U

S

.
.
.
Mówiąc bardziej formalnie: aplikacja ma na wejściu (rura po lewej na obrazku) ciąg znaków i wyprowadza na wyjście (rura po prawej na obrazku) również ciąg znaków. Aplikacja może pobierać kolejno znaki z wejścia i kolejno wyprowadzać je na wyjście. W międzyczasie może je sobie mielić. Na tym, dokładnie na tym, kończy się działanie tej aplikacji. Nie ma praktycznie żadnego wpływu ani na źródło danych, ani na odbiorcę danych. Do rur mogą być podłączane najróżniejsze rzeczy i aplikacji nie interesuje, co tam będzie. Przykładowo może to być plik, inny program, połączenie TCP/IP, port komputera z podłączonym pod niego czujnikiem, mikrofon/głośnik, sterownik jakiegoś urządzenia, drukarka lub nawet "nic". To tylko proste przykłady, nie pełna lista! Co by jednak nie było, jest to zwykle poza zainteresowaniami aplikacji. Aplikacja normalnie nie ma też większego wpływu na to, skąd się dane biorą i jak są wysyłane. Akceptuje po prostu to, co dostaje. Gdy uruchamiasz w shellu polecenia takie jak ls, netstat czy cat, programów tych nie interesuje, co się dzieje z danymi, które wyprowadza na wyjście. Może trafiają do pliku? Może zostały wywołane ze skryptu, który przechwytuje ich wynik? Może wynik trafia do grepa lub seda, które przetwarzają i filtrują dane? Może, co jest dosyć prawdopodobne do sterownika konsoli, dzięki czemu użytkownik może je zobaczyć. Nie wiadomo. I to są właśnie aplikacje strumieniowe: dwie rury do młynka. Proste, ale w odpowiednich rękach potężne narzędzie.

Praktycznie wszyscy programiści C++ zaczynali przygodę z tym językiem właśnie od aplikacji strumieniowych. Niestety nikt im wcześniej o tym nie powiedział, a domyślne działanie wielu systemów polegające na podłączeniu strumieni pod sterownik konsoli nie ułatwia wcale zauważenia tego prostego faktu. Na dodatek niejeden ich nauczyciel nagadał bzdur, które tylko zaciemniły obraz wszystkiego. Potem pojawiają się pytania takie, jak trzy posty wyżej - o konsolę. Pomimo, że aplikacja nie jest aplikacją konsolową, lecz strumieniową.
Jeżeli padnie pytanie, jak to się zatem dzieje, że wpisywany w konsoli tekst trafia do programu i tekst wyprowadzany jest na ekranie, to wcale się nie zdziwię. Sprzeczności jednak nie ma. Po prostu rury zostały podłączone pod sterownik konsoli. Kolejny wielki młynek. Odbiera od systemu wiadomości o wciśnięciu klawiszy, przetwarza je na znaki, które to znaki sobie zbiera i co jakiś czas wysyła do rury wejściowej podłączonej pod niego aplikacji. Co ile? Różnie, ale najbardziej typowe rozwiązanie to po każdym napotkaniu znaku nowej linii, co jest często równoznaczne naciśnięciu entera. Czyli jeśli naciśniesz kolejno klawisze: A, L, A, <spacja>, M, A, <spacja>, K, O, T, A, <enter>, 1, 2, 4, 8, 1, 2, <enter>, Q, W, E... taki sterownik konsoli wykona następujące rzeczy:
Klawisz     Bufor sterownika        Rura wejściowa
---------------------------------------------------------------
"" ""
A "A" ""
L "AL" ""
A "ALA" ""
<spacja> "ALA " ""
M "ALA M" ""
A "ALA MA" ""
<spacja> "ALA MA " ""
K "ALA MA K" ""
O "ALA MA KO" ""
T "ALA MA KOT" ""
A "ALA MA KOTA" ""
<enter> -- wysłanie ------> "ALA MA KOTA\n"
1 "1" "ALA MA KOTA\n"
2 "12" "ALA MA KOTA\n"
4 "124" "ALA MA KOTA\n"
8 "1248" "ALA MA KOTA\n"
1 "12481" "ALA MA KOTA\n"
2 "124812" "ALA MA KOTA\n"
<enter> -- wysłanie -------> "ALA MA KOTA\n124812\n"
Q "Q" "ALA MA KOTA\n124812\n"
W "QW" "ALA MA KOTA\n124812\n"
E "QWE" "ALA MA KOTA\n124812\n"
Jeżeli przed pierwszym naciśnięciem entera program będzie próbował czytać z wejścia, to implementacja zobaczy, że rura jest pusta i będzie czekała, aż coś się w rurze pojawi. Gdy wreszcie po naciśnięciu entera coś się pojawi, będzie to czytała. Potem znowu się zablokuje, gdy rura się opróżni i znowu będzie czekała, aż będzie coś nadającego się do "zjedzenia". Z tego wynika "czekanie" aplikacji na wpisanie czegoś od użytkownika. To nie operator >> powoduje, że aplikacja zatrzymuje się, a ty możesz coś wpisać i potwierdzić to enterem. Aplikcja blokuje się, bo czeka na dane. Dane te otrzymuje od sterownika konsoli, który sam je sobie zbiera. Aplikacja nie ma większego wpływu ani na to, skąd te dane tak naprawdę będą, ani na to, jak będą wpisywane. Teraz rozumiesz, czemu pytanie o to, jak uniemożliwić wpisanie jakiegoś znaku jest bez sensu? Dlatego, że to nie twoja aplikacja robi to czarne okienko, nie ona odczytuje wciśnięte klawisze, nie ona wyświetla odpowiadające im znaki przy kursorze i nie on przesuwa kursor - to wszystko robi zupełnie inny program, który dopiero w pewnym momencie już skompletowane dane wysyła do twojej aplikacji. Przenosząc się do strony, że się tak wyrażę, tylnej - dlaczego dane wyświetlają się na ekranie? Bo sterownik konsoli odczytuje dane z rury wylotowej. Sprawdza co to za dane, interpretuje je w razie konieczności i wyświetla. Po raz kolejny: to nie twoja aplikacja cokolwiek wyświetla. Ona nawet nie wydaje polecenia wyświetlenia. Jedyne co robi, to w rurę wyjściową pakuje dane, które trafiają dopiero do urządzenia, które po ich analizie przeprowadza operację ich wyświetlenia. Ponieważ nie twoja aplikacja cokolwiek rysuje i nie ona wyświetla to okienko, to również pytanie o to, jak zmienić twoją aplikację, by sterowała tym okienkiem, są nieporozumieniem. Twoja aplikacja nie może tego zrobić bezpośrednio, bo to nie ona tym wszystkim zarządza.
W najlepszym przypadku może wykorzystać fakt, że sterowniki konsoli mogą używać protokołu zawierającego jakieś polecenia. Np. terminale ANSI miały zbiór poleceń pozwalający powiedzieć im (bo to nadal one wszystko kontrolowały), że mają zmienić kolor czcionki, przenieść kursor czy wyczyścić ekran. Stosowanie takich technik ma jednak dwie istotne wady. Po pierwsze są nieprzenośne. Po drugie w dzisiejszych czasach istnieją gotowe biblioteki do sterowania konsolą, w których można wszystko zrobić kilkoma prostymi funkcjami zamiast zapychania kodu programu masą brzydkich krzaczków.

Posługując się jeszcze jedną analogią: twój program jest krową na pastwisku. Pytanie o zmianę sposobu wprowadzania danych do programu jest równie sensowne, co pytanie, jak przerobić krowę, żeby trawa szybciej rosła. Pytanie o zmianę sposobu wyświetlania danych z programu jest równie sensowne, co pytanie, jak przerobić krowę, żeby pan Józio przyjeżdżał po jej odchody w poniedziałki, a nie środy. Oczywiście mućki nie zmienisz. Krowa zawsze będzie jedną stroną pobierała zielone dane wejściowe, a drugą stroną wyrzucała plackowate dane wyjściowe, bo taki jest jej żywot (jak zresztą każdego większego żywego stworzenia).
Powrót do góry
Zobacz profil autora Wyślij prywatną wiadomość
hobson



Dołączył: 13 Sie 2007
Posty: 439
Skąd: Trójmiasto

PostWysłany: Sro Kwi 15, 2009 11:55 am      Temat postu: Tablice dwuwymiarowe Odpowiedz z cytatem Pisownia

Q: Jak zrobić dynamiczną tablicę dwuwymiarową (macierz)?

W C++ nie istnieją gotowe klasy reprezentujące dwuwymiarowe dynamiczne tablice, lub macierze w sensie matematycznym. Biblioteka standardowa zawiera jednak kilka elementów, które wspomagają utworzenie tego typu rzeczy. Jeśli musisz zrobić taką tablicę, możesz posłużyć się kilkoma sposobami

Założenia:
  • Przykłady poniżej używają kodu C++, aby ich użyć w C niezbędne są pewne przeróbki.
  • Obsługa błędów w poniższych próbkach kodu to jedynie przykłady, można je dostosować do własnych okoliczności.
  • Terminy 'macierz' i 'tablica dwuwymiarowa' używane są przemiennie, nie oznaczają one 'macierzy liczb' czy też 'tablic obiektów'.


1. Użycie kontenerów dostarczanych przez bibliotekę standardową.

Jest to w większości przy padków najlepszy sposób na realizację tego zadania. Do jego głównych zalet należy fakt, że kontenery automatycznie zarządzają pamięcią, i nie trzeba po nich sprzątać. Ilość potrzebnego do napisania kodu spada, trudniej popełnić błąd, i nie trzeba się przejmować zwalnianiem pamięci. W bibliotece standardowej istnieje spora gama kontenerów do wyboru, w razie potrzeby pracą każdego z nich można sterować przy pomocy własnego alokatora. W większości zastosowań dobrym wyborem będzie szablon std::vector. Ale jeśli np. spodziewasz się, że twoje macierze będą wykorzystywane w operacjach arytmetycznych, jako kontenera możesz użyć klasy std::valarray, która implementuje kilka podstawowych operacji na tablicach liczb:

Kod:

    //z uzyciem std::valarray
    {
        typedef std::valarray<int> Wiersz;
        typedef std::valarray<Wiersz> Macierz;

        Macierz macierz(Wiersz(kolumny), wiersze);
        
        for(size_t i=0; i < wiersze; ++i) {
            for(size_t j=0; j < kolumny; ++j) {
                macierz[i][j] = i;
            }
        }
    }

    //z uzyciem std::vector

    {
        typedef std::vector<int> Wiersz;
        typedef std::vector<Wiersz> Macierz;

        Macierz macierz(wiersze, Wiersz(kolumny));
        
        for(size_t i=0; i < wiersze; ++i) {
            for(size_t j=0; j < kolumny; ++j) {
                macierz[i][j] = i;
            }
        }
    }



W razie potrzeby, po niewielkich modyfikacjach, możliwe jest przekształcenie powyższego kodu w taki sposób, aby używał wierszy o niejednakowej długości (tzw. jagged arrays). Jako dwuwymiarowej tablicy znaków do przechowywania linijek tekstu możesz użyć std::vector<std::string> >.


Nie zawsze użycie kontenerów jest jednak możliwe. W języku C trzeba alokować pamięć ręcznie. Czasami, w ramach zajęć na uczelni, nie możemy korzystać z kontenerów, ponieważ profesorowi wydaje się, że C++ to takie trochę bogatsze C. W każdym razie, jakie by nie były powody, trzeba użyć innych środków.


2. Ręczna alokacja jednego dużego fragmentu pamięci.

W tej metodzie alokowana jest od razu pamięć na całą macierz, a potem wskaźniki na fragmenty tego obszaru zapisywane są do tablicy wierszy:

Kod:

    {
        int* dane = NULL;
        int** macierz = NULL;

        try {
            //alokacja
            dane = new int[wiersze * kolumny];        
            macierz = new int* [wiersze];

            for(size_t i =0; i < wiersze; ++i) {
                macierz[i] = dane + i * kolumny;
            }

            //operacje na macierzy...
            for(size_t i=0; i < wiersze; ++i) {
                for(size_t j=0; j < kolumny; ++j) {
                    macierz[i][j] = i;
                }
            }

            //zwolnienie pamieci
            delete[] macierz;
            delete[] dane;

        } catch(const bad_alloc& ) {

            //gdyby nastapil blad alokacji
            delete[] macierz;
            macierz = NULL;

            delete[] dane;
            dane = NULL;
        }
    }



Użycie tej metody pociąga za sobą wszystkie uciążliwe aspekty ręcznego zarządzania pamięcią. Trzeba pamiętać o zwolnieniu zaalokowanych tablic, zarówno w sytuacji, gdy wszystko idzie dobrze, jak i w chwili wystąpienia błędów.

Metoda ta używa tylko dwóch dynamicznych alokacji pamięci, co sprawia, że powinna być nieco wydajniejsza od trywialnego alokowania każdego wiersza z osobna, a sprzątanie jest wiele łatwiejsze, ponieważ nie trzeba śledzić, które tablice już zostały zaalokowane, a które jeszcze nie. Jednak w przypadku bardzo dużych tablic może się ona okazać zawodna, ponieważ w systemie może nie być możliwe odnalezienie dostatecznie dużego obszaru pamięci (jest podatna na fragmentację pamięci). W większości zastosowań jest to jednak mało prawdopodobne. Używając tego sposobu nieco trudniejsze jest utworzenie tablicy o niejednakowej długości wierszy.


3. Alokacja wiersz po wierszu

W tej metodzie najpierw alokowana jest tablica wskaźników na każdy z wierszy macierzy, a potem każdy wiersz jest alokowany z osobna:

Kod:

    {
        int** macierz = NULL;
        try {
            //alokacja
            macierz = new int* [wiersze];
            for(size_t i=0; i<wiersze; ++i) {
                macierz[i] = NULL;
            }        

            for(size_t i=0; i < wiersze; ++i) {
                macierz[i] = new int[kolumny];
            }

            //operacje na macierzy...
            for(size_t i=0; i < wiersze; ++i) {
                for(size_t j=0; j < kolumny; ++j) {
                    macierz[i][j] = i;
                }
            }

            //zwolnienie pamieci
            for(size_t i=0; i < wiersze; ++i) {
                delete[] macierz[i];
            }
            delete[] macierz;
            
        } catch(const bad_alloc& ) {

            //gdyby nastapil blad alokacji, trzeba
            //zwolnic zaalokowane do tej pory wiersze
            if(macierz) {
                for(size_t i=0; i < wiersze; ++i) {
                    delete[] macierz[i];
                    macierz[i] = NULL;
                }
                delete[] macierz;
                macierz = NULL;
            }
        }
    }



Generalnie nie jest to zbyt dobry sposób: wprowadza nadmiarową fragmentację pamięci, bardzo uciążliwe w nim jest obsługiwanie błędów, a duża ilość alokacji dynamicznych spowalnia pracę programu. Jego chyba jedyną zaletą jest fakt, że w miarę łatwo pozwala na tworzenie tablic o niejednorodnej długości wierszy.


Jeśli to możliwe, bardzo dobrym pomysłem by było opakowanie któregoś z powyższych sposobów w klasę, i udostępnienie przez nią interfejsu pozwalającego na dostęp do danych i wykonywanie wymaganych przez nas operacji. W przypadku ręcznej alokacji pamięci trzeba pamiętać oczywiście o konstruktorze kopiującym, operatorze przypisania, i destruktorze. Jeszcze lepiej, można tą klasę uczynić szablonem, i sprawić, aby ten sam kod nadawał się do tworzenia tablic dwuwymiarowych przechowujących obiekty niemal dowolnego typu.

Komentarz
Powrót do góry
Zobacz profil autora Wyślij prywatną wiadomość Numer GG
Quentin



Dołączył: 10 Lip 2009
Posty: 193
Skąd: Warszawa

PostWysłany: Sro Sie 12, 2009 8:37 pm      Temat postu: Odpowiedz z cytatem Pisownia

Deklarowanie przyjaźni z szablonami

Q1: Dlaczego przy pisaniu deklaracji przyjaźni z funkcją szablonową muszę pisać ostre nawiasy <> po jej nazwie ? Co by się stało jakbym ich tam nie wymienił ?

Q2: Jak mogę zaprzyjaźnić moją klasę z całym szablonem klas/funkcji ?


---------------------------------------------------------------------------------------------------------------------------------------------------------

Ad. 1

Na początek trzeba uświadomić sobie, że nazwa funkcji wygenerowanej przez szablon to tak na prawdę nazwa + parametry w <>. Przykładowo takie oto wywołanie:

Kod:
template <class typ1, class typ2>
void funkcja(typ1 arg1, typ2 arg2)
{    /* ... */    }

int main()
{
    funkcja(34, 3.0);
}


Powoduje powstanie funkcji funkcja<int, double>. Czyli jak widać nawiasy <> przy deklaracji przyjaźni są potrzebne - inaczej zadeklarujemy przyjaźń ze zwykła funkcją o takich argumentach (która niekoniecznie istnieje).

Jednak można się spotkać z dwoma ich wersjami:

~ friend void funkcja<>(int, double);
~ friend void funkcja<int, double>(int, double);

Formułkę - aby to lepiej zapamiętać - można sformułować sobie następująco:

Cytat:
Nazwy typów, które występują w okrągłych nawiasach, kompilator automatycznie umieści w nawiasach ostrych (chyba, że sami je tam wymienimy)


Można więc zapytać - po co więc wymyślono taką dłuższą wersję deklaracji przyjaźni jak przy "wężyku" drugim ?

Od czasu możliwości pisania przy wywołaniu funkcji szablonowej ostrych nawiasów (wcześniej nie było to możliwe) - parametr szablonu funkcji może zawierać również takie stałe wyrażenia, które wcześniej mogły pojawić się jedynie przy szablonach klas. Są to:

- wartość całkowita
- adres obiektu globalnego
- adres funkcji globalnej
- adres składnika statycznego klasy

Można więc napisać taki szablon:

Kod:
template <int nr, class typ1, class typ2>
void funkcja(typ1 arg1, typ2 arg2)
{    /* ... */    }


I zadeklarować z nim przyjaźń w taki oto sposób:

Kod:
friend void funkcja<32>(int, double);
//LUB też:
friend void funkcja<32, int, double>(int, double);    //(ALE - patrz regułka wyżej)


Bo jak widać parametr 1-szy nie występuje na liście argumentów.

Mam nadzieję, że teraz jasne jest, jak ważne jest użycie ostrych nawiasów w deklaracji przyjaźni.

----------------------------------------------------------------------------------------------------------------------------------------

Ad. 2

1. Przyjaźń z całym szablonem funkcji:

~ W zwykłej klasie:

Kod:
class klasa
{
    //...
    template <class typ>
    friend void fun(typ cos);
};


~ W klasie szablonowej:

Kod:
template <class X, class Y>
class klasa
{
    //...
    template <class XX, class YY>
    //Ważne, żeby nie były to takie same
    //parametry jak tej klasy 'klasa' !!!
    friend void fun(XX cos1, YY cos2);
};


O różnicy parametrów trzeba zapamiętać - w tym miejscu najwięcej początkujących robi zazwyczaj błędy.

2. Przyjaźń z całym szablonem klas:

~ W zwykłej klasie:

Kod:
class klasa
{
    //...
    template <class typ>
    friend class klasa_zaprzyjaz;
};


~ W klasie szablonowej:

Kod:
template <class X, class Y>
class klasa
{
    //...
    template <class XX, class YY>
    //Ważne, żeby nie były to takie same
    //parametry jak tej klasy 'klasa' !!!
    friend class klasa_zaprzyjaz;
};



---------------------------------------------------------------------------------------------------------------------------------------------------------

W załączniku znajduje się plik PDF, który podsumowuje cały mój post - może się przyda :-)

Jeżeli masz jakieś pytania, sugestie - napisz PW.



Deklaracje przyjaźni z szablonami.pdf
 Opis:
Plik PDF, który zawiera wskazówki co do deklaracji przyjaźni z szablonami.

Pobierz
 Nazwa pliku:  Deklaracje przyjaźni z szablonami.pdf
 Wielkość pliku:  686.92 KB
 Pobierano:  495 raz(y)

Powrót do góry
Zobacz profil autora Wyślij prywatną wiadomość Wyślij email
marcin_an



Dołączył: 26 Maj 2005
Posty: 18822

PostWysłany: Sro Wrz 16, 2009 2:41 am  OP    Temat postu: Odpowiedz z cytatem Pisownia

Używanie using w nagłówkach
Tagi: C++
Q
Czy prawidłowy jest kod z nagłówka:
Kod:
#ifndef FUN_INCLUDED__
#define FUN_INCLUDED__

#include <iostream>

namespace x
{
    using namespace std;
    
    inline void fun()
    {
        cout << "Ala ma kota\n";
    }
}

#endif


A
Sam plik - w oddzieleniu od innych plików, jego użycia oraz utrzymania - tak, nie zawiera błędów składniowych. W praktyce jednak linia 8 jest proszeniem się o kłopoty i nigdy nie powinna była się pojawić.

Proste pytanie: co definiuje/deklaruje ten nagłówek w przestrzeni nazw x? Wbrew pozorom nie tylko x::fun. Wprowadza on także co najmniej osiem deklaracji obiektów: x::cin, x::cout, x::cerr, x::clog itd., a znając faktyczny sposób implementacji nagłówków: także dziesiątki innych deklaracji, często nawet nieudokumentowanych "wewnętrznych śmieci" implementacji biblioteki. Czy faktycznie chciałeś, aby to wszystko znalazło się w x? Nie sądzę. using użyty w ten sposób w celu skrócenia sobie pisania jest zły głównie dlatego, że powoduje zaśmiecenie przestrzeni nazw rzeczami, które nigdy nie miały się tam znaleźć.

Należy też pamiętać o tym, że dyrektywa include powoduje dołączenie zawartości (tekstu) wskazanego pliku. Jeżeli dołączymy nagłówek z nieodpowiednio użytym using, to w efekcie nasze using może trafić przed inne nagłówki dołączane po tym. To jest samo w sobie błędem, a w szczególnych sytuacjach może spowodować błędną interpretację kolejnych nagłówków.

Skutkiem mogą być też trudne do zlokalizowania błędy. Przeanalizuj sobie taki kod:
Kod:
namespace ns
{
    class X;
}

using namespace ns;

class X;

void test()
{
    X* x; // Błąd, kompilator nie wie, o którą klasę `X` chodzi!
}

X* x; // To samo...

O sytuację taką wbrew pozorom nietrudno, a żeby było zabawniej, np. g++ w drugim przypadku daje komunikat, z którego trudno wywnioskować, co się stało.

Gdzie zatem można stosować using w nagłówkach? We wszystkich zakresach, których zawartość nigdy nie będzie osiągalna przez użytkownika. Są nimi oczywiście implementacje funkcji:
Kod:
inline void fun()
{
    using namespace std;
    cout << "Ala ma kota\n";
}
Tak, ten kod jest prawie (patrz niżej) poprawny. Ponadto można sobie stworzyć "prywatną" przestrzeń nazw, która zgodnie z dokumentacją będzie zastrzeżona dla implementacji biblioteki. W niej można robić w zasadzie co się chce. Jeżeli ktoś się do niej odwoła, to sam sobie robi problemy - nie są one wynikiem zaniedbania autora nagłówka.

Wcześniej napisałem, że ostatni kod jest "prawie poprawny". Nadal jest jednak podatny na ostatni z wymienionych problemów. Często niezalecane jest w ogóle używanie konstrukcji using namespace, jeśli nie jest to dokładnie to, co chcemy zrobić. Lepszym rozwiązaniem jest zwykle importowanie poszczególnych nazw, czyli w tym przypadku lepszym byłoby: using std::cout;. W ten sposób wiemy, co dokładnie ściągamy do swojego kodu.

Kiedy using poza wymienionymi wyżej przypadkami może wystąpić w nagłówku? Wtedy, gdy faktycznie chcemy, by dane deklaracje znalazły się we wskazanym miejscu. Nie jest to częste, czasami jest problemotwórcze, ale samo w sobie nie jest jeszcze błędem.

Bo profesor tak wymaga: jeśli profesor będzie wymagał, żeby podczas pisania kodu chodzić po ścianach, to student ma obowiązek chodzić po ścianach, bo inaczej nie zaliczy. Podane informacje dotyczą normalnych sytuacji. Profesorowi pisz jak profesor chce.
Powrót do góry
Zobacz profil autora Wyślij prywatną wiadomość
hobson



Dołączył: 13 Sie 2007
Posty: 439
Skąd: Trójmiasto

PostWysłany: Pon Gru 07, 2009 3:12 pm      Temat postu: Odpowiedz z cytatem Pisownia

Q: Gdy użytkownik poda nieprawidłowe dane wejściowe, na przykład litery zamiast liczby, program wpada w nieskończoną pętlę. Jak temu zapobiec?
http://www.parashift.com/c++-faq-lite/input-output.html#faq-15.2
http://www.parashift.com/c++-faq-lite/input-output.html#faq-15.3

Q: Czemu mój program ignoruje wywołania getline lub inne żądania pobrania danych z wejścia?
http://www.parashift.com/c++-faq-lite/input-output.html#faq-15.6

Q: Czemu mój program dwa razy przetwarza ostatnią porcję danych wejściowych?
http://www.parashift.com/c++-faq-lite/input-output.html#faq-15.5
Powrót do góry
Zobacz profil autora Wyślij prywatną wiadomość Numer GG
Wyświetl posty z ostatnich:   
Odpowiedz do tematu    Forum Coders' city Strona Główna -> C i C++ Wszystkie czasy w strefie CET (Europa)
Idź do strony 1, 2  Następny
Strona 1 z 2

 
Skocz do:  
Możesz pisać nowe tematy
Możesz odpowiadać w tematach
Nie możesz zmieniać swoich postów
Nie możesz usuwać swoich postów
Nie możesz głosować w ankietach
Możesz dodawać załączniki na tym forum
Możesz pobierać pliki z tego forum




Debug: strone wygenerowano w 0.27840 sekund, zapytan = 13
contact

| Darmowe programy i porady Jelcyna | Tansze zakupy w Helionie | MS Office Blog |