DYNAMIC STORAGE ALLOCATION FOR CLASSES ■ 459 The operators new and delete were designed to create and destroy instances of a class dynamically. In this case, in addition to allocating memory, a suitable constructor must be called. Before releasing memory, the destructor must be called to perform cleaning up tasks. However, the operators new and delete ensure that this happens. ᮀ Calling new with a Default Constructor A call to new for a class is not much different from a call for a fundamental type. Unless explicitly initialized, the default constructor is called for each new object, but you must make sure that a default constructor exists! Example: Euro* pEuro = new Euro; This statement allocates memory for an object of the Euro class. If enough memory is available, the default constructor for Euro is executed and the address of a new object returned. ᮀ Explicit Initialization To initialize an object explicitly, you can state its initial values in parentheses when you call new. Syntax: Type *ptr = new Type(initializing_list); The values in the initialization list are passed as arguments to the constructor. If the compiler is unable to locate a suitable constructor, an error message occurs. Example: Euro *pE = new Euro( -123,77); This statement assigns the address of a new Euro class object to the pointer pE. The object is initialized using the supplied values. The expression *pE thus represents the entire object. Example: *pE += 200; // To add 200 euros. The public members are referred to via the member access operator ->. Example: cout << pE->getCents() << endl; // 33 ᮀ Releasing Memory When an object that was created dynamically is destroyed, the delete operator makes sure that the object is cleaned up. The destructor is first called, and only then is the memory space released. As previously discussed in the section on fundamental types, when you call delete you must ensure that the pointer is addressing a dynamic object or that you are dealing with a NULL pointer. 460 ■ CHAPTER 21 DYNAMIC MEMORY ALLOCATION // DynArr.cpp // Operators new[] and delete[] for dynamic arrays. // #include <iostream> #include <iomanip> using namespace std; int main() { cout << "Using a dynamic array.\n" << endl; int size = 0, cnt = 0, step = 10, i; float x, *pArr = NULL; cout << "Enter some numbers!\n" "End with q or another character " << endl; while( cin >> x) { if( cnt >= size) // Array too small? { // => enlarge it. float *p = new float[size+step]; // Copy the numbers: for( i = 0; i < size; ++i) p[i] = pArr[i]; delete [] pArr; // Release old array: pArr = p; size += step; } pArr[cnt++] = x; } // Work with the numbers: if( cnt == 0) cout << "No invalid input!" << endl; else { float sum = 0.0; cout << "Your input: " << endl; for( i = 0; i < cnt; i++) // To output and { // add. cout << setw(10) << pArr[i]; sum += pArr[i]; } cout << "\nThe average: " << sum/cnt << endl; } delete [] pArr; // To free the storage return 0; } ■ DYNAMIC STORAGE ALLOCATION FOR ARRAYS Sample program DYNAMIC STORAGE ALLOCATION FOR ARRAYS ■ 461 Imagine you are compiling a program that will store an unknown quantity of elements in an array. Your best option is to let the program create the array dynamically. An array of this type is known as a dynamic array. ᮀ The new[ ] Operator The new[ ] operator is available for creating dynamic arrays. When you call the opera- tor, you must supply the type and quantity of the array elements. Syntax: vekPtr = new Type[cnt]; The pointer vekPtr will then reference the first of a total of cnt array elements. vekPtr has to be a pointer to Type for this reason. Of course, Type can also be a class. Example: Account *pk = new Account[256]; This statement allocates memory for 256 Account type objects and uses the default con- structor to initialize them. Those objects are pk[0], pk[1], . . . , pk[255], or in pointer notation: *pk, *(pk + 1), , *(pk + 255). If the array elements are of a class type, the class must have a default constructor, since you cannot supply an initialization list when calling new[]. Starting values for the array ele- ments cannot be assigned until later. ᮀ The delete[ ] Operator It is always a good idea to release the memory space occupied by a dynamic array, if the array is no longer needed. To do so, simply call the delete[] operator. The braces [] tell the compiler to release the whole array, and not just a single array element. Example: delete[] pk; The operand for delete[]—the pointer pk in this case—must reference the place in memory that was allocated by a call to new[]! The destructor belonging to the current class is called for each array element. This shows the big difference to delete, which would merely call the destructor for *pk, i.e. for the first array element. The program on the opposite page stores numbers in a dynamic array. The size of the array is adjusted as required. To do so, a newer bigger array is created, the data is copied to the new array, and the memory occupied by the old array is released. 462 ■ CHAPTER 21 DYNAMIC MEMORY ALLOCATION first last 1st element 2nd element 3rd element Info Info Info first New last element last 1st element 2nd element 3rd element Info Info Info Info ■ APPLICATION: LINKED LISTS A simple linked list Appending a list element Deleting a list element first Removed element New first element New second element Info Info InfoInfo APPLICATION: LINKED LISTS ■ 463 ᮀ Dynamic Data Structures Now, let’s implement a linked list as a sample application. A linked list is a dynamic data structure that allows easy insertion and deletion of data. A data structure defines how data can be organized in units, stored, and manipulated—as arrays, lists, or trees, for example. The type of data structure you choose has a far-reaching effect on the amount of memory you need, the speed of access to the data involved, and the complexity (or sim- plicity) of the algorithms (data operations) you need. In contract to a static data structure, whose size is known before a program is launched, a dynamic data structure can change size while a program is running. One example of this is an array whose size can be changed during runtime. ᮀ Defining a Linked List Another example is a linked list that is stored in main memory and has the following characteristics: ■ each list element contains a data store for the live data and a pointer to the next element in the list ■ each list element—except the first and last elements—has exactly one predeces- sor and one successor. The first element in the list has no predecessor and the last element no successor. Some elementary operations are defined for linked lists, such as inserting and deleting list elements, or searching for and retrieving the information stored in a list element. ᮀ Advantages The storage used for the list elements need not be contiguous. The main advantage of linked lists is: ■ memory for the list elements is only allocated when needed ■ you only need to move a pointer when inserting or deleting list elements. When an array element is inserted or deleted, the other array elements have to be moved to make room or fill up the “gap” in the array. If there is no room left, you need to allo- cate memory for a new array and copy the data to it before inserting a new element. 464 ■ CHAPTER 21 DYNAMIC MEMORY ALLOCATION // List.h // Defines the classes ListEl and List. // #ifndef _LISTE_H_ #define _LISTE_H_ #include "Date.h" // Class Date from Chapter 14 #include <iostream> #include <iomanip> using namespace std; class ListEl // A list element. { private: Date date; // Date double amount; // Amount of money ListEl* next; // Pointer to successor public: ListEl( Date d = Date(1,1,1), double b = 0.0, ListEl* p = NULL) : date(d), amount(b), next(p) {} // Access methods: // getDate(), setDate(), getAmount(), setAmount() ListEl* getNext() const { return next; } friend class List; }; // // Defining the class List class List { private: ListEl* first, *last; public: List(){ first = last = NULL; } // Constructor ~List(); // Destructor // Access to the first and last elements: ListEl* front() const { return first; } ListEl* back() const { return last; } // Append a new element at the end of the list: void pushBack(const Date& d, double b); // Delete an element at the beginning of the list void popFront(); }; #endif // _LIST_H_ ■ REPRESENTING A LINKED LIST Classes of header file List.h REPRESENTING A LINKED LIST ■ 465 ᮀ Representing List Elements You can use a recursive data structure to represent a linked list. A recursive data structure is a data structure containing a pointer to a data structure of the same type. Of course, the data structure cannot contain itself—that would be impossible—but it does contain a pointer to itself. Now let’s look at a linked list used to represent transactions in a bank account. A transaction is characterized by a date, a sum of money, and the reason for the transac- tion. Thus, the list element needed to represent a transaction will contain the transac- tion data in its data store and a pointer to the next element in the list. The class shown on the opposite page, ListEl, was designed to represent list ele- ments. To keep things simple, the data store contains only the date and a sum of money. The public declaration includes a constructor and access methods for the live data. Later, we will be overloading the << operator in order to output the list. It is common practice to let the pointer for the last element in the list point to NULL. This also provides a termination criterion—the next pointer just needs to be queried for NULL. ᮀ Representing a List To identify a linked list, you just point a pointer at the first element in the list. You can then use the pointer to the successor of each element to address any element in the list. A pointer to the last element in the list is useful for appending new elements. The opposite page shows the class definition for the List class. The private section comprises two pointers, which reference the first and last list elements respectively. The constructor has an easy job—it simply points both pointers to NULL, thus creating an empty list. The destructor has a more complex task: it has to release the memory occupied by the remaining list elements. The pushBack() method is used to append a new element to the end of the list. To do so, memory is allocated dynamically and the new element becomes the successor of what was previously the last element and the last pointer is updated. In addition, the method must deal with a special case, where the list is empty. The popFront() method deletes the first element in the list. This involves turning the pointer to the first element around to the second element and releasing the memory occupied by the first element. The special case with an empty list also applies. exercises 466 ■ CHAPTER 21 DYNAMIC MEMORY ALLOCATION ■ EXERCISES Notes on exercise 1 Effects of the splice() function Insert Position Result 1 st Array 2 nd Array 735962 91426835 73914268355962 EXERCISES ■ 467 Exercise 1 Write a global function called splice() that “splices” two int arrays together by first allocating memory for a dynamic array with enough room for both int arrays, and then copying the elements from both arrays to the new array, as follows: ■ first, the elements of the first array are inserted up to a given position, ■ then the second array is inserted, ■ then the remainder of the first array is appended. Arguments: The two int arrays, their length, and the position at which they are to be spliced. Return value: A pointer to the new array Exercise 2 Write a global function called merge() that merges two sorted int arrays by first allocating memory for a dynamic array with enough room for both int arrays and then inserting the elements of both arrays into the new array in sequence. Arguments: The two int arrays and their length. Return value: A pointer to the new array To test the function, modify the program used to sort arrays in Exercise 4 of Chapter 17. Exercise 3 Complete and test the implementation of a linked list found in this chapter. ■ First define the access methods shown opposite.Then overload the << operator for the class ListEl to allow formatted output of the data in the list elements.You can use the asString() in the date class to do so. ■ Then implement the destructor for the List class.The destructor will release the memory used by all the remaining elements. Make sure that you read the pointer to the successor of each element before destroying it! ■ Implement the methods pushBack() and popFront() used for append- ing and deleting list elements. ■ Overload the operator << in the List class to output all the data stored in the list. ■ Test the List class by inserting and deleting several list elements and repeatedly outputting the list. solutions 468 ■ CHAPTER 21 DYNAMIC MEMORY ALLOCATION ■ SOLUTIONS Exercise 1 // // Splice.cpp // Implements the splice algorithm. // #include <iostream> #include <iomanip> #include <cstdlib> // For srand() and rand() #include <ctime> // and for time(). using namespace std; // Prototype: int *splice( int v1[], int len1, int v2[], int len2, int pos); int main() { cout << "\n * * * Testing the splice function * * *\n" << endl; int i, len1 = 10, len2 = 5; int *a1 = new int[len1], *a2 = new int[len2]; // Initialize the random number generator // with the current time: srand( (unsigned)time(NULL)); for( i=0; i < len1; ++i) // Initialize the arrays: a1[i] = rand(); // with positive and for( i=0; i < len2; ++i) a2[i] = -rand(); // negative numbers. // To output the array: cout << "1. array: " << endl; for( i = 0; i < len1; ++i) cout << setw(12) << a1[i]; cout << endl; cout << "2. array: " << endl; for( i = 0; i < len2; ++i) cout << setw(12) << a2[i]; cout << endl; cout << "\n At what position do you want to insert " "\n the 2nd array into 1st array?" "\n Possible positions: 0, 1, , " << len1 << " : "; int pos; cin >> pos; . [] pArr; // To free the storage return 0; } ■ DYNAMIC STORAGE ALLOCATION FOR ARRAYS Sample program DYNAMIC STORAGE ALLOCATION FOR ARRAYS ■ 461 Imagine you are compiling a program that will store. DYNAMIC STORAGE ALLOCATION FOR CLASSES ■ 459 The operators new and delete were designed to create and destroy instances of a class dynamically. In this case, in addition to allocating memory, a. can be changed during runtime. ᮀ Defining a Linked List Another example is a linked list that is stored in main memory and has the following characteristics: ■ each list element contains a data