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

błąd w tablicy c++

Object Storage Arubacloud
0 głosów
735 wizyt
pytanie zadane 19 kwietnia 2018 w C i C++ przez michalnt Użytkownik (520 p.)

Witam, napisał o to taki kod i chce, żeby zapisywał całą "Bazę" do pliku tekstowego, ale z tego co widzę to błąd występuję w tej linijce 

plik<<Baza[i]<<endl;

o nie mam pojęcia dlaczego

Cały kod:

#include<iostream>
#include<fstream>

using namespace std;

struct Kontakty
{
    string imie;
    string nazwisko;
    int wiek;
    string telefon;
};
const int zakres = 5;
Kontakty Baza[zakres];

void WprowadzImie(int a)
{
    cout<<"Podaj imie: "<<endl;
    cin>>Baza[a].imie;
}

void WprowadzNazwisko(int a)
{
    cout<<"Podaj nazwisko: "<<endl;
    cin>>Baza[a].nazwisko;
}

void WprowadzWiek(int a)
{
    cout<<"Podaj wiek: "<<endl;
    cin>>Baza[a].wiek;
}

void WprowadzTelefon(int a)
{
    cout<<"Podaj telefon: "<<endl;
    cin>>Baza[a].telefon;
}

void WprowadzDane(int licznik)
{
    WprowadzImie(licznik);
    WprowadzNazwisko(licznik);
    WprowadzWiek(licznik);
    WprowadzTelefon(licznik);

}

void WyswietlDane(int licznik)
{
    cout<<"------------------------------------------"<<endl;
    cout<<"Imie: "<<Baza[licznik].imie<<endl;
    cout<<"Nazwisko: "<<Baza[licznik].nazwisko<<endl;
    cout<<"Wiek: "<<Baza[licznik].wiek<<endl;
    cout<<"Telefon: "<<Baza[licznik].telefon<<endl;
}

void Zapis()
{
    fstream plik("BazaWizytowek.txt", ios::app);
    for(int i=0;i<zakres;i++)
    {

        plik<<Baza[i]<<endl;
    }
    plik.close();
}


int main()
{

    for(int i=0;i<zakres;i++)
    {
          WprowadzDane(i);
    }
    for(int i=0;i<zakres;i++)
    {
          WyswietlDane(i);
    }
    Zapis();
    return 0;
}

 

3 odpowiedzi

+5 głosów
odpowiedź 19 kwietnia 2018 przez Patrycjerz Mędrzec (192,320 p.)

Chcąc zapisać dany obiekt do pliku używasz operatora <<, który nie posiada przeciążenia dla twojej struktury. Musisz więc takowe zdefiniować na własną rękę. Wyglądałoby to mniej więcej tak:

std::ostream& operator<<(std::ostream& out, const Kontakty& obj)
{
    return out << "Imie: "     << obj.imie     << std::endl
               << "Nazwisko: " << obj.nazwisko << std::endl
               << "Wiek: "     << obj.wiek     << std::endl
               << "Telefon: "  << obj.telefon  << std::endl;
}
komentarz 19 kwietnia 2018 przez Hiskiel Pasjonat (22,830 p.)
A dlaczego bez referencji tego nie można?
komentarz 19 kwietnia 2018 przez Patrycjerz Mędrzec (192,320 p.)
Spróbuj się domyślić, jest to dość oczywiste.
komentarz 19 kwietnia 2018 przez Hiskiel Pasjonat (22,830 p.)

Ja to rozumiem tak:

#define original(x) addressof(x)

Klasa k1, k2, k3;

stream << copyof(k1) << copyof(k2) <<copyof(k3); //bez referencji

stream << original(k1) << original(k2) << original(k3); //z referencją
//tak mógłbym to w "pseudokodzie" napisać
komentarz 20 kwietnia 2018 przez Hiskiel Pasjonat (22,830 p.)
Nie chcę być nachalny, ale mógłbym dostać odpowiedź?

Według mnie to nie ma nic do rzeczy. IMO stream ma tylko zapisywać jakieś dane,a czy te dane są oryginalne, czy są to kopie, to już go nie obchodzi.
1
komentarz 20 kwietnia 2018 przez RafalS VIP (122,820 p.)
Chyba chodzi o same strumien a nie kopie danych. Kopie danych niczego nie zmieniaja. Nie jestem pewien jak zadziałałby kopiowanie strumiena. Obstawiam ze moglby być problem. Przez referencje pracujesz caly czas na tym samym.
+3 głosów
odpowiedź 20 kwietnia 2018 przez mokrowski Mędrzec (155,460 p.)
edycja 20 kwietnia 2018 przez mokrowski

Kolega @Patrycjerz podał Ci już informację o bezpośrednim błędzie w Twoim kodzie. Mam jeszcze szereg uwag co do dobrych praktyk. Jeśli będziesz chciał je uwzględnić, będzie Ci łatwiej później (a i innym którzy czytają Twój kod):

1. Staraj się nie używać using namespace std; . W małych przykładach to może być dopuszczalne, ale im wcześniej będziesz miał ten nawyk, tym lepiej.

2. Struktura z linii 6 to raczej Kontakt (jeden) a nie Kontakty (wiele). Wiele kontaktów jest w BazieKontaktow. Wszelkie indeksy mają typ std::size_t a nie int.

3. Możesz łączyć std::cout w dłuższe wyrażenie.

4. Nadużywasz std::endl; To wykonuje wyprowadzenie '\n' i opróżnienie strumienia które nie jest potrzebne tak często. U Ciebie wcale.

5. Stosuj std::ifstream lub std::ofstream. To jednoznacznie załatwia flagi otwarcia. Oczywiście możesz je podać ale domyślne (input/output) już masz poprzez rodzaj klasy strumienia.

6. Zbędne zamykanie pliku. Zamknięcie nastąpi po destrukcji obiektu strumienia.

Nie ingerowałem w strukturę Twojego kodu w sposób istotny. Myślę jednak że ta wersja jest czytelniejsza:

#include <iostream>
#include <fstream>
#include <string>
#include <cstddef>
 
constexpr static std::size_t ilosc_kontaktow = 5;

struct Kontakt
{
    std::string imie;
    std::string nazwisko;
    unsigned wiek;
    std::string telefon;
};

Kontakt BazaKontaktow[ilosc_kontaktow];
 
void WprowadzImie(std::size_t indeks)
{
    std::cout << "Podaj imie:\n";
    std::cin >> BazaKontaktow[indeks].imie;
}
 
void WprowadzNazwisko(std::size_t indeks)
{
    std::cout << "Podaj nazwisko:\n";
    std::cin >> BazaKontaktow[indeks].nazwisko;
}
 
void WprowadzWiek(std::size_t indeks)
{
    std::cout << "Podaj wiek:\n";
    std::cin >> BazaKontaktow[indeks].wiek;
}
 
void WprowadzTelefon(std::size_t indeks)
{
    std::cout << "Podaj telefon:\n";
    std::cin >> BazaKontaktow[indeks].telefon;
}
 
void WprowadzDaneKontaktu(std::size_t indeks)
{
    WprowadzImie(indeks);
    WprowadzNazwisko(indeks);
    WprowadzWiek(indeks);
    WprowadzTelefon(indeks);
}
 
void WyswietlDaneKontaktu(std::size_t indeks)
{
    std::cout << std::string(42, '-') << '\n'
              << "Imie: " << BazaKontaktow[indeks].imie << '\n'
              << "Nazwisko: " << BazaKontaktow[indeks].nazwisko << '\n'
              << "Wiek: " << BazaKontaktow[indeks].wiek << '\n'
              << "Telefon: " << BazaKontaktow[indeks].telefon << '\n';
}
 
std::ostream& operator<<(std::ostream& out, const Kontakt& kontakt)
{
    return out << "Imie: " << kontakt.imie << '\n'
               << "Nazwisko: " << kontakt.nazwisko << '\n'
               << "Wiek: " << kontakt.wiek << '\n'
               << "Telefon: " << kontakt.telefon << '\n';
}

void ZapisBazyKontaktow()
{
    std::ofstream plik("BazaWizytowek.txt");
    for(const auto& kontakt: BazaKontaktow) {
        plik << kontakt << '\n';
    }
}
 
int main()
{
    for(std::size_t i = 0; i < ilosc_kontaktow; ++i)
    {
          WprowadzDaneKontaktu(i);
    }
    for(std::size_t i = 0; i < ilosc_kontaktow; ++i)
    {
          WyswietlDaneKontaktu(i);
    }
    ZapisBazyKontaktow();
}

Jeśli coś sugerować, zwróć uwagę że wprowadzasz dane typu std::string i unsigned int (w moim przykładzie). Różnice w funkcjach to tylko komunikat i pole które należy wypełnić. 

Masz także widoczną nadmiarowość w funkcji WyswietlDaneKontaktu i operator<<. Także operowanie numerycznym indeksem w bazie danych nie jest wygodne. 

Zadanie można więc wykonać nieco lepiej:

#include <iostream>
#include <fstream>
#include <string>
#include <cstddef>
 
constexpr static std::size_t ilosc_kontaktow = 1;

struct Kontakt
{
    std::string imie;
    std::string nazwisko;
    unsigned wiek;
    std::string telefon;
};

Kontakt BazaKontaktow[ilosc_kontaktow];

std::string WprowadzNapis(const std::string& komunikat)
{
    std::string napis;
    std::cout << komunikat;
    std::cin >> napis;
    return napis;
}

unsigned WprowadzLiczbe(const std::string& komunikat)
{
    unsigned wartosc;
    std::cout << komunikat;
    std::cin >> wartosc;
    return wartosc;
}
 
void WprowadzDaneKontaktu(Kontakt& kontakt)
{
    kontakt.imie = WprowadzNapis("Imie: ");
    kontakt.nazwisko = WprowadzNapis("Nazwisko: ");
    kontakt.wiek = WprowadzLiczbe("Wiek: ");
    kontakt.telefon = WprowadzNapis("Telefon: ");
}
 
std::ostream& operator<<(std::ostream& out, const Kontakt& kontakt)
{
    return out << "Imie: " << kontakt.imie << '\n'
               << "Nazwisko: " << kontakt.nazwisko << '\n'
               << "Wiek: " << kontakt.wiek << '\n'
               << "Telefon: " << kontakt.telefon << '\n';
}

void ZapisBazyKontaktow(const std::string& NazwaPliku)
{
    std::ofstream plik(NazwaPliku);
    for(const auto& kontakt: BazaKontaktow)
    {
        plik << kontakt << '\n';
    }
}
 
int main()
{
    for(auto& kontakt: BazaKontaktow)
    {
        WprowadzDaneKontaktu(kontakt);
    }
    for(auto& kontakt: BazaKontaktow)
    {
        std::cout << std::string(42, '-')
                  << '\n' << kontakt << '\n';
    }
    ZapisBazyKontaktow("BazaWizytowek.txt");
}

 

 

komentarz 20 kwietnia 2018 przez Hiskiel Pasjonat (22,830 p.)

Nie znam typu danych wiek. Poza tym nie znam ludzi z ujemnym wiekiem, po co ten unsigned? laugh

unsigned wiek;

 

2
komentarz 20 kwietnia 2018 przez mokrowski Mędrzec (155,460 p.)
edycja 20 kwietnia 2018 przez mokrowski
unsigned oznacza unsigned int ... co oznacza że masz jedynie nieujemny wiek bo to typ bez znaku. Pierwotny int mógł przechować wartość ujemną bo był typem ze znakiem.
komentarz 20 kwietnia 2018 przez Hiskiel Pasjonat (22,830 p.)
A to przepraszam. Nie wiedziałem, że samo "unsigned" oznacza "unsigned int"
komentarz 20 kwietnia 2018 przez Eryk Andrzejewski Mędrzec (164,260 p.)

@mokrowski, będę czepialski, ale unsigned int nie przechowuje wyłącznie liczb dodatnich, pamiętajmy o zerze. wink

Chyba lepiej byłoby to określić w ten sposób: przechowuje liczby nieujemne, lub przechowuje liczby bez znaku.

komentarz 20 kwietnia 2018 przez mokrowski Mędrzec (155,460 p.)

@Eryk Andrzejewski. oczywiście być może zbytni skrót myślowy. Niemniej jednak lepiej dla wieku mieć unsigned niż signed... może nawet short signed. Choćby ze względu na kontrolowane przepełnienie tych typów i niekontrolowane typów ze znakiem. Z drugiej strony nie wydało mi się konieczne "honowanie" typu do np. uint8_t (na marginesie ponoć niedawno zmarł najstarszy człowiek w Chinach... 121 lat... czyli teoretycznie int8_t by to zmieścił... ) bo pytała osoba początkująca i nie ma to zbytniego sensu w tym kontekście. Sam przykład także nie jest z gatunku jedyny i ostateczny. Np. nie ma kontroli poprawności wprowadzonych danych. Ważniejsze jednak wydawało mi się pokazanie pytającemu że może odczepić się od magii zmiennej i jako indeksu...

komentarz 20 kwietnia 2018 przez Eryk Andrzejewski Mędrzec (164,260 p.)
No oczywiście, nie neguję tego, że lepiej w tym przypadku użyć unsigned. Tylko ktoś czytając Twój post mógłby sobie dojść do wniosku, że typ unsigned int przechowuje liczby: 1, 2, 3, 4, a zera już nie, dlatego wolałem to doprecyzować.
komentarz 20 kwietnia 2018 przez Beginer Pasjonat (22,110 p.)
To w takim razie czym różni się typ unsignet od typu size_t (niefortunnie nazwany, gdyż myli się z długością stringa lub vectora)?
–1 głos
odpowiedź 20 kwietnia 2018 przez Beginer Pasjonat (22,110 p.)

Za bardzo rozdrobniłeś podział i ilość funkcji, co zaciemnia całość i utrudnia analizę kodu. Proponuję następującą zmianę, na trzy (naturalne) funkcje:

#include<iostream>
#include<fstream>
using namespace std;

struct Kontakty
{
    string imie;
    string nazwisko;
    int wiek;
    string telefon;
};

const int zakres = 5;
Kontakty Baza[zakres];

void WprowadzDane(int a)
{
    cout << "Podaj imie: ";
    cin >> Baza[a].imie;

    cout << "Podaj nazwisko: ";
    cin >> Baza[a].nazwisko;

    cout << "Podaj wiek: ";
    cin >> Baza[a].wiek;

    cout << "Podaj telefon (bez spacji): ";
    cin >> Baza[a].telefon;
}

void WyswietlDane(int b)
{
    cout << "------------------------------------------" << endl;
    cout << "Imie: " << Baza[b].imie << endl;
    cout << "Nazwisko: " << Baza[b].nazwisko << endl;
    cout << "Wiek: " << Baza[b].wiek << endl;
    cout << "Telefon: " << Baza[b].telefon << endl;
}

void Zapis()
{
    ofstream plik("BazaWizytowek.txt");

    for(int i = 0; i < zakres; i++)
    {
     plik << "------------------------------------------" << endl;
     plik << "Imie: " << Baza[i].imie << endl;
     plik << "Nazwisko: " << Baza[i].nazwisko << endl;
     plik << "Wiek: " << Baza[i].wiek << endl;
     plik << "Telefon: " << Baza[i].telefon << endl;
    }
    plik.close();
}

int main()
{
    cout << "** BAZA WIZYTOWEK **" << endl;

    for(int i = 0; i < zakres; i++)
    {
      WprowadzDane(i);
      cout << endl;
    }

    for(int i = 0; i < zakres; i++)
    {
      WyswietlDane(i);
    }

    Zapis();

    return 0;
}

Ponadto:

1. Poprawiłem nieco format wprowadzania danych

2. Usunąłem zmienną licznik, gdyż takiej nazwy używa się zwykle do jakiegoś licznika, a w zadaniu chodziło o prosty indeks tablicy (użyłem litery 'b').

3. Uruchomiłem funkcję zapisu do pliku

komentarz 20 kwietnia 2018 przez Aisekai Nałogowiec (42,190 p.)
Jak wg Ciebie rozdrobnienie funkcji zaciemnia przejrzystość kodu i utrudnia analizę? Jeżeli funkcje są dobrze nazwane, a są (z nazwy funkcji, od razu widać co dana funkcja robi) to taki zabieg polepsza analizę kodu (łatwiej zlokalizować błąd).

Kolejny powód, jest taki że gdyby użytkownik chciał teraz rozbudować aplikację (a tak się powinno, myśleć pisząc aplikację, że kiedyś będzie się ją rozbudowywać) o np zmianę imienia to ma już gotową (działającą, jeżeli przeszła testy) funkcje do zmiany tego imienia. Hipotetycznie, jeżeli funkcje byłyby bardziej rozbudowane - znacząco polepsza to możliwości rozbudowy oraz bardziej, przynajmniej dla mnie, sprawia to że funkcja jest odpowiedzialna tylko za jedną rzecz (SOLID i DRY).
komentarz 20 kwietnia 2018 przez Beginer Pasjonat (22,110 p.)
To proste zadanie ma trzy proste, naturalne funkcje:

1. Wprowadzenie danych

2. Wypisanie bazy wizytówek

3. Zapis do pliku

Nie było więc potrzeby rozbijania wprowadzania danych na oddzielne funkcje, osobno dla każdego pola struktury. Możliwość łatwej rozbudowy i tak pozostała.

Zauważ również, że w wersji oryginalnej autor miał jeszcze funkcję: Wprowadź dane(), która była zupełnie niepotrzebnym pośrednikiem, z dodatkowymi zmiennymi. Tylko komplikowała kod.

Biorąc pod uwagę, że autor ćwiczył użycie funkcji, to i tak było tego stanowczo za dużo.

Widać, że programista się przyłożył, pisał kod b.starannie. (Z jedną tylko uwagą - zbyt zwięźle w poziomie, operatory trzeba rozdzielać spacjami).

Podobne pytania

0 głosów
1 odpowiedź 1,142 wizyt
pytanie zadane 22 kwietnia 2017 w C i C++ przez muchomor Nowicjusz (210 p.)
+1 głos
1 odpowiedź 205 wizyt
pytanie zadane 26 listopada 2016 w C i C++ przez czujek22 Dyskutant (7,670 p.)
0 głosów
1 odpowiedź 537 wizyt

92,575 zapytań

141,424 odpowiedzi

319,649 komentarzy

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

...