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

Aplikacja pseudo bazodanowa z wykorzystaniem plików tekstowych

Object Storage Arubacloud
0 głosów
739 wizyt
pytanie zadane 1 września 2016 w C i C++ przez rubiikk Obywatel (1,900 p.)
otagowane ponownie 3 września 2016 przez draghan
Cześć,

Chciałbym skorzystać trochę z Waszej wiedzy i pomocy. Przyznam się szczerze, że jest to pierwszy taki większy program, który chcę wykonać. Całą aplikację chcę napisać języku C++ i musi ona zawierać m.in. obsługę plików, edycję plików, dodawanie/usuwanie rekordów, wyszukiwanie rekordów i jakieś sortowanie wyświetlanych danych. Myślałem sobie, że dane z pliku można wczytać do listy (biblioteka STL). Tylko zasadnicze pytanie? W jakiej kolejności powinienem zbudować taki program?

1. Stworzenie klas, które odpowiednio będą przechowywać dane (jedna klasa zawierająca informacje o pacjentach, druga o lekarzach, trzecia o jakaś historia chorób pacjenta).

2. Menu z funkcją case.
3. Wczytywanie danych z pliku do listy i wyświetlanie ich na ekran.
4. Zapisywanie do pliku.

Ktoś ma jakieś rady, które ułatwią mi napisanie pierwszego programu?

3 odpowiedzi

0 głosów
odpowiedź 1 września 2016 przez rubiikk Obywatel (1,900 p.)
Mam pytanie. Jak dane wczytane z pliku tekstowego przekazać do vectora?
komentarz 1 września 2016 przez draghan VIP (106,230 p.)
Nie da się jednoznacznie i dobrze odpowiedzieć na to pytanie. Wszystko zależy od formatu pliku, jaki sobie wybierzesz do przechowywania danych.
komentarz 1 września 2016 przez rubiikk Obywatel (1,900 p.)
To będzie zwykły plik tekstowy.
komentarz 1 września 2016 przez draghan VIP (106,230 p.)
No jasne, ale żeby wczytać dane do std::vector, należy mieć je zorganizowane w znanym Ci porządku. Musisz znać strukturę tego pliku - co, gdzie i jak jest zapisane.
komentarz 1 września 2016 przez rubiikk Obywatel (1,900 p.)

Mógłbyś nakierować co dokładnie masz na myśli? Wiesz. Ja myślałem sobie, żeby strukturę pliku wyglądała na początku dość prosto. Tworzę plik, a w środku kilka kolumn, które przechowają mi na samym początku takie dane, jak:

Imię nazwisko
Imię nazwisko
Imię nazwisko
Imię nazwisko

Specjalnie w pliku chcę przechowywać na początku tak małą ilość, żeby zrozumieć samą ideę i działanie kodu. Dopiero później będę rozwijał działanie swojego programu, jak zrozumiem nieszczęsne pliki tekstowe i operowanie na nich. Teraz chcę po prostu z pliku tekstowego wczytać dane i przechować je w vektorze.

 

 

komentarz 1 września 2016 przez unknown Nałogowiec (39,560 p.)

Najprostszy sposób:

#include <vector>
#include <string>
#include <iterator>
#include <fstream>

int main(){
	std::ifstream file{"file.txt"};
	std::vector<std::string> vec{std::istream_iterator<std::string>{file}, std::istream_iterator<std::string>{}};
}

 

komentarz 1 września 2016 przez draghan VIP (106,230 p.)
No i super, podałeś jakiś konkret. Każdy wiersz to osobny rekord z imieniem i nazwiskiem rozdzielonym białym znakiem. :)

Skoro już wiadomo, jak wygląda struktura pliku, trzeba się zastanowić nad przechowywaniem tych danych w pamięci programu.

Wspomniałeś już, że chcesz wykorzystać std::vector. Dobry wybór. :) Teraz tylko trzeba określić, CO ten wektor ma przechowywać - i wtedy będzie można się wziąć za implementację odczytu z pliku.

Proponuję stworzyć klasę, która będzie opakowywała te dane. Wspominałeś coś o lekarzach - domyślam się więc, że to imiona i nazwiska lekarzy. Czy potrafisz napisać taką prostą klasę, która będzie reprezentacją lekarza? :)
komentarz 1 września 2016 przez rubiikk Obywatel (1,900 p.)

Tak, stworzyłem już taką klasę od razu przed napisaniem tematu na tym forum ;) Aktualnie plik patient.h wygląda następująco:

#include <iostream>

using namespace std;

class Patient
{
    string name;
    string surname;
    int age;
};

Hmm... spróbuję teraz coś wykombinować z kodem, który mi podałeś.

komentarz 1 września 2016 przez draghan VIP (106,230 p.)
Jakby co, to ja nie dawałem żadnego kodu. ;P

Masz podstawowy zarys klasy - super. :) Twoja klasa potrzebuje metod dostępowych do pól, przydałby się też jakiś konstruktor ewentualnie.

Kiedy będziesz mógł modyfikować pola Twoich obiektów, będziesz mógł się zabrać za wczytywanie. Póki co nie możesz nic zapisać do zmiennych Patient::name, Patient::surname, Patient::age. Popracuj nad tym. ;)
komentarz 1 września 2016 przez rubiikk Obywatel (1,900 p.)
Dzięki, pokombinuję. Efekty mojej pracy postaram się wrzucić jutro tutaj :P
komentarz 1 września 2016 przez draghan VIP (106,230 p.)
Super. W takim razie czekam. :)
komentarz 2 września 2016 przez rubiikk Obywatel (1,900 p.)

Plik patient.cpp:

#include <iostream>
#include "patient.h"

using namespace std;

void Patient::saveToFile()
{
    cout << endl << "Podaj imie: ";
    cin >> name;
    cout << endl << "Podaj nazwisko: ";
    cin >> surname;
    cout << endl << "Podaj wiek: ";
    cin >> age;
}

void Patient::loadFromFile()
{
    cout << endl << name << " " << surname << " || Wiek: " << age << " lat." << endl;
}

Patient::Patient(string n, string s, int a)
{
    name = n;
    surname = s;
    age = a;
}

Patient::~Patient()
{
    cout << endl << "Destruktor ";
}

Plik patient.h:
 

#include <iostream>

using namespace std;

class Patient
{
    string name;
    string surname;
    int age;

public:

    Patient(string="Imie", string="Nazwisko", int=0);
    ~Patient;

    void loadFromFile();
    void saveToFile();
};

Według mnie teraz wypadałoby w mainie zrobić przechowywanie danych z programu w vetorze, a następnie obsługę plików tekstowych, czyli zapisywanie i wczytywanie do pliku.

komentarz 2 września 2016 przez rubiikk Obywatel (1,900 p.)

I na ten moment, to jest mój kod, który wyświetla dane z pliku tekstowego (nie połączyłem tego jeszcze z klasą, ani z vectorem):

string name, surname;
int age;

int main()
{
     fstream file;
     file.open("patient.txt", ios::in);

     if(file.good() == false)
     {
         cout << "Ten plik nie istnieje - blad.";
         exit(0);
     }

     string line; //przechowuje pobrana linie tekstu:
     int number_line=1;
     while(getline(file, line))
     {
         switch(number_line)
         {
             case 1: name = line; break;
             case 2: surname = line; break;
             case 3: age = atoi(line.c_str()); break;
         }
         number_line++;
     }
    file.close();

    cout << name << " " << surname << " " << age << " lat" << endl;

}

Struktura pliku tekstowego wygląda tak:
 

Imie
Nazwisko
Wiek
Imie
Nazwisko
Wiek

 

1
komentarz 3 września 2016 przez draghan VIP (106,230 p.)

Najpierw zajmijmy się drobnostkami. Uprzedzam - możesz uznać, że na tym etapie niepotrzebnie czepiam się szczegółów. Ale uznaję, że lepiej jest wskazać od razu dobry kierunek. Jeśli chcesz się dowiedzieć tylko o wczytywaniu, przejdź do kolejnego komentarza (kiedy już go napiszę - to może trochę potrwać... Niewykluczone że wpis pojawi się dopiero rano, z uwagi na już późną godzinę - mam nadzieję że wybaczysz).

Najpierw na tapetę weźmy plik nagłówkowy. Pierwsze dwie rzeczy, które zrobiłeś, to włączenie nagłówka iostream i włączenie identyfikatorów z grupy std:: do globalnej przestrzeni nazw. Co do using namespace std; polecam poczytać trochę tu, tutu czy tu. A więc wywalamy to z nagłówka w ogóle.
Co więc zamiast? Albo każdy identyfikator specyfikuj jawnie (std::string), albo użyj deklaracji using (using std::string) - chociaż to drugie rozwiązanie również jest w nagłówkach niewskazane.
A dlaczego wspominam o iostream? Ano... Znajdź mi, proszę, fragment kodu, który w nagłówku korzysta z biblioteki strumieni wejścia-wyjścia. :) Jak znajdziesz, dostaniesz ode mnie piwko. Za to korzystasz z innej części biblioteki standardowej - typu std::string. To właśnie nagłówek zawierający ten interfejs powinieneś dołączyć.

Nie zastosowałeś też ochrony przed wielokrotnym dołączaniem. System plików nagłówkowych w C i C++ jest odrobinę nie na czasie. Jeśli do jednego pliku kompilowanego (*.cpp) dołączysz dwa lub więcej razy ten sam nagłówek z deklaracją interfejsu klasy czy też innym pokrewnym tworem, kompilator zaprotestuje, twierdząc że nie możesz mieć w programie dwóch takich samych klas.
Oczywiście powiesz zaraz, że przecież nie będziesz dołączał dwa razy tego samego nagłówka i problem z głowy... Ale dołączanie nagłówków jest rekursywne - włączenie do pliku X nagłówka, który includuje inny nagłówek, spowoduje w efekcie dołączenie do pliku X wspomnianych dwóch nagłówków. A programy większe niż te z początków nauki z reguły potrzebują takich wzajemnych powiązań.
Żeby się przed tym ustrzec, wystarczy tylko dopisać do nagłówka dyrektywę preprocesora pragma once. Nie jest ona częścią standardu (dyrektywy pragma są rozszerzeniami zależnymi od kompilatora), jednak wszystkie szanujące się kompilatory wspierają ten ficzer. Jeśli trafisz na taki, który tego nie robi, poszukaj w Sieci hasła "inlcude guard".

Dobrym zwyczajem jest określanie najpierw publicznych, dopiero później prywatnych czy chronionych składowych. To ułatwia czytanie kodu. Patrzysz na czyjąś klasę i od razu rzuca Ci się w oczy publiczny interfejs, zaś pozostałe składowe nie powinny Cię wiele obchodzić (jeśli oczywiście jesteś tylko użytkownikiem klasy - bo jeśli ją projektujesz, to jednak powinieneś wiedzieć, co i dlaczego jest ukryte ;).

Dorzucę jeszcze moją osobistą preferencję: zawsze w deklaracjach używam nazwanych parametrów. Dzięki temu rzut okiem na sam nagłówek pozwala na zrozumienie przeznaczenia każdego z argumentów.
Idąc dalej tym tropem, należałoby nazwać zmienne jakoś bardziej sensownie, niż n, s czy a. Można tu śmiało użyć nazw name, surname czy age - C++ posiada mechanizmy, które jednoznacznie określają przynależność danego identyfikatora, więc nie będą one kolidowały ze zmiennymi instancyjnymi.

A więc lekko liftingowany nagłówek będzie wyglądał tak:

// patient.h
#pragma once
#include <string>
  
class Patient
{
public: 
    Patient(std::string name = "Imie", std::string surname = "Nazwisko", int age = 0);
    ~Patient();
 
    void loadFromFile();
    void saveToFile();

private:
    std::string name;
    std::string surname;
    int age;
};

Teraz plik z definicją klasy Patient. Na początku brakuje mi włączenia nagłówka string.
Usuwamy dyrektywę using, w zamian wrzucając tylko deklaracje używanych identyfikatorów.

Do wypisania znaku nowej linii staramy się nie używać std::endl, tylko manipulatora '\n' (wyjaśnienie).

W oczy rzuca się pewna niekonsekwencja znaczeniowa. Metoda Patient::saveToFile() powinna kierować dane z programu na zewnątrz, natomiast Patient::loadFromFile() winna przyjmować dane z zewnątrz. U Ciebie jest odwrotnie - więc po prostu zamienię zawartość tych dwóch metod.

Kolejny jest konstruktor. W kontekście wcześniejszej zmiany nazw parametrów z deklaracji, należałoby takie zmiany wprowadzić i tu.
Wtedy konieczne będzie bardziej specyficzne odwołanie się do zmiennych instancyjnych. Można to zrobić tak:

Patient::Patient(string name, string surname, int age)
{
    this->name = name;
    this->surname = surname;
    this->age = age;
}

ale dużo bardziej eleganckim rozwiązaniem jest skorzystanie z listy inicjalizacyjnej. W takim wypadku oszczędzisz trochę procesora (przypisanie w ciele konstruktora to zwykła operacja przypisania na już utworzonym i zainicjalizowanym obiekcie, zaś lista inicjalizacyjna służy do nadawania wartości już w momencie tworzenia obiektów, poza tym pozbywasz się późniejszego przypisania). No i w taki sposób zostawiasz ciało konstruktora puste, co może skłonić kompilator do pewnych optymalizacji.

Destruktor w tej klasie nie jest za bardzo potrzebny, ale niech sobie istnieje. ;)

Efekt końcowy powyższych operacji:

// patient.cpp
#include <iostream>
#include <string>
#include "patient.h"

using std::string;
using std::cin;
using std::cout;

void Patient::saveToFile()
{
    cout << '\n' << name << " " << surname << " || Wiek: " << age << " lat.\n";
}

void Patient::loadFromFile()
{
    cout << "\nPodaj imie: ";
    cin >> name;
    cout << "\nPodaj nazwisko: ";
    cin >> surname;
    cout << "\nPodaj wiek: ";
    cin >> age;
}

Patient::Patient(string name, string surname, int age):
    name{name}, surname{surname}, age{age}
{
}

Patient::~Patient()
{
    cout << "\nDestruktor ";
}

No i plik main.

Na początek - sekcja dołączanych bibliotek - iostream, fstream, string, patient (tak, Twoją klasę można nazwać małą biblioteką :)
Później porządek z namespacem (tym razem jawna specyfikacja - zobacz że tak również jest czytelnie!) i std::endl.

Pozbywamy się zmiennych globalnych - wystarczy ograniczyć ich zasięg do funkcji main() (dlaczego...? a poszukaj w Sieci - nietrudno znaleźć dobrą odpowiedź).

Koniecznie należy wyrzucić funkcję exit() - to jest szkodliwy przeżytek z C. Do wyłączenia programu wystarczy zakończyć funkcję main(), a to zrobimy najzwyklejszym returnem. Funkcja exit() uniemożliwia wywołanie destruktorów Twoich obiektów, a destruktory wykonują bardzo ważną robotę.

Jeśli już przy "przeżytkach" z C jesteśmy, to wypadałoby zamienić atoi() na jej odpowiednik z C++, przeznaczony do pracy z typem std::string - std::stoi()

Jeśli nie planujesz ponownego użycia uchwytu do pliku, nie potrzebujesz wywoływać na nim metody close() - wszystko ładnie posprząta i pozamyka destruktor.
A skoro mowa o zwalnianiu pewnych zasobów w destruktorze w kontekście uchwytu do pliku, powiedzieć można od razu o zasadzie RAII, która w skrócie mówi o tym, żeby zasoby pozyskiwać przy konstrukcji obiektu, zaś przy jego niszczeniu je oddawać. W związku z tym można pominąć metodę open() i podać odpowiednie argumenty do konstruktora.

Po tych modyfikacjach stylistycznych, kod wygląda tak:

// main.cpp
#include <string>
#include <iostream>
#include <fstream>
#include "patient.h"

int main()
{
    std::string name, surname;
    int age;
    std::fstream file("patient.txt", std::ios::in);

    if(file.good() == false)
    {
        std::cout << "Ten plik nie istnieje - blad.";
        return 0;
    }
    std::string line; //przechowuje pobrana linie tekstu:
    int number_line=1;
    while(getline(file, line))
    {
        switch(number_line)
        {
        case 1:
            name = line;
            break;
        case 2:
            surname = line;
            break;
        case 3:
            age = std::stoi(line);
            break;
        }
        number_line++;
    }
    std::cout << name << " " << surname << " " << age << " lat\n";
}

To tyle tytułem kosmetyki. Może wydawać się sporo, ale sądzę że to jest ważne.

1
komentarz 3 września 2016 przez draghan VIP (106,230 p.)

Pierwszym krokiem, który należałoby wykonać, jest odcięcie się od stałej ilości linii w pliku. W tej chwili masz na sztywno ustaloną ilość na trzy linie - przyznaj że nie jest to satysfakcjonujące rozwiązanie. Bardzo prosto można to osiągnąć, wykorzystując operator modulo - jeśli podzielisz aktualną liczbę wczytanych linii na trzy, to reszta z takiego dzielenia może być równa 0, 1 lub 2 - w taki sposób rozróżnisz co w danej linii się znajduje, niezależnie od rzeczywistego numeru linii. Dla poprawności tego sposobu należy zacząć od zerowego numeru linii.
Można teraz wypisywać na bieżąco każdego pacjenta, który został wczytany w całości z pliku. Aby to osiągnąć, trzeba tylko przenieść wypisywanie pacjenta na ekran w miejsce, w którym następuje wczytanie kompletu danych o pacjencie - czyli tam, gdzie zapisujemy sobie jego ostatnią daną - wiek.
Zmieniony fragment wczytywania wygląda u mnie tak:

    int number_line=0;
    while(getline(file, line))
    {
        switch(number_line % 3)
        {
        case 0:
            name = line;
            break;
        case 1:
            surname = line;
            break;
        case 2:
            age = std::stoi(line);
            std::cout << name << " " << surname << " " << age << " lat\n";
            break;
        }
        number_line++;
    }

Mamy teraz prawie to, o co chodziło - wczytujemy wszystkie dane pacjentów z pliku, tylko nie zapisujemy ich nigdzie do dalszych operacji, a jedynie rzucamy je na ekran. Brakuje tylko dwóch rzeczy. Upragnionego wektora i wykorzystania klasy Patient.

Zajmijmy się wpierw wektorem. W takiej postaci będziesz potrzebował trzech wektorów, po jednym dla imienia, nazwiska i wieku. Każda pozycja w każdym z wektorów będzie powiązana z danymi jednego pacjenta - i tak pozycja zerowa w wektorze imię będzie dopełnieniem zerowej pozycji w wektorze wiek i nazwisko - dopiero razem te trzy zmienne o indeksie zero dadzą kompletną informację o pacjencie.
Trochę takie porozrzucanie jest mało wygodne, ale na początek musi wystarczyć.

Do dzieła. Dołączamy nagłówek vector i definiujemy trzy instancje std::vector. Jest to szablon klasy, więc musi dostać informację o typie danych, które ma przechowywać - to własnie oznacza się w nawiasach kątowych.

// ...
#include <vector>
// ...

int main()
{
    std::vector<std::string> names;
    std::vector<std::string> surnames;
    std::vector<int> ages;
    // ...

Zostało tylko zmienić lekko zachowanie pętli, wczytującej dane z pliku. Zamiast wrzucania informacji na ekran, zapakujmy je do odpowiednich wektorów.

No i przy tym dobrze byłoby wypisać zawartość na ekranie, żeby było wiadomo że to naprawdę działa jak należy.

#include <string>
#include <iostream>
#include <fstream>
#include <vector>
#include "patient.hpp"

int main()
{
    std::vector<std::string> names;
    std::vector<std::string> surnames;
    std::vector<int> ages;

    std::string name, surname;
    int age;
    std::fstream file;

    file.open("patient.txt", std::ios::in);
    if(file.good() == false)
    {
        std::cout << "Ten plik nie istnieje - blad.";
        return 0;
    }
    std::string line; //przechowuje pobrana linie tekstu:
    int number_line=0;
    while(getline(file, line))
    {
        switch(number_line % 3)
        {
        case 0:
            name = line;
            break;
        case 1:
            surname = line;
            break;
        case 2:
            age = std::stoi(line);

            names.push_back(name);
            surnames.push_back(surname);
            ages.push_back(age);
            break;
        }
        number_line++;
    }

    for(size_t i = 0; i < names.size(); ++i)
    {
        std::cout << names[i] << " " << surnames[i] << " " << ages[i] << " lat\n";
    }
}

W takiej postaci jest to już w miarę funkcjonalne. Mało eleganckie jest trzymanie trzech tablic z danymi pacjentów. Ale już niedługo się tym zajmiemy.

Tylko najpierw musisz nieco podrasować swoją klasę pacjenta, bo charakteryzuje się kilkoma wadami.
Nie podoba mi się, że klasa ta posiada metody zapisu i odczytu z pliku. Ta klasa powinna reprezentować pacjenta - a przecież pacjent nie powinien mieć obowiązku się zapisywać czy wczytywać z pliku. To jest osoba - ma imię, nazwisko, wiek, być może jakieś schorzenia... Ale z plikiem nie powinien mieć nic do czynienia. Obsługa przechowywania danych nie leży w zakresie klasy Patient, powinna być częścią innego mechanizmu (np. klasy zarządzającej pacjentami, czy po prostu częścią funkcji main). Pacjent mógłby jedynie zostać poproszony o udostępnienie danych do zapisu pliku, żeby zewnętrzny mechanizm mógł takie dane zapisać.
Przydałaby się również metoda, wypisująca wizytówkę pacjenta na ekranie.
Mam pewne obiekcje co do typu danych, który przeznaczyłeś na wiek pacjenta. W tej chwili możesz bez problemu zapisać do zmiennej Patient::age wartości np. -20, czy 3000. Bardziej odpowiedni zakresowo byłby typ unsigned char, który może pomieścić wartości z zakresu <0; 255>.

Ale to już zadania dla Ciebie. :)

komentarz 7 września 2016 przez rubiikk Obywatel (1,900 p.)

W tym momencie tak wygląda mój plik main.cpp:

#include <string>
#include <vector>
#include <fstream>
#include <stdlib.h>
#include <iostream>

int main()
{
    std::vector<std::string> names;
    std::vector<std::string> surnames;
    std::vector<int> ages;
    std::vector<unsigned int> phone_numbers;

    std::string name, surname;
    unsigned char age;
    std::fstream file;
    unsigned int phone_number;

    file.open("patient.txt", std::ios::in);
    if(file.good() == false)
    {
        std::cout << "Ten plik nie istnieje - blad.";
        return 0;
    }
    std::string line; //przechowuje pobrana linie tekstu:
    int number_line=0;
    while(getline(file, line))
    {
        switch(number_line % 4)
        {
        case 0:
            name = line;
            break;
        case 1:
            surname = line;
            break;
        case 2:
            age = atoi(line.c_str());
            break;
        case 3:
            phone_number = atoi(line.c_str());

            names.push_back(name);
            surnames.push_back(surname);
            ages.push_back(age);
            phone_numbers.push_back(phone_number);
            break;
        }
        number_line++;
    }

    for(size_t i = 0; i < names.size(); ++i)
    {
        std::cout << names[i] << " " << surnames[i] << " " << ages[i] << " lat || " << "Numer telefonu: " << phone_numbers[i] << "\n";
    }
}

Plik patient.cpp:

#include <iostream>
#include <string>
#include <stdlib.h>
#include "patient.h"

using std::string;
using std::cin;
using std::cout;

Patient::Patient(string name, string surname, unsigned char age, unsigned int phone_number):
    name{name}, surname{surname}, age{age}, phone_number{phone_number}
{
}

Patient::~Patient()
{
    cout << "\nDestruktor ";
}

Plik patient.h

#pragma once
#include <string>

class Patient
{
public:
    Patient(std::string name = "Imie", std::string surname = "Nazwisko", unsigned char age = 0, unsigned int phone_numer = 0);
    ~Patient();

    //void saveToFile();

private:
    std::string name;
    std::string surname;
    unsigned char age;
    unsigned int phone_number;
};

 

1
komentarz 10 września 2016 przez draghan VIP (106,230 p.)

Nie dołączaj nagłówka stdlib.h (nagłówek języka C), tylko cstdlib (nagłówek z biblioteki standardowej C, przystosowany do języka C++).

W definicji wektora ages nie zmieniłeś typu danych na odpowiedni do wieku - nadal możesz tam wrzucić -9000 lat. ;)

Można się spierać, czy unsigned int jest odpowiednim typem do przechowywania informacji o numerze telefonu, ale na Twoje potrzeby myślę że nie ma co sobie tym zaprzątać głowy. :P

Konwersja z std::string do unsigned char jest w miarę okej. Zauważ, że nie sprawdzasz nigdzie, czy konwersja się udała - takie działanie jest w zasadzie niedopuszczalne i należy to poprawić. Popatrz na dokumentację i zaradź temu.

No i nadal należy poprawić klasę pacjenta, żeby móc zabrać się za dalsze działania na nim - w tej chwili jedyne co możesz zrobić z obiektem typu Patient, to go utworzyć. Popatrz na mój post wyżej i dodaj odpowiednie metody.

W razie pytań - pytaj tutaj. ;)

komentarz 11 września 2016 przez rubiikk Obywatel (1,900 p.)

Masz rację, moja wina. Zapomniałem w wektorze napisać:

std::vector<unsigned int> ages;

Już sobie popatrzę na dokumentację i będę bawił się dalej ;)

komentarz 11 września 2016 przez rubiikk Obywatel (1,900 p.)
W sumie, to jeszcze się zastanawiam. Sprawdziłem, czy konwersja jest poprawna, ale jaki jest sens dodawać to w programie przy użyciu std::cout? Nie można tych informacji wydrukować w jakiś sposób w debuggu?
komentarz 11 września 2016 przez draghan VIP (106,230 p.)
Można - do tego służy strumień std::cerr. Ale dla aplikacji konsolowych jest on domyślnie skojarzony z tym samym wyjściem, co std::cout. Moim zdaniem nie powinieneś tego ukrywać przed użytkownikiem - jeśli program schodzi na złą drogę, użytkownik powinien wiedzieć, co jest nie tak. W ten sposób może on samodzielnie spróbować naprawić problem, zgłosić błąd do twórców czy szukać pomocy w Sieci.
komentarz 12 września 2016 przez rubiikk Obywatel (1,900 p.)
edycja 13 września 2016 przez rubiikk
No dobra, rozumiem. Czyli teraz zajmuję się klasą, bo w tym momencie moje dane z pliku zapisywane są tylko i wyłącznie w wektorze i nie korzystają w ogóle z klasy, mam rację?

Obecnie zrobiłem zapisywanie danych do pliku i zmienna ages specjalnie jest typy unsigned int. Miałem drobne problemy z konwersją znaków i stwierdziłem, że nie będę sobie na razie utrudniał nauki.
komentarz 13 września 2016 przez draghan VIP (106,230 p.)

Problem był, rozumiem, z operatorami strumieni (>>, <<)? Można tu zastosować kilka wyjść - jednym z nich jest rzutowanie, innym przeładowanie tychże operatorów. Można też zastosować inny typ danych - ale tę kwestię poruszono już w innym wątku, w którym dziś coś napisałem.

Jak napisałeś - teraz powinieneś zabrać się za dopracowanie swojej klasy pacjenta. Metoda, która zwraca string z jego wszystkimi danymi byłaby tutaj dobrym posunięciem - te dane mógłbyś wysyłać do mechanizmu zapisującego plik z pacjentami. Równie pomocna byłaby funkcja, drukująca na ekranie informacje (human-readable) o danym pacjencie.

Co do konstrukcji Twojego pliku main - nie widzi mi się tam parę rzeczy, ale to kwestie organizacyjne (np. w mainie wywołujesz jedną jedyną funkcję - menu - czy to oznacza że cała Twoja aplikacja to tylko menu?). Wprawę w organizacji kodu aplikacji nabierzesz wraz z doświadczeniem - jak na początek nauki jest całkiem nieźle.

Co oznacza ID pacjenta? Do czego je w aplikacji zamierzasz wykorzystać?

komentarz 13 września 2016 przez rubiikk Obywatel (1,900 p.)
ID pacjenta chciałbym wykorzystać do drugiej klasy i drugiego pliku tekstowego, który będzie przechowywał informacje o chorobach pacjenta lub historii jego leczenia. Chcę w jakiś sposób połączyć ID pacjenta z konkretną chorobą/wpisem o historii leczenia, a ich może być wiele (relacja jeden do wielu, tak?).

Chyba jeszcze nie nabrałem odpowiedniej wprawy i doświadczenia i nie za bardzo rozumiem, co mam zrobić z klasą. Mam stworzyć metodę w klasie, która zwraca stringa ze wszystkimi danymi konkretnej osoby? Jeżeli tak, to jaki jest tego cel?
komentarz 13 września 2016 przez draghan VIP (106,230 p.)

Relacja pacjenci - choroby to raczej wiele do wielu, ale już pacjenci - historie leczenia jeden do wielu.

Chyba jeszcze nie nabrałem odpowiedniej wprawy i doświadczenia i nie za bardzo rozumiem, co mam zrobić z klasą. Mam stworzyć metodę w klasie, która zwraca stringa ze wszystkimi danymi konkretnej osoby? Jeżeli tak, to jaki jest tego cel?

Dokładnie tak. Chodzi o metodę (np. getInfo()), która zwróci string z danymi, które wrzucisz do pliku.

Twoim celem jest utworzenie wektora obiektów klasy Patient. Jeśli będziesz chciał zapisać wszystkich pacjentów z wektora do pliku, to po prostu robisz prostą pętelkę:

std::vector <Patient> patients;

// ...

ofstream file( /* ... */);
// ...
for(const auto &patient : patients) // zakresowa pętla for - jeśli jeszcze nie znasz, to ta pętla przebiega po wszystkich zmiennych w wektorze patients i każdą z nich "umieszcza" chwilowo w zmiennej patient
{
   file << patient.getInfo();
}
// ...

 

komentarz 13 września 2016 przez rubiikk Obywatel (1,900 p.)
Przyznam szczerze, że w tym momencie się zgubiłem i nie za bardzo wiem, jaki podjąć kolejny krok. Mam teraz 5 wektorów i każdy z nich przechowuje inne dane. Teraz mam umieścić 5 wektorów w jednym wektorze Patients? Dobrze rozumiem, czy gdzieś się po drodze zgubiłem?
1
komentarz 13 września 2016 przez draghan VIP (106,230 p.)
Zagubiłeś się. Klasa Patient posiada wszystkie wymagane dane dla każdego z pacjentów - a więc tworząc jeden wektor obiektów Patient będziesz miał komplet informacji o pacjentach i będziesz mógł wyrzucić te 5 wektorów z porozrzucanymi danymi.

Już czujesz czy jeszcze nie do końca?
komentarz 13 września 2016 przez rubiikk Obywatel (1,900 p.)

Chyba czuję, ale w takim razie dobrze by było, żebym zmienił całkowicie strukturę pliku, np:

[DB:001] ID, Imię, Nazwisko, Wiek, Numer Telefonu
[DB:002] ID, Imię, Nazwisko, Wiek, Numer Telefonu
etc.

 

komentarz 13 września 2016 przez draghan VIP (106,230 p.)
Dlaczego? Obecna struktura daje radę. Łańcuch, zwracany przez funkcję, może przecież zawierać przełamania linii.
komentarz 13 września 2016 przez rubiikk Obywatel (1,900 p.)

No dobra, to w takim razie przy strukturze pliku nic nie zmieniam i zostawiam tak, jak jest, czyli:

ID
Imię
Nazwisko
Wiek
Numer Telefonu

W takim razie muszę zmodyfikować moją funkcję WczytywanieDanych o to, co napisałeś?

1
komentarz 13 września 2016 przez draghan VIP (106,230 p.)
Najpierw metoda klasy, o której rozmawiamy. Bo bez niej nie dostaniesz się do danych pacjenta - wszak wszystkie są prywatne (i tak powinno zostać).

A potem modyfikacja zarówno wczytywania danych (tutaj możesz mieć problem z dodaniem obiektu Patient do wektora, ale myślę że sobie poradzisz) jak i zapisywania danych (gdzieś wyżej już jakiś wstępny szkic Ci przedstawiłem).
komentarz 14 września 2016 przez rubiikk Obywatel (1,900 p.)

Tak szczerze mówiąc, to nie za bardzo wiem jak to zrobić. W mainie mam 5 intów do których zapisywane są dane po wczytaniu i wyświetleniu danych. Może powinienem najpierw wczytać dane do programu, inną opcją w menu je wyświetlać/zapisywać? W tym momencie stworzyłem sobie funkcję getInfo w klasie, ale kompletnie nie wiem, co mam zrobić dalej. Zapisać tutaj w wektorze Patients wszystkie dane o pacjentach z pliku?

void Patient::getInfo()
{
    cout << "Podaj numer ID pacjenta: ";
    cin >> id;
}

 

1
komentarz 17 września 2016 przez draghan VIP (106,230 p.)

Pokażę Ci, jak należy zmodyfikować Twój aktualny program, aby korzystał z Twojej klasy Patient.

Na początek dodałem dwie metody do klasy:

    std::string get_data_string() const;
    void say_hello() const;

Pierwsza służy do tego, aby zebrać wszystkie dane z obiektu i wypluć stringa, którego możesz wrzucić do pliku i tym samym zapisać dane tego pacjenta.
Druga przedstawia pacjenta, drukując informacje o nim na ekranie.
Modyfikatory const za listą parametrów funkcji obiecują kompilatorowi, że nie będą modyfikowały obiektu, a jedynie jakoś nieinwazyjnie przetworzą jego dane.

Definicje tych funkcji są bardzo proste. Nowością dla Ciebie może być stringstream, ale myślę że łatwo go ogarniesz - krótkie wyjaśnienie masz w komentarzu.

Usunąłem destruktor, który tylko zaciemniał wydruki programu.

No i w oczy powinny Ci się rzucić zmiany w pliku main.cpp.

// patient.h
#pragma once
#include <string>

class Patient
{
public:
    Patient(int id, std::string name = "Imie", std::string surname = "Nazwisko", unsigned int age = 0, unsigned int phone_numer = 0);

    std::string get_data_string() const;
    void say_hello() const;

private:
    int id;
    std::string name;
    std::string surname;
    unsigned int age;
    unsigned int phone_number;
};

// patient.cpp
#include <iostream>
#include <string>
#include <sstream> // +
#include <cstdlib> // !
#include "patient.h"

using std::string;
using std::cin;
using std::cout;

Patient::Patient(int id, string name, string surname, unsigned int age, unsigned int phone_number):
    id{id}, name{name}, surname{surname}, age{age}, phone_number{phone_number}
{
}

string Patient::get_data_string() const
{
    std::stringstream data; // obiekt strumienia łańcucha - coś jak cout dla std::string
    data << id << '\n';
    data << name << '\n';
    data << surname << '\n';
    data << age << '\n';
    data << phone_number << '\n';

    return data.str(); // metoda std::stringstream::str() zwraca ze strumienia obiekt std::string
}

void Patient::say_hello() const
{
    cout << "ID: [" << id << "] " << name << " " << surname << " " << age << " lat || " << "Numer telefonu: " << phone_number << "\n";
}
// main.cpp
#include <string>
#include <vector>
#include <fstream>
#include <cstdlib>
#include <iostream>
#include "patient.h"

void WczytywanieDanych()
{
    std::vector<Patient> patients;
    int id;
    std::string name, surname;
    unsigned char age;
    unsigned int phone_number;

    std::ifstream file("patient.txt");
    if(file.good())
    {
        std::string line; //przechowuje pobrana linie tekstu:
        int number_line = 0;
        while(getline(file, line))
        {
            switch(number_line % 5)
            {
            case 0:
                id = atoi(line.c_str());
                break;
            case 1:
                name = line;
                break;
            case 2:
                surname = line;
                break;
            case 3:
                age = atoi(line.c_str());
                break;
            case 4:
                phone_number = atoi(line.c_str());

                patients.push_back(Patient{id, name, surname, age, phone_number});
                break;
            }
            number_line++;
        }

        for(size_t i = 0; i < patients.size(); ++i)
        {
            patients[i].say_hello();
        }
    }
    else
    {
        std::cout << "Ten plik nie istnieje - blad.";
    }
}

void SaveToFile()
{
    int id;
    std::string name, surname;
    unsigned int ages = 0;
    unsigned int phone_number;
    std::cout << "Podaj identyfikator: ";
    std::cin >> id;
    std::cout << "Podaj imie: ";
    std::cin >> name;
    std::cout << "Podaj nazwisko: ";
    std::cin >> surname;
    do
    {
        std::cout << "Podaj wiek: ";
        std::cin >> ages;
        if (ages > 125)
        {
            system("CLS");
            std::cout << "Za duzy wiek. Podaj poprawna wartosc! \n \n";
        }
    }
    while (ages > 125);
    std::cout << "Podaj numer telefonu: ";
    std::cin >> phone_number;
    std::ofstream file("patient.txt", std::ios::app);

    if(file.good())
    {
        Patient patient{id, name, surname, ages, phone_number};
        file << patient.get_data_string();
        file.close();
    }
    else
    {
        std::cout << "Ten plik nie istnieje - blad.";
    }
}

void Menu()
{
    int zmienna;
    std::cout << "1. Wyswietl dane o pacjentach. \n";
    std::cout << "2. Wprowadz nowego pacjenta. \n \n";
    std::cout << "Twoj wybor: ";
    std::cin >> zmienna;
    system("CLS");
    switch(zmienna)
    {
    case 1:
    {
        WczytywanieDanych();
        std::cout << "\nAby wrocic do menu kliknij przycisk: [1]. ";
        int choise;
        std::cin >> choise;
        if (choise = 1)
        {
            system("CLS");
            Menu();
        }
    }
    break;
    case 2:
    {
        SaveToFile();
        std::cout << "\nAby wrocic do menu kliknij przycisk: [1]. ";
        int choise;
        std::cin >> choise;
        if (choise = 1)
        {
            system("CLS");
            Menu();
        }
    }
    break;
    default:
        std::cout << "Zly wybor - wybierz ponownie.";
    }
}

int main()
{
    Menu();
}

Obsługa tego programu jest mało intuicyjna. Nie ma żadnej opcji "wyjdź", co by się jednak przydało. ;)

No i pytanie: jesteś pewny, że chciałeś w warunku przypisywać wartość do zmiennej choise (po angielsku "wybór" to jest "choice" ;)?

if (choise = 1)

W ten sposób zawsze przypisujesz tej zmiennej wartość 1, co w efekcie zawsze spowoduje że warunek będzie prawdziwy. ;)

Jeśli masz jakieś pytania, to pytaj śmiało.

0 głosów
odpowiedź 1 września 2016 przez manjaro Nałogowiec (37,390 p.)
Zastanów się nad rozwiązaniem lepszym i nowocześniejszym. Zamiast plików tekstowych baza danych sqlite. Nie ma czego się bać operacje na bazie danych są podobne jak na plikach. Otwierasz bazę operujesz na danych i zamykasz bazę. Prawie to samo a jesteś 20 lat do przodu ;)
komentarz 1 września 2016 przez draghan VIP (106,230 p.)
Ręczne zakodowanie parsowania plików jest rozwijające. Owszem - jak najbardziej baza danych jest rozwiązaniem lepszym technologicznie niż pliki plain-text - ale i te pliki są potrzebne.
komentarz 1 września 2016 przez rubiikk Obywatel (1,900 p.)
Na pewno zaraz po plikach tekstowych wezmę się za bazy danych - wszystko po kolei. Na razie męczę się z jednym problemem, czyli z przestarzałą technologią i plikami :D
komentarz 17 września 2016 przez efiku Szeryf (75,160 p.)
edycja 17 września 2016 przez efiku
No ale co mu szkodzi zrobić interfejs do operacji na bazie? :d

Apka -> interfejs bazy -> implementacja operacji na bazie txt|sqlite|mysql

Ale to taki wyższy lvl :P
0 głosów
odpowiedź 13 września 2016 przez rubiikk Obywatel (1,900 p.)
edycja 13 września 2016 przez rubiikk

Cześć, witajcie ponownie. Odświeżam trochę ten temat i proszę wszystkich o jakieś rady. Poprawiłem trochę błędów, obecnie wyświetlają mi się dane z pliki i zapisują do wektora. Mam również klasę i jak rozumiem w tym momencie moje dane z pliku zapisywane są tylko i wyłącznie w wektorze i nie korzystają w ogóle z klasy, mam rację? Co do zapisywania danych do pliku, to będę musiał popracować w jakiś sposób nad automatycznym tworzeniem unikalnego klucza pacjentów, czyli ID. Może powinienem zrobić menu w inny sposób? Najpierw czytuję zawartość plików do wektora za pomocą jednego z wyborów w głównym menu, a dopiero potem mogę działać z tymi danymi, czyli:
1 - wyświetlanie
2 - zapisywanie danych do pliku
3 - wyszukiwanie danych w pliku
4 - sortowanie danych
5 - usuwanie

Aktualnie tak prezentuje się mój kod. Proszę o wszelkie rady i nakierowania, czy mój tok myślenia jest dobry.

Plik patient.cpp:

#include <iostream>
#include <string>
#include <stdlib.h>
#include "patient.h"

using std::string;
using std::cin;
using std::cout;

Patient::Patient(int id, string name, string surname, unsigned char age, unsigned int phone_number):
    id{id}, name{name}, surname{surname}, age{age}, phone_number{phone_number}
{
}

Patient::~Patient()
{
    cout << "\nDestruktor ";
}

Plik patient.h:

#pragma once
#include <string>

class Patient
{
public:
    Patient(int id, std::string name = "Imie", std::string surname = "Nazwisko", unsigned char age = 0, unsigned int phone_numer = 0);
    ~Patient();

    //void saveToFile();

private:
    int id;
    std::string name;
    std::string surname;
    unsigned char age;
    unsigned int phone_number;
};

 

komentarz 13 września 2016 przez rubiikk Obywatel (1,900 p.)
edycja 13 września 2016 przez rubiikk

Plik main (po drobnych zmianach):

#include <string>
#include <vector>
#include <fstream>
#include <cstdlib>
#include <iostream>

void WczytywanieDanych()
{
    std::vector<int> ids;
    std::vector<std::string> names;
    std::vector<std::string> surnames;
    std::vector<unsigned int> ages;
    std::vector<unsigned int> phone_numbers;

    int id;
    std::fstream file;
    std::string name, surname;
    unsigned char age;
    unsigned int phone_number;

    file.open("patient.txt", std::ios::in);
    if(file.good() == false)
    {
        std::cout << "Ten plik nie istnieje - blad.";
        //return NULL;
    }

    std::string line; //przechowuje pobrana linie tekstu:
    int number_line = 0;
    while(getline(file, line))
    {
        switch(number_line % 5)
        {
            case 0:
                id = atoi(line.c_str());
                break;
            case 1:
                name = line;
                break;
            case 2:
                surname = line;
                break;
            case 3:
                age = atoi(line.c_str());
                break;
            case 4:
                phone_number = atoi(line.c_str());

                ids.push_back(id);
                names.push_back(name);
                surnames.push_back(surname);
                ages.push_back(age);
                phone_numbers.push_back(phone_number);
                break;
            }
                number_line++;
        }

    for(size_t i = 0; i < names.size(); ++i)
    {
        std::cout << "ID: [" << ids[i] << "] " << names[i] << " " << surnames[i] << " " << ages[i] << " lat || " << "Numer telefonu: " << phone_numbers[i] << "\n";
    }

}

void SaveToFile()
{
    int id;
    std::string name, surname;
    unsigned int ages = 0;
    unsigned int phone_number;

    std::cout << "Podaj identyfikator: ";
    std::cin >> id;
    std::cout << "Podaj imie: ";
    std::cin >> name;
    std::cout << "Podaj nazwisko: ";
    std::cin >> surname;

    do
    {
        std::cout << "Podaj wiek: ";
        std::cin >> ages;

        if (ages > 125)
        {
            system("CLS");
            std::cout << "Za duzy wiek. Podaj poprawna wartosc! \n \n";
        }
    } while (ages > 125);

    std::cout << "Podaj numer telefonu: ";
    std::cin >> phone_number;

    std::fstream file;
    file.open("patient.txt", std::ios::app | std::ios::out);
    if(file.good() == false)
    {
        std::cout << "Ten plik nie istnieje - blad.";
        //return 0;
    }

    file << id << "\n";
    file << name << "\n";
    file << surname << "\n";
    file << ages << "\n";
    file << phone_number << "\n";

    file.close();
}

void Menu()
{
    int zmienna;
    std::cout << "1. Wyswietl dane o pacjentach. \n";
    std::cout << "2. Wprowadz nowego pacjenta. \n \n";
    std::cout << "Twoj wybor: ";
    std::cin >> zmienna;
    system("CLS");

    switch(zmienna)
    {
        case 1:
            {
                WczytywanieDanych();
                std::cout << "\nAby wrocic do menu kliknij przycisk: [1]. ";
                int choise;
                std::cin >> choise;

                if (choise = 1)
                {
                    system("CLS");
                    Menu();
                }
            }
            break;

        case 2:
            {
                SaveToFile();
                std::cout << "\nAby wrocic do menu kliknij przycisk: [1]. ";
                int choise;
                std::cin >> choise;

                if (choise = 1)
                {
                    system("CLS");
                    Menu();
                }
            }
            break;
        default:
            std::cout << "Zly wybor - wybierz ponownie.";
    }
}

int main()
{
    Menu();
}

 

Podobne pytania

0 głosów
1 odpowiedź 137 wizyt
pytanie zadane 12 czerwca 2016 w C i C++ przez rubiikk Obywatel (1,900 p.)
–1 głos
1 odpowiedź 145 wizyt
pytanie zadane 30 kwietnia 2017 w C i C++ przez Aragedens Obywatel (1,120 p.)
0 głosów
1 odpowiedź 281 wizyt
pytanie zadane 8 stycznia 2021 w C i C++ przez bartx3 Początkujący (270 p.)

92,617 zapytań

141,466 odpowiedzi

319,783 komentarzy

61,999 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!

...