CS 311 Fall 2024 > Exam Review Problems, Set A
CS 311 Fall 2024
Exam Review Problems, Set A
This is the first set of exam review problems.
Problems
Review problems are given below. Answers are in the Answers section of this document. Do not turn these in.
- Consider the following C++ code.
Classify each of the following as Lvalue or Rvalue.vector<int> data(10); const int m = 3; data[0] = m; data[m] = 37+data[0];
data
data[0]
m
data[m]
37
37+data[0]
- What is abstraction and why do we use it?
- What is an invariant?
- 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; }
- What is a class invariant?
- How can the use of well chosen class invariants make it clearer how to write a class’s member functions?
- In terms of class invariants, what is a constructor for?
- 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; };
- What is generally the best type to use for the index passed to a smart array’s bracket operator?
- Why is this type a better choice than
int
?
- In C++, we use four different methods to pass a parameter or return value.
List all of these.
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.
- What is an expression?
- 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.
- For each of the following operators, indicate whether we generally want to
declare it as a member or a global function and how the parameter(s) are to be passed
(by value, etc.). Briefly justify your answers.
operator+=
.- Binary
operator+
. operator<<
(for stream output).
- What are the Big Five?
- What is the Rule of Five?
- In what circumstances do we usually need to write the Big Five?
- If we do not write the Big Five, then who does?
- What is the Rule of Zero?
- What does it mean to own a resource?
- What does RAII stand for, and what is it?
- Give two examples of resources what it means to release each one.
- Suppose you are writing a class
Widget
that has two data members, abool
called_valid
and anint *
called_p
. If_valid
istrue
, then_p
points to memory, allocated withnew []
, which the object owns. If_valid
isfalse
, then the value of_p
is unknown. Write the destructor for classWidget
. Include preconditions, if there are any. You may assume that the information above is already in the class invariants.
- In C++ programming we typically access containers, and ranges of data items, through what kind of object?
- What operations are typically defined on the objects from part a?
- In modern C++ programming,
how do we usually pass a list of values to a function?
- What is an iterator?
- List some things we use iterators for.
- Suppose we have a variables
v1
andv2
, declared as follows:const std::vector<int> v1(SIZE); std::vector<int> v2(SIZE);
- Write a range-based for-loop
that prints the items in
v1
, each on a separate line. - Using one of the Standard Template Library algorithms,
write a single function call that
copies the data in
v1
tov2
. Note that they are already the same size.
- Write a range-based for-loop
that prints the items in
- What is a Linked List?
- Why not simply use an array for everything? Give an advantage of a Linked List, as compared to an array.
- Give a disadvantage of a Linked List, as compared to an array.
- What do we mean by an error condition (often just “error”)?
- List three ways of dealing with a possible error condition in a function (not ways of signaling the client code).
- List three ways of signaling client code that an error condition has occurred.
Answers
- Lvalue
- Lvalue
- Lvalue
- Lvalue
- Rvalue
- Rvalue
- Abstraction means considering a software component in terms of how and why it is used—what it looks like from the outside—separate from its internal 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.
- An invariant is a condition that is always true at a particular point in a computation.
- Preconditions
p
must point to an array of at least 3int
s.p[1] >= 0
.
p[0] == 5
.p[1]
is unchanged.p[2] == int(sqrt(p[1]))
.- Return value
== 6.2
.
- 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.
- 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.
- The job of a constructor is to make sure that all class invariants hold for the constructed object.
- Class invariants:
_n > 0
._p
points to memory sufficient to hold_n
int
s, allocated withnew []
.*this
owns the memory pointed to by_p
.
std::size_t
.- If the array is large,
int
may not be able to hold values large enough to express the index for each item in the array.std::size_t
, on the other hand, is guaranteed to be able to hold values large enough to express the size of anything that can fit in memory, so it will certainly be able to hold any array index we may need.
- The four methods are passing by value, by reference, by reference-to-const, and by Rvalue reference.
Makes a Copy Allows Passing of Const Values Supports Polymorphism & Virtual Dispatch Allows Implicit Type Conversions By Value YES YES no YES By Reference no no YES no By Reference-to-Const no YES YES YES By Rvalue Reference no no YES YES - An expression is something that has a value.
- Automatically generated member functions:
- Default constructor. Silently written when no constructor is declared.
- Destructor.
- Copy constructor.
- Copy assignment operator.
- Move constructor.
- Move assignment operator.
- 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 useoperator+=
). Its parameter (the second argument) is generally 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 is generally prototyped something like this:Foo & Foo::operator+=(const Foo & other);
- We generally want binary
operator+
to be a global function, to allow for implicit type conversions on both arguments. Its parameters are generally passed by reference-to-const, for the same reasons as in part a. This operator is generally prototyped something like this:Foo operator+(const Foo & a, const Foo & b);
- 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) is passed by reference, since we modify it. Its second parameter (the value to be output) is generally passed by reference-to-const for the same reasons as in part a. This operator is generally prototyped something like this:std::ostream & operator<<(std::ostream & theStream, const Foo & printMe);
- We generally want
- The Big Five are the following member functions of a C++ class: destructor, copy constructor, copy assignment operator, move constructor, move assignment operator.
- The Rule of Five says if you define one of the Big Five, then consider whether to define or =delete each of the others. If, for one of these functions, you decide not to, then =default that one.
- We generally need to write the Big Five when our class directly owns a resource, for example, dynamically allocated memory/objects.
- The Big Five can be silently written by the compiler, if the proper conditions are met.
- The Rule of Zero says, where possible, do not explicitly define any of the Big Five. Resources should be managed by data members that are objects of RAII classes.
- To own a resource means to be responsible for releasing it.
- RAII stands for “Resource Acquisition is Initialization”. It refers (somewhat misleadingly) to the idea that a resource is owned by an object, and therefore released by the object’s destructor.
- Dynamically allocated memory.
delete
ing a pointer to it. - An open file. Closing it.
- Dynamically allocated memory.
The destructor has preconditions, of course, but they are all class invariants, and so do not need to be given here.// Dctor // Pre: None. ~Widget() { if (_valid) delete [] _p; }
- In C++ programming, we typically access containers using iterators.
- Typically, we define at least the following operations on iterators:
- Construction & destruction.
- Assignment.
- Equality/inequality test.
- Increment.
- Dereference.
In addition, some iterators also have the following operations defined.
- Adding an integer to an iterator.
- Subtracting an integer from an iterator.
- Subtracting two iterators, giving an integer.
- Bracket operator.
- 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.
- 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.
- We use iterators to iterate over the items in a container (thus the name). We also use them to specify ranges of data.
for (auto n : v1) { std::cout << n << std::endl; // Must #include <iostream> }
- We can copy using
std::copy
.std::copy(begin(v1), end(v1), begin(v2)); // Must #include <algorithm>
- 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.
- Given an appropriate pointer, 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.
- 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.
- An error condition is a condition that occurs at runtime; it is something that cannot be handled by the normal flow of execution.
- 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.
- We can signal an error in three ways:
- Using a return code.
- Setting a flag that can be checked later.
- Throwing an exception.