[C++ Win32 Console] deklaracja zmiennych, nieskończona pętla


(Bartlomiej Kwiatkowski) #1

Nie byłem pewien, jak zatytułować temat, więc pomysły mile widziane (w razie potrzeby zmienię).

Mam za zadanie napisać program, który obliczy sumę zakupów dokonanych przez klienta z uwzględnieniem upustów i podatku.

Założenia:

a) Klient może kupić dowolną ilość produktów

b) Dla każdego produktu, klient musi mieć możliwość podania ilości i ceny jednostkowej, po czym powinno nastąpić przeliczenie kosztu tego produktu wg wzoru: ilość * cena jednostkowa

c) Jeżeli koszt któregokolwiek z produktów przekracza $100, dla tego produktu należy odliczyć upust stanowiący 2.5% kosztu

d) Następnie klient jest pytany, czy chce kupić jeszcze jakiś produkt

e) Po przeliczeniu kosztu ostatniego produktu, program musi wyświetlić informację z podanym sumarycznym kosztem zakupów bez i z uwzględnieniem podatku, który stanowi 5% sumy wszystkich zakupów.

Oto mój program:

#include 


using namespace std;


int main()

{

	int QuantityPurchased;

	float AmountDue;

	float UnitPrice;

	int ItemCount;

	float ItemCost;

	float TotalAmountOfSales;

	float Tax;

	char UserResponse;


	do

	{

		cout << "Enter the quantity purchased: ";

		cin >> QuantityPurchased;

		cout << "Enter the unit price: ";

		cin >> UnitPrice;

		ItemCount++;

		cout << "Item number: " << ItemCount << ":";

		ItemCost = QuantityPurchased * UnitPrice;

		if (ItemCost > 100.0f)

			ItemCost = ItemCost - (ItemCost * 0.025f);

		cout << "\n\tItem cost: " << ItemCost;

		TotalAmountOfSales += ItemCost;

		cout << "\n\nIs there another item in the customer's cart?";

		cout << "\nEnter Y or N ----> ";

		cin >> UserResponse;

		UserResponse = toupper(UserResponse);

	} while ((UserResponse == 'Y')? true : false);


	cout << "\nTotal amount of sales: " << TotalAmountOfSales;

	Tax = TotalAmountOfSales * 0.05f;

	cout << "\nTax: " << Tax;

	AmountDue = TotalAmountOfSales + Tax;

	cout << "\nAmount due: " << AmountDue;

	cout << endl;


	return 0;

}

Podczas kompilacji i linkowania nie było żadnych problemów, ale pojawiły się po uruchomieniu programu: Błąd #1:

Microsoft Visual C++ Debug Library

Program: c:\assignment007\Debug\assignment007.exe

Module: c:\assignment007\Debug\assignment007.exe

File: c:\assignment007\assignment007.cpp

Line 22


Run-Time Check Failure #3 - The variable 'ItemCount' is being used without being defined.

Błąd #2

...

Line 28


Run-Time Check Failure #3 - The variable 'TotalAmountOfSales' is being used without being defined.

Poprawiłem je ustalając wartości początkowe dla tych zmiennych, odpowiednio na 0 (dla int) i na 0.0f (dla float).

Kolejna rzecz, jaką odkryłem przez przypadek: jeśli odpowiadając na komunikat "Is there another item in the customer's cart?", wpiszemy przez przypadek: yy zamiast y lub Y , to program wpada w nieskończoną pętlę. Moje pytania są takie:

  • czy należy ustalać wartość początkową dla każdej zmiennej liczbowej?

  • jak ustrzec się przed pętlą?

  • jakie macie inne sugestie?

Z góry wielkie dzięki!


(Ryan) #2

ItemCount nie ma wartości początkowej - deklarujesz zmienną, nie przypisujesz jej wartości a następnie ją inkrementujesz. Nie wiadomo ile + 1 = ile? To samo tyczy się TotalAmountOfSales. Przy odpowiednio wysokim poziomie ostrzeżeń (/W4) powyższe kompilator pewnie zaraportowałby jako błędy kompilacji. Ale, że to źle - już wiesz.

Tak, dla każdej zmiennej - nie tylko liczbowej. Deklarujesz char* -- przypisujesz NULL (null). Alokujesz pamięć, używasz, dealokujesz - przypisujesz natychmiast null!

Rady ode mnie:

  • skoro nie piszesz obiektowo -- nie używaj cin/cout

  • waliduj pobrane wartości -- Twój program się posypie, jeśli jakaś wartość otrzyma literę

  • staraj się nie czytać wejścia do zmiennych innego typu niż znakowego (mam na myśli char* a nie char - char też dobry nie jest)

  • upewnij się, że znak ma długoś jednego bajtu (char) a nie dwóch (short)

  • deklarując zmienne grupuj je typami albo ustawiaj alfabetycznie

  • staraj się wyrównywać kod na znakach równości i/lub pierwszych literach deklaracji a będzie czytelniejszy

  • komentarze nie bolą

  • puste linie też nie

  • używaj inkrementacji i dekrementacji prefiksowych a nie postfiksowych kiedy tylko możesz.


(Bartlomiej Kwiatkowski) #3

Na razie nie stosujemy obiektów, ale pod koniec semestru będziemy. Nie wiem co innego tu zastosować, żeby przesłać komunikat na ekran i wprowadzić wartość podaną przez użytkowanika do zmiennej.


(Ryan) #4

Funkcje

IF THEN nie jest pętlą, to jedno. Operator >> cin nie sprawdza rozmiaru bufora i jest na liście niebezpiecznych funkcji

https://buildsecurityin.us-cert.gov/dai ... language=1

Użyj metody get() strumienia z rozsądnej wielkości buforem:

char str[256];

cin.get (str,256);

Skonwertuj przy pomocy funkcji ato* (np. atoi, atol, atof). Pamiętaj, żeby uważać na przepełnienie arytmetyczne - używaj odpowiedniej funkcji dla odpowiedniego typu zmiennych.

http://msdn2.microsoft.com/en-us/library/hc25t012(VS.71.aspx

Opisane powyżej. Czytaj do char* i konwertuj. Do kładnie to samo się dzieje jak czytasz bezpośrednio do zmiennej np. liczbowej, ale wykonywane jest jawnie i masz nad tym kontrolę.

Zależy od środowiska. W czym piszesz?


(Bartlomiej Kwiatkowski) #5

Witam ponownie,

Ryan , próbuję wykombinować jak się stosuje podane przez Ciebie funkcje. Nie chciałem wklejać znowu kodu całego programu, więc pokażę moje "osiągnięcia" na przykładowym programiku.

Jeżeli chciałbym twoim sposobem wyświetlić komunikat na ekranie, a następnie wczytać wartość wprowadzoną z klawiatury do zmiennej int QuantityPurchased, to jak to zrobić?

Na razie wskórałem tyle, że program poprawnie wyświetla komunikat:

#include 


int main()

{

	char str[256];

		char str[] = "Enter the quantity purchased: ";

		puts (str); // ale poniżej coś nieźle namieszałem

		cin.get (str,256);

		double atoi(

			char str

			);


	return 0;

}

Mam trzy błędy w kompilacji:

error C2086: 'char str[256]' : redefinition

error C2065: 'cin' : undeclared identifier

error C2228: left of '.get' must have class/struct/union type

Nie wiem też, w którym miejscu powinna być umieszczona nazwa zmiennej QuantityPurchased, żeby wartość z klawiatury została do niej wczytana.


(Ryan) #6

Kompletnie pomieszałeś. :slight_smile:

Jeśli chcesz wyświetlić tekst, wciąż używasz cout, np:

cout << "Podaj liczbę";

Problem pojawia się w momencie odczytu. Żeby zrozumieć problem trzeba rozumieć działanie pamięci. Pamięć jest przestrzenią liniowo adresowalną, czyli składa się z określonej ilości bajtów, z których każdy ma swój indeks (określone położenie). Bajt zerowy, pierwszy, drugi, itd. aż do ostatniego określonego ilością Twojego ramu. W bardzo dużym uproszczeniu ładując program ładuje się on gdzieś do pamięci i zajmuje tyle miejsca ile plik wykonywalny.

Pamięć: (.) wolna (X) zajęta przez program

..................XXX....................................

Na program składa się kod i dane. Znowu w uproszczeniu wygląda to tak, że masz kawałek kodu, zadeklarowałeś zmienną po czym masz kawałek kodu, więc informacje te będą w pamięci ułożone

(K) kod programu (D) dane

.....KKKDKKDKKKKKKDK

Kiedy w programie masz zadeklarowane zmienne - operując na nich zmieniasz wartość danych w pamięci, np:

byte bX;

int iValue;

char wszName[20];

Pod pewnym adresem w pamięci (adres = pozycja danej w pamięci komputera) umieści zmienną bX, pod innym iValue, pod innym tab. Znowu upraszczając dane te będą ułożone jedna po drugiej:

(b) bX (i) iValue (w) wszName

...biiiiwwwwwwwwwwwwwwwwwwww....

Każda zmienna zajmuje określoną przestrzeń: byte i char 1, chort 2, int 4, itd. (tak naprawdę wiele zależy od kompilatora i architektury komputera - kolejne uproszczenie). Problemem z cin >> zmienna jest to, że cin nie wie jakiej wielkości jest zmienna do której wczytujesz znaki. Za powyższym obszarem gdzie przechowujesz znaki (wwwww...) coś z pewnością jest - inne zmienne, kod programu - nie wiadomo. Jeśli cin od adresu początkowego zacznie upychać to co wklepałeś z klawiatury, nadpisana zostanie pamięć za tą pamięcią, która przeznaczona jest na bufor znakowy. Co się stanie? Zmieni się wartość innej (nie wiadomo której) zmiennej lub zmianie ulegnie kod programu. Żeby mi tutaj nikt nie zarzucał gadania bzdur - to bardzo prosty, wręcz naiwny model działania pamięci i jej reperkusji w programie. Problem zatem dotyczy wyłącznie odczytu danych. Jeśli zadeklarujesz bufor znakowy na 200 znaków to nie chcesz czytać więcej niż 200 znaków. Kod:

char tab[] = "napis";

stworzy zmienną (tablicę) zdolną przechować 6 znaków (napis ma 5 liter, szósty znak to znak o kodzie 0, który oznacza koniec ciągu). Wczytując do takiego stringa dane chcesz, żeby tekst nie przekraczał 5 znaków (ostatni znak musi być zawsze \0 - jeśli wczytasz 6 znaków, siódmy - czyli już poza buforem, zostanie nadpisany znakiem zerowym). Kod:

char tab[200];

Stworzy tablicę 200 znaków. cin >> tab odczyta nieokreśloną ilość znaków. W ten sposób nie masz kontroli nad odczytem. Z kolei cin.get pozwala Ci określić ine znaków chcesz wczytać. Kod:

char str[256];

char str[] = "Enter the quantity purchased: ";

jest błędny, bo w drugiej linijce ponownie starasz się zadeklarować zmienną o nazwie str, tym razem krótszą niż 256 znaków. char* albo czhar[] (są drobne różnice) jest zwykłą tablicą. Kompilator wie jak skopiować char do chara, int do inta, ale nie wie jak przenosić tablice, bo nie wie kiedy się która kończy, nie wie zatem ile tych danych przenieść. Od kopiowania treści jednej tablicy do drugiej służy funkcja strcpy. Pamiętaj jegnak, żeby sprawdzić rozmiar docelowej tablicy, czy aby nie jest mniejsza niż źródłowa, bo przepełnisz bufor. Skąd to wiedzieć? Możesz trzymać zmienną, która określa ilość bajtów w tablicy. Są i inne sposoby (szczególnie w przypadku czhar[]). Drugi błąd: deklaracja zmiennej atoi mówi, że pobierasz char* a zwraca inta.

int atoi(char *);

Karmisz ją zatem tablicą znaków a zwróconą wartość przypisujesz do zmiennej liczbowej:

char buf[100];

int i;

cin.get(buf, 100);

i = atoi(buf);

cout << i;

Wywołując funkcję nie podajesz typów w nawiasach czy przed wywołaniem jej. Poza deklaracją zmiennej typy wykorzystuje się wyłącznie przy rzutowaniu typów i określaniu rozmiaru zmiennej, ale tego tutaj nie potrzebujesz.