This homework assignment will once again have you write several functions. These functions will work with arrays (particularly strings) and dynamic memory allocation. Your main function will call each of these functions. You decide the details of your main function. As always, it’s fine for you to write helper functions, and it’s fine for your functions to call any of your other functions.
For any functions that have you dynamically allocate memory, you should test whether the allocation was successful. An allocation (with malloc, calloc, or realloc) is unsuccessful iff the memory request was for >0 bytes and the allocation function returned NULL.
double * copy( const double * a, unsigned elements );
On entry, a points to an array that holds elements doubles. copy’s job is to dynamically allocate a new array of the same size, and to set the values in the new array to match the values in the arg array. As usual, if the allocation fails, complain and die. The deallocation of the returned array is the caller’s responsibility. For example, if a were {1.1, 2.2, 3.3} and elements were 3, then copy would return the address of a dynamically allocated array in a different location from a that held {1.1, 2.2, 3.3}
double * copyPositive( unsigned * positiveElements, const double * a, unsigned elements );
On entry, a points to an array that holds elements doubles. copyPositive dynamically allocates an array that is large enough to hold the positive elements of a (so the dynamically allocated array will generally have fewer elements than a has). Don’t allocate more elements than you need. Copy the positive elements from a to the DA array. Set *positiveElements to the number of elements in the returned array. For instance, if a were {1.1, -2.2, 3.3} and elements were 3, then copy would return the address of a dynamically allocated array that held {1.1, 3.3} and set *positiveElements to 2.
char * copyString( const char * s );
copyString returns a dynamically allocated copy of the arg string. For instance, if s were “CAT”, then copyString allocated a 4-byte array, sets that array to “CAT”, and returns the address of the first element of the dynamically allocated array. As usual, deallocation is the caller’s responsibility.
char * concatenate( const char * a, const char * b );
concatenate dynamically allocates an array big enough to hold the concatenation of a and b, then sets it to the concatenation of a and b, and returns it. For example, if a were “DOG” and b were “OPUS”, then concatenate would dynamically allocate an array of 8 bytes (complaining and dying in the face of allocation error), set it to “DOGOPUS”, and return the address of “DOGOPUS”. Deallocation of the array we allocate is the caller’s responsibility. Deallocating a and b wouldn’t make sense, since we don’t even know that they were dynamically allocated.
char * concatenate2( const char * a, const char * b );
concatenate2 is like concatenate, except that concatenate2 makes a couple of additional assumptions and has an additional responsibility. concatenate2 assumes that arrays a and b were dynamically allocated and that the caller doesn’t need to access a or b after we return. concatenate2 has the additional responsibility of deallocating arrays a and b.