Computer Science 2611
Fall 2004
Laboratory Assignment 8

Handed out: 11/15/04


Dynamic arrays

We have seen how to create arrays. To create an array of characters, you can write

  char C[20];
This kind of array is useful, but it has some limitations. One limitation is that the memory that it occupies is taken back by the system as soon as the function that created this array returns. Sometimes you want to make an array and keep it for a long time. Dynamic arrays allow you to do that.

Pointers and memory addresses

In C++, a pointer is a memory address. It refers to something in the computer's memory by telling exactly where it is stored.

You can refer to an array by giving the address of the first byte of the array. In fact, C++ treats an array just as if it were the first byte of that array. When you pass an array as a parameter to a function, for example, what is passed is the memory address of the first byte. So, in C++, an array is the same thing as the memory address of the first byte in the array. More briefly, an array is a pointer.

If T is any type, then type T* is another type. A variable of type T* holds a memory address either of a single variable of type T or of an entire array of T's. For example, a variable of type char* holds a memory address where a character (or an array of characters) is stored.

Creating a dynamic array

You can create a new array of characters using expression new char[n], where n is the number of characters that you want in the array. This expression (1) finds unused memory for you to use, (2) marks it used by you, and (3) gives you the address of that memory. If you write

  char* A;

  A = new char[20];
then you have, in variable A, the address of a newly allocated array of 20 characters. An array created this way is called a dynamic array.

Using a dynamic array

To repeat a key point, an array is the same as the address of the array's first byte. So you should be able to use an address in exactly the way you use an array. Here is an example.

  char* A;
  A = new char[20];
  A[0] = 'x';
  A[1] = 'y';
  A[2] = '\0';
This stores the null-terminated string "xy" into the array to which A refers.

The standard string functions work on arrays, so they must work on dynamic arrays too. For example,

  char* A;

  A = new char[35];
  strcpy(A, "elephant");
copies string "elephant" into dynamic array A.


Arrays of arrays

You can think of a two-dimensional array as an array of arrays. But it is stored in a way so that all of the rows have the same length. Sometimes you do not want that. Two dimensional arrays also require a function to work only on a two-dimensional array with a fixed number of columns. You cannot make a function that works on a two-dimensional array with an arbitrary number of columns.

But, since an array is really the same as a pointer, you can create an array of arrays by creating an array of pointers. For example,

  char* T[20];
creates an array T, where each member of T is a pointer. (Array T is not a dynamic array, since it is allocated in the old way. But each member of T is a dynamic array.)

Be careful. When T is created, all of its pointers are undefined. You have not actually put anything in it. Think of T as like a rack for CDs. Just buying the rack is not the same as filling it with CDs. It starts out holding nothing but empty slots.

  T[0] = ?
  T[1] = ?
  T[2] = ?
  ...

To put some arrays into array T, you need to allocate each one, just as you need to buy each CD to put into your rack. For example,

  char* T[20];
  T[0] = new char[50];
makes T[0] hold a newly allocated array of 50 characters. (T[1] is still undefined.)
              ____________________
  T[0] = --->|____________________|
  T[1] = ?
  T[2] = ?
  ...
Now if you can copy a string into T[0].
   strcpy(T[0], "elephant");
yields the following.
              ____________________
  T[0] = --->|elephant            |
  T[1] = ?
  T[2] = ?
  ...

An array of arrays is a pointer too. Since an array of characters has type char*, and array of pointers to characters has type char**. The two stars say that what you have is a pointer to a pointer, or an array of arrays.


Your assignment

One nice thing about using an array of pointers to simulate a two-dimensional array is that the rows do not all have to have the same length. In your solution to programming assignment 5, you used a two-dimensional array to store an array of strings. When you choose the number of colums, you select a number that you think is at least as long as the longest string that you will ever put in the array. If you think that you might encounter a string with 50 characters, then you need to make all of the columns have 51 members (one extra for the null character) even if most of the strings are very short. That results in a lot of wasted memory.

Your assignment is to modify you solution to program 5 in two ways.

  1. Use an array of pointers to store the arrays of strings. When you read a string, read into a tempory array that you think is large enough. Then allocate a dynamic array that is just large enough to hold the string. Be sure to leave enough room for the null character. Copy the string from the temporary array into the dynamic array. Put the dynamic array into your array of pointers. Now you can reuse the temporary array. When you are done, you might have something like the following in your regularWord array.

                               __
         regularWord[0] = --->|is|
                               __
         regularWord[1] = --->|Is|
                               ___
         regularWord[2] = --->|are|
                               ___
         regularWord[3] = --->|Are|
                               _____
         regularword[4] = --->|drink|
      

    This will greatly reduce the amount of memory used, when there are a lot of translations to store.

  2. Your current program reads the information about what to translate from file pirate.txt. Then it reads the information to translate from the standard input, and writes it to the standard output.

    Your modified program should take three strings as command line parameters.

    1. The name of the file that contains the translations.
    2. The name of a file to translate
    3. The name of a file that should be created to hold the translated version

    Command line parameters are written on the command line. If your program is called a.out, and you run command

         a.out pirate.txt mytext.txt newtxt.txt
      
    then your program should read the translations from file pirate.txt, then read file mytext.txt, and write a translated version of mytext.txt into file newtxt.txt.

    You can get the strings on the command line in the main function. Make the heading of your main function be

         int main(int argc, char** argv)
      
    Now you can use argc and argv in main. Parameter argc tells the total number of parts of the command, including the command name. Array-of-strings argv contains the parts themselves. For example, if your command is
         a.out pirate.txt mytext.txt newtxt.txt
      
    then argc will be 4 and argv will hold the following strings.
         argv[0] = "a.out"
         argv[1] = "pirate.txt"
         argv[2] = "mytext.txt"
         argv[3] = "newtxt.txt"
      
    So instead of opening a file with the fixed name "pirate.txt" for reading, open the file whose name is in argv[1]. Instead of reading the input from cin, open argv[2] for reading, and use the ifstream object for reading. Instead of writing to cout, create an ofstream object that is tied to file argv[3], and write to that object.