Search

7.x — Chapter 7 comprehensive quiz

Chapter summary

Another chapter down! The next chapter is the best one, and you’re almost there! There’s just this pesky quiz to get past…

Function arguments can be passed by value, reference or address. Use pass by value for fundamental data types and enumerators. Use pass by (const) reference for structs, classes, or when you need the function to modify an argument. Use pass by address for passing pointers or built-in arrays.

Values can be returned by value, reference, or address. Most of time, return by value is fine, however return by reference or address can be useful when working with dynamically allocated data, structs, or classes. If returning by reference or address, remember to make sure you’re not returning something that will go out of scope.

Inline functions allow you to request that the compiler replace your function call with the function code. You should not need to use this.

Function overloading allows us to create multiple functions with the same name, so long as they have different parameters. The return value is not considered a parameter.

A default parameter is a function parameter that has a default value provided. If the caller doesn’t explicitly pass in a value for the default parameter, the default value will be used. You can have multiple parameters with default values. All parameters with default values must be to the right of non-default parameters. A parameter can only be defaulted in one location. Generally it is better to do this in the forward declaration. If there are no forward declarations, this can be done on the function definition.

Function pointers allow us to pass a function to another function. This can be useful to allow the caller to customize the behavior of a function, such as the way a list gets sorted.

Dynamic memory is allocated on the heap.

The call stack keeps track of all of the active functions (those that have been called but have not yet terminated) from the start of the program to the current point of execution. Local variables are allocated on the stack. The stack has a limited size. std::vector can be used to implement stack-like behavior.

A recursive function is a function that calls itself. All recursive functions need a termination condition.

A syntax error occurs when you write a statement that is not valid according to the grammar of the C++ language. The compiler will catch these. A semantic error occurs when a statement is syntactically valid, but does not do what the programmer intended. Two common semantic errors are logic errors, and violated assumptions. The assert statement can be used to detect violated assumptions, but has the downside of terminating your program immediately if the assertion statement is false.

Command line arguments allow users or other programs to pass data into our program at startup. Command line arguments are always C-style strings, and have to be converted to numbers if numeric values are desired.

Ellipsis allow you to pass a variable number of arguments to a function. However, ellipsis arguments suspend type checking, and do not know how many arguments were passed. It is up to the program to keep track of these details.

Quiz time!

1) Write function prototypes for the following cases. Use const if/when necessary.

a) A function named max() that takes two doubles and returns the larger of the two.

Show Solution

b) A function named swap() that swaps two integers.

Show Solution

c) A function named getLargestElement() that takes a dynamically allocated array of integers and returns the largest number in such a way that the caller can change the value of the element returned (don’t forget the length parameter).

Show Solution

2) What’s wrong with these programs?

a)

Show Solution

b)

Show Solution

c)

Show Solution

d)

Show Solution

e)

Show Solution

3) The best algorithm for determining whether a value exists in a sorted array is called binary search.

Binary search works as follows:

  • Look at the center element of the array (if the array has an even number of elements, round down).
  • If the center element is greater than the target element, discard the top half of the array (or recurse on the bottom half)
  • If the center element is less than the target element, discard the bottom half of the array (or recurse on the top half).
  • If the center element equals the target element, return the index of the center element.
  • If you discard the entire array without finding the target element, return a sentinel that represents “not found” (in this case, we’ll use -1, since it’s an invalid array index).

Because we can throw out half of the array with each iteration, this algorithm is very fast. Even with an array of a million elements, it only takes at most 20 iterations to determine whether a value exists in the array or not! However, it only works on sorted arrays.

Modifying an array (e.g. discarding half the elements in an array) is expensive, so typically we do not modify the array. Instead, we use two integer (min and max) to hold the indices of the minimum and maximum elements of the array that we’re interested in examining.

Let’s look at a sample of how this algorithm works, given an array { 3, 6, 7, 9, 12, 15, 18, 21, 24 }, and a target value of 7. At first, min = 0, max = 8, because we’re searching the whole array (the array is length 9, so the index of the last element is 8).

  • Pass 1) We calculate the midpoint of min (0) and max (8), which is 4. Element #4 has value 12, which is larger than our target value. Because the array is sorted, we know that all elements with index equal to or greater than the midpoint (4) must be too large. So we leave min alone, and set max to 3.
  • Pass 2) We calculate the midpoint of min (0) and max (3), which is 1. Element #1 has value 6, which is smaller than our target value. Because the array is sorted, we know that all elements with index equal to or lesser than the midpoint (1) must be too small. So we set min to 2, and leave max alone.
  • Pass 3) We calculate the midpoint of min (2) and max (3), which is 2. Element #2 has value 7, which is our target value. So we return 2.

Given the following code:

3a) Write an iterative version of the binarySearch function.

Hint: You can safely say the target element doesn’t exist when the min index is greater than the max index.

Show Solution

3b) Write a recursive version of the binarySearch function.

Show Solution

8.1 -- Welcome to object-oriented programming
Index
7.14 -- Ellipsis (and why to avoid them)

82 comments to 7.x — Chapter 7 comprehensive quiz

  • Kushagra

    In 3b , can this code can be used?
    #include <iostream>
    bool odd( int x , int y )
    {
    double f = (x+y)/2;
    int i = (x+y)/2;
    if (f!=i)
    return true;
    else
    return false;

    }
    int binarySearch( int * array , int test , int min, int max)
    {
    int centre;

    if ( odd(min , max))
    {
    centre =( (min + max)/2) ;
    }
    else
    centre = (min + max)/2 ;

    if (test > array[centre])
    min = centre +1;
    if ( test < array[centre])
    max = centre -1;
    if (test == array[centre])
    return centre;
    if (min > max)
    return -1;
    return  binarySearch(array ,  test , min , max);

    }
    int main()
    {
    int array[] = { 2 ,4 ,6 ,8 ,21, 23 , 56 ,73 ,74 , 86 ,99 ,100};
    int numtest = 6;
    int test[] = { 0 , 3 ,5 ,54 ,56 ,73 };
    int expected[] = { -1,-1,-1, -1 , 6 , 7};
    for (int count = 0; count < numtest ; count++)
    {
    int index = binarySearch(array , test[count] , 0 , 11);

    if ( index >=0)
    std::cout << test[count] << " is found \n";
    else
    std::cout << test[count] << " is not found \n";
    }
    return 0;
    }

    • Alex

      I think so. But a couple of minor things:

      1) Your function to determine if an integer is odd is way more complicated than necessary. You can use this:

      2) Your midpoint calculation is subject to overflow if one or both of the numbers is large.

Leave a Comment

Put C++ code inside [code][/code] tags to use the syntax highlighter