EXERCISE ■ 449 Exercise Enhance the numerical class Fraction, which you know from the last chapter, to convert both double values to fractions and fractions to double. In addition, fractions should be rounded after arithmetic operations. ■ First declare the simplify() method for the Fraction class and insert the definition on the opposite page in your source code.The method computes the largest common divisor of numerator and denominator. The numerator and the denominator are then divided by this value. ■ Add an appropriate call to the simplify() function to all operator func- tions (except ++ and ). ■ Then add a conversion constructor with a double type parameter to the class. Example: Fraction b(0.5); // yields the fraction 1/2 Double values should be converted to fractions with an accuracy of three decimal places.The following technique should suffice for numbers below one million. Multiply the double value by 1000 and add 0.5 for rounding. Assign the result to the numerator. Set the value of the denominator to 1000.Then proceed to simplify the fraction. ■ You now have a conversion constructor for long and double types.To allow for conversion of int values to fractions, you must write your own conversion constructor for int! ■ Now modify the class to allow conversion of a fraction to a double type number. Define the appropriate conversion function inline. Use the function main() to test various type conversions. More specifically, use assignments and arithmetic functions to do so.Also compute the sum of a fraction and a floating-point number. Output the operands and the results on screen. solution 450 ■ CHAPTER 20 TYPE CONVERSION FOR CLASSES ■ SOLUTION // // Fraction.h // A numerical class to represent fractions. // The class converts Fraction < > double // and simplifies fractions. // #ifndef _FRACTION_ #define _FRACTION_ #include <iostream.h> #include <stdlib.h> class Fraction { private: long numerator, denominator; public: Fraction(long z, long n); Fraction(double x); // double-constructor // Default long- and int-constructor: Fraction(long z=0) : numerator(z), denominator(1) {} Fraction(int z) : numerator(z), denominator(1) {} void simplify(); operator double() // Fraction -> double { return (double)numerator / (double)denominator; } Fraction operator-() const { return Fraction(-numerator, denominator); } Fraction& operator+=(const Fraction& a) { numerator = a.numerator * denominator + numerator * a.denominator; denominator *= a.denominator; simplify(); return *this; } Fraction& operator-=(const Fraction& a) { *this += (-a); simplify(); return *this; } // The rest of the class including methods // operator++() and operator () // and friend declarations are unchanged. }; #endif SOLUTION ■ 451 // // Fraction.cpp // Defines methods and friend functions // that are not inline. // #include <iostream.h> #include <stdlib.h> #include "Fraction.h" // Constructors: Fraction::Fraction(long z, long n) { // Unchanged! Same as in Chapter 19. } Fraction::Fraction( double x) { x *= 1000.0; x += (x>=0.0) ? 0.5 : -0.5; // Round the 4th digit. numerator = (long)x; denominator = 1000; simplify(); } Fraction operator+(const Fraction& a, const Fraction& b ) { Fraction temp; temp.denominator = a.denominator * b.denominator; temp.numerator = a.numerator*b.denominator + b.numerator * a.denominator; temp.simplify(); return temp; } // The functions // operator-() operator<<() operator>>() // are left unchanged. // The functions // operator*() and operator/() // are completed by a call to temp.simplify() // just like the function operator+(). // // The code of method Fraction::simplify(), as // specified in the exercise, should be here. 452 ■ CHAPTER 20 TYPE CONVERSION FOR CLASSES // // Fract_t.cpp // Tests the class Fraction with type conversions. // #include <iostream.h> #include "Fraction.h" int main() { Fraction a, b(-1,5), c(2.25); double x = 0.5, y; a = x; // double -> Fraction cout << "\nSome test results:\n" << endl; cout << " a = " << a << endl; cout << " b = " << b << endl; cout << " c = " << c << endl; cout << "\nThe fractions as double values:\n" << endl; // Fraction -> double: cout << " a = " << (double)a << endl; cout << " b = " << (double)b << endl; cout << " c = " << (double)c << endl; cout << "\nAnd calculate with:\n" << endl; cout << " a + b = " << (a + b) << endl; cout << " a - b = " << (a - b) << endl; cout << " a * b = " << (a * b) << endl; cout << " a / b = " << (a / b) << endl; cin >> a; // Enter a fraction. cout << "\nYour input: " << a << endl; a.simplify(); cout << "\nSimplified: " << a << endl; cout << "\nAs double value: " << (double)a << endl; cout << "\nEnter a floating point value: "; cin >> x; cout << "\nThis is in fraction form: " << (Fraction)x << endl; // To calculate the sum b + x : cout << " b = " << b << endl; cout << " x = " << x << endl; // a = b + x; // Error: ambiguous! a = b + Fraction(x); // ok! To compute fractions. y = (double)b + x; // ok! To compute doubles. cout << " b + x as fraction: " << a << endl; cout << " b + x as double: " << y << endl; return 0; } 453 Dynamic Memory Allocation This chapter describes how a program can allocate and release memory dynamically in line with current memory requirements. Dynamic memory allocation is an important factor in many C++ programs and the following chapters will contain several additional case studies to help you review the subject. chapter 21 454 ■ CHAPTER 21 DYNAMIC MEMORY ALLOCATION Heap ptr_long ptr_double 1234567 1.9 // Dynamic objects of type long and double // long *ptr_long; ptr_long = new long; // No initialization // of the long object. *ptr_long = 1234567; // Assign a value double *ptr_double; double z = 1.9; ptr_double = new double(z); // With initialization ++(*ptr_double); // Increment the value *ptr_double += *ptr_long; // ok to add long value ptr_long = new double(2.7); // Error: ptr_long not // pointing to double! ■ THE OPERATOR new Sample calls to new On the heap THE OPERATOR new ■ 455 ᮀ Dynamic Memory Allocation When a program is compiled, the size of the data the program will need to handle is often an unknown factor; in other words there is no way to estimate the memory require- ments of the program. In cases like this you will need to allocate memory dynamically, that is, while the program is running. Dynamically allocated memory can be released to continually optimize memory usage with respect to current requirements. This in turn provides a high level of flexibility, allowing a programmer to represent dynamic data structures, such as trees and linked lists. Programs can access a large space of free memory known as the heap. Depending on the operating system (and how the OS is configured), the heap can also occupy large amounts of unused space on the hard disk by swapping memory to disk. C++ uses the new and delete operators to allocate and release memory, and this means that objects of any type can be created and destroyed. Let’s look at the scenario for fundamental types first. ᮀ Calling new for Fundamental Types The new operator is an operator that expects the type of object to be created as an argu- ment. In its simplest form, a call to new follows this syntax Syntax: ptr = new type; Where ptr is a pointer to type. The new operator creates an object of the specified type and returns the address of that object. The address is normally assigned to a pointer variable. If the pointer belongs to a wrong type, the compiler will issue an error message. Example: long double *pld = new long double; This statement allocates memory for a long double type object, that is, sizeof(long double) bytes. The previous call to new does not define an initial value for the new object, however, you can supply a value in parentheses to initialize the object. Example: pld = new long double(10000.99); Following this statement pld points to a memory address containing a long double type with a value of 10000.99. The statement cout << *pld << endl; will output this value. 456 ■ CHAPTER 21 DYNAMIC MEMORY ALLOCATION // DynStd.cpp // The operators new and delete for built-in types. // The program contains errors! // ==> Save all data before starting. // #include <iostream> using namespace std; int main() { cout << "\nTesting dynamic storage allocation! " << endl; // To allocate storage: double width = 23.78; double* ptrWidth = &width; double* ptrLength = new double(32.54); double* ptrArea = new double; // To work with ptrWidth, ptrLength, and ptrArea: *ptrArea = *ptrWidth * *ptrLength; delete ptrLength; // Error: The object is still // in use! cout << "\nWidth : " << *ptrWidth << "\nLength : " << *ptrLength << "\nArea : " << *ptrArea << endl; // To free storage: delete ptrWidth; // Error: The object has not // been dynamically reserved delete ptrLength; // ok delete ptrArea; // ok delete ptrLength; // Error: Pointer doesn't // address any object. ptrLength = new double(19.45); // ok // To give a name to a dynamic object: double& length = *ptrLength; // Reference cout << "\nNew length : " << length << "\nCircumference : " << 2 * width * length << endl; return 0; // On terminating the program } // allocated storage will be freed. ■ THE OPERATOR delete Sample program THE OPERATOR delete ■ 457 A program should make careful use of available memory and always release memory that is no longer needed. Failure to do so can impact the performance of your computer sys- tem. Memory that is released is available for further calls to new. ᮀ Calling delete Memory that has been allocated by a call to new can be released using the delete oper- ator. A call to delete follows this syntax Syntax: delete ptr; The operand ptr addresses the memory space to be released. But make sure that this memory space was dynamically allocated by a call to new! Example: long *pl = new long(2000000); . . . . // to work with *pl. delete pl; If you do not call delete, the dynamically allocated memory space is not released until the program terminates. You can pass a NULL pointer to delete when you call the operator. In this case nothing happens and delete just returns, so you do not need to check for NULL point- ers when releasing memory. A delete expression is always a void type, so you cannot check whether memory has been successfully released. As the sample program illustrates, misuse of delete can be disastrous. More specifi- cally ■ do not call delete twice for the same object ■ do not use delete to release statically allocated memory. ᮀ Error Handling for new If there is not enough memory available, the so-called new handler is called. The new handler is a function designed for central error handling. Thus, you do not need to design your own error handling routines each time you call new. The new handler is activated by default and throws an exception. Exceptions can be caught by the program, allowing the error condition to be remedied (refer to Chapter 28, Exception Handling). Any exception that is not caught will terminate the program, however, you can install your own new handler. If you are working with an older compiler, please note that new returns a NULL pointer if not enough memory is available. 458 ■ CHAPTER 21 DYNAMIC MEMORY ALLOCATION // DynObj.cpp // The operators new and delete for classes. // #include "account.h" #include <iostream> using namespace std; Account *clone( const Account* pK); // Create a copy // dynamically. int main() { cout << "Dynamically created objects.\n" << endl; // To allocate storage: Account *ptrA1, *ptrA2, *ptrA3; ptrA1 = new Account; // With default constructor ptrA1->display(); // Show default values. ptrA1->setNr(302010); // Set the other ptrA1->setName("Tang, Ming"); // values by access ptrA1->setStand(2345.87); // methods. ptrA1->display(); // Show new values. // Use the constructor with three arguments: ptrA2 = new Account("Xiang, Zhang", 7531357, 999.99); ptrA2->display(); // Display new account. ptrA3 = clone( ptrA1); // Pointer to a dyna- // mically created copy. cout << "Copy of the first account: " << endl; ptrA3->display(); // Display the copy. delete ptrA1; // Release memory delete ptrA2; delete ptrA3; return 0; } Account *clone( const Account* pK) // Create a copy { // dynamically. return new Account(*pK); } ■ DYNAMIC STORAGE ALLOCATION FOR CLASSES Sample program . (double)numerator / (double)denominator; } Fraction operator-() const { return Fraction(-numerator, denominator); } Fraction& operator+=(const Fraction& a) { numerator = a. numerator * denominator +. Fraction& a, const Fraction& b ) { Fraction temp; temp.denominator = a. denominator * b.denominator; temp.numerator = a. numerator*b.denominator + b.numerator * a. denominator; temp.simplify(); return. numerator and denominator. The numerator and the denominator are then divided by this value. ■ Add an appropriate call to the simplify() function to all operator func- tions (except ++ and ). ■