1.
|
True False
|
|
C++ uses a special internal mechanism to
allow the use of << and >> with streams; it is not possible for a
programmer to use this mechanism with other classes.
False. C++ just uses operator overloading on the << and >> operators. A programmer can use operator overloading at any time. |
2.
|
True False
|
|
A precondition on a function can document
what inputs are legal to a function, but it can't prevent the program from
attempting to call the function with illegal inputs.
True. Preconditions can stop a function from executing with bad inputs, but it can't stop you from trying to call the function illegally. |
3.
|
True False
|
|
Given an arbitrary binary tree, a postorder
traversal of that tree will visit the nodes in the exact reverse order
of a preorder traversal.
False. For most binary trees, the preorder and postorder traversals will not be reverses of each other. Consider, for example, the tree with 2 as the root, 1 as the left child, and 3 as the right child. Preorder = 213, postorder = 132. |
4.
|
True False
|
|
For a stack, "push" and "pop" cancel each
other out: a push of any value followed by a pop leaves the stack in exactly
the state it was in initially.
True. |
5.
|
True False
|
|
For a queue, "enqueue" and "dequeue" cancel
each other out: an enqueue of any value followed by a dequeue leaves the
queue in exactly the state it was in initially.
False. The dequeue operation won't remove the item just enqueued. So if the queue contains '1', and you enqueue 2 and dequeue, the queue will contain '2'. |
6.
|
True False
|
|
The "new" operator in C++ returns a fresh,
statically-allocated instance of a class.
False. The fresh instance is dynamically-allocated, not statically allocated. |
7.
|
True False
|
|
If a function f(n) is always less than another
function g(n), then f(n) = O(g(n))
True, by definition of big-O. f(n) = O(g(n)) when g is 'eventually always bigger'. In this case, g is bigger right from the start. Just because a question has the word 'always' in it, the answer isn't necessarily false. |
8.
|
True False
|
Different binary search trees can have the
same inorder traversal.
True. The inorder traversal is just the elements of the tree in sorted order. Different BSTs can have the same set of elements, so they can have the same traversals. |
|
9.
|
True False
|
Although binary searching a sorted array
has logarithmic runtime on average, it can take as much as linear time
in the worst case.
False. Binary search of a sorted array always takes logarithmic time, no matter what. |
|
10.
|
True False
|
If a class overloads the + operator, the
compiler will check that the associated function really does perform an
addition-like operation.
False!! The programmer is responsible for providing the 'meaning' of operator + on a class. If the 'meaning' is not addition, too bad. |
|
11.
|
True False
|
Every binary tree with more than three nodes
must have an interior node that is not the root.
True. |
|
12.
|
True False
|
C++ references are a purely syntactic convenience
that do not make the language inherently more powerful than C.
True. References are just a nice way to say something. There's nothing that you couldn't say before. |
|
13.
|
True False
|
Since a derived class does not have access
to its base class's private data, instances of the derived class do not
set aside space for the base class's data members.
False. Even though they're private, the hidden data of the base class is still part of the derived class. |
|
14.
|
True False
|
The C++ inheritance mechanism allows us to
rewrite any recursive function non-recursively.
False. You can rewrite any function non-recursively, but inheritance has nothing to do with it! |
|
15.
|
True False
|
Activation records are allocated in a special
region of memory that is managed automatically by the language.
True. They contain the 'automatic' variables. |
|
16.
|
True False
|
According to the definition of the Set ADT,
if I have a set of 10 integers, and I add an integer to the set, I will
always end up with a set of 11 integers.
False. Consider the case where the set already contains the item you want to add. The size doesn't change. |
|
17.
|
True False
|
assert( false ) at any point in
a program will cause that program to immediately abort.
True, since the 'false' condition always fails. |
|
18.
|
True False
|
It is impossible to write a binary search
function for a sorted linked list of integers that runs in O(log
n) time for a list of length n.
True. Binary search of an array works because we have direct access. Direct access is impossible with a linked list. |
|
19.
|
True False
|
The this pointer is a global variable
in C++ programs that points to a single, statically-allocated object.
False. this is a local variable to member functions that points to the receiver of the call. |
|
20.
|
True False
|
The bool type with the values true
and false, and the int type with the values 1 and 0 can
both be used to represent boolean values, but 1 and 0 should be used when
possible because it makes the program faster.
False! As I've tried to say, you should always use true and false, since they preserve the 'boolean' abstraction. And 1 and 0 will NOT be faster. |
[12] Question 2:
Here is a C++ function that implements a new kind of searching algorithm:
int deluxeSearch( int data[], int size, int item )
{
return deluxeSearchInRange( data, item, 0, size - 1 );
}int deluxeSearchInRange( int data[], int item, int lo, int hi )
{
if( lo hi ) {
return -1;
}
int mid1 = lo + (hi-lo)/3;
int mid2 = lo + 2 * (hi - lo)/3;
if( item < data[ mid1 ] ) {Answer the following questions:
return deluxeSearchInRange( data, item, lo, mid1 - 1 );
} else if( item == data[ mid1 ] ) {
return mid1;
} else if( item data[ mid1 ] && item < data[ mid2 ] ) {
return deluxeSearchInRange( data, item, mid1 + 1, mid2 - 1 );
} else if( item == data[ mid2 ] ) {
return mid2;
} else {
return deluxeSearchInRange( data, item, mid2 + 1, hi );
}
}
Give two questions that you would like to ask about the specification
of this ADT before you start programming. For each question, write
a sentence or two explaining why the answer to this question would affect
your approach to the task.
There are many possible answers to this question.
Here are some samples:
Will the number of students remain fixed, or change a lot over time?
(If the number is fixed, I might want to implement
my ADT using an array. If it's going to change a lot, arrays
can get inefficient and I'll want to use something more like a linked list.)
Is there a fixed upper bound on class size?
(This can have a serious effect on my implementation.
If there is a small fixed upper limit, I'll definitely use an array.
If the upper limit is really big, arrays will be no good. If there's
no upper limit, obviously I can't use an array. I can also
use an upper limit for debugging, to make sure nobody tries to overfill
the class.)
What information will be stored with each student?
(This will allow me to write the struct of info associated
with every student. )
How will this information be searched/accessed?
(This will influence whether I should implement
a simple collection or a dictionary. If I'm implementing a dictionary,
I'll need to figure out which piece of student information will act as
the key.)
Will the information be searched frequently?
(If there will be frequent searching, then I might
want to choose an ADT that makes this fast. For instance, I might
use an array and keep the students sorted by name, which will greatly speed
up searching for students by name.)
Note that questions like "should I use a linked list or array" and
not valid, because they are questions about the implementation and not
the specification. Your job in this question was to ask questions
of someone that will allow YOU to choose a good implementation.
You were not supposed to ask for an implementation. Assume the person
you're asking doesn't know anything about computers; they just know what
information they'll need.
[14] Question 4:
Here is a portion of a program:
#include <iostream.h>class Simple
{
public:
virtual int goAhead();
};
int Simple::goAhead() {
return 17;
}
class Complicated
{
public:
virtual int goAhead();
};
int Complicated::goAhead() {
return 51;
}
Simple *getsimpleObject()
{
return new Complicated();
}
int main( void )This is obviously not a complete program, because it's missing at least the definition of the function getSimpleObject(). Surprisingly, when I compile and run the whole program, it prints out not 17, but 51! It's possible to finish writing this program in such a way that it prints out 51. Provide the rest of the program so that this happens. You may not modify the code that's already written down; you should instead add a definition for the getSimpleObject function (and probably add some other code too). You are also not permitted to make any use of cout in the code you add. (Hint: what kind of method is goAhead)?
{
Simple *obj = getSimpleObject();
cout << (obj-goAhead()) << endl;
return 0;
}
[10] Question 5:
Suppose you are given the following definition of a Dir type:
Write a function printDir that takes a direction and prints (using cout) the textual representation of that direction.
void printDir( Dir d )
{
switch( d ) {
case NORTH:
cout << "north";
return;
case EAST:
cout << "east";
return;
case SOUTH:
cout << "south";
return;
case WEST:
cout << "west";
return;
}
}Using the numbers 0, 1, 2 and 3 instead of the contants NORTH, EAST, SOUTH and WEST is a serious violation of the abstraction barrier!!
A malicious programmer could potentially cause an error in your printDir function using a cast, as follows:
for( int idx = 0; idx < 10; ++idx ) {
Dir sneakyDir = (Dir)(idx);
printDir( sneakyDir
);
}
Choose a graceful method of error handling for printDir so that this malicious code doesn't cause unpredictable behaviour in the program. How would you rewrite printDir to use this error handling mechanism?
Graceful means that the program should not abort. I would add a default: case to the switch statement in printDir that prints out a special direction name, like "invalid-direction". That indicates that something was wrong, but allows the program to continue functioning. After all, the printDir function is harmless. A faulty direction won't break the program. If some other function really relies on a correct direction, do the abort there.
To store movie ratings, you decide to create a table-style ADT called
MovieDict. An instance of the MovieDict ADT should be
able to record votes, return the ratings for movies by name, and provide
the name of the highest-rated movie. Although the final version of
the program will be used from a WWW turnin-like form, you are provided
with the following sample client program to get an idea of how MovieDict
will be used:
#include "moviedict.h"
int main( void )
{
MovieDict dict;
dict.vote( "Citizen Kane", 9 ); // Bob votes 9 on "Citizen Kane"
dict.vote( "The Avengers", 1 ); // Fred votes 1 on "The Avengers"
dict.vote( "Citizen Kane", 10 ); // Fred votes 10 on "Citizen Kane"
dict.vote( "Fargo", 10 ); // Alice votes 10 on "Fargo"
// Prints out 1.0, the average of all votes for "The Avengers"
cout << dict.getRating( "The Avengers" ) << endl;
// Prints out 9.5, the average of all votes for "Citizen Kane"
cout << dict.getRating( "Citizen Kane" ) << endl;
char buf[ 100 ];
// Copies "Fargo" into the buffer buf, because it has
// the highest overall rating, 10.
dict.findBest( buf );
cout << "The movie to bet on is " << buf << "." << endl;
return 0;You've already completed some of the design of the ADT. You've decided to use a linked-list representation to store the dictionary entries. You've even created a declaration for a Node struct (that goes into the header file "moviedict.h"), which the ADT will use to store information for each movie:
}
#ifndef __MOVIEDICT_H__
#define __MOVIEDICT_H__
struct Node
{
char name[ MAX_NAME_LENGTH ]; // The name of
this movie
int sum_of_votes; // The sum of all votes cast
so far
int number_of_votes; // The number of votes
cast, needed
// to compute the average
Node *next; // Linked-list pointer
};
#endif // __MOVIEDICT_H__
double MovieDict::getRating( const
char *name )
{
for( Node *cur = front; cur != NULL; cur = cur->next
) {
if( strcmp( cur->name,
name ) == 0 ) {
return
(double)( cur->sum_of_votes ) / (double)( cur->number_of_votes );
}
}
return -1.0; // a safe value.
}
MovieDict m1;
m1.vote( "Pi the Movie", 9 );
// ... other stuff here
MovieDict m2 = m1;
What potential dynamic memory problem can arise by doing this assignment?
You could get a dangling pointer if you did some
operation that destroyed m2.front while not updating m1.front. This
is similar to the example presented in class about the need for a deep
copy when assigning stacks.
[10] Bonus Question 2:
Suppose that we redefine the nodes we use in binary search trees as
follows:
struct FancyNodeIn this representation, every node points to its left and right children, and also to its immediate parent (through the parent pointer). It turns out that given a pointer to a FancyNode which is the root of a binary search tree, it's possible to do an inorder traversal of the tree with O(1) additional space (that is, the amount of extra memory you use is constant: no recursion, no stacks, no extra dynamic memory of any kind!) Write a function cheapInOrder( FancyNode *root ) that does this.
{
int data;FancyNode *parent;
FancyNode *left;
FancyNode *right;
};
// This is a pretty hard question. Here's
a piece of code that probably does the trick.
// The idea is to remember what direction you're traveling in and use that
information
// to decide what to do next. I don't guarantee that this works,
and I welcome comments
// about potential bugs.
enum Direction { NW, NE, SW, SE };
Direction upFrom( FancyNode *node )
{
if( node->parent != NULL && node->parent->left
== node ) {
return NE;
} else {
return NW;
}
}
void cheapInOrder( FancyNode *root )
{
if( root == NULL ) {
return;
}
FancyNode *cur = root;
Direction dir = SW;
while( cur != NULL ) {
if( dir == NW ) {
dir
= upFrom( cur );
cur
= cur->parent;
} else if( dir == NE ) {
cout
<< cur->data << " ";
if( cur->right
!= NULL ) {
cur = cur->right;
dir = SE;
} else
{
dir = upFrom( dir );
cur = cur->parent;
}
} else if( cur->left !=
NULL ) {
cur
= cur->left;
dir
= SW;
} else if( cur->right != NULL ) {
// There's a right child, and we're not coming from it.
// Print out the current node and go there.
cout << cur->data << " ";
cur = cur->right;
dir = SE;
} else {
dir = upFrom( cur );
}
}
}