Nowe posty

Autor Wątek: getline nie wczytuje danych  (Przeczytany 4558 razy)

KelThuzad

  • Gość
getline nie wczytuje danych
« dnia: 2016-08-14, 23:32:31 »
Witam mam program który oblicza budżet domowy i z pliku zewnętrznego chce wczytać ilość pieniędzy na początku. Jest to tylko jedna liczba ale w cout w ogóle nic tam nie ma nawet jak liczbę zmieniłem na jakiś wyraz (wszystko robię na stringach a później liczbę zmieniam na double przez atof ).
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <fstream>
#include <cstdlib>

using namespace std;

void openFile ( fstream& file );
void fileIsEmpty ( fstream& file );
string addDate ( fstream& file );

int main()
{
    fstream file;

    string helpfulVariable;
    double budget = 0 ;

    openFile ( file );
    fileIsEmpty ( file );
    helpfulVariable =  addDate ( file );
    cout << helpfulVariable;
    budget = atof( helpfulVariable );

    cout << "Ile wynosi budźet: " << budget << endl;

    return 0;
}

//==========================================================================================

void openFile (fstream& file)                               //otwieranie pliku
{
    file.open( "dane.txt" , ios::in);
    if ( file.good() == false )
    {
        cout << "nie mozna otworzyć pliku" << endl;
    }
}

//==========================================================================================

void fileIsEmpty ( fstream& file )                       //sprawdzanie czy plik jest pusty
{

    file.seekp( 0 , ios::end );
    size_t size = file.tellg();
    if ( size == 0 )
        cout << "Plik jest pusty" << endl;
}

//==========================================================================================

string addDate ( fstream& file )
{
    string line;
    while ( getline( file , line ));
    cout << line << endl;
    return line;
}

Offline ultr

  • Users
  • Guru
  • *****
  • Wiadomości: 1177
    • Zobacz profil
Odp: getline nie wczytuje danych
« Odpowiedź #1 dnia: 2016-08-15, 10:39:36 »
Po zerowe kod się nie kompiluje bo w atof(helpfulVariable) brakuje odwołania do metody string::c_str().

Po pierwsze funkcja fileIsEmpty przenosi kursor na koniec pliku i tak go zostawia, co uniemożliwia dalsze czytanie.
Dodaj na jej końcu powrót kursora na początek:
file.seekp(0 , ios::beg);

Po drugie while w funkcji addDate ("addData"?) zakończony jest średnikiem tuż za nawiasem, dzięki czemu nie robi absolutnie nic.

Po trzecie pętla while w addDate jest całkowicie bezsensowna jeśli jest to tylko jedna liczba.
Dodatkowo najpewniej ostatecznie zwróci ostatnią *pustą* linię. Na pewno o to chodzi?

Po czwarte do takiego bawienia się we wczytywanie liczb z pliku można użyć prosto cin zamiast kombinować się getline.

KelThuzad

  • Gość
Odp: getline nie wczytuje danych
« Odpowiedź #2 dnia: 2016-08-15, 15:17:57 »
Poprawiłem z tym średnikiem ale mam trochę inne pytanie a mianowicie nie wiem o co chodzi ci z string::c_str(). Dopiero się uczę i nie wszystko ogarniam jedyne co znalazłem to http://www.cplusplus.com/reference/string/string/c_str/ ale tam jest tylko zmiana na char i myślałem że c_str to jakaś tam zmiana na char i to coś chyba jest jeszcze z C by w C++ można było odczytywać zmienne znakowe. Ale nie wiem czy dobrze rozumiem.

Offline Paweł Kraszewski

  • Administrator
  • Guru
  • *****
  • Wiadomości: 3066
  • Lenistwo jest matką potrzeby = babcią wynalazku
    • Zobacz profil
Odp: getline nie wczytuje danych
« Odpowiedź #3 dnia: 2016-08-15, 18:28:15 »
Jest specjalne piekło dla robiących obliczenia finansowe na typach zmiennoprzecinkowych (float/double/extended i spółka).

nie wiem o co chodzi ci z string::c_str().

- helpfulVariable jest typu std::string (należy do świata C++)

- atof akceptuje char* (należy do świata C)

- w wywołaniu musisz skonwertować std::string na char* metodą c_str() albo użyć funkcji std::stof akceptującej std::string.

- nazywaj funkcje zgodznie z ich działaniem.

OT:

Naprawdę źle czyta się Twoje programy. Funkcje wyłącznie mutujące swój parametr to zły styl programowania (TM). "Optycznie" nie widać, że funkcja zmienia stan systemu. Ale to opinia zgreda od paru lat siedzącego w programowaniu funkcyjnym (Erlang/Elixir).

Lepiej by było dla czytelności, gdybyś zrobił pustą klasę dziedziczącą jawnie po fstream i swoje funkcje porobił jako metody - "optycznie" mutujące swoją zawartość.

Porównaj:
openFile(myfile);  // Nie widać, że myfile jest modyfikowane. Cały cywilizowany świat będzie
                   // oczekiwał, że ta funkcja zwróci otwarty obiekt pliku a myfile jest nazwą tego pliku.

myfile.open(); // Lepiej, bo trochę bardziej widać, że myfile może być modyfikowane

// Tutaj ewidentnie widać tworzenie obiektu:
myfile f = myfile::open(); // metoda fabryczna
myfile f = myfile()        // konstrukcja z przypisaniem
myfile f()                 // "goły" konstruktor

// No i oczywiście rezygnując z zaszycia nazwy pliku w metodzie:
myfile f = myfile::open("dane.txt"); // metoda fabryczna
myfile f = myfile("dane.txt")        // konstrukcja z przypisaniem
myfile f("dane.txt")                 // "goły" konstruktor

czyż nie czytelniej?
« Ostatnia zmiana: 2016-08-15, 18:42:18 wysłana przez Paweł Kraszewski »
Paweł Kraszewski
~Arch/Void/Gentoo/FreeBSD/OpenBSD/Specjalizowane customy

KelThuzad

  • Gość
Odp: getline nie wczytuje danych
« Odpowiedź #4 dnia: 2016-08-15, 19:24:35 »
Wiem że moje kody są słabe(Ale tak jak mówię dopiero się ucze ) ale jeszcze nie kumam klas i dlatego postanowiłem zrobić na razie (tak jak robię niestety ale mam nadzieje że kiedyś będzie lepiej). I teraz przez to że wszystko w funkcjach trochę uczę się zasięgu różnych zmiennych.

Offline Paweł Kraszewski

  • Administrator
  • Guru
  • *****
  • Wiadomości: 3066
  • Lenistwo jest matką potrzeby = babcią wynalazku
    • Zobacz profil
Odp: getline nie wczytuje danych
« Odpowiedź #5 dnia: 2016-08-16, 08:39:33 »
Cytuj
Wiem że moje kody są słabe(Ale tak jak mówię dopiero się ucze )
Wiesz, nie o to chodzi, że są słabe (każdy kiedyś zaczynał), tylko o to, że wyrabiają ci się fatalne nawyki programistyczne.

Cytuj
ale jeszcze nie kumam klas
Cytując pewną śliczną blondynkę "Now is the fucking time!". Albo już zacznij pracować na klasach, albo odpuść sobie C++ w ogóle. Nie ma "później", bo jak teraz sobie złe nawyki wyrobisz, to ich się nie pozbędziesz.

Cytuj
I teraz przez to że wszystko w funkcjach trochę uczę się zasięgu różnych zmiennych.
Musisz koniecznie znaleźć jakiegoś białkowego nauczyciela/przewodnika, bo zaczynasz iść w fatalnym kierunku. Stosujesz niezwykle egzotyczne rozwiązania (choć formalnie poprawne z punktu widzenia gramatyki języka). Póki kręcisz się w obrębie własnego kodu jest OK, ale taka radosna twórczość kończy się bolesnym upadkiem w sytuacji łączenia tego z kodem innych osób. Nagle okazuje się, że zupełnie inaczej rozumiesz konstrukcje programowe/struktury danych/API, niż reszta świata i musisz zaczynać naukę od początku.

Ucz się na kodzie innych - w ten sposób "opatrzysz" się z rozwiązaniami stosowanymi powszechnie przez innych. Z mojej strony mogę doradzić:

* zapoznanie się z kodem źródłowym OpenBSD - jest to jeden z najładniej napisanych i skomentowanych dużych kodów w C, jakie znam. Zacznij od przejrzenia prostych kodów źródłowych, typu kod polecenia bin/pwd, bin/echo czy usr.bin/wc (generalnie możesz sobie "wyciąć" wszystkie kawałki zawierające funkcję pledge - to nowy mechanizm zabezpieczeń OBSD).

* Ładnym kodem jest też kod źródłowy interpretera Lua.

* Warto zapoznać się też z Google C++ Style Guide, tam są naprawdę cenne wskazówki.

* Ładny i dobrze skomentowany kod C++ (także z bardziej zaawansowanymi konstrukcjami) ma OpenFst
Paweł Kraszewski
~Arch/Void/Gentoo/FreeBSD/OpenBSD/Specjalizowane customy

KelThuzad

  • Gość
Odp: getline nie wczytuje danych
« Odpowiedź #6 dnia: 2016-08-16, 12:55:16 »
Panie Pawle a czy mógłby mi pan tak w paru punktach napisać co robię źle na podstawie tego kody to już bym starał się poprawić. I przeskoczę od razu do klas. Kiedyś robiłem w klasach ale napisał mi pan że nie rozumiem klas http://forum.linux.pl/index.php/topic,24537.msg132415.html#msg132415 i wtedy postanowiłem na razie uczyć się podstaw, ale jak klasy są aż tak ważne to zacznę powoli je znowu wprowadzać chociaż chyba rozumiem źle ich znaczenie.

Offline Paweł Kraszewski

  • Administrator
  • Guru
  • *****
  • Wiadomości: 3066
  • Lenistwo jest matką potrzeby = babcią wynalazku
    • Zobacz profil
Odp: getline nie wczytuje danych
« Odpowiedź #7 dnia: 2016-08-16, 15:51:04 »
Nie da się krótko opisać co jest źle, poza napisaniem "tak się nie pisze" - ale to nie jest dydaktycznie poprawna odpowiedź. Dlatego skierowałem do poczytania istniejącego kodu.

Załączam kod "rozplątany" z punktu widzenia przepływu danych i robiący sprawę na jednej, bardzo prostej klasie.
Kod jest zredukowany i absolutnie nie najlepszy z możliwych, bo:
* prawnie nie ma obsługi błędów (zasadniczo powinna być robiona na wyjątkach, ale to następny krok)
* wszystko jest w jednym pliku, choć deklaracja klasy powinna być we własnym pliku .h
* nie byłem pewien, co ma robić funkcja z while-m. Napisałem "na czuja".
* typ std::decimal jeszcze nie jest oficjalnie w standardzie a finanse na zmiennym przecinku to nie po chrześcijańsku. Warto by było zrobić (jako wprawkę do obiektów) własną definicję typu decimal/currency (stałoprzecinkowego). Fajne ćwiczenie na konstuktory, destruktory, przeciążenie operatorów arytmetycznych czy strumienie we-wy.

// "Jednoplikowo", bo nie chcę mieszać plikami .H

#include <fstream>
#include <iostream>
#include <string>

// using namespace std;
//   Powyższa linijka to ZŁO wcielone
//   nie po to powstały namespace'y, żeby je obchodzić.

// Klasa poniżej ma zwykłe osadzenie obiektu strumienia, więc nie
// potrzebuje jawnego destruktora. Wystarczy domyślny, wywołujący destruktory
// obiektów osadzonych

class mojPlik {
 private:
  std::fstream plik;  // Osadzony obiekt strumienia

 public:
  mojPlik( std::string nazwa );   // Konstruktor
  bool czyPlikPustyLubBledny();   // Funkcja zwraca info, czy plik jest pusty
  std::string ostatniaLinijka();  // Funkcja zwraca ostatnią linię pliku
};

////////////////////////////////////////////////////////////////////////////////
/// Funkcja MAIN
///
int main( int argc, char* argv[] ) {
  // Otwórz plik
  mojPlik mp( "dane.txt" );

  // Sprawdź, czy można z niego czytać
  if( mp.czyPlikPustyLubBledny() ) {
    // Komunikat błędu
    std::cerr << "Plik jest pusty lub nie istnieje" << std::endl;

    // Sygnalizacja błędu (każda wartość różna od 0)
    return 1;
  }

  // Wczytaj ostatnią linię
  auto ostatniaLinia = mp.ostatniaLinijka();

  // Skonwertuj ją na long double (specjalne piekło, pamiętasz?)
  // To może rzucać wyjątek std::invalid_argument, jeżeli string nie jest
  // poprawną liczbą. Nie obsługujemy tu tego.
  auto budzet = std::stold( ostatniaLinia );

  // Wyświetl na ekranie
  std::cout << "Budżet wynosi " << budzet << std::endl;

  // Sygnalizacja braku błędu
  return 0;
}

////////////////////////////////////////////////////////////////////////////////
/// Konstruktor
///
mojPlik::mojPlik( std::string nazwa ) {
  // Tutaj powinna być obsługa błędów z rzucaniem wyjątków, ale
  // "Today is not this day".
  plik.open( nazwa, std::ios::in );
}

// Funkcja powinna robić JEDNĄ rzecz, tą opisaną w nazwie funkcji.
// Poniższa funkcja zwraca wynik testów jako boolean i nie powinna
// robić nic więcej, w szczególności nic skrobać po ekrane.
// Jeżeli trzeba coś wyświetlić, to powinno to być zrobione w procedurze
// wołającej, na podstawie wyniku uzyskanego stąd.

////////////////////////////////////////////////////////////////////////////////
/// Metoda czyPlikPustyLubBledny
///
bool mojPlik::czyPlikPustyLubBledny() {
  // seekg - tellg <- odczyt (istream), g ot GET
  // seekp - tellp <- zapis (ostream), p od PUT

  // Jeżeli plik jest otwarty z błędem, to od razu
  // to zasygnalizuj
  if( !plik.good() ) return true;

  // Zapamiętaj starą pozycję w pliku
  auto stara = plik.tellg();

  // Pojedź na koniec i wczytaj rozmiar
  plik.seekg( 0, std::ios::end );
  auto rozmiar = plik.tellg();

  // Wróć na zapamiętaną pozycję
  plik.seekg( stara, std::ios::beg );

  // Zwróć wynik porównania
  return ( rozmiar == 0 );
}

////////////////////////////////////////////////////////////////////////////////
/// Metoda ostatniaLinijka
///
std::string mojPlik::ostatniaLinijka() {
  // Nie mam zielonego pojęcia co oryginalnie miała robić ta funkcja
  // std::getline(plik,string) zwraca "plik&", więc
  // nigdy nie będzie równe 0, żeby zakończyć while(){}

  // _Zakładam_, że chodzi o pobranie ostatniej linii z pliku.

  // Jak jest enter na końcu ostatniej linii, to z punktu widzenia
  // std::getline plik kończy się pustym stringiem. Trzeba to złapać
  // i zwrócić ostatni _niepusty_ string.

  std::string linia, niepusta_linia;

  do {
    // Pobierz kolejną linię
    std::getline( plik, linia );

    // Jeżeli nie jest pusta, to ją zapamiętaj
    if( linia.length() > 0 ) niepusta_linia = linia;

    // Dupóki nie dojdziemy do końca pliku
  } while( !plik.eof() );

  // Zwróć ostatnią _niepustą_ linię
  return niepusta_linia;
}

a co do wprawki, warto zrobić coś co by się zachowywało tak:

(...)

int main(){
 gotowka G1(5); // Automatycznie 0 miejsc po przecinku
 std::cout << G1.precyzja() << std::endl; // Wynik: 0
 gotowka G2("0.20"); // Wyliczone 2 miejsca po przecinku
 std::cout << G2.precyzja() << std::endl; // Wynik: 2

 auto G = G1 + G2; // Suma jest automatycznie typu gotowka
 std::cout << G.precyzja() << std::endl; // Wynik: 2, wymagany do pokazania wyniku
 std::cout << G << std::endl; // Wynik: 5.20, niemożliwy do przechowania na zmiennym przecinku

 G += 10; // Operacje ze zmiennymi typu int
 std::cout << G.precyzja() << std::endl; // Wynik: 2, wymagany do pokazania wyniku
 std::cout << G << std::endl; // Wynik: 15.20, niemożliwy do przechowania na zmiennym przecinku
 
}
Paweł Kraszewski
~Arch/Void/Gentoo/FreeBSD/OpenBSD/Specjalizowane customy

KelThuzad

  • Gość
Odp: getline nie wczytuje danych
« Odpowiedź #8 dnia: 2016-08-16, 17:33:32 »
Bardzo dziękuje. Pouczę się trochę na tym kodzie, nie wszystko umiem, szczególnie jeśli chodzi o pliki (To jest dopiero drugi program jaki robię na plikach). A chciałem się już trochę pouczyć na różnych działaniach na plikach. A mam jeszcze jedno pytanie jak pan napisał funkcja ma robić dokładnie to co w nazwie a klasę do czego się najczęściej stosuje? Na razie na szybko postaram się dokończyć ten program tak jak ja robiłem ale już wszystkie następne będę robił na klasach i funkcjach. I mam jeszcze dwa pytanie:
1) Bo mam pan cerr a widziałem w książce że też robi się to przez try{}catch i tu dopiero cerr. I nie wiem z czego lepiej korzystać?

2) I czy źle że używam using namespace std; i czy powinienem robić np std::cout ??

Offline Paweł Kraszewski

  • Administrator
  • Guru
  • *****
  • Wiadomości: 3066
  • Lenistwo jest matką potrzeby = babcią wynalazku
    • Zobacz profil
Odp: getline nie wczytuje danych
« Odpowiedź #9 dnia: 2016-08-17, 09:45:52 »
Pierwsze Primo: to jest forum nieformalne i nie "panujmy" sobie. To sprawia, że czuję się starszy, niż na to zasługuję :)

A mam jeszcze jedno pytanie jak pan napisał funkcja ma robić dokładnie to co w nazwie a klasę do czego się najczęściej stosuje?

Funkcja ma robić to co ma napisane w nazwie: jak funkcja nazywa się open/otwórz, to ma otwierać to coś, do czego jest napisana i w jakiś sposób sygnalizować błąd (wyjątek, zwróćenie specjalnej wartości oznaczającej błąd, whatever). I nic więcej. Jak ma jeszcze coś dopisać do logu to powinna nazywać się OtwórzIZaloguj. Idea jest taka, że po nazwie funkcji, typie zwracanej wartości i argumentach można określić, do czego funkcja służy. I że w miarę możliwości funkcja nie robi więcej, niż nam się wydaje.

U Ciebie takim przykładem była funkcja fileIsEmpty(&plik), która
- nic nie zwracała, a po nazwie można było się spodziewać, że zwróci informację, np jako zmienną logiczną
- zmieniała wskaźnik położenia w pliku na koniec i go tam zostawiała

i funkcja addDate(&plik)która... Nie wiem co robiła, ale na pewno nie dodawała daty...

Co do klasy: zadaniem klasy jest opakowanie jakiejś funkcjonalności w zamkniętą całość (tzw enkapsulacja) w taki sposób, aby użytkownik nie musiał wiedzieć co się dzieje w środku. Taka "paczka" składa się z danych przechowujących stan danego obiektu (tzw pola albo składowe) i z funkcji pozwalających sprawdzać i modyfikować ten stan (to są tzw. metody). W idealnym przypadku użytkownik nie widzi żadnych składowych i może manipulować obiektem wyłącznie za pomocą metod.

W przypadku programu do budżetu podział może wyglądać tak:
- Klasa Transkacja opisująca pojedynczą transakcję, zawierająca identyfikator, datę, typ, kwotę i opis. Metody to konstruktor do robienia nowej transakcji, metody do wyciągania parametrów transakcji (tzw akcesory albo metody akcesorowe) i metoda do formatowania transakcji do wydruku.
- Klasa Konto opisująca całe konto. Zawiera identyfikator, saldo początkowe, listę transakcji robionych na tym koncie i opis. Metody to założenie konta z danym saldem początkowym, dodanie (i ewentualnie skasowanie) transakcji, wyliczenie salda końcowego, iterację po transakcjach, szukanie transakcji o danych ID, formatowanie konta do wydruku.
- Klasa Budżet zawierająca wszystkie konta. Zawiera listę kont. Metody to zrobienie nowego budżetu, dodanie i usuwanie kont, odczyt i zapis budżetu na dysk, iterację po kontach, wyszukiwanie konta o konkretnym identyfikatorze.

1) Bo mam pan cerr a widziałem w książce że też robi się to przez try{}catch i tu dopiero cerr. I nie wiem z czego lepiej korzystać?

std::cerr to prawie to samo co std::cout. Różnica jest taka, że jak uruchomisz program przez ./program > plik.txt, to do pliku pojadą rzeczy wysłane do std::cout - ale rzeczy wysłane na std::cerr dalej będą się pojawiać na ekranie. Patrząc na podręczniki do C, odpowiedniki są takie:
- C ~~ C++
- stdout ~~ std::cout
- stdin ~~ std::cin
- stderr ~~ std::cerr

2) I czy źle że używam using namespace std; i czy powinienem robić np std::cout ??

Namespace'y powstały jako rozwiązanie problemu, że kilka bibliotek ma funkcję, która robi to samo i mogłaby się tak samo nazywać - ale nie może, bo nie można robić kilku funkcji tak samo się nazywających.

Wyobraź sobie funkcję open: w bibliotece sieciowej otwiera połączenie TCP, w bibliotece video otwiera i wyświetla film, w bibliotece systemowej otwiera plik, w bibliotece do ZIPa otwiera archiwum. Dlatego nagle masz miliard funkcji typu tcpopen, openvideo, fopen, openzip, itd. Zagadnienie to nazywa się "namespace pollution", zaśmieceniem przestrzeni nazw.

Namespace'y powstały, aby rozwiązać ten problem. Funkcje dotyczące jednego zagadnienie można łączyć pod jedną grupą. Dzięki temu możesz mieć std:open, video::open, zip::open czy nawet hierarchiczne net::tcp::open.
Użycie "using namespace X" powoduje "wciągnięcie" całego namespace'a X do bieżącego zakresu i podważa sam sens istnienia namespace'ów. Niestety, konstrukcja ta jest promowana przez wiele podręczników.

Jeżeli dopisywanie std:: jest takie uciążliwe, to można "wciągnąć" tylko te elementy, których się używa, np:
#include <iostream>

/* W całym pliku można korzystać z "krótkiej" wersji cout */
using std::cout;

/* Robimy własnego namespace'a z funkcją */
namespace kuchnia {

  /* W tym namaspace'u można użyć krótkiej wersji endl */
  using std::endl;

  void open( const std::string &s ) {
    cout << "Otwieram słoik z " << s << endl;
  }
}

int main( int argc, char *argv[] ) {
  /* W tej funkcji można używać krótkiej wersji string */
  using std::string;

  auto s = string( "dżemem" );

  kuchnia::open( s );

  /* Już nie jesteśmy w namespace'u "kuchnia", więc trzeba użyć std::endl */
  cout << "Koniec" << std::endl;

  return 0;
}

« Ostatnia zmiana: 2016-08-17, 09:48:42 wysłana przez Paweł Kraszewski »
Paweł Kraszewski
~Arch/Void/Gentoo/FreeBSD/OpenBSD/Specjalizowane customy

KelThuzad

  • Gość
Odp: getline nie wczytuje danych
« Odpowiedź #10 dnia: 2016-08-17, 19:15:35 »
Dzięki za tak wyczerpującą odpowiedz już mi trochę jaśniej ale jeszcze 2 pytanka
1) O co chodzi z konstruktorami wiem (chyba tyle co gdzieś przeczytałem) że to takie odpowiedniki funkcji ale nie do końca??

2) I w takim razie jak prawie wszytko da się zrobić na klasach to po co używać funkcji. Czy z nich się korzysta tylko przy małych programach??

Offline Paweł Kraszewski

  • Administrator
  • Guru
  • *****
  • Wiadomości: 3066
  • Lenistwo jest matką potrzeby = babcią wynalazku
    • Zobacz profil
Odp: getline nie wczytuje danych
« Odpowiedź #11 dnia: 2016-08-18, 05:48:31 »
1. Cykl życia obiektu klasy:
 - stworzenie za pomocą konstruktora
 - zarządzanie za pomocą metod
 - niszczenie za pomocą destruktora.

2. Ekipa tworząca Javę zadała to samo pytanie i tam nie ma funkcji, są tylko klasy i metody.
Paweł Kraszewski
~Arch/Void/Gentoo/FreeBSD/OpenBSD/Specjalizowane customy

Offline Paweł Kraszewski

  • Administrator
  • Guru
  • *****
  • Wiadomości: 3066
  • Lenistwo jest matką potrzeby = babcią wynalazku
    • Zobacz profil
Odp: getline nie wczytuje danych
« Odpowiedź #12 dnia: 2016-08-18, 08:52:35 »
#include <cinttypes>
#include <ctime>
#include <iomanip>
#include <iostream>
#include <string>

/*
 * Cały kod pisany jest pod C++11
 *
 * Nie jest mistrzostwem świata optymalizacji, ma pokazać podstawowe aspekty
 * działania klas.
 *
 */

namespace Rozliczenia {
  /* Wciągnięcie do przestrzeni nazw wybranych typów */
  using std::int64_t; /* Liczby 64 bitowe ze znakiem */
  using std::string;  /* Napisy */
  using std::time_t;  /* Typ przechowujący czas */
  using std::time;    /* Funkcja zwracająca bieżący czas */
  using std::ostream; /* Typ standardowego strumienia wyjściowego */

  /* Dzięki namespace'owi nie musimy się przejmować, że jakaś biblioteka
   * też zdefiniuje typ_t.
   *
   * C++11 - silnie typowane wyliczenia (magiczne słówko class).  Opis na
   * http://www.cprogramming.com/c++11/c++11-nullptr-strongly-typed-enum-class.html
   */
  enum class typ_t { Wplata, Wyplata };

  /* Klasa opisująca jedną transakcję */
  class Transakcja {
    /* Składowe prywatnie - można się do nich dostać jedynie z
     * metod tej klasy. i funkcji zaprzyjaźnionych
     */
   private:
    int64_t kwota; /* Kwota w groszach. Brak błędów zaokrągleń */
    typ_t   typ;   /* Kierunek transakci: wpłata czy wypłata */
    string  opis;  /* Opis transakcji */
    time_t  data;  /* Data transakcji */

    /* Składowe publiczne - każdy może się do nich dostać */
   public:
    /* Wyłącznie konstruktor i destruktor nic nie zwracają i nie mają void
     * na początku. Wszystkie "normalne" metody, które nic nie zwracają
     * muszą mieć void na początku */

    /* Konstruktor. Nazywa się tak jak klasa i nic nie zwraca. Przyjmuje
     * kwotę w groszach
     */
    Transakcja( int64_t _kwota, string _opis, typ_t _typ = typ_t::Wplata );

    /* Drugi konstruktor. Przyjmuje kwotę w złotówkach */
    Transakcja( double _kwota, string _opis, typ_t _typ = typ_t::Wplata );

    /*
     * Metody akcesorowe do pobierania szczegółów transakcji.
     * Żadna z tych metod nie zmienia zawartości obiektu, więc dopisujemy
     * const na końcu. Jeżeli się pomylimy i wywołamy coś, co może zmienić
     * stan obiektu (a nie chcemy tego), to kompilator zgłosi błąd.
     * Taki "pas bezpieczeństwa" a równocześnie informacja dla użytkownika.
     *
     * Metody akcesrowe są na ogół krótkie i nadają się do "wbudowania", czyli
     * tzw inline'u. Dlatego ciało tych metod pojawia się już tutaj.
     */

    /* Pobranie kwoty w groszach */
    inline int64_t getKwota() const { return kwota; }

    /* Pobranie w złotówkach, uwaga na zaokrągnienia! */
    inline double getKwotaAsDouble() const {
      /* Nowy sposób rzutowania typów w C++11 */
      return static_cast<double>( kwota ) / 100.0;
    }

    /* Pobranie typu */
    inline typ_t getTyp() const { return typ; }

    /* Pobranie typu jako tekst */
    inline string getTypAsStr() const {
      switch( typ ) {
        case typ_t::Wplata: return string( "wpłata" );
        case typ_t::Wyplata: return string( "wypłata" );
      }
    }

    /* Pobranie opisu */
    inline string getOpis() const { return opis; }

    /* Pobranie daty. Bardziej złożona metoda, więc tu tylko deklaracja */
    string getData() const;

    /*
     * Możliwość wypisania transakcji na cout (typu ostream).
     *
     * I tu jest problem. Gdybyśmy zrobili << jako metodę, zapis
     *
     * cout << transakcja;
     *
     * zamieniony by został na takie wywołanie:
     *
     * cout.operator<<(transakcja)
     *
     * Czyli musielibyśmy rozbudować klasę ostream o obsługę naszych transakcji,
     * modyfikując bibliotekę standardową (bad thing TM).
     *
     * Ale także możemy zrobić zupełnie normalną funkcję (nie metodę!):
     *
     * ostream& operator<<(ostream& os, const Transakcja& t);
     *
     * Problem z tą funkcją jest taki, że nie należy ona ani do ostream ani do
     * Transakcja, więc nie może się dostać do składowych prywatnych. Oczywiście
     * może skorzystać z publicznych metod akcesorowych (po to właśnie są).
     *
     * Rozwiązaniem jest zadeklarowanie tej funkcji jako zaprzyjaźnionej
     * w klasie Transakcja, przez co - mimo że jest zwykłą funkcją - może jednak
     * dostać się do składowych prywatnych.
     *
     * Funkcja zwraca ostream&, żeby można było robić cout << T1 << T2, bo
     * rozwija się to tak:
     *
     * (cout << T1) << T2;
     *
     * czyli wynik wysłania T1 na cout jest nowym coutem dla wysłania T2
     *
     * a dalej na:
     *
     * operator<<(operator<<(cout,T1),T2)
     */

    friend ostream& operator<<( ostream& os, const Transakcja& t );
  };

  /* Można to też zrobić tak (oba zapisy są prawie równoważne):
  Transakcja::Transakcja( int64_t _kwota, string _opis, typ_t _typ ) {
      kwota = _kwota;
      typ = _typ;
      opis = _opis;
      data = time( nullptr )
  }
  */

  Transakcja::Transakcja( int64_t _kwota, string _opis, typ_t _typ )
      : kwota( _kwota ) /* Tak inicjalizujemy składowe stałymi albo zmiennymi */
      , typ( _typ )
      , opis( _opis )
      , data( time( nullptr ) ) /* time(nullptr) zwraca czas "teraz" */
  { /* Puste ciało, wszystko jest zainicjalizowane wyżej */
  }

  Transakcja::Transakcja( double _kwota, string _opis, typ_t _typ )
      : kwota( static_cast<int64_t>( _kwota * 100.0 ) )
      , typ( _typ )
      , opis( _opis )
      , data( time( nullptr ) ) {}

  /* Konwersja daty transakcji na tekst */
  string Transakcja::getData() const {
    using std::strftime;
    using std::localtime;

    auto data_zdekodowana = localtime( &data ); /* zamiana na std::tm */
    char bufor[11];                             /* 0000-00-00 plus znak NULL */

    /* Zamiana struktury std::tm na stringa. Niestety, nie ma w bibliotece
     * funckji zamieniającej w prosty sposób std::tm na std::string
     * */
    strftime( bufor, sizeof( bufor ), "%F", data_zdekodowana );

    /* Więc musimy ręcznie to zrobić */
    return string( bufor );
  }

  /* Wypisanie transakcji do strumienia */
  ostream& operator<<( ostream& os, const Transakcja& t ) {
    /* To tzw manipulatory, opisane tutaj:
     * http://www.cplusplus.com/reference/iomanip/
     */
    using std::setw;    /* Ustawienie długości pola */
    using std::setfill; /* Czym uzupełnić brakujące miejsca */

    os << "Transakcja{";
    os << t.getData() << ", "; /* Metoda akcesorowa */
    os << t.getTypAsStr() << " ";
    os << t.kwota / 100 << "."; /* Pełne złotówki */

    /* Grosze uzupełnione do 2 cyfr zerami na początku */
    os << setfill( '0' ) << setw( 2 ) << t.kwota % 100;

    os << " PLN, ";
    os << "\"" << t.opis << "\" }"; /* Bezpośrednia wartość (dzięki friend) */
    return os;
  }
}

int main() {
  using std::cout;
  using std::endl;

  /* Jawne uruchomienie konstruktora */
  Rozliczenia::Transakcja t1( 80.0, "Za zakup podręcznika do C++",
                              Rozliczenia::typ_t::Wyplata );

  /* A tu tzw konstrukcja z przypisaniem, kwota w groszach */
  auto t2 =
      Rozliczenia::Transakcja( 10000l, "Za napisanie programu o rozliczeniach",
                               Rozliczenia::typ_t::Wplata );

  /* Teraz to tak prosto wygląda... */
  cout << t1 << endl;
  cout << t2 << endl;

  /* A to spowoduje błąd kompilacji, bo nie można sobie zaglądać do składowych
   * prywatnych
   * */

  /*
  cout << t1.kwota << endl;
  */
}

/*
 * A po co ostream& a nie ostream? To w następnym przykładzie.
 *
 * */

Paweł Kraszewski
~Arch/Void/Gentoo/FreeBSD/OpenBSD/Specjalizowane customy

Offline Paweł Kraszewski

  • Administrator
  • Guru
  • *****
  • Wiadomości: 3066
  • Lenistwo jest matką potrzeby = babcią wynalazku
    • Zobacz profil
Odp: getline nie wczytuje danych
« Odpowiedź #13 dnia: 2016-08-19, 09:23:36 »
Dziś mały behemoth.

Implementacja listy jednokierunkowej z głębokim kopiowaniem (nieistotne) i pokazanie, czym się różnią funkcje z argumentem przekazanym przez wartość, przez referencję i przez wskaźnik. Specjalnie na potrzeby dydaktycznie program wyświetla wszystkie operacje pośrednie.

* Prześledź dokładnie co i kiedy jest kopiowane (po to zawsze masz drukowany adres danej zmiennej), w których funkcjach masz kopię argumentu, kiedy jest kopiowana, w których jest oryginał.

Prześledź, kiedy zmiana argumentu "wycieka" z funkcji.

Prześledź, kiedy dane są niszczone.

#include <cstring>
#include <iostream>

using std::cout;
using std::endl;

/*
 * Prosta lista jednokierunkowa z głębokim kopiowaniem
 */

#define HERE "| " << __PRETTY_FUNCTION__ << ", linia " << __LINE__

/* ************************************************************************
 * W "pierwszym czytaniu możesz pominąć zrozumienie fragmentu od tąd do
 * drugiego takiego znacznika
 * ************************************************************************/

class Wezel {
 private:
  char   Napis[16]; /* Dana OSADZONA w węźle */
  Wezel* Nastepny;  /* Wskaźnik na następny element albo nullptr */

 public:
  /* Konstruktory */
  Wezel();                     /* Konstruktor domyślny */
  Wezel( const char* _Napis ); /* Konstruktor z inicjalizacją napisu */

  /* Dwie formy kopiowania istniejącego obiektu */
  Wezel( const Wezel& oryginal );            /* Konstruktor kopiujący */
  Wezel& operator=( const Wezel& oryginal ); /* Operator przypisania */

  /* Destruktor */
  ~Wezel();

  /* Operator dołączenia elementu do listy
   *
   * W przeciwieństwie do zabaw z ostream z poprzedniego przykładu, tutaj
   * l1 << l2 jest metodą i jest zamieniane na l1.operator<<(l2)
   */
  Wezel& operator<<( Wezel* nowy );

  /* Funkcja wyświetla całą listę zaczynającą się od danego elementu
   * Nie chiałem tego robić na operatorze << z ostream, żeby nie mieszać
   */
  void wypisz();

  /* Wyszukuje i zwraca ostatni węzeł na liście */
  Wezel& ostatni();
};

/* Tworzenie pustego elementu */
Wezel::Wezel() : Nastepny( nullptr ) {
  cout << "\tKonstruktor " << HERE << endl;
  cout << "\t\tWęzeł @ " << this << endl << endl;
  std::memset( Napis, 0, sizeof( Napis ) );
}

/* Tworzenie elementu z napisem */
Wezel::Wezel( const char* _Napis ) : Nastepny( nullptr ) {
  cout << "\tKonstruktor " << HERE << endl;
  cout << "\t\tWęzeł @ " << this << " = \"" << _Napis << "\"" << endl << endl;
  std::strncpy( Napis, _Napis, sizeof( Napis ) );
}

/* "Głębokie" kopiowanie elementu */
Wezel::Wezel( const Wezel& oryginal ) {
  cout << "\tKonstruktor kopiujący " << HERE << endl;

  cout << "\t\tWęzeł @ " << this << ", oryginał @ " << &oryginal << endl
       << endl;
  std::memcpy( Napis, oryginal.Napis, sizeof( Napis ) );

  if( oryginal.Nastepny != nullptr ) {
    Nastepny = new Wezel( *oryginal.Nastepny );
  } else {
    Nastepny = nullptr;
  }
}

/* Przypisanie elementu z głębokim kopiowaniem.
 *
 * Uwaga! Zaawansowana magia! Nie musisz tego teraz rozumieć !
 */
Wezel& Wezel::operator=( const Wezel& oryginal ) {
  cout << "\tPrzypisanie " << HERE << endl;
  cout << "\t\tWęzeł @ " << this << ", oryginał @ " << &oryginal << endl
       << endl;

  std::memcpy( Napis, oryginal.Napis, sizeof( Napis ) );

  /* Jeżeli po lewej stronie = jest już lista, to ją wyczyść */

  if( Nastepny != nullptr ) delete Nastepny;

  /* I przypisz nową */
  if( oryginal.Nastepny != nullptr ) {
    /* Jeżeli oryginał nie jest ostatni, to zrób nowy pusty element */
    Nastepny = new Wezel;

    /* "Głębokie" kopiowanie.
     *
     * Pamiętaj, że Nastepny i oryginal.Nastepny są wskaźnikami!
     *
     * Gdyby zlikwidować obie gwiazdki, to byłoby "płytkie" kopiowanie
     * samych adresów.
     * Dzięki gwiazdkom, wywołujemy operator= rekurencyjnie dla kolejnych
     * elementów listy
     */

    *Nastepny = *oryginal.Nastepny; /* "niewidzialna" rekurencja */

  } else {
    /* Jeżeli nie ma więcej nic do kopiowania */
    Nastepny = nullptr;
  }

  return *this; /* Żeby można było robić a = b = c */
}

/* Destruktor */
Wezel::~Wezel() {
  cout << "\tDestruktor " << HERE << endl;
  cout << "\t\tWęzeł @ " << this << " = \"" << Napis << "\"" << endl << endl;

  /* Ususwaj wszystko do końca listy */
  if( Nastepny != nullptr ) delete Nastepny;
}

/* Dopisanie elementu listy */
Wezel& Wezel::operator<<( Wezel* nowy ) {
  cout << "\tDopisanie " << HERE << endl;
  cout << "\t\tWęzeł @ " << this << ", nastepny @ " << nowy << endl << endl;
  if( Nastepny != nullptr ) delete Nastepny;

  Nastepny = nowy;
  return *nowy; /* Można zrobić l1 << new l2 << new l3; */
}

/* Wypisanie listy  */
void Wezel::wypisz() {
  auto wskaznik = this; /* Zaczynamy od samego siebie */
  cout << "\tLista:" << endl;

  /* Powtarzaj, dopóki nie dojdziesz do końca */
  do {
    cout << "\t\tWęzeł @ " << wskaznik << " = \"" << ( wskaznik->Napis ) << "\""
         << endl;
    wskaznik = wskaznik->Nastepny;
  } while( wskaznik != nullptr );

  cout << "\tKoniec listy" << endl << endl;
}

/* Szukanie ostatniego */
Wezel& Wezel::ostatni() {
  if( Nastepny != nullptr )
    /* Jeżeli jest jakiś następny, to może on jest ostatni? */
    return Nastepny->ostatni();
  else
    /* Nie ma następnego, my jesteśmy ostatni */
    return *this;
}

/* ************************************************************************
 * Tutaj zaczynają się funkcje, których działanie chcemy testować
 * ************************************************************************/

void argument_kopia( Wezel arg ) {
  cout << "\tPoczątek funkcji " << HERE << endl;
  cout << "\t\tPrzed zmianą: ";
  arg.wypisz();

  arg.ostatni() << new Wezel( "kopia" );

  cout << "\t\tPo zmianie: ";
  arg.wypisz();

  cout << "\tKoniec funkcji " << HERE << endl;
}

void argument_referencja( Wezel& arg ) {
  cout << "\tPoczątek funkcji " << HERE << endl;
  cout << "\t\tPrzed zmianą: ";
  arg.wypisz();

  arg.ostatni() << new Wezel( "referencja" );

  cout << "\t\tPo zmianie: ";
  arg.wypisz();

  cout << "\tKoniec funkcji " << HERE << endl;
}

void argument_wskaznik( Wezel* arg ) {
  cout << "\tPoczątek funkcji " << HERE << endl;
  cout << "\t\tPrzed zmianą: ";
  arg->wypisz();

  arg->ostatni() << new Wezel( "Wskaznik" );

  cout << "\t\tPo zmianie: ";
  arg->wypisz();

  cout << "\tKoniec funkcji " << HERE << endl;
}

int main( int argc, char* argv[] ) {
  cout << "************************************************" << endl;
  cout << "Konstrukcja baza " << HERE << endl;
  Wezel baza( "Bazowy" );

  cout << "************************************************" << endl;
  cout << "Konstrukcja dodatkowych zmiennych " << HERE << endl;

  Wezel wart, ref, wsk;

  wart.wypisz();
  ref.wypisz();
  wsk.wypisz();

  cout << "************************************************" << endl;
  cout << "Dołączenie nowych węzłów do baza " << HERE << endl;
  baza << new Wezel( "nowy1" ) << new Wezel( "nowy2" );

  cout << "Po dopisaniu baza=";
  baza.wypisz();

  cout << "************************************************" << endl;
  cout << "Wywołanie funkcji 'przez wartość' " << HERE << endl;
  cout << "Kopiowanie bazy" << endl;
  wart = baza;
  cout << "Przed wywołaniem=";
  wart.wypisz();
  cout << ">>> Wołanie funkcji" << endl;
  argument_kopia( wart );
  cout << "<<< po powrocie=";
  wart.wypisz();

  cout << "************************************************" << endl;
  cout << "Wywołanie funkcji 'przez referencję'" << HERE << endl;
  cout << "Kopiowanie bazy" << endl;
  ref = baza;
  cout << "Przed wywołaniem=";
  ref.wypisz();
  cout << ">>> Wołanie funkcji" << endl;
  argument_referencja( ref );
  cout << "<<< po powrocie=";
  ref.wypisz();

  cout << "************************************************" << endl;
  cout << "Wywołanie funkcji 'przez wskaźnik'" << HERE << endl;
  cout << "Kopiowanie bazy" << endl;
  wsk = baza;
  cout << "Przed wywołaniem=";
  wsk.wypisz();
  cout << ">>> Wołanie funkcji" << endl;
  argument_wskaznik( &wsk );
  cout << "<<< po powrocie=";
  wsk.wypisz();

  cout << "************************************************" << endl;
  cout << "Wywołanie funkcji 'przez wartość' dla obiektu tymczasowego " << HERE
       << endl;
  argument_kopia( Wezel( "Tymczasowy1" ) );

  cout << "************************************************" << endl;
  cout << "Koniec funkcji " << HERE << endl;
  return 0;
}


« Ostatnia zmiana: 2016-08-19, 09:49:21 wysłana przez Paweł Kraszewski »
Paweł Kraszewski
~Arch/Void/Gentoo/FreeBSD/OpenBSD/Specjalizowane customy

Offline Paweł Kraszewski

  • Administrator
  • Guru
  • *****
  • Wiadomości: 3066
  • Lenistwo jest matką potrzeby = babcią wynalazku
    • Zobacz profil
Odp: getline nie wczytuje danych
« Odpowiedź #14 dnia: 2016-08-19, 10:36:30 »
Wersja z bardziej szczegółownym śledzeniem jest tutaj.
Paweł Kraszewski
~Arch/Void/Gentoo/FreeBSD/OpenBSD/Specjalizowane customy