[C++] Program wypisujący wyrazy parzyste i nieparzyste


(Quentin) #1

Witam!

Miałem napisać taki program, który wczytuje string z tablicy napis1 i wyrazy parzyste mają znaleźć się w tablicy (znakowej) parzyste , a nieparzyste w tablicy nieparzyste. String ma 5 wyrazów więc ostatni jest nieparzysty stąd taki dość dziwny warunek przy while. (Zaznaczyłem to miejsce wykrzyknikiem w komentarzu)...

Gdy go kompiluje wyświetla się taki komunikat:

lolbb1.gif

#include 

using namespace std;

void strcpy(char zrodlo[], char parzyste[], char nieparz[]);

int main()

{


	char napis1[23] = { "It is very stupid test" }; // 22 elementów + NULL (0)

	char parzyste[23];

	char nieparzyste[23];


	strcpy(napis1, parzyste, nieparzyste);


	cout "Mamy ciag wyrazow w tablicy 'napis1': " napis1 "\n"

		  "\tWyrazy parzyste to: " parzyste

		  "\tWyrazy nieparzyste to: " nieparzyste;



cout "\n\n\n\n\n\n---------------------------------------------------------------\n";

system("pause");

}

void strcpy(char zrodlo[], char parzyste[], char nieparz[])

{

	int i = 0;

	int x = 0;

	int bbb = 0;


	while(nieparz[i] != 0) // --- to ten dość dziwny warunek, no ale lepszego nie wymyśliłem...

	{


		for( ; ; i++)

		{

			nieparz[i] = zrodlo[bbb];

                        b++;

			if(nieparz[i] == ' ')

			{

				break;

			}

		}

		for( ; ; x++)

		{

			parzyste[x] = zrodlo[bbb];

			bbb++;

			if(parzyste[x] == ' ')

			{

				break;

			}

		}


	}


}

Co jest w nim nie tak :?:


(somekind) #2

A jakie są komunikaty o błędach?


(camper) #3

funkcja strcpy posiada dwa parametry:

http://www.cplusplus.com/reference/clibrary/cstring/strcpy.html

nie umiem dobrze c++ ale wydaje mi się, że po pierwsze wywołujesz funkcję samą funkcją (sic), która z resztą jest (jw) źle napisana, mhh

po drugie jeśli nawet użyjesz prawidłowo strcpy na początku, (co wydaje mi się jest błędem), to już na początku będziesz miał w tablicach parzyste i nieparzyste skopiowane znaki.

wydaje mi się, że trzeba by użyć jedną pętlę

for(;:wink: i sprawdzać indeksy czy są parzyste czy nie, i wtedy wrzucać znaki do poszczególnych tablic

jak myślicie?


(Quentin) #4
First-chance exception at 0x00000000 in Zamiana_tablicy_znakowej_na_parzyste_i_nieparzyste_wyrazy.exe: 0xC0000005: Access violation reading location 0x00000000.

I tak ze 100 linijek tego...

Dwa parametry będzie miała, jak jej nie napiszę ja, tylko wywołam z jakiejś biblioteki, chyba ... Zresztą i tak nawet po zmianie nazwy jest źle... Takie komunikaty na moje oko znaczą, że coś jest na pewno z pętlą while, pewnie pamięć zapełniona albo cuś...

EDIT:

Ale co podejrzewacie, że jest źle z tą pętlą :?: Może jakiś inny warunek :?:


(somekind) #5

Nie chcę wnikać w Twój kod, bo kompletnie nie mam pomysłu co chciałeś w ten sposób osiągnąć. Widzę tylko dwa nieskończone fory w jednym while'u i straszny galimatias w indeksach. Mieszasz coś w pamięci i dziwisz się, że błędy są. Jeśli chcesz się dowiedzieć dlaczego, to najprościej będzie Ci chyba użyć debbugera.

A najlepiej będzie NAJPIERW wymyślić algorytm, a POTEM go zapisać w języku.


(Quentin) #6

Mamy string "It is very stupid test". W funkcji jest przypisany do tablicy zrodlo[bbb]. bbb zawsze na koniec pętli się inkrementuje. Przykładowo pierwszy wyraz będzie na pewno nie parzysty bo można powiedzieć, że jest pierwszy, a 1 jest nie parzysta. Dlatego jest on w pierwszej pętli for:

nieparz[i] = zrodlo[bbb];

bbb++;

if(nieparz[i] == ' ')

Pierwszy element tablicy zrodlo jest przypisywany do pierwszego elementu tablicy nieparz. Jest to literka "I", następnie jest "t" i spacja " ". Jeżeli jest spacja to oznacza to, że to koniec wyrazu i wyskakujemy z pętli. Zgodnie z logiką po wyrazie nieparzystym przychodzi kolej na parzysty :expressionless: Za to odpowiada 2 pętla for. W tym przypadku jest dokładnie tak samo jak w pierwszym, tyle, że zamiast tablicy nieparz jest tablica parzyste. Po "przebiegnięciu" wyrazu "is" zaczyna się ponownie wykonywać pętla while, ponieważ aktualny element tablicy parzyste jest różny od zera - warunek jest związku z tym prawdziwy. C-string kończy się po wyrazie "test". Znajduje się tam znak 0, a więc w końcu nastąpi przypisanie NULL'a do tablicy nieparz. (Potem już nie ma żadnych wyrazów, zapomniałem więc o takiej modyfikacji 2 pętli for):

for( ; nieparz[i] != 0 ; x++)

To jest naprawdę, aż tak zawiłe :?: :!:


(Sawyer47) #7

Owszem, twój kod jest bardzo zawiły i do tego błędny. Radzę napisać wszystko od początku, może lepiej najpierw pomyśleć nad sposobem z kartką i ołówkiem?


(somekind) #8

Wiesz - można komentować kod w trakcie (używając // i /* */), wtedy nie trzeba pisać oddzielnych wyjaśnień. Masz dziwnie ułożone pętle, na dodatek dwa fory bez ograniczenia przebiegu pętli, a jedynie z break, który nie musi nigdy nastąpić. Obstawiam, że dlatego wyjeżdżasz w pętli poza pamięć tablicy i masz błąd pamięci.

Nie wiem, czemu używasz trzech pętli, gdy wystarczy jedna, a do zmienna logiczna przechowująca informację o tym, czy wyraz jest parzysty czy nie. W pętli wystarczy w konstrukcji if-else przypisać znaki ze źródła do odpowiedniej tablicy w zależności od wartości zmiennej logicznej.


(Quentin) #9

Ehh, chodzi tu o zmienną typu bool, tak :?: Nie mogę poradzić sobie z jedną rzeczą - przecież gdy nastąpi spacja musi oznacza to, że następuje drugi wyraz - parzysty lub nie. No ale na końcu nigdy nie ma spacji... :confused:

char napis1[23] = { "It is very stupid test" };

A przecież nie pisze się tak:

char napis1[23] = { "It is very stupid test " };

Mógłbym to zrobić jakoś poprzez przesłanie arg. o rozmiarze stringu, ale funkcja niestety musi mieć tylko te 3 argumenty...


(somekind) #10

Przechodzisz po wszystkich znakach w pętli, dopóki wartość znaku = 0, czyli nie trafisz koniec łańcucha.

while(zrodlo[i] != 0)

{

   ...

   i++;

}

Masz zmienną typu bool, która określa, czy wyraz jest parzysty, czy nieparzysty - pierwszy jest oczywiście nieparzysty. W pętli sprawdzasz ten warunek i na tej podstawie kopiujesz znak ze zrodlo do odpowiedniej tablicy. A następnie sprawdzasz, czy znak ze źródła nie jest spacją. Jeśli jest, to po prostu zmieniasz wartość zmiennej logicznej. I nie ma znaczenia, czy na końcu jest spacja, czy nie.

Teraz jasne?


(Quentin) #11

Tak, jak słońce :smiley:

Nie wiem jak Ci dziękować :wink: Tyle, że tam jest mały błąd, inkrementacja i powinna być na początku, bo do tych jednej z tablic nie dopisze się 0 i będzie niepoprawny C-string...

#include 

using namespace std;

void zamiana(char zrodlo[], char parzyste[], char nieparz[]);

int main()

{


	char napis1[23] = { "it is very stupid test" }; // 22 elementów + NULL (0)

	char parzyste[100];

	char nieparzyste[100];


	zamiana(napis1, parzyste, nieparzyste);


	cout << "Mamy ciag wyrazow w tablicy 'napis1': " << napis1 << "\n"

		 << "\tWyrazy parzyste to: " << parzyste << "\n"

		 << "\tWyrazy nieparzyste to: " << nieparzyste;



cout << "\n\n\n\n\n\n---------------------------------------------------------------\n";

system("pause");

}

void zamiana(char zrodlo[], char parzyste[], char nieparz[])

{

	int x = 0; // specjalnie dla naszej tablicy 'nieparz'

	int b = 0; // specjalnie dla naszej tablicy 'parzyste'

	int i = -1; // specjalnie dla oryginalnego zrodla

	bool wyraz = false;

	while(zrodlo[i] != 0)

    {

		i++;

        if(wyraz)

		{

			parzyste[b] = zrodlo[i];

			b++; // po parzyste[0] następuje element [1] więc za każdym

			if(zrodlo[i] == ' ') // razem inkrementujemy 'b'

				wyraz = false;

		}

		else

		{

			nieparz[x] = zrodlo[i];

			x++; // po nieparz[0] następuje element [1] więc za każdym

			if(zrodlo[i] == ' ') // razem inkrementujemy 'b'

				wyraz = true;

		}

    }


}

W DevC++ to działa poprawnie, w Visual 2008 Express niestety nie.....


([alex]) #12

Nudziło mi się :smiley:

#include 

using namespace std;


//void strcpy(char zrodlo[], char parzyste[], char nieparz[]);

void strcpy(const char *zrodlo,char *parzyste,char *nieparz);


int main()

  {

   char napis1[100] =" It is very stupid test "; // BEZ KLAMER

   char parzyste[100];

   char nieparzyste[100];


   //strcpy(napis1,parzyste,nieparzyste);

   strcpy(napis1,parzyste,nieparzyste);


   cout

      <<"Mamy ciag wyrazow w tablicy 'napis1': \""<
      << "\tWyrazy parzyste to: \""<
      << "\tWyrazy nieparzyste to: \"" << nieparzyste<<'"'<
   ;

   cin.get();

   return 0;

  }


/* //rozwiązanie primitiwne

void strcpy(char zrodlo[], char parzyste[], char nieparz[])

  {

   int ip=0,in=0; // poczatkowe indeksy;

   bool wyr=false,par=true; // wyr - czy jesteśmy w słowie (false=pomiędzy słowami), par - czy parzysty

   for(int i=0;zrodlo[i];++i) // pętla znak po znaku ze źródła

     {

      if(zrodlo[i]!=' ') // jeżeli nie spacja

        {

         if(!wyr) // jeżeli to pierwsza litera słowa

           {

            par=!par; // przełącz

            if(par) { if(ip) parzyste[ip++]=' '; } // jeżeli w parzystym i parzyste[] puste to dopisz spacje do parzyste[]

            else { if(in) nieparz[in++]=' '; } // jeżeli w nieparzystym i nieparz[] puste to dopisz spacje do nieparz[]

            wyr=true; // już jesteśmy w słowie

           }

         if(par) parzyste[ip++]=zrodlo[i]; // jeżeli w parzystym to dopisz znak do parzyste[]

         else nieparz[in++]=zrodlo[i]; // jeżeli w nieparzystym to dopisz znak do nieparz[]

        }

      else wyr=false; // już jesteśmy pomiędzy słowami

     }

   parzyste[ip]=0; // znak końca napisu dla słów parzystych

   nieparz[in]=0; // znak końca napisu dla słów nieparzystych

  }

*/


/* //rozwiązanie normalne

void strcpy(const char *zrodlo,char *parzyste,char *nieparz)

  {

   bool wyr=false,par=true,fp=false,fn=false; // wyr - czy jesteśmy w słowie (false=pomiędzy słowami), par - czy parzysty

                                                                  // fp/fn - czy trzeba dodać spacje do nieparzystych/parzystych odpowiednio

                                                                  // fp/fn - jeżeli true to przed kolejnym słowem dopisz spacje

   for(;*zrodlo;++zrodlo) // pętla znak po znaku ze źródła

     {

      if(*zrodlo!=' ') // jeżeli nie spacja

        {

         if(!wyr) // jeżeli to pierwsza litera słowa

           {

            par=!par; // przełącz

            if(par?fp:fn) *((par?parzyste:nieparz)++)=' '; // do odpowiedniej listy (w zależności od par) dopisz spacje jeżeli potrzebna

            wyr=true; // już jesteśmy w słowie

           }

         *((par?parzyste:nieparz)++)=*zrodlo; // do odpowiedniej listy (w zależności od par) dopisz znak 

         (par?fp:fn)=true; // ustaw odpowiednią flagę (w zależności od par) - przed następnym słowem dopisz spację

        }

      else wyr=false; // już jesteśmy pomiędzy słowami

     }

   *parzyste=0; // znak końca napisu dla słów parzystych

   *nieparz=0; // znak końca napisu dla słów nieparzystych

  }

*/


// rozwiązanie ambitne

void strcpy(const char *zrodlo,char *parzyste,char *nieparz)

  {

   char **w=0; // wskaźnik na parzyste/nieparz będzie zależało od zmiennej par

   bool wyr=false,par=true,fp=false,fn=false,*f=0; // wyr - czy jesteśmy w słowie (false=pomiędzy słowami), par - czy parzysty

                                                                         // fp/fn - czy trzeba dodać spacje do nieparzystych/parzystych odpowiednio

                                                                         // fp/fn - jeżeli true to przed kolejnym słowem dopisz spacje

                                                                         // f wskaźnik na fp/fn będzie zależało od zmiennej par

   for(;*zrodlo;++zrodlo) // pętla znak po znaku ze źródła

     {

      if(*zrodlo!=' ') // jeżeli nie spacja

        {

         if(!wyr) // jeżeli to pierwsza litera słowa

           {

            if(par) { f=&fn; w=&nieparz; par=false; } // jeżeli poprzednie słowo parzyste, ustaw w/f na niepaz/fn odpowiednio oraz przełącz

            else { f=&fp; w=&parzyste; par=true; } // jeżeli poprzednie słowo nieparzyste, ustaw w/f na parzyste/fp odpowiednio oraz przełącz

            if(*f) *((*w)++)=' '; // jeżeli potrzebna spacja (wg fp/fn) to dopisz spacje do parzyste/niepaz

            wyr=true; // już jesteśmy w słowie

           }

         *((*w)++)=*zrodlo; // dopisz znak do parzyste/niepaz

        (*f)=true; // staw flagę fp/fn - przed następnym słowem dopisz spację

        }

      else wyr=false; // już jesteśmy pomiędzy słowami

     }

   *parzyste=0; // znak końca napisu dla słów parzystych

   *nieparz=0; // znak końca napisu dla słów nieparzystych

  }

EDIT: Dopisałem komentarzy, specjalnie dla osób którzy wolą programy "przejrzyste" od działających


(somekind) #13

Quentin - Twój kod chodzi w VS poprawnie, tzn. tak jak jest napisany.

Parę uwag:

1) zmienne i, b, x - ich nazwy niewiele mówią. Ja bym je nazwał np iz, in, ip od "iterator źródła", "iterator nieparzystych", "iterator parzystych" - tak będzie się łatwiej zorientować później. Można definiować wiele zmiennych w jednej linii, wiesz o tym?

int iz = 0, in = 0, ip = 0;

2) zmienna "wyraz" - gorzej jej nazwać się nie dało, ta nazwa kompletnie nic nie mówi. Zmienne logiczne najlepiej nazywać np. "czyCośTam", wtedy łatwo zrozumieć do czego służą, ja proponuję:

bool czyParzysty = false;

3) Jeśli i++ (w mojej wersji iz++) dasz na koniec pętli, to nie będziesz musiał zaczynać od -1. Ja bym tak zrobił, bo dla mnie logiczna wydaje się iteracja na koniec pętli :slight_smile: 4) Dwa razy sprawdzasz, czy znak jest spacją - ale po co tak? Przecież możesz to zrobić po if - else kopiującym znaki, np tak:

if(zrodlo[iz] == 32) //jeśli to spacja

    czyParzysty = !czyParzysty; //to wyraz nie jest taki, jaki był do tej pory

Prawda, że prościej? 5) I najważniejsze - zapomniałeś o "zakończeniu" skopiowanych łańcuchów, przez co mogą być one niepoprawnie wyświetlane, tzn. mogą być wyświetlane śmieci znajdujące się w tablicy, za tekstem, który wpisałeś. I pewno o ten problem z VS Ci chodziło. Po prostu pod koniec pętli trzeba dać:

parzyste[ip] = 0;

nieparz[in] = 0;

[alex] - moim zdaniem każde Twoje rozwiązanie, które jest siecią zagnieżdżonych if-else jest prymitywne. Ale za to jest świetnym przykładem jak nie programować i jak sobie utrudnić życie motając prosty problem w stopniu niesamowitym.

Na dodatek nie sądzę, żeby Quentin był na etapie wskaźników, więc mieszanie mu tutaj nimi jest co najmniej nie na miejscu.

No i wklejanie gotowego kodu (zwłaszcza takiego) ma zerowy skutek dydaktyczny i wcale nikomu nie pomaga, a wręcz odwrotnie. Tak przy okazji, to chyba coś o tym regulamin nawet mówi.


(Quentin) #14

Wiem, ale ja po prostu do tego nie przywykłem jeszcze, tak samo jak nie przywykłem do wyrażeń typu i *= 2 :wink:

Dzięki za rade, rzeczywiście z tym 'czy' dobry pomysł :slight_smile:

Myślałem, że nie będzie działać, bo jak i pod koniec pętli dojdzie do zera to następnej pętli już nie będzie i nie przypisze się tego null'a do końcowego wyrazu w tablicy parzyste lub nieparz - w zależności na jakim wyrazie się kończy to. Rozwinę ten temat bardziej żeby upewnić się czy dobrze to rozumiem. Przykładowo jak zapomniałem o przypisaniu tych NULLi jak to piszesz w 5) i i++ było na "starym" miejscu to jest coś takiego w cmd: 58422885ca7.jpg


(somekind) #15

W dziesiętnym systemie liczbowym pewno tak.

Ale nie o to tu chodziło przecież :slight_smile:

Pomyliłem się pisząc poprzedniego posta - ja to zrobiłem na koniec funkcji, już za pętlą. Ale nawet pod koniec ciała pętli też działa (tylko jest to bez sensu). Dlaczego? Bo w tym momencie ip i in wskazują na następne niezapisane jeszcze elementy tablic docelowych, więc zerami nadpisywane jest to, czego w zasadzie jeszcze nie używałeś wcześniej.

No chyba właśnie jest tak, jak piszesz, że inicjalizowane są zerami. Bo Twój kod jest przecież równoważny takiemu:

char A[10] = { 'a', 'b', 'c', 'd', 'e', 'f', 0, 0, 0, 0 };


for(int x = 0 ; x < 10 ; x++)

      cout << A[x] << "\n";

A czemu się nie wyświetlają? A co się ma wyświetlać, przecież 0 to NULL, jest niedrukowalne. Widzisz różnicę między

char a = '0';

a

char a = 0;

?

Zauważ, że to pierwsze ma kod ASCII 48, a to drugie ma kod ASCII 0.


(Quentin) #16

No w tym przypadku rzeczywiście, ale w pozostałych działa jak zwykła dyrektywa #define zamieniająca NULL na 0:

cout << NULL;

U mnie wyświetli się 0 - ale to zależy od kompilatora czy obsługuje to jeszcze...


(Fiołek) #17

Wartości w G++ są inicjowane zerami, w VC++ jest to maksymalna wartość dla danego typu(dla int - INT_MAX, dla short - SHORT_MAX itd.), więc w tym wypadku reszta będzie inicjowana w zależności od kompilatora(AFAIK inicjalizacji zbiorowa, jak to nazwałeś, występuje tylko wtedy, gdy podamy jeden element).

EDIT:

W C++ NULL to zwykłe 0, czyli int. W C NULL to zero rzutowane na void*, czyli ((void*)0) :wink:


([alex]) #18

Jakoś nikt nie zauważył że na pierwszym kroku pętli sprawdzasz czy zrodło[-1] nie jest końcem napisu.

Skutkami tego może być:

  1. Access violation

  2. Brak nawet jednego kroku pętli mimo że napis nie jest pusty.

Popatrz na trzy rozwiązania podane tu


(somekind) #19

[alex] - przestań wreszcie wciskać swój chłamowaty kod. Co z tego, że szpanujesz użyciem wskaźników, które powinny być wydajniejsze, niż dostęp po indeksach, skoro po Twojej gmatwaninie instrukcji warunkowych procesor skacze jak konik polny? Kod powinien być przede wszystkim przejrzysty i łatwy do zrozumienia.

Jeśli uważasz, że Twoje dzieła są lepsze niż to, do czego Quentin doszedł tutaj z naszą pomocą, to podaj jakiś argument - jestem pewien, że nie tylko ja chętnie go poznam.

Dla przypomnienia:

void strcpy(char zrodlo[], char parzyste[], char nieparz[])

{

    int iz = 0, in = 0, ip = 0; //iteratory poszczególnych tablic

    bool czyParzysty = false; //bo pierwszy wyraz musi być nieparzysty

    while(zrodlo[iz] != 0) //dopóki nie natrafiliśmy na koniec łańcucha

    {

        if(czyParzysty) //jeśli wyraz jest parzysty

        {

            parzyste[ip] = zrodlo[iz]; //to jego znaki dopisujemy do tablicy parzystych

            ip++; //iteracja odpowiedniego iteratora

        }

        else //a jeśli nieparzysty

        {

            nieparz[in] = zrodlo[iz]; //to jego znaki dopisujemy do tablicy nieparzystych

            in++; //iteracja odpowiedniego iteratora

        }

        if(zrodlo[iz] == 32) //jeśli trafiliśmy na spację

            czyParzysty = !czyParzysty; //to przełączamy flagę wyrazu parzystego/nieparzystego

        iz++; //iteracja iteratora znaków źródłowych

    };

    parzyste[ip] = 0; //tu koniec parzystych

    nieparz[in] = 0; //a tu nieparzystych (żeby nie wyświetlało śmieci)

}

A jeśli już konieczna wersja na wskaźnikach, to np. taka:

void strcpyWsk(char* zrodlo, char* parzyste, char* nieparz)

{

    bool czyParzysty = false; //bo pierwszy wyraz musi być nieparzysty

    while(*zrodlo != 0)

    {

        if(czyParzysty)

            *parzyste++ = *zrodlo;

        else

            *nieparz++ = *zrodlo;

        if(*zrodlo == 32)

            czyParzysty = !czyParzysty;

        zrodlo++;

    };

    *parzyste = 0;

    *nieparz = 0;

}

([alex]) #20

somekind - przestań wreszcie wciskać swój chłamowaty kod. Kod powinien być przede wszystkim działający zaś przejrzysty to już na drugim miejscu wg ważności. Co do "łatwy do zrozumienia" - wytłumacz mi proszę czym to się różni od "przejrzysty" ?

Każde z "moich dzieł", poprawnie podzieli na parzyste i nieparzyste napis:

char napis[]="Ala ma kota" // UWAGA! po dwie spacji pomiędzy słowami

no i jest oczywiste że to co podałeś w "Dla przypomnienia:" nie podzieli tego napisu poprawnie.