Computer Science 3510
Summer 2000
Answers to practice questions for final exam

  1. What is a dangling pointer?

    A dangling pointer is a pointer to memory that you do not own, either because you have not allocated it or because the memory has been deallocated, either explicitly by you or implicitly by the run-time system.

  2. What are the consequences of using a dangling pointer in a program?

    The consequences can be devastating to the program. You can destroy data structures irreparably, you can change the values of variables by accident, and you can even put a dagger in the heart of the memory allocator, making it impossible to allocate any more memory.

    Dangling pointer errors can be very difficult to track down, because they often show up far from the position where they occur. A great deal of care should be taken to avoid using dangling pointers. You do not want to deallocate memory before you are ready to.

  3. Why is it necessary for a C++ programmer to understand his or her program at a physical level of detail?

    The physical level of detail is concerned with memory management and low level organization of data structures. If you ignore that, you can get a program that looks reasonable from a conceptual point of view, but that does not work because it does not allocate or deallocate memory correctly.

  4. How do you deallocate a variable or array that is allocated by each of the following statements?
    1. int x;

      You don't deallocate x. It is deallocated automatically, since it is in the run-time stack.

    2. int* x = new int;

      Say delete x;

    3. int x[10];

      You don't deallocate x. It is deallocated automatically, since it is in the run-time stack.

    4. int* x = new int[10];

      Say delete[] x;

  5. What is a memory leak?

    A memory leak occurs when a program does not deallocate memory that it is done with. A small memory leak is often not a problem. A large memory leak will cause a program to stop running because it has run out of memory, even though the total memory actually in use by the program might be small.

  6. Explain the difference between the interface and the implementation of a function. What belongs to the interface? What belongs to the implementation?

    The interface tells anyone who wants to use a function what he or she needs to know. It consists of the contract and the function prototype.

    The implementation of a function is the function body. It tells how the function works, and is what the computer performs when the function is called.

  7. Same as the preceding question, but for an abstract data type rather than a function.

    The interface of an abstract data type consists of a type and a collection of functions on that type. Each function is given by a function interface.

    The implementation of an abstract data type consists of a representation for the type and implementations of the functions.

  8. What is the usual consequence to a programmer of violating an abstraction? How does it affect the ease of programming?

    Typically, the consequence of violating an abstraction is that a program becomes more difficult to modify. Programs that use few abstractions, or that violate the abstractions that they do use, can be so difficult to modify that nobody is willing to try to change them.

  9. How do you compare two strings to see if they are equal?

    To compare A and B, use strcmp(A,B). The result is 0 if A and B are equal, and nonzero if they are not equal. (In fact, the result is related to zero in the same way in which A and B are related. So if r = strcmp(A,B), then

  10. How can you copy null-terminated string A into array B?

    strcpy(B,A);

    Note that strcpy use the same order as assignment statements. It copies the right-hand thing into the left-hand thing.

  11. Where is the smallest value in a binary search tree located? The largest value? A value that is close to the middle? How can you find the value that immediately follows the value at the root?

    The smallest value is as far to the left as possible. The largest value is as far to the right as possible. A value that is near the middle (median) of all of the values is found in the root. You can also find a value that is close to the middle as the value that immediately follows the root (the smallest value in the right subtree) or that immediately precedes the root (the largest value in the left subtree).

  12. What does an object contain?

    An object contains variables and functions. You can also say that an object contains data and functions, or data and methods, or variables and methods.

  13. Which objects can make use of a private variable or function in an object?

    All objects of the same class as an object can use the private variables that are in an object.

    (Technically, the limitation is on where the code occurs. All code that is part of class A can use the private things in ANY object of class A.)

  14. In what parts of a program can a protected variable or function of a class be used?

    The protected parts of class A can be used in class A and in all subclasses of class A. A subclass of A is a class that is derived from A, either directly or indirectly.

  15. Explain the purpose of a wrapper class.

    A wrapper class is used to encapsulate a non-object-oriented implementation of a data type into an object-oriented module. It can make a classical data structure easier to use.

  16. Suppose that s is a stack object of class IntStack. Write a statement to pop s and put the popped value in variable x.

    x = s.pop();

  17. What do C++ templates provide? Where would they most likely be used?

    C++ templates provide a form of polymorphism, making programs more general. They are typically used in general purpose libraries.

  18. What is the purpose of a destructor in a class?

    A destructor is used to clean up the resources that are used by an object when the object is destroyed. Typically, it deallocates memory that the object is using, but that is not physically part of the object. (The object refers to that memory using pointers, or indirectly using pointers to pointers, etc.)

  19. What does a copy contructor do? When it is called? What is a prototype for the copy constructor for class Widget?

    A copy constructor makes a copy of an object. It is called when an assignment is done, or when a parameter is passed by value, where the type of the thing being copied is the class to which the copy constructor belongs. The prototype for the copy constructor of class Widget is

        Widget(const Widget&);
     

  20. To within a constant factor, how long do each of the following take?

    1. Look up a value in a balanced binary search tree that has n values in it.

      log2(n)

    2. Insert a value into a binary search tree that has n values in it.

      log2(n)

    3. Compute the length of a linked list where the length is n.

      n

    4. Add a value to the front of a linked list of length n.

      Constant, independent of n.

    5. Sort a linked list using Lsort, where the list has n members.

      n log2(n)

    6. Push a value onto a stack of size n.

      Constant, independent of n.

  21. Write a function called reverse that takes a null-terminated string s and returns null-terminated string that consists of s written backwards. For example, if s is ``foo'' then reverse(s) returns string ``oof''. The result string should be put into newly allocated memory. String s should not be modified.

    Here is one solution.

        char* reverse(char *s)
        {
          int k;
          int slen = strlen(s);
          char* result = new char[slen + 1];
    
          for(k = 0; k < slen; k++) {
            result[slen - k - 1] = s[k];
          }
          result[slen] = '\0';
          return result;
        }
     

  22. Suppose that you insert 20 into the following tree using the algorithm that does rotations to keep the tree height-balanced. What is the resulting tree? Be sure to do the rotations correctly.
                              49
                             /  \
                            /    \
                          26      75
                         /  \       \
                        /    \       \                     
                      16      42      85
                     /  \
                    /    \
                  12      19 
      

    After inserting 20 without doing rebalancing, you get

                              49
                             /  \
                            /    \
                          26      75
                         /  \       \
                        /    \       \                     
                      16      42      85
                     /  \
                    /    \
                  12      19 
                           \
                            \
                            20
      
    Backing up from the 20, the first unbalanced node is the one holding 26. (The left subtree has height 3, and the right subtree has height 1). The path from 26 down to 20 starts left-right, which is a zig-zag. This calls for a double rotation. Performing the double rotation at the node holding 26 yields
                              49
                             /  \
                            /    \
                          19      75
                         /  \       \
                        /    \       \                     
                      16      26      85
                     /       / \
                    /       /   \
                  12       20    42
      
    Backing up further, to the root, you can see that the root is already balanced. So this is the answer.

  23. Types TreeNode and Tree are shown below. They are used in a classical implementation of binary search trees. Write a function called largestEven that takes a binary search tree T and returns the largest even number that occurs in T. If T does not have any even numbers in it, then largestEven(T) should return -1.

    Presume that T contains no negative numbers. (This requirement is part of the contract.)

          struct TreeNode {
            int data;
            TreeNode* left;
            TreeNode* right;
            TreeNode(int x, TreeNode* L, TreeNode *R)
            {
              data = x;
              left = L;
              right = R;
            }
          };   
          typedef TreeNode* Tree;
    
    
      

    Here is one solution.

          int largestEven(Tree T)
          {
            if(T == NULL) return -1;
            else {
              int x = largestEven(T->right);
              if(x >= 0) return x;
              else if(T->data % 2 == 0) return T->data;
              else return largestEven(T->left);
            }
          }
      

  24. Write a function called flip that computes a mirror image of a binary tree, using the types of the preceding problem. The mirror image is obtained by flipping the tree over, or by holding it up to a mirror. The mirror image of a binary search tree has values in descending order from left to right, rather than in ascending order.

        Tree flip(Tree T)
        {
          if(T == NULL) return NULL;
          else return new TreeNode(T->data, flip(T->right), flip(T->left));
        }
     

  25. You are using an object-oriented implementation of a queue of integers. (The class is called Queue.) The operations that you can do on a queue are to remove(), insert(x), and test isEmpty(). You are writing outside the queue implementation. Write a function that performs a peek in the queue. That is, it returns what is at the front of the queue, without modifying the queue. It is allowed to modify the queue temporarily, as long as the queue is put back the way it was. Presume that the queue has something in it.

    This one is a little tricky. You have not been told that there is a copy constructor available, so you cannot assume there is one. That means making a copy using the assignment operator is not an option.

    We need to get the front of the queue and remember it. Then we need to put it back into the queue, and repeatedly move things from the front of the queue to the back until the original front comes back to the front. But, since we don't know how many things are in the queue, we cannot just keep putting things at the back of the same queue, or we won't know when we are done.

    One approach is to put things into another queue, and then back into the original queue.

      int peek(Queue q)
      {
        Queue other;
        int result = q.remove();
    
        // Make queue other be a copy of q.
    
        other.insert(result);
        while(!q.isEmpty()) other.insert(q.remove());
    
        // Now copy from other back into q.
    
        while(!other.isEmpty()) q.insert(other.remove());
    
        return result;
      }