CS 311 Fall 2020  >  Midterm Exam Review Problems, Part 1


CS 311 Fall 2020
Midterm Exam Review Problems, Part 1

This is the first of three sets of review problems for the Midterm Exam. For all three sets, see the class web page, or the following links:

Midterm Exam Information

The Midterm Exam will be made available on Wednesday, October 7. The exam will cover all class material from the beginning through Wednesday, October 7. It will be worth 120 points. It will be due at 2 pm on Friday, October 9; there will be no class meeting on that day.

The exam is to be done individually. You may refer to any class materials or anything on the web, but not other people.

Problems

Review problems are given below. Answers are in the Answers section of this document. Do not turn these in.

  1. Consider the following C++ code.
          vector<int> data(10);
          const int m = 3;
          data[0] = m;
          data[m] = 37+data[0];
    Classify each of the following as Lvalue or Rvalue.
    1. data
    2. data[0]
    3. m
    4. data[m]
    5. 37
    6. 37+data[0]

     
  2. What is abstraction and why do we use it?
     
  3. What is an invariant?
     
  4. Briefly explain the basics of operation contracts for functions. (Hint: pre & post.)
     
  5. Write reasonable preconditions and postconditions for the following function:
    double myFunc(int * p)
    {
        p[0] = 5;
        p[2] = int(std::sqrt(p[1]));
        return 6.2;
    }

     
    1. What is a class invariant?
    2. How can the use of well chosen class invariants make it clearer how to write a class’s member functions?
    3. In terms of class invariants, what is a constructor for?
       

     
  6. Write reasonable class invariants for the following class, which maintains an array whose size is stored in the data member _n. Hint: The destructor is already written. Write your invariants so that the destructor is correctly written.
    class MyClass {
    public:
        // Ctors, dctor
    
        // Default ctor & ctor from int
        // Pre:
        //     theSize > 0
        // Post:
        //     _n == theSize
        // May throw std::bad_alloc
        explicit MyClass(int theSize = 10):
            _p(new int[theSize]),
            _n(theSize)
        {}
    
        // Dctor
        // Pre: None.
        // Post: None.
        // Does not throw
        ~MyClass()
        { delete [] _p; }
    
    private:
        // No copy ctor, copy assn
        MyClass(const MyClass & other);
        MyClass & operator=(const MyClass & rhs);
    
    public:
        // General public functions
    
        [Various functions go here]
    
    private:
        // Data members
        int * _p;
        int _n;
    };

     
  7. List the four ways of passing a parameter (or return value) in C++. For each, indicate:
    • Whether the method makes a copy of the object passed.
    • Whether the method allows passing of const objects.
    • Whether the method supports polymorphism & virtual dispatch.
    • Whether the method allows implicit type conversions to be performed.

     
  8. What is an expression?
     
  9. List the six primary member functions that C++ compilers will automatically generate. For one of the six, indictate when the compiler will write it for you.
     
  10. For each of the following operators, indicate whether we should declare it as a member or a global function and how the parameter(s) should be passed (by value, etc.). Briefly justify your answers.
    1. operator+=.
    2. Binary operator+.
    3. operator<< (for stream output).

     
    1. What are the Big Five?
    2. What is the Rule of Five?
    3. In what circumstances do we usually need to write the Big Five?
    4. If we do not write the Big Five, then who does?
    5. What is the Rule of Zero?

     
    1. What does it mean to own a resource?
    2. What does RAII stand for, and what is it?
    3. Give two examples of resources what it means to release each one.
    4. How can ownership of a resource be shared?

     
  11. Suppose you are writing a class Widget that has two data members, a bool called valid_ and an int * called _p. If valid_ is true, then _p points to memory, allocated with new [], which the object owns. If valid_ is false, then the value of _p is unknown. Write the destructor for class Widget. Include preconditions, if there are any. You may assume that the information above is already in the class invariants.
     
    1. In C++ programming we typically access containers, and ranges of data items, through what kind of object?
    2. What operations are typically defined on the objects from part a?

     
  12. In modern C++ programming, how do we usually pass a list of values to a function?
     
    1. What is an iterator?
    2. List some things we use iterators for.

     
  13. Suppose we have a variables v1 and v2, declared as follows:
    const std::vector<int> v1(SIZE);
    std::vector<int> v2(SIZE);
    1. Write a range-based for-loop that prints the items in v1, each on a separate line.
    2. Using one of the Standard Template Library algorithms, write a single function call that copies the data in v1 to v2. Note that they are already the same size.

     
    1. What is a Linked List?
    2. Why not simply use an array for everything? Give an advantage of a Linked List, as compared to an array.
    3. Give a disadvantage of a Linked List, as compared to an array.

     
    1. What do we mean by an error condition (often just “error”)?
    2. List three ways of dealing with a possible error condition in a function (not ways of signalling the client code).
    3. List three ways of signalling client code that an error condition has occurred.

     

Answers

    1. Lvalue
    2. Lvalue
    3. Lvalue
    4. Lvalue
    5. Rvalue
    6. Rvalue
  1. Abstraction means separating the purpose and interface of a module from its implementation. Abstraction has a number of advantages, most of which involve the fact that, when we use abstraction, clients typically do not deal with the implementation at all. Thus, the implementation can be changed (bug fixes, etc.) without breaking client code. Furthermore, implementation decisions can be made without consulting everyone involved.
  2. An invariant is a condition that is always true at a particular point in a program.
  3. An operation contract for a function is a set of “preconditions” and “postconditions”. A precondition is a statement that must be true when the function is called. A postcondition is a statement that will be true when the function returns (assuming the preconditions were true when it was called). Thus, the operation of a function is described as a contract: the function says to its caller, “I will fulfill my postconditions if you fulfill my preconditions.”
  4. Preconditions
    • p must point to an array of at least 3 int’s.
    • p[1] >= 0.
    Postconditions
    • p[0] == 5.
    • p[1] is unchanged.
    • p[2] == int(sqrt(p[1])).
    • Return value == 6.2.
    1. A class invariant is an invariant that holds for an object of the class outside of public member functions. That is, it is a precondition of all public member functions, except constructors, and it is a postcondition of all public member functions except the destructor. Generally class invariants are statements about a class’s data members that tell what it means for an object to be in a valid (usable) state.
    2. The existence of class invariants means that public functions can make certain assumptions (usually about class data) when they are called. And it is always easier to code when you know something about the environment your code will be executed in.
    3. The job of a constructor is to make sure that all class invariants hold for the constructed object.
  5. Class invariants:
    • _n > 0.
    • _p points to memory sufficient to hold _n int’s, allocated with new [].
    • *this owns the memory pointed to by _p.
  6. The four methods are passing by value, by reference, by reference-to-const, and by Rvalue reference.

    Passing by value:

    • Makes a copy.
    • Allows passing of const objects.
    • Does not support polymorphism.
    • Allows implicit type conversions.

    Passing by reference:

    • Does not make a copy.
    • Does not allow passing of const objects.
    • Supports polymorphism.
    • Does not allow implicit type conversions.

    Passing by reference-to-const:

    • Does not make a copy.
    • Allows passing of const objects.
    • Supports polymorphism.
    • Allows implicit type conversions.

    Passing by Rvalue reference:

    • Does not make a copy.
    • Does not allow passing of const objects.
    • Supports polymorphism.
    • Allows implicit type conversions.
  7. An expression is something that has a value.
  8. Automatically generated member functions:
    • Default constructor. Silently written when no constructor is declared.
    • Destructor.
    • Copy constructor.
    • Copy assignment operator.
    • Move constructor.
    • Move assignment operator.
    1. We generally want operator+= to be a member function. First, we have no reason to make it a global. Second, this operator is tightly tied to its first argument (which it generally modifies), and provides basic functionality (operator+ is usually written to use operator+=). Its parameter (the second argument) should generally be passed by reference-to-const. This ensures that it can be called with a const object, avoids making an unnecessary copy, properly handles derived types and virtual function calls, and allows for implicit type conversions, where defined. This operator should generally be something like this:
      Foo & Foo::operator+=(const Foo & other);
    2. We generally want binary operator+ to be a global function, to allow for implicit type conversions on both arguments. Its parameters should generally be passed by reference-to-const, for the same reasons as in part a. This operator should generally be something like this:
      Foo operator+(const Foo & a,
                    const Foo & b);
    3. We must declare the stream output operator as a global function; it cannot be a member, because it would have to be a member of std::ostream, and we are not allowed to create new members of this class. Its first parameter (the stream) should be passed by reference, since we modify it. Its second parameter (the value to be output) should be passed by reference-to-const for the same reasons as in part a. This operator should generally be something like this:
      std::ostream & operator<<(std::ostream & theStream,
                                const Foo & printMe);
    1. The Big Five are the following member functions of a C++ class: destructor, copy constructor, copy assignment operator, move constructor, move assignment operator.
    2. The Rule of Five says that if you define one of the Big Five, then you probably need to define or =delete all of them.
    3. We generally need to write the Big Five when our class directly owns a resource, for example, dynamically allocated memory/objects.
    4. The Big Five can be silently written by the compiler, if the proper conditions are met.
    5. The Rule of Zero says, where possible, define none of the Big Five. Resources should be managed by data members that are objects of RAII classes.
    1. To own a resource means to be responsible for it, in particular, for releasing it.
    2. RAII stands for “Resource Acquisition is Initialization”. It refers (somewhat misleadingly) to the idea that a resource should be owned by an object, and therefore released by the object’s destructor.
      • Dynamically allocated memory. deleteing a pointer to it.
      • An open file. Closing it.
      Other answers are possible.
    3. The usual way to share resource ownership is by reference counting. A reference count keeps track of the number of owners of a resource. It is incremented when ownership is granted to a new object, and decremented when an object relinquishes ownership. When the reference count reaches zero, the resource is released.
  9. // Dctor
    // Pre: None.
    ~Widget()
    {
        if (valid_)
            delete [] _p;
    }
    The destructor has preconditions, of course, but they are all class invariants, and so do not need to be given here.
    1. In C++ programming, we typically access containers using iterators.
    2. Typically, we define at least the following operations on iterators:
      • Assignment.
      • Equality/inequality test.
      • Increment.
      • Dereference.
  10. We pass a list of values by passing two iterators: one pointing to the first item in the list, and another pointing just past the last item in the list.
    1. An iterator is an object that acts like a pointer. In particular, it is a pointer-like object that is used to access items in some data set, and which does not own what it points to.
    2. We use iterators to iterate over the items in a container (thus the name). We also use them to specify ranges of data.
    1. for (auto n : v1)
      {
          std::cout << n << std::endl; // Must #include <iostream>
      }
    2. We can copy using std::copy.
      std::copy(begin(v1), end(v1), begin(v2)); // Must #include <algorithm>
    1. A Linked List is a data structure for holding a Sequence. A Linked List consists of a series of nodes. Each holds a single data item and a pointer to the next node. The last node’s next-node pointer is null. There is generally also a head pointer, that points to the first node in the list, or is null if the list is empty.
    2. A Linked List is much faster at inserting or removing a data item than an array. These operations are constant time for a Linked List, but linear time for an array.
    3. A Linked List is much slower at doing look-up by index than an array. This operation is linear time for a Linked List, but constant time for an array.
    1. An error condition is a condition that occurs at runtime; it is something that cannot be handled by the normal flow of execution.
    2. We can deal with a possible error condition in a function before, during, of after the function:
      • Before. We prevent the error, using a precondition.
      • During. We fix the problem inside the function.
      • After. We signal the client code that an error condition has occurred.
    3. We can signal an error in three ways:
      • Using a return code.
      • Setting a flag that can be checked later.
      • Throwing an exception.