Types, Control Structures, and Procedural Abstraction

We'll continue our tour of C++ in this lecture, with a particular focus on three areas:

  • Data types, both fundamental and those from the C++ standard library.
  • Control flow structures for branching and looping.
  • Defining and using functions (for procedural abstraction), including when they're split across several files.

Also, this lecture is a bit long. They won't all be like this, I promise!

Updated Fall 2024

1: Fundamental Types and Implicit Conversions

Let's take a look at the set of fundamental data types built in to the C++ language, as well as the rules for implicit conversion between them.


I'll also point out that explicit conversions are possible, where we directly request a conversion. In some cases this may be necessary. In others, it's stylistically preferrable to make an otherwise implicit conversion more obvious. Here's a few examples:

int main() {
  double value = 4.3;

  // implicit conversion, too easy to miss
  int x = value;

  // C-style cast, avoid doing this
  int x = (int)value;

  // C++-style cast, this is preferred
  int x = static_cast<int>(value);
}

In C++, the static_cast form is preferred because the compiler generally performs stronger checks to ensure the conversion makes sense.



2: Functions

In more complex programs, it's essential to define functions to abstract away details.




3: Standard Library Types

The C++ Standard Library provides a variety of container and utility types. We'll take a look at a few now, including std::vector which is used extensively in project 1.




4: Iteration
4.1 Not Started

In imperative programming, loops allow us to iterate through a set of instructions multiple times as long as some condition is true. C++ has two primary looping constructs, for and while.


4.1 Exercise: Vector Sum

Fill in the blanks so that the code computes the sum of elements in the vector.

If your code compiles, but you're not getting credit, try clicking the "Simulate" button to step through the code and see where it's going wrong.


Sample solution…

  #include <iostream>
  #include <vector>
  using namespace std;

  int main() {
    vector<double> v = {1, 5, 3.5, 6.5};

    // Declare accumulator variable to hold the sum
    double sum = 0;

    // Traverse by index from 0 ... v.size()-1
    for (int i = 0; i < v.size(); ++i) {

      // Access each element by index and add to sum
      sum += v[i];
    }

    cout << "Sum: " << sum << endl;
  }

Make sure to return to finish the video after completing the exercise!


5: Branching
5.1 Not Started

The if and else constructs are used for branching in C++, often in conjunction with loops.


5.1 Exercise: Vector Minimum

Fill in the blanks so that the code finds the minimum value in the vector.

If your code compiles, but you're not getting credit, try clicking the "Simulate" button to step through the code and see where it's going wrong.


Sample solution…

  #include <iostream>
  #include <vector>
  using namespace std;

  int main() {
    vector<double> v = {1, 5, 3.5, 6.5};

    // Keep track of the "best" candidate we've seen.

    double min = v[0];

    for (size_t i = 0; i < v.size(); ++i) {
      // If v[i] is less than the current min, update min.
      if (v[i] < min) {
        min = v[i];
      }
    }

    cout << "Min: " << min << endl;
  }

Make sure to return to finish the video after completing the exercise!


6: Logical Operations and Short-Circuit Evaluation

Sometimes we need to create compound boolean expressions using the &&, ||, and ! operators. In C++ (and some other languages), && and || have special behavior called short-circuit evaluation. Here's the details.




7: break; and continue;

Finally, a miscellaneous topic. C++ also has special break; and continue statements that affect the execution of loops.




8: Procedural Abstraction

Switching gears a bit, let's take a look at the high-level organization of a program using procedural abstraction to make our code easier to write, understand, and maintain.




9: Header Files, Makefiles, and Project 1
9.1 Not Started

As projects grow more complex, we often need to split the code into several different modules. In C++, we often use use a .hpp header files to provide declarations of the interfaces for implementation code in a .cpp file. These headers facilitate compilation across many files. But, as a project grows and compilation becomes more complex, we'll also turn to using build tools like Makefiles to automate the process.

We'll use project 1 as an example to illustrate each of these. First, we'll look at the role of function prototpyes and header files.


Now, some discussion of the overall structure of project 1 and the Makefile we provide with the project.


9.1 Exercise: Interface vs. Implementation

Categorize each of the following according to whether they are part of the interface or implementation (write "interface" or "implementation" in each box).

Function declaration in .h file

Function definition in .cpp file

Code inside the function's curly braces

Which input values are valid or invalid for the function

Comments inside the function to clarify tricky lines of code

RME comment before the function declaration in .h file

Sample solution…

Function declaration in .h file

Function definition in .cpp file

Code inside the function's curly braces

Which input values are valid or invalid for the function

Comments inside the function to clarify tricky lines of code

RME comment before the function declaration in .h file



10: RMEs for Interface Specification

It's useful to adopt a common patten for comments that specify function interfaces. In EECS 280, we'll use RMEs:

  • REQUIRES Are there restrictions on the allowed inputs to the function?
  • MODIFIES Does the function change our program state when it is run?
  • EFFECTS What does the function do? What (if any) result does it return?





11: Unit Testing
11.1 Not Started

Finally, let's take a bit of time to talk about unit testing. We need to make sure the code we write actually works.

In particular, we'll look at unit testing as a strategy for making sure that the implementation we write for a function actually works according to the interface we've decided for it to have. We'll look at some examples and general strateiges for writing good tests.


11.1

Which of the following are true statements about unit tests?

You've reached the end of this lecture! Your work on any exercises will be saved if you re-open this page in the same web browser.

Participation Credit
Make sure to sign in to the page, complete each of the exercises, and double check the participation indicator at the top left of this page to ensure you've earned credit.