Przesyłanie danych z pliku do struktur.

0

Witam mam problem z przesyłaniem danych z pliku do struktur. Mam taki program i chcę poprzez niego przesłać dane z pliku do struktur. Plik wygląda tak:

1 Adam Warzecha 14 10 8 5
2 Dariusz Kolodziej 15 11 6 4
3 Pawel Kowalski 17 11 4 6
4 Krzysztof Suliga 11 8 4 9

I jest problem ponieważ gdy odpalam program wyskakują mi takie krzaczki: http://imageshack.us/photo/my-images/52/53143238.jpg/
Nie wiem czym jest to spowodowane, wydaje mi się, że wszystko robię ok

#include<stdio.h>
#include<ctype.h>
#include<string.h>
#include<stdlib.h>

struct gracz
{
	int numer;
	char imienazwisko[41];
	int rzuty;
	int celne;
	int asysty;
	int faule;
	float skutecznosc;
};

int main(void)
{
	FILE *wsk;
	int i;
	struct gracz tab[4];


	if ((wsk = fopen("plik.dat", "r + b")) == NULL)
	{
		puts("Blad otwarcia pliku");
		exit(1);
	}
	else
	{
		rewind(wsk);
		for (i = 0; i < 4; i++)
		{
			fread(&tab[i], sizeof(struct gracz), 1, wsk);
		}
		for (i = 0; i < 4; i++)
		{
			printf("Numer: %d gracz: %s rzuty: %d celne: %d asysty: %d faule %d, skutecznosc: %.2f\n", tab[i].numer, tab[i].imienazwisko, tab[i].rzuty, tab[i].asysty, tab[i].faule, tab[i].faule, tab[i].skutecznosc);
		}
		fclose(wsk);
	}
	getchar();
	getchar();
	return 0;
}
0

Jeżeli plik wygląda tak:

1 Adam Warzecha 14 10 8 5
2 Dariusz Kolodziej 15 11 6 4
3 Pawel Kowalski 17 11 4 6
4 Krzysztof Suliga 11 8 4 9

To nie sądzę, aby dało się go tak łatwo wczytać, tak, jak próbujesz.

fread(&tab[i], sizeof(struct gracz), 1, wsk);

Na C się nie znam, dlatego nie pomogę; po prostu moja uwaga ;)

1
olek1 napisał(a)

Witam mam problem z przesyłaniem danych z pliku do struktur. Mam taki program i chcę poprzez niego przesłać dane z pliku do struktur. Plik wygląda tak:

1 Adam Warzecha 14 10 8 5
2 Dariusz Kolodziej 15 11 6 4
3 Pawel Kowalski 17 11 4 6
4 Krzysztof Suliga 11 8 4 9

I jest problem ponieważ gdy odpalam program wyskakują mi takie krzaczki: http://imageshack.us/photo/my-images/52/53143238.jpg/

Źle, źle, źle, no źle no...

olek1 napisał(a)

Nie wiem czym jest to spowodowane, wydaje mi się, że wszystko robię ok

Nie robisz ok. W pliku masz wartości liczbowe jako (nazwijmy to) string, a próbujesz wczytywać jako binarki.

olek1 napisał(a)
struct gracz
{
	int numer;
	char imienazwisko[41];
	int rzuty;
	int celne;
	int asysty;
	int faule;
	float skutecznosc;
};

Tu masz strukturę, która przechowuje integer, 41 znaczków, 4 integery i floata.

olek1 napisał(a)
fread(&tab[i], sizeof(struct gracz), 1, wsk);

A to Ci z pliku wczytuje (zakładając, że int i float mają 4 bajty) 65 bajtów (+ pewnie do tego wyrównanie do słowa) i nadpisuje tym strukturę w formie binarnej. Bez patrzenia gdzie tam jest int, char czy float.
Popełniasz następujące błędy:

  1. zlewasz endianess i wielkość typów w zależności od architektury (to niekoniecznie musi mieć znaczenie, np. kiedy się tylko bawisz),
  2. wartości liczbowe wczytujesz jako binarki, zamiast sparsować je z char* do int/float
  3. imię i nazwisko wczytujesz z pliku od offsetu 4 do 45 (zakładając 4 bajty na int), niezależnie od tego jak długie to pole jest w pliku
  4. wczytując ciąg znaków zlewasz bajt zerowy

Jak powinieneś to robić:

  1. wczytujesz linię z pliku
  2. odrzucasz (białe?) spacje z poczatku
  3. jedziesz do pierwszej (białej?) spacji i to, co jest przed nią konwertujesz na int (strtol!)
  4. jedziesz dalej, aż napotkasz znak będący cyfrą (niech Latający Potwór Spaghetti ma Cię w opiece jeśli imię lub nazwisko zawiera cyfry), tę część stripujesz ze spacji na początku i końcu, sprawdzasz czy jest ok (np. nie ma w środku bajtów zerowych, nie jest dłuższe niż 40 bajtów) i ładujesz do tablicy w strukturze, na końcu dając bajt zerowy
  5. resztę splitujesz po (białej?) spacji i konwertujesz na inty (strtol!) i floaty (strtod!)

Wszystko to uważnie sprawdzając wyniki konwersji itp.

0

Przemęczyłem się przez ten program i napisałem.;d Działa raczej poprawnie, ale kod to taki napisałem, że chyba żal patrzeć.;d Mam tylko jeszcze jeden problem z tą linijką:

tab[x].skutecznosc = float(tab[x].rzuty)/float(tab[x].celne);

Wyskakuje mi błąd "syntax error type". Nie wiem za bardzo czemu.

Oraz u ostatniego zawodnika w zmiennej u przypisuje wartość 81 zamiast 8. U innych jest poprawnie.

#include<stdio.h>
#include<ctype.h>
#include<string.h>
#include<stdlib.h>

struct gracz
{
	int numer;
	char imienazwisko[41];
	int rzuty;
	int celne;
	int asysty;
	int faule;
	float skutecznosc;
};

int main(void)
{
	FILE *wsk;
	int i;
	int j;
	int n;
	char c[4];
	char c1[4];
	char c2[4];
	char c3[4];
	char c4[4];
	char in[41];
	int r;
	int u;
	int x;
	int f;
	int a;
	int w;
	char *KONIEC;
	char t[101];
	struct gracz tab[4] = 
	{
		{0, "0", 0, 0, 0, 0, 0},
		{0, "0", 0, 0, 0, 0, 0},
		{0, "0", 0, 0, 0, 0, 0},
		{0, "0", 0, 0, 0, 0, 0}
	};


	if ((wsk = fopen("plik.dat", "r + b")) == NULL)
	{
		puts("Blad otwarcia pliku");
		exit(1);
	}
	else
	{
		rewind(wsk);
		for (x = 0; x < 4; x++)
		{
			fgets(t, 100, wsk);
			i = 0; j = 0;
			while(t[i] != ' ')
			{
				c[j] = t[i];
				j++;
				i++;
			}
			n = strtol(c, &KONIEC, 10);
			tab[x].numer = n;
			j = 0;
			i++;
			while(!isdigit(t[i]))
			{
				in[j] = t[i];
				i++;
				j++;
			}
			in[j -1 ] = '\0';
			for (w = 0; w < j; w++)
			{
			tab[x].imienazwisko[w] = in[w];
			}
			j = 0;
			while(t[i] != ' ')
			{
				c1[j] = t[i];
				j++;
				i++;
			}
			r = strtol(c1, &KONIEC, 10);
			tab[x].rzuty = r;
			j = 0;
			i++;
			while(t[i] != ' ')
			{
				c2[j] = t[i];
				j++;
				i++;
			}
			u = strtol(c2, &KONIEC, 10);
			tab[x].celne = u;
			j = 0;
			i++;
			while(t[i] != ' ')
			{
				c3[j] = t[i];
				j++;
				i++;
			}
			a = strtol(c3, &KONIEC, 10);
			tab[x].asysty = a;
			j = 0;
			i++;
			while(t[i] != '\0')
			{
				c4[j] = t[i];
				j++;
				i++;
			}
			f = strtol(c4, &KONIEC, 10);
			tab[x].faule = f;
			tab[x].skutecznosc = float(tab[x].rzuty)/float(tab[x].celne);
		}
		if (fclose(wsk) != 0)
		{
			puts("Blad zamkniecia pliku");
			exit(1);
		}
		for (i = 0; i < 4; i++)
		{
			printf("%d %s %d %d %d %d %.2f\n", tab[i].numer, tab[i].imienazwisko, tab[i].rzuty, tab[i].celne, tab[i].asysty, tab[i].faule, tab[i].skutecznosc);
        }

	}
	getchar();
	getchar();
	return 0;
}
1
olek1 napisał(a)

Przemęczyłem się przez ten program i napisałem.;d Działa raczej poprawnie, ale kod to taki napisałem, że chyba żal patrzeć.;d Mam tylko jeszcze jeden problem z tą linijką:

tab[x].skutecznosc = float(tab[x].rzuty)/float(tab[x].celne);

Źle rzutujesz.

tab[x].skutecznosc = (float)tab[x].rzuty / (float)tab[x].celne;
olek1 napisał(a)

Oraz u ostatniego zawodnika w zmiennej u przypisuje wartość 81 zamiast 8. U innych jest poprawnie.

Pokaż plik z danymi.
Co do Twojego kodu, to powinieneś wiedzieć, że funkcje nie gryzą. Wręcz przeciwnie, są bardzo użyteczne. Napisz sobie funkcję parsującą, zamiast powtarzać ten sam kod.

olek1 napisał(a)
	FILE *wsk;
	int i;
	int j;
	int n;

Czytelniej jest zapisać to tak:

int i, j, n, r, u, x, f, a, w;

Jeszcze lepiej jest użyć tablicy. Nie potrzebujesz jednak tworzyc tylu zmiennych. Możesz recyklować jedną.

olek1 napisał(a)
struct gracz tab[4] = 
	{
		{0, "0", 0, 0, 0, 0, 0},
		{0, "0", 0, 0, 0, 0, 0},
		{0, "0", 0, 0, 0, 0, 0},
		{0, "0", 0, 0, 0, 0, 0}
	};

To jest zbyt rozwlekłe. Wyzeruj blok pamięci:

memset(tab, 0, rows * cols);

Lepszym rozwiązaniem byłoby tworzenie struktur dynamicznie i ich zerowanie:

s = (struct gracz*)calloc(1, sizeof(struct gracz));

Trzeba tylko pamiętać o zwalnianiu pamięci. Calloc() zaalokuje Ci pamięć pod strukturę i ją wyzeruje. Przy okazji prawidłowo zostanie zainicjowana tablica znaków, czyli bajtami zerowymi (pusty ciąg) zamiast wartością "0" (ciąg "0\0<śmieci>").

olek1 napisał(a)
if ((wsk = fopen("plik.dat", "r + b")) == NULL)
	{
		puts("Blad otwarcia pliku");
		exit(1);
	}
	else
	{ ...

Dla użytkownika bardziej istotna jest informacja dlaczego nie udało się otworzyć pliku. Z takim komunikatem zmuszasz go do zgadywania co jest źle.
W if'ie robisz exit(), zatem blok "else" jest tutaj niepotrzebny.
Zamiast exit(1) użyj "return 1" skoro jesteś w main().

olek1 napisał(a)
		rewind(wsk);
		for (x = 0; x < 4; x++)

Wczytujesz tylko 4 rekordy z pliku. Może warto byłoby pomyśleć o dynamicznej tablicy, lub (bardzo fajne ćwiczenie C przy okazji) liście powiązanej.

olek1 napisał(a)
i = 0; j = 0;

To możesz także zapisać tak:

i = j = 0;
olek1 napisał(a)
while(t[i] != ' ')
			{
				c[j] = t[i];
				j++;
				i++;
			}

To prosty i skuteczny sposób, ale mam inną propozycję. Spróbuj wykorzystać funkcję strchr() do znalezienia danego znaku. Teoretycznie ona też skanuje ciąg w podobny sposób, ale jej użycie da zwięźlejszy kod. Samo kopiowanie możesz też wtedy przeprowadzić blokowo, a nie znak po znaku. strchr() wymaga zabawy wskaźnikami, co jest odrobinę bardziej złożone, więc jeśli podejmiesz rękawicę i będziesz miał problemy, to daj znać.

olek1 napisał(a)
n = strtol(c, &KONIEC, 10);
			tab[x].numer = n;

Nie obsługujesz błędów. Jeśli w "c" znajduje się ciąg znaków niemożliwy do konwersji na wartość liczbową, strtol() zwraca 0. Warto obsłużyć takie przypadki jako błąd składni pliku z danymi. A jak rozpoznać "dobre" 0 od "złego"? Wskaźnik KONIEC powie Ci w którym miejscu tablicy wejściowej strtol() natknął się na zły bajt. Jeśli "KONIEC" (wskaźnik) ma taką samą wartość jak "c" (też wskaźnik), to znaczy że wartości w tablicy wejściowej są złe. Jeśli "KONIEC" wskazuje na bajt inny niż zerowy, to znaczy że na początku ciągu jest liczba, ale dalej jest już coś innego (dlatego najlepiej jest zestripować ciąg z białych spacji na końcu, bo z poczatkowymi strtol() sobie poradzi). Daj znać jeśli potrzebujesz przykładu.

olek1 napisał(a)
while(t[i] != ' ')
			{
				c1[j] = t[i];
				j++;
				i++;
			}

Jeszcze jedna uwaga. Zlewasz możliwość wczytania "urwanej" linii. Jedziesz po tablicy w poszukiwaniu spacji, a powinieneś też szukać znaku nowej linii (koniec ciągu wczytanego przez fgets(); uciekasz z pętli) i bajtu zerowego (urwana ostatnia linia w pliku, błędne dane).

olek1 napisał(a)
if (fclose(wsk) != 0)
		{
			puts("Blad zamkniecia pliku");
			exit(1);
		}

W przypadku plików otwartych tylko do odczytu, błędy zamknięcia nie mają żadnego znaczenia, więc nie musisz tego sprawdzać. Nic złego się nie stanie jeśli z jakiegoś powodu (chociaż trudno mi sobie takowy wyobrazić w przypadku otwarcia RO) system nie będzie w stanie zamknąć pliku. Dane nie zostaną utracone, gdyż nic w buforze wyjściowym nie ma.

Krotko - dobrze Ci idzie, ale jest jeszcze kilka niewielkich niedociągnięć. W języku C trzeba pamiętać o sprawdzaniu błędów. Ignorowanie ich może prowadzić do problemów, nawet trudnych do zlokalizowania.

1
Kumashiro napisał(a)
olek1 napisał(a)
struct gracz tab[4] = 
	{
		{0, "0", 0, 0, 0, 0, 0},
		{0, "0", 0, 0, 0, 0, 0},
		{0, "0", 0, 0, 0, 0, 0},
		{0, "0", 0, 0, 0, 0, 0}
	};

To jest zbyt rozwlekłe. Wyzeruj blok pamięci:

memset(tab, 0, rows * cols);

Autokorekta. Nie wiem dlaczego uznałem tutaj, że tab przechowuje tablice charów... Oczywiście powinno być:

memset(tab, 0, rows * sizeof(struct gracz));
0
Kumashiro napisał(a)
olek1 napisał(a)

Oraz u ostatniego zawodnika w zmiennej u przypisuje wartość 81 zamiast 8. U innych jest poprawnie.

Pokaż plik z danymi.

Plik z danymi taki sam jak na początku:

1 Adam Warzecha 14 10 8 5
2 Dariusz Kolodziej 15 11 6 4
3 Pawel Kowalski 17 11 4 6
4 Krzysztof Suliga 11 8 4 9

Później spróbuje zastosować się do twoich porad i coś zdziałać. Jakbym miał problemy to napiszę.
A tak poza tym to wielkie dzięki za wyczerpującą odpowiedź.

2
olek1 napisał(a)
Kumashiro napisał(a)
olek1 napisał(a)

Oraz u ostatniego zawodnika w zmiennej u przypisuje wartość 81 zamiast 8. U innych jest poprawnie.

Pokaż plik z danymi.

Plik z danymi taki sam jak na początku:

1 Adam Warzecha 14 10 8 5
2 Dariusz Kolodziej 15 11 6 4
3 Pawel Kowalski 17 11 4 6
4 Krzysztof Suliga 11 8 4 9

Problemem tutaj jest to, że kiedy skończysz kopiować znaki do tablicy "c2", nie zakańczasz danych bajtem zerowym (i ogólnie źle to obsługujesz). Jak to wygląda:
Alokujesz sobie czteroelementową tablicę charów. Wygląda ona tak ("[]" to bajt w pamięci, "?" to śmieć czyli bajt o jakiejśtam wartości):

[?][?][?][?]

Kopiując do niej dane nadpisujesz bajty, zatem dla tego pliku tablica ta zmienia się tak:

[8][?][?][?]   /* Pierwsza linia w pliku - "8" */
[1][1][?][?]   /* Druga linia - "11" */
[1][1][?][?]   /* Trzecia linia - "11" */
[8][1][?][?]   /* Czwarta linia - "8" */

Co się zatem dzieje? Ponieważ w żaden sposób nie "czyścisz" tablicy po jej użyciu, zostają w niej wartości z poprzednich zapisów. W przypadku ostatniej linii w tablicy leżały na jej początku dwa bajty: "11" z poprzedniego cyklu. Z pliku odczytałeś tylko jeden bajt "8" i wpisałeś go pod indeksem 0, ale druga "1" wciąż tam sobie była.
Miałeś szczęście, że pierwsza linia miała wartość jednocyfrową, a pozostałe (prócz ostatniej) dwucyfrową, więc problem objawił się jedynie dla ostatniej linii. Dlaczego strtol() nie wyleciał z błędem? Ta funkcja parsuje ciąg do czasu, aż nie natknie się na bajt zerowy, lub znaczek nie będący cyfrą (z wyłączeniem początku, gdzie dopuszczalne są jeszcze znaki "-" i "+"). Zatem poprawnie sparsowała liczby i kiedy doszła do "śmiecia", skończyła pracę i zwróciła co jej się udało sparsować oraz (w "KONIEC") wskaźnik do pierwszego śmiecia. W ostanim cyklu dostała tablicę znaków z "81" (wartość "8" z ostatniej linii oraz "1" z poprzedniego cyklu), więc sparsowała ją jako 81.
Co zrobiłeś źle? Musisz pamiętać, że cstringi (ciągi znaków) w C muszą kończyć się bajtem zerowym oznaczającym koniec. Gdybyś na końcu doklejał bajt zerowy, nadpisałbyś nim wartość z poprzedniego cyklu i strtol() sparsowałby tak, jak tego oczekujesz.

Czyli jeszcze raz:

  • ciągi znaków zawsze zakańczaj bajtem zerowym, jeśli co innego o to nie zadba
  • sprawdzaj strtol() pod kątem błędów (w tym przypadku wskaźnik KONIEC wskazywałby na odpowiednio: drugi, trzeci, trzeci i trzeci bajt w tablicy, a wskazywana przez niego wartość byłaby różna od '\0' i '\n')
  • staraj się zerować tablice znaków, do których będziesz kopiował dane (w tym przypadku i tak miałbyś ten sam objaw, ale chociaż ciągi znaków byłyby prawidłowe).
0

Na początku przerobiłem program, zmniejszyłem ilość zmiennych i dodałem te znaki zerowe i program wyświetlał prawidłowo dane. Ale potem to namieszałem chyba.;d Zrobiłem jedną funkcję, ale kompilator zgłasza mi błąd przy tej linijce:

while(t[*x] != ' ' && t[*x] != '\n')

Jeszcze wracając do tego:

memset(tab, 0, rows * cols);

to u mnie podkreśla rows i piszę, że nie zna takiego wyrażenia. W C jeszcze nie doszedłem to wszelkich list, kolejek i struktur dynamicznych, więc też nie wiem dokładnie co ten kod oznacza.

Jeszcze jedna uwaga. Zlewasz możliwość wczytania "urwanej" linii. Jedziesz po tablicy w poszukiwaniu spacji, a powinieneś też szukać znaku nowej linii (koniec ciągu wczytanego przez fgets(); uciekasz z pętli) i bajtu zerowego (urwana ostatnia linia w pliku, błędne dane).

Szukanie znaku nowej linii przydaje się przy odczytywaniu ostatniej wartości, ale nie bardzo rozumiem, w którym mogę natrafić na bajt zerowy. Domyślam się, że może chodzi o to, że przy wczytywaniu ostatniej 4 linii na końcu nie znajduje się znak nowej linii '\n' tylko EOF czyli '\0'?

Nie obsługujesz błędów. Jeśli w "c" znajduje się ciąg znaków niemożliwy do konwersji na wartość liczbową, strtol() zwraca 0. Warto obsłużyć takie przypadki jako błąd składni pliku z danymi. A jak rozpoznać "dobre" 0 od "złego"? Wskaźnik KONIEC powie Ci w którym miejscu tablicy wejściowej strtol() natknął się na zły bajt. Jeśli "KONIEC" (wskaźnik) ma taką samą wartość jak "c" (też wskaźnik), to znaczy że wartości w tablicy wejściowej są złe. Jeśli "KONIEC" wskazuje na bajt inny niż zerowy, to znaczy że na początku ciągu jest liczba, ale dalej jest już coś innego (dlatego najlepiej jest zestripować ciąg z białych spacji na końcu, bo z poczatkowymi strtol() sobie poradzi). Daj znać jeśli potrzebujesz przykładu.

Chyba przykład się przyda, bo nie wiem czy do końca dobrze rozumuję.
Z tym drugim argumentem funkcji strtol to rozumiem, że jeśli w tej tablicy C będą np takie dane: 12f'\0' to ustawi on wskaźnik na to f? A jak np. 124'\0' to na '\0', które jest równoważne bajtowi zerowemu NULL? Nie rozumiem określenia zestripować.;d Znaczy co miałbym z tymi spacjami zrobić? Wydaje mi się, że jednak nigdzie ich nie mam w programie bo pomijam je zawsze przy kopiowaniu z tablicy t[] poprzez instrukcję i++ przed przejściem do funkcji.

Z tym błędem strtol coś takiego zrobiłem:

if (*KONIEC != NULL)
{
	printf ("Blad podczas konwersji\n");
	return 1;
}

Nie rozumiem też za bardzo co ma do rzeczy to czy mamy w tablicy c[] 12f czy 12 jeśli strtol konwertuje znaki do napotkania takiego nie będącego cyfrą. Jakie to ma w takim razie konsekwencje dalej?

Dla użytkownika bardziej istotna jest informacja dlaczego nie udało się otworzyć pliku. Z takim komunikatem zmuszasz go do zgadywania co jest źle.
W if'ie robisz exit(), zatem blok "else" jest tutaj niepotrzebny.
Zamiast exit(1) użyj "return 1" skoro jesteś w main().

Ale w jaki sposób mogę określi co dokładnie spowodowało błąd?

#include<stdio.h>
#include<ctype.h>
#include<string.h>
#include<stdlib.h>

int konwert(char t[], int *x);

struct gracz
{
	int numer;
	char imienazwisko[41];
	int rzuty;
	int celne;
	int asysty;
	int faule;
	float skutecznosc;
};

int main(void)
{
	FILE *wsk;
	int i, k, x, w;
	char in[41], t[101];
	struct gracz tab[4] = 
	{
		{0, "0", 0, 0, 0, 0, 0},
		{0, "0", 0, 0, 0, 0, 0},
		{0, "0", 0, 0, 0, 0, 0},
		{0, "0", 0, 0, 0, 0, 0}
	};


	if ((wsk = fopen("plik.dat", "r + b")) == NULL)
	{
		puts("Blad otwarcia pliku");
		return 1;
	}
	else
	{
		rewind(wsk);
		for (x = 0; x < 4; x++)
		{
			fgets(t, 100, wsk);
			i = 0;
			tab[x].numer = konwert(t, &i);
			k = 0;
			i++;
			while(!isdigit(t[i]))
			{
				in[k] = t[i];
				i++;
				k++;
			}
			in[k - 1] = '\0';
			for (w = 0; w < k; w++)
			{
			tab[x].imienazwisko[w] = in[w];
			}
			tab[x].rzuty = konwert(t, &i);;
			i++;
			tab[x].celne = konwert(t, &i);
			i++;
			tab[x].asysty = konwert(t, &i);;
			i++;
			tab[x].faule = konwert(t, &i);
			tab[x].skutecznosc = ((double)tab[x].celne/(double)tab[x].rzuty) * 100;
		}
		if (fclose(wsk) != 0)
		{
			puts("Blad zamkniecia pliku");
			exit(1);
		}
		for (i = 0; i < 4; i++)
		{
			printf("%d %s %d %d %d %d %.2f %%\n", tab[i].numer, tab[i].imienazwisko, tab[i].rzuty, tab[i].celne, tab[i].asysty, tab[i].faule, tab[i].skutecznosc);
        }

	}
	getchar();
	getchar();
	return 0;
}

int konwert(char t[], int *x)
{
	int j = 0;
	int n;
	int a;
	char c[4];
	char *KONIEC;

	while(t[*x] != ' ' && t[*x] != '\n')
	{
		c[j] = t[*x];
		j++;
		*x++;
	}
	c[j] = '\0';
	n = strtol(c, &KONIEC, 10);
	for (a = 0; a < 4; a++)
	{
		c[a] = NULL;
	}
	/*if (*KONIEC != NULL)
	{
		printf ("Blad podczas konwersji\n");
		return 1;
	}*/
	return n;
}
1
olek1 napisał(a)

Na początku przerobiłem program, zmniejszyłem ilość zmiennych i dodałem te znaki zerowe i program wyświetlał prawidłowo dane. Ale potem to namieszałem chyba.;d Zrobiłem jedną funkcję, ale kompilator zgłasza mi błąd przy tej linijce:

while(t[*x] != ' ' && t[*x] != '\n')

A jaki to konkretnie błąd? Bo u mnie gcc z -Wall i -pedantic nie czepia się tej linii (czepia się za to innych).

olek1 napisał(a)

Jeszcze wracając do tego:

memset(tab, 0, rows * cols);

to u mnie podkreśla rows i piszę, że nie zna takiego wyrażenia. W C jeszcze nie doszedłem to wszelkich list, kolejek i struktur dynamicznych, więc też nie wiem dokładnie co ten kod oznacza.

To nie jest kod stricte dla kolejek i struktur dynamicznych. Funkcja memset() wypełnia zadany obszar pamięci (przekazywany wskaźnikiem, o wielkości przekazanej w trzecim argumencie) daną wartością (drugi argument). W tym przypadku zeruje całą tablicę "tab". "rows" (i zapewne "cols") podkreśla Ci dlatego, że ja podałem to jako przykład. W Twoim kodzie nie ma takiej zmiennej. Patrz też na moje sprostowanie dotyczące tego fragmentu. W Twoim kodzie zamiast "rows" powinna być ilość elementów w tablicy, zaś zamiast "cols" rozmiar tych elementów, czyli "sizeof(struct gracz)".

olek1 napisał(a)

Jeszcze jedna uwaga. Zlewasz możliwość wczytania "urwanej" linii. Jedziesz po tablicy w poszukiwaniu spacji, a powinieneś też szukać znaku nowej linii (koniec ciągu wczytanego przez fgets(); uciekasz z pętli) i bajtu zerowego (urwana ostatnia linia w pliku, błędne dane).

Szukanie znaku nowej linii przydaje się przy odczytywaniu ostatniej wartości, ale nie bardzo rozumiem, w którym mogę natrafić na bajt zerowy. Domyślam się, że może chodzi o to, że przy wczytywaniu ostatniej 4 linii na końcu nie znajduje się znak nowej linii '\n' tylko EOF czyli '\0'?

Nie, chodzi o to, że w pliku może być błąd w postaci bajtu zerowego. Dobrze jest coś takiego wykryć, gdyż w wynikach będzie to widoczne jako "urwana" dana i będziesz się zastanawiał dlaczego nie wczytuje całości. Jestem na to wyczulony odkąd system plików XFS miał manierę zerowania plików (lub ich części) w przypadku nieprawidłowego odmontowania i aplikacja dostała "zajoba" po restarcie maszyny.
Natomiast co do znaku nowej linii to dobrze myślisz. W ostatniej może go nie być.

olek1 napisał(a)

Nie obsługujesz błędów. Jeśli w "c" znajduje się ciąg znaków niemożliwy do konwersji na wartość liczbową, strtol() zwraca 0. Warto obsłużyć takie przypadki jako błąd składni pliku z danymi. A jak rozpoznać "dobre" 0 od "złego"? Wskaźnik KONIEC powie Ci w którym miejscu tablicy wejściowej strtol() natknął się na zły bajt. Jeśli "KONIEC" (wskaźnik) ma taką samą wartość jak "c" (też wskaźnik), to znaczy że wartości w tablicy wejściowej są złe. Jeśli "KONIEC" wskazuje na bajt inny niż zerowy, to znaczy że na początku ciągu jest liczba, ale dalej jest już coś innego (dlatego najlepiej jest zestripować ciąg z białych spacji na końcu, bo z poczatkowymi strtol() sobie poradzi). Daj znać jeśli potrzebujesz przykładu.

Chyba przykład się przyda, bo nie wiem czy do końca dobrze rozumuję.

Mówisz i masz. Program wczytuje dane z stdin i przeprowadza konwersję:

#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

int main(int  argc, const char  *argv[]) {
    char            *endptr, buf[128];
    long            value;


    errno = 0;
    buf[sizeof(buf) - 1] = '\0';
    fgets(buf, sizeof(buf) - 1, stdin);
    value = strtol(buf, &endptr, 10);
    if ( errno || ((*endptr != '\n') && (*endptr != '\0')) || (endptr == buf) ) {
        fprintf(stderr, "Invalid value: %s\n", buf);
        return 1;
    } else
        fprintf(stdout, "Value: %li, Rest: %s\n", value, endptr);

    return 0;
}

Zmienna "errno" w przypadku błędu może też zawierać bardziej szczegółową przyczynę, jeśli chcesz to wykorzystać.

olek1 napisał(a)

Z tym drugim argumentem funkcji strtol to rozumiem, że jeśli w tej tablicy C będą np takie dane: 12f'\0' to ustawi on wskaźnik na to f? A jak np. 124'\0' to na '\0', które jest równoważne bajtowi zerowemu NULL? Nie rozumiem określenia zestripować.;d Znaczy co miałbym z tymi spacjami zrobić? Wydaje mi się, że jednak nigdzie ich nie mam w programie bo pomijam je zawsze przy kopiowaniu z tablicy t[] poprzez instrukcję i++ przed przejściem do funkcji.

Zestripować - od "strip" (usuwać, obdzierać), usunąć spacje. Funkcja to robiąca w różnych językach programowania zazwyczaj nazywa się strip().

olek1 napisał(a)

Z tym błędem strtol coś takiego zrobiłem:

if (*KONIEC != NULL)
{
	printf ("Blad podczas konwersji\n");
	return 1;
}

Źle. fgets() wczytuje wraz ze znakiem nowej linii, zatem przy ostatniej wartości w linii "KONIEC" wskazuje nie na bajt zerowy, tylko na znak nowej linii. Zobacz w przykładzie wyżej jak to rozwiązać.

olek1 napisał(a)

Nie rozumiem też za bardzo co ma do rzeczy to czy mamy w tablicy c[] 12f czy 12 jeśli strtol konwertuje znaki do napotkania takiego nie będącego cyfrą. Jakie to ma w takim razie konsekwencje dalej?

Masz jakiś plik z danymi. Plik ten ma określony format. Jeśli spodziewasz się liczby, to powinna to być liczba, a np. "12f" oznacza albo błąd danych/formatu, albo błąd parsowania pliku. Musisz na takie rzeczy uważać i odpowiednio reagować (np. komunikatem błędu i zakończeniem przetwarzania pliku). Jeśli to zignorujesz i wykorzystasz właściwość strtol(), możesz wczytać nieprawidłowe dane i uzyskać błędne wyniki obliczeń. Ponieważ nie obsługujesz tego jako błąd, możesz tego nigdy nie wykryć.

olek1 napisał(a)

Dla użytkownika bardziej istotna jest informacja dlaczego nie udało się otworzyć pliku. Z takim komunikatem zmuszasz go do zgadywania co jest źle.
W if'ie robisz exit(), zatem blok "else" jest tutaj niepotrzebny.
Zamiast exit(1) użyj "return 1" skoro jesteś w main().

Ale w jaki sposób mogę określi co dokładnie spowodowało błąd?

W globalnej zmiennej "errno" (musisz zaincludować errno.h) znajduje się kod błędu. Możesz go użyć w funkcjach strerror() czy perror() do wyświetlenia komunikatu z przyczyną błędu. Ale uwaga! Jeśli piszesz pod Windows, to nie wiem jak tam wygląda sprawa z errno i funkcjami. Musisz sprawdzić w dokumentacji.

olek1 napisał(a)
rewind(wsk);

Nie musisz wywoływać rewind() na początku. Po otwarciu pliku w trybie innym niż dopisywanie ("a") kursor zawsze znajduje się na jego początku.

olek1 napisał(a)
tab[x].numer = konwert(t, &i);

Podoba mi się ten pomysł z przekazywaniem indeksu przez wskaźnik. Raczej nie spotykane w takim zastosowaniu, ale na swój sposób eleganckie rozwiązanie.

olek1 napisał(a)
			while(!isdigit(t[i]))
			{
				in[k] = t[i];
				i++;
				k++;
			}
			in[k - 1] = '\0';
			for (w = 0; w < k; w++)
			{
			tab[x].imienazwisko[w] = in[w];
			}

Można to załatwić jedną pętlą. Nie pilnujesz też zakresów. Jeśli w pliku pole z imieniem i nazwiskiem będzie dłuższe niż 41 znaki, wyjedziesz poza tablicę i zaczniesz pisać po innych zmiennych i/lub dostaniesz SEGFAULT (błąd segmentacji). Pamiętaj, że tablice w C nie pilnują swoich zakresów.

k = i;
while ( !isdigit(t[i]) && (i - k < 41) ) {
	tab[x].imienazwisko[i - k] = t[i];
	i++;
}
tab[x].imienazwisko[i - k] = '\0';
olek1 napisał(a)
int konwert(char t[], int *x)
{
	[...]
	for (a = 0; a < 4; a++)
        {
		c[a] = NULL;
	}
	[...]
}

To jest bardzo złe. Kompilator przy tym szczeka. NULL powinien być wykorzystywany tylko przy wskaźnikach. Nie powinieneś nigdy liczyć na to, że NULL będzie miał wartość 0. Wyżej też napisałeś "bajt zerowy NULL", co także nie jest prawidłowe. NULL to jest pusty wskaźnik, który tak się składa że akurat na x86* ma wartość 0, ale na innych platformach może to być co innego.
Skąd wziąłeś tą czwórkę?
Nie musisz zerować wszytkich komórek do końca. Wystarczy, że po ostatnim znaku ciągu znaków będzie jeden bajt zerowy. Kiedy funkcje operujące na ciągach znaków dotrą do bajtu zerowego, ignorują resztę. '\0' jest terminatorem.

1
Kumashiro napisał(a)

Można to załatwić jedną pętlą. Nie pilnujesz też zakresów. Jeśli w pliku pole z imieniem i nazwiskiem będzie dłuższe niż 41 znaki, wyjedziesz poza tablicę i zaczniesz pisać po innych zmiennych i/lub dostaniesz SEGFAULT (błąd segmentacji). Pamiętaj, że tablice w C nie pilnują swoich zakresów.

k = i;
while ( !isdigit(t[i]) && (i - k < 41) ) {
	tab[x].imienazwisko[i - k] = t[i];
	i++;
}
tab[x].imienazwisko[i - k] = '\0';

Echhh... Późno już i nie myślę. W tym przykładzie, który Ci podałem (i zresztą w Twoim kodzie też) jest pewien mały błąd. Ten kod w tab[x].imienazwisko zapisze także spację, która rozdziela imię i nazwisko od kolejnego pola. Aby naprawić to niedopatrzenie, wystarczy w powyższym przykładzie zmienić to:

tab[x].imienazwisko[i - k] = '\0';

na to:

tab[x].imienazwisko[i - k - 1] = '\0';

...chyba. Sorry.

0

A jaki to konkretnie błąd? Bo u mnie gcc z -Wall i -pedantic nie czepia się tej linii (czepia się za to innych).

Ja piszę w VS. I po próbie kompilacji przy tej linijce pojawia się strzałeczka, po najechaniu, której pojawia się napis: "This is the next statement that will be executed. To change which statments is executed next, drag the arrow. This may have unintended consequence.

Na dole w błędach pisze takie coś: http://imageshack.us/photo/my-images/836/40560414.jpg/

Nie bardzo wiem co może być nie tak, bo całość do funkcji skopiowałem z wcześniejszego programu, tylko i przekazałem jako adres. Program niestety się nie kompiluje.

Masz jakiś plik z danymi. Plik ten ma określony format. Jeśli spodziewasz się liczby, to powinna to być liczba, a np. "12f" oznacza albo błąd danych/formatu, albo błąd parsowania pliku. Musisz na takie rzeczy uważać i odpowiednio reagować (np. komunikatem błędu i zakończeniem przetwarzania pliku). Jeśli to zignorujesz i wykorzystasz właściwość strtol(), możesz wczytać nieprawidłowe dane i uzyskać błędne wyniki obliczeń. Ponieważ nie obsługujesz tego jako błąd, możesz tego nigdy nie wykryć.

Tak przerobiłem tą funkcję. Przeczytałem też, że w plikach nowa linia oznaczana jest jako \r\n. Podczas odczytu tekstowego kompilator zamienia to \r\n na \n, ale binarnego odczytuje oba znaki. Dlatego zmieniłem warunek w funkcji konwert z (t[*x] != '\n') na (t[*x] != '\r'). Dodałem też (t[*x] != EOF) kiedy odczytywana jest ostatnia wartość z czwartej linii.

while((t[*x] != ' ') && (t[*x] != '\r') && (t[*x] != '\0') && (t[*x] != EOF))

W globalnej zmiennej "errno" (musisz zaincludować errno.h) znajduje się kod błędu. Możesz go użyć w funkcjach strerror() czy perror() do wyświetlenia komunikatu z przyczyną błędu. Ale uwaga! Jeśli piszesz pod Windows, to nie wiem jak tam wygląda sprawa z errno i funkcjami. Musisz sprawdzić w dokumentacji.

Przeczytałem o tych strerror i perror i pododawałem je.

#include<stdio.h>
#include<ctype.h>
#include<string.h>
#include<stdlib.h>
#include<errno.h>

int konwert(char t[], int *x);
void strip(char t[]);

struct gracz
{
	int numer;
	char imienazwisko[41];
	int rzuty;
	int celne;
	int asysty;
	int faule;
	float skutecznosc;
};

int main(void)
{
	FILE *wsk;
	int i, k, x, w;
	char in[41], t[101];
	struct gracz tab[4];

	memset(tab, 0, sizeof(tab));

	if ((wsk = fopen("plik.dat", "r + b")) == NULL)
	{
        perror("Wystapil blad");
		return 1;
	}
	else
	{
		for (x = 0; x < 4; x++)
		{
			fgets(t, 100, wsk);
			i = 0;
			tab[x].numer = konwert(t, &i);
			i++;
			k = i;
			while(!isdigit(t[i]) && ((i - k) < 41))
			{
				tab[x].imienazwisko[i - k] = t[i];
				i++;
			}
			tab[x].imienazwisko[i - k - 1] = '\0';
			tab[x].rzuty = konwert(t, &i);;
			i++;
			tab[x].celne = konwert(t, &i);
			i++;
			tab[x].asysty = konwert(t, &i);;
			i++;
			tab[x].faule = konwert(t, &i);
			tab[x].skutecznosc = ((double)tab[x].celne/(double)tab[x].rzuty) * 100;
		}
		if (fclose(wsk) != 0)
		{
			perror("Wystapil blad");
			return 1;
		}
		for (i = 0; i < 4; i++)
		{
			printf("%d %s %d %d %d %d %.2f %%\n", tab[i].numer, tab[i].imienazwisko, tab[i].rzuty, tab[i].celne, tab[i].asysty, tab[i].faule, tab[i].skutecznosc);
        }

	}
	getchar();
	getchar();
	return 0;
}

int konwert(char t[], int *x)
{
	int j = 0;
	int n;
	int a;
	char c[4];
	char *KONIEC;

	while((t[*x] != ' ') && (t[*x] != '\r') && (t[*x] != '\0') && (t[*x] != EOF))
	{
		c[j] = t[*x];
		j++;
		*x++;
	}
	c[j] = '\0';
	n = strtol(c, &KONIEC, 10);
	if (errno || ((*KONIEC != '\r') && (*KONIEC != '\0')) || (KONIEC == c))
	{
		fprintf(stderr, "Nieprawidlowa wartosc: %s\n", c);
		perror("Blad");
		return 1;
	}
	return n;
}

Zestripować - od "strip" (usuwać, obdzierać), usunąć spacje. Funkcja to robiąca w różnych językach programowania zazwyczaj nazywa się strip().

Domyślałem się o co mniej więcej chodzi, ale co mam z tymi spacjami zrobić? Wypełnić je '\0'? W C nie ma chyba takiej gotowej funkcji. Napisałem coś takiego, ale nie wiem czy o to chodzi:

void strip(char tab[])
{
	while(*tab)
	{
		if (*tab == ' ')
			*tab = '\0';
	}
}

Ogólnie nie mógłbym użyć takiej funkcji przed wczytanie imienia i nazwiska bo wtedy imie i nazwisko zostałoby rozdzielone niewidocznym '\0' i by się wszystko zlało w jedno zdanie.

1
olek1 napisał(a)

A jaki to konkretnie błąd? Bo u mnie gcc z -Wall i -pedantic nie czepia się tej linii (czepia się za to innych).

Ja piszę w VS. I po próbie kompilacji przy tej linijce pojawia się strzałeczka, po najechaniu, której pojawia się napis: "This is the next statement that will be executed. To change which statments is executed next, drag the arrow. This may have unintended consequence.

Na dole w błędach pisze takie coś: http://imageshack.us/photo/my-images/836/40560414.jpg/

Nie bardzo wiem co może być nie tak, bo całość do funkcji skopiowałem z wcześniejszego programu, tylko i przekazałem jako adres. Program niestety się nie kompiluje.

Program się kompiluje, tylko dostaje błąd segmentacji po uruchomieniu. Zmień w pętli:

*x++;

na:

*x = *x + 1;

i będzie działać :)

olek1 napisał(a)

Tak przerobiłem tą funkcję. Przeczytałem też, że w plikach nowa linia oznaczana jest jako \r\n. Podczas odczytu tekstowego kompilator zamienia to \r\n na \n, ale binarnego odczytuje oba znaki. Dlatego zmieniłem warunek w funkcji konwert z (t[*x] != '\n') na (t[*x] != '\r'). Dodałem też (t[*x] != EOF) kiedy odczytywana jest ostatnia wartość z czwartej linii.

Zmień raczej tryb otwarcia pliku z binarnego na tekstowy.
Wyrzuć też ten EOF. To nigdy nie trafi do Twojej tablicy.

olek1 napisał(a)
perror("Wystapil blad");

To jest OK, ale perror() stosuje się raczej do skrótowego zapisu, kiedy treść komunikatu nie ma znaczenia dla użytkownika. Np. tak:

perror("open()");

Przy bardziej deskryptywnych opisach proponuję użyć strerror():

fprintf(stderr, "Błąd otwarcia pliku: %s\n", strerror(errno));
olek1 napisał(a)

Zestripować - od "strip" (usuwać, obdzierać), usunąć spacje. Funkcja to robiąca w różnych językach programowania zazwyczaj nazywa się strip().

Domyślałem się o co mniej więcej chodzi, ale co mam z tymi spacjami zrobić? Wypełnić je '\0'? W C nie ma chyba takiej gotowej funkcji. Napisałem coś takiego, ale nie wiem czy o to chodzi:

Nie, wystarczy w odpowiednim miejscu wstawić bajt zerowy. Po zmianach w Twoim kodzie jednak nie musisz się tym już przejmować :)

No to zostały Ci jeszcze niewielkie szlify:

  • zamień "41" na #define
  • dodaj do konwert() argument, którym powiesz pętli jaki jest rozmiar tablicy, żeby nie kręciła poza jej rozmiar
  • obkomentuj

...i gotowe :)

0

Ok wielkie dzięki za pomoc.

Zmień raczej tryb otwarcia pliku z binarnego na tekstowy.
Wyrzuć też ten EOF. To nigdy nie trafi do Twojej tablicy.

Jeszcze ostatnie pytanie. Przy odczytywaniu ostatniej wartości w 4 linii, skąd program ma wiedzieć dokąd odczytywać? Nie spotka tam ani \n ani \0? To myślałem, że na końcu znajduje się znak końca pliku i do niego można zrobić odczytywanie. Jednak na wikipedii przeczytałem:

Funkcja fgets() czyta kolejne znaki ze strumienia stream i umieszcza je w tablicy znakowej wskazywanej przez str. Czytanie przerywa, gdy przeczyta size - 1 znaków, natrafi na koniec pliku lub znak końca linii (znak ten jest zapisywany do str). Na końcu fgets() dopisuje znak '\0'.

Czyli chodzi o to, że jak pobieram jedną linię za pomocą fgets(t, 100, wsk); to funkcja fgets na końcu dopisuje '\0' i przy odczytywaniu ostatniej wartości w ostatniej linii za nią znajduje się to '\0' i działa ten warunek w while: (t[*x] != '\0')?

1
olek1 napisał(a)

Jeszcze ostatnie pytanie. Przy odczytywaniu ostatniej wartości w 4 linii, skąd program ma wiedzieć dokąd odczytywać? Nie spotka tam ani \n ani \0? To myślałem, że na końcu znajduje się znak końca pliku i do niego można zrobić odczytywanie. Jednak na wikipedii przeczytałem:

Funkcja fgets() czyta kolejne znaki ze strumienia stream i umieszcza je w tablicy znakowej wskazywanej przez str. Czytanie przerywa, gdy przeczyta size - 1 znaków, natrafi na koniec pliku lub znak końca linii (znak ten jest zapisywany do str). Na końcu fgets() dopisuje znak '\0'.

Czyli chodzi o to, że jak pobieram jedną linię za pomocą fgets(t, 100, wsk); to funkcja fgets na końcu dopisuje '\0' i przy odczytywaniu ostatniej wartości w ostatniej linii za nią znajduje się to '\0' i działa ten warunek w while: (t[*x] != '\0')?

Dokładnie tak.

Jeszcze jedna rzecz. W przypadku wystąpienia błędu w konwert() printujesz błąd i zwracasz 1. Ta wartość poleci do main(), która przypisze ją do pola w strukturze i będzie jechać dalej. Dwie uwagi:
a) Ta funkcja nie powinna być interaktywna. Nie powinna nic wypisywać na ekranie, ani przyjmować z stdin.
b) Nie powinna też kończyć działania programu, więc odradzam umieszczanie w niej exit().

Jak zazwyczaj wygląda obsługa błędów w funkcjach. Jest kilka sposobów na to (najlepszym są wyjątki, których w C niestety nie ma), a najczęściej wykorzystywanym jest przekazanie statusu (luz/kod błędu) w zmiennej wskazywanej przez wskaźnik. Np. weźmy przykładową funkcję, która przyjmuje argument char* i zwraca int:

int  frobnicate(char  *what, int  *err);

int  main(void) {
	int		errcode = 0, val;
	const char	*text = "That";

	
	val = frobnicate(text, &errcode);
	if ( errcode != 0 )
		/* Błąd */
	else
		/* Zrób coś z val */
}

Innym sposobem jest zwracanie kodu błędu, zaś obliczona wartość ląduje w zmiennej przekazanej przez wskaźnik:

int  frobnicate(char  *what, int  *result);

int  main(void) {
	int		val;
	const char	*text = "That";

	
	if ( frobnicate(text, &value) != 0 )
		/* Błąd */
	else
		/* Zrób coś z val */
}

Można też kod błędu przekazać przez zmienną globalną (np. jak w przypadku errno), ale to jest relikt przeszłości, który mocno przeszkadza w programowaniu wielowątkowym.

0

Ok. Wielkie dzięki za ogromną pomoc w tym temacie i za to, że chciało Ci się to wszystko tak dokładnie opisywać. Wile się nauczyłem dzięki twoim radom.

0

Nie ma problemu.
W odróżnieniu od wielu pytających tutaj na Forum, sam starałeś się rozwiązać zadanie i dobrze kombinowałeś, a nie oczekiwałeś gotowego kodu. Ja tylko starałem się Ciebie nakierować na w miarę właściwe tory. Masz potencjał, brakuje Ci tylko praktyki i zrozumienia kilku rzeczy, np. jak to wszystko tak naprawdę wygląda w pamięci, czy wyczulenia na właściwą obsługę tablic (pamiętaj o pilnowaniu zakresów, to naprawdę jest bardzo ważne). Ale wszystko przyjdzie z czasem, w miarę zdobywania doświadczenia.
Czytaj dużo dobrego kodu innych ludzi, nie ograniczaj się do przykładów w książkach czy Internecie. Podpatruj jak inni rozwiązują określone problemy, weryfikuj wszystko z dokumentacją i pisz, pisz, pisz :)

Powodzenia.

0

Pozwolę sobie zadać jeszcze jedno pytanie. Nie dotyczy ono poprzedniego zadania bezpośrednio, ale jest z nim związane dlatego nie będę zakładał nowego tematu. W książce mam taki przykład.

/* ksplik.c -- zapisuje zawartosc struktury w pliku */
#include <stdio.h>
#include <stdlib.h>
#define MAXTYT   40
#define MAXAUT   40
#define MAXKS    10                 /* maksymalna liczba ksiazek   */
struct ksiazka {                    /* utworzenie szablonu ksiazka */
    char tytul[MAXTYT];
    char autor[MAXAUT];
    float wartosc;
};
int main(void)
{
     struct ksiazka bibl[MAXKS]; /* tablica struktur */
     int licznik = 0;
     int index, licznikp;
     FILE * pksiazki;
     int rozmiar = sizeof (struct ksiazka);
     if ((pksiazki = fopen("ksiazki.dat", "a+b")) == NULL)
     {
         fputs("Nie moge otworzyc pliku ksiazki.dat\n",stderr);
         exit(1);
     }
     rewind(pksiazki);            /* przejscie na poczatek pliku */
     while (licznik < MAXKS &&  fread(&bibl[licznik], rozmiar,
                 1, pksiazki) == 1)
     {
         if (licznik == 0)
             puts("Biezaca zawartosc pliku ksiazki.dat:");
         printf("%s by %s: %.2f zl\n",bibl[licznik].tytul,
             bibl[licznik].autor, bibl[licznik].wartosc);
         licznik++;
     }
     licznikp = licznik;
     if (licznik == MAXKS)
     {
         fputs("Plik ksiazki.dat jest pelny.", stderr);
         exit(2);
     }
     puts("Podaj nowe tytuly ksiazek.");
     puts("Aby zakonczyc, wcisnij [enter] na poczatku wiersza.");
     while (licznik < MAXKS && gets(bibl[licznik].tytul) != NULL
                         && bibl[licznik].tytul[0] != '\0')
     {
         puts("Teraz podaj autora.");
         gets(bibl[licznik].autor);
         puts("Teraz podaj wartosc.");
         scanf("%f", &bibl[licznik++].wartosc);
         while (getchar() != '\n')
             continue;                /* czysci wiersz wejsciowy */
         if (licznik < MAXKS)
             puts("Podaj nastepny tytul.");
     }
     puts("Oto lista Twoich ksiazek:");
     for (index = 0; index < licznik; index++)
         printf("%s, autor: %s, cena: %.2f zl\n", bibl[index].tytul,
             bibl[index].autor, bibl[index].wartosc);
             fwrite(&bibl[licznikp], rozmiar, licznik - licznikp,
             pksiazki);
     fclose(pksiazki);
     return 0;
}

I tutaj moje pytanie. Bo na początku próbując poprzednie zadanie do odczytania struktury chciałem użyć następującej instrukcji:

fread(&tab[i], sizeof(struct gracz), 1, wsk);

i okazało się, że jest to nieskuteczne bo funkcja wczytuje i nie patrzy gdzie jakie wartości są. A w tym zadaniu autor używa najpierw fwrite(&bibl[licznikp], rozmiar, licznik - licznikp, pksiazki)

 do zapisania struktur do pliku a potem odczytuje je <code class="c">fread(&bibl[licznik], rozmiar, 1, pksiazki) == 1

I wszystko u niego działa prawidłowo. Czemu? Dane tutaj chyba również mogłyby się źle nadpisać.

1
olek1 napisał(a)

I wszystko u niego działa prawidłowo. Czemu? Dane tutaj chyba również mogłyby się źle nadpisać.

Nie rozumiesz różnicy między wartością, a jej reprezentacją. Autor książki zapisuje i odczytuje dane liczbowe binarnie, natomiast w Twoim pliku wartości liczbowe były ich reprezentacją znakową. W uproszczeniu, przy zapisie binarnym dane (liczbowe!) są liczbami binarnymi, natomiast przy zapisie znakowym są ciągami znaków, które na liczby trzeba przekonwertować.
Wiem, że od tego może się zakrecić w głowie, dlatego zróbmy mały eksperyment. Poniżej jest prosty program, który w pliku zapisuje strukturę w foracie binarnym i znakowym (tekstowym). Dla czytelności pominąłem obsługę błędów... nie wzoruj się na tym ;)

#include <stdio.h>
#include <string.h>


struct data {
    char    string_v[14];
    long    long_v;
    float   float_v;
};


int main(void) {
    struct data     test_data;
    FILE            *fh;


    /* Inicjujemy wartości testowe */
    strncpy(test_data.string_v, "To jest tekst", 13);
    test_data.long_v = 7689;
    test_data.float_v = 84.92;

    /* Zapisujemy dane w formacie binarnym
       Pod Windows możesz dodać "b", ale w tym
       przykładzie nie ma to znaczenia, gdyż nie
       będzie znaków nowej linii  */
    fh = fopen("binarny.dat", "w");
    fwrite(&test_data, sizeof(struct data), 1, fh);
    fclose(fh);

    /* Zapisujemy dane w formacie tekstowym */
    fh = fopen("tekstowy.dat", "w");
    fprintf(fh, "%s:%li:%f", test_data.string_v, test_data.long_v, test_data.float_v);
    fclose(fh);

    return 0;
}

Teraz zobaczmy jak te pliki wyglądają w środku. Użyję programu, który wyświetla offset (pierwsza kolumna) zawartość w postaci liczb szesnastkowych (środkowe kolumny) i zawartość w postaci znaków ASCII, zamieniając niedrukowalne bajty na kropki ("$" to prompt w uniksach).

$ hexdump -C binarny.dat 
00000000  54 6f 20 6a 65 73 74 20  74 65 6b 73 74 00 00 00  |To jest tekst...|
00000010  09 1e 00 00 00 00 00 00  0a d7 a9 42 00 00 00 00  |...........B....|
00000020
$ hexdump -C tekstowy.dat 
00000000  54 6f 20 6a 65 73 74 20  74 65 6b 73 74 3a 37 36  |To jest tekst:76|
00000010  38 39 3a 38 34 2e 39 31  39 39 39 38              |89:84.919998|
0000001c

Jak widzisz, w pliku binarnym są "krzaczki", a wartości liczbowe są nieczytelne. To dlatego, że wartość 7689 binarnie to jest (u mnie) 8 bajtów: 09 1e 00 00 00 00 00 00.
W przypadku zapisu tekstowego, liczby są stringami, czyli wartość 7689 jest przez fprintf() zamieniana na reprezentację znakową (char*) "7689". W pliku jest ona czytelna, gdyż zapisana jest jako 4 bajty: 37 36 38 39, które to w tablicy znaków ASCII są przyporządkowane odpowiednio znakom: "7", "6", "8" i "9".

Zapis binarny jest prostszy, gdyż praktycznie zrzucasz do pliku zawartość pamięci i nie musisz konwertować ciągu znaków na wartość liczbową przy odczycie (po prostu obszar pamięci zaalokowany dla zmiennej zapisujesz zawartością z pliku). Jest jednakże obciążony wadami.
Po pierwsze są dwa główne standardy przechowywania wartości liczbowych (wielobajtowych) w pamięci: Big Endian i Little Endian. Ponieważ zrzucasz do pliku strukturę, czyli zawartość obszaru pamięci przez nią zajmowaną, plik zapisany na platformie LE na platformie BE zawiera inne wartości, gdyż tam jest inna kolejność bajtów. W przypadku programów wieloplatformowych, które zapisują (lub wysyłają przez sieć) dane binarnie, ustalany jest format transportu (na LE lub BE) i przy zapisie/odczycie dokonuje się konwersji jeśli jest to konieczne.
Po drugie, w systemach 32bit i 64bit wielkości typów (ile bajtów zajmuje np. long) są różne. Np. wartość long zapisana pod systemem operacyjnym 64bit będzie odczytana tylko w połowie pod systemem 32bit.

Dodatkowo, jeśli zrzucasz "na chama" strukturę, dochodzi jeszcze jedna wada (której da się jednakże zaradzić): wyrównanie do słowa. Na różnych platformach długość słowa może być różna. Zwórć uwagę na daną tekstową w zapisie binarnym. W strukturze mamy tablicę char o wielkości 14 znaków, w której zapisaliśmy tekst o długości 13 znaków. W pliku jednak tych znaków jest 16. Za tekstem są trzy bajty zerowe. Pierwszy z nich to nasz słynny terminator (rozmawialiśmy o nim wcześniej), ale co tam robią dwa pozostałe bajty zerowe? To jest właśnie to wyrównanie do słowa. U mnie słowo ma 8 bajtów, zaś 14-stoelementowa tablica znaków zajmuje 14 bajtów. Zatem jedno-i-trochę słowa. Ten element struktury w pamięci został wyrównany do wielokrotności słowa bajtami zerowymi. 2 * 8 = 16. Zatem w naszym pliku są dwa bajty "od czapy".
Dlaczego w pliku tektowym nie ma naszego terminatora? Bo fprintf() używa bajtu zerowego jako informacji gdzie się kończy łańcuch znaków i nie jest on (bajt zerowy) częścią zawartości, zatem funkcja ta nie wysyła tego bajtu na wyjście. W formacie tekstowym narzędzia (funkcje) operujące na cstringach nie umieszczają bajtów zerowych. Dlatego m.in. uczulałem Cię na ich obecność w pliku. Ale uwaga! Przy zapisie binarnym bajty zerowe pojawić się mogą (co widać wyżej w wyjściu hexdump dla danych binarnych).

Na początku może się to wydawać skomplikowane, ale nie jest. Zainstaluj sobie hexedytor (pod Windows był AFAIR np. WinHex) i pooglądaj sobie jak wyglądają w rzeczywistości pliki różnych typów.

1 użytkowników online, w tym zalogowanych: 0, gości: 1