Notes from Grading

A couple of days ago I had the chance to grade your project 2. There were a few common mistakes that I'd like to review.

...and if you've already gotten your grades and comments back, know this: I'm very picky and will point out seemingly minor flaws because I love you and want you to be perfect!

If you find yourself repeating the same code over and over... there's probably a better way to do whatever you're trying to do.


Remember to keep your code DRY (Don't Repeat Yourself); remember that you can always abstract commonalities using the control flow we've already learned.

Comments should describe, conversationally, what a piece of code is doing. Do not simply echo the code syntax!

  // If texts is less than 400
  if (texts < HIGH_TEXT_THRESHOLD) {
      bill += someComplicatedQuantity;
  } else {
      bill += someOtherComplicatedQuantity;
  }

What is wrong with the above comment?

  • It tells me nothing about what that code is doing.

  • It's restating the syntax.

  • It lists the constant 400 that might be true for the CURRENT value of HIGH_TEXT_THRESHOLD, but not in general.


Better, would be something like:

  // Adjust the bill if a user has texted over the higher
  // text-message threshold, and will be penalized accordingly
  if (texts < HIGH_TEXT_THRESHOLD) {
      bill += someComplicatedQuantity;
  } else {
      bill += someOtherComplicatedQuantity;
  }

Don't be afraid to scrub a segment of code and start over if, during the course of writing that bad code, you think of a better way to do it. (save the original first, of course)


I saw a lot of instances where there was rampant repetition of clauses...

People probably thought of a better way while they were typing the like... 50th if clause, but then thought, "Well crap... I've typed 50 if-clauses, now I'm invested!"

Just start over... your grade will thank you, and you'll be a better programmer as a result (we all do it, trust me).



Introduction to Functions

We know that functions in the most general sense are not unique to programming or C++. We see functions in a variety of disciplines.

The functions that we probably see first in life are of the mathematical variety.

For example, I could create a function called f that takes in a value (we'll call that value x), and then returns another value based on my definition of f.
f(x) = x + x^2

Notice I haven't used this function yet, I've just told it what I want it to do in case one day I decide my calculator is too far away and I want to just type f(5) or f(-3), depending on my needs.

So say that day does come along (spoiler: it will), I can pass in some number (I've used x as a placeholder to refer to it) and get out some result, that I expect to be a number.

f(5) = 5 + 5^2 = 5 + 25 = 30

So, above, I give my function f the value 5, and get the value 30 back.

What, then, is a general definition of a function?

A function uses its definition to describe a relationship between its inputs and how they map to outputs.


Andrew, that definition is lame and isn't on the homework, teach me C++.

OK, fine. We must first remember that our computer is dumb, and needs us to tell it a lot about our function definitions.

Above, I said that I wanted a function called f (its name), which takes in a number called x (its parameter type and name) and spits back another number (its return type).

A function definition in C++ has the following syntax:

  /*
   * Note: My top level comment above a function
   * goes here using the multi-line comment, and
   * should describe the function's input and output.
   * The first line below is commonly called
   * a "function signature"
   */
  return_type function_name (parameter1_type parameter1_name, parameter2_type parameter2_name, ...) {
      // function body
  }

Of course, the above contains placeholders, so let's start by defining some things:

Parameters are the ZERO or MORE inputs to your functions. Each has its type defined, and a placeholder name assigned to it.

Return type is the type of value I expect to receive back from a function (e.g. ints, doubles, strings, etc.)

So, how would I convert my simple mathematical function f(x) = x + x^2 into a C++ function? (assume double x)

  double f (double x) {
      return x + x * x;
  }

Later, if I wanted to use this function, then I would just say f(5.3) or even pass in a variable that's a double like f(someDoubleVar). We call this... "calling" the function.

Arguments are what you call the values you pass into a function when you're calling it.


Calling a function means that you've passed in some inputs (or none, if none are expected) and are expecting it to execute, possibly returning some result.

Note that we call the inputs "arguments" when we call a function, and "parameters" when we are designing its signature.

Parameters are what you call the placeholder values passed into a function when you're defining it.



Functions in C++

Our analogy with f(x) = x + x^2 can take us far with C++ functions, but not all the way.

Let's look at a complete program with our running example:

Will the following code compile? If so, what will it print out?

  #include <iostream>
  #include <string>
  using namespace std;
  
  int main () {
      double x = f(5.0);
      
      cout << x << endl;
  }
  
  double f (double x) {
      return x + x * x;
  }

Click for answer.

No! I run my program from the first line of code down to the last. When I hit the line saying double x = f(5.0); f has yet to be defined!


So how do we get around that? I want to put my main function first because it makes sense for the first thing I run to be up towards the top.

Function Prototypes are hints to the compiler that say, "Hey, here's a function with a name, a return type, and some parameters... I'm not going to define it now, but if you use it, I promise I'll have it defined later!"

They consist of a function signature without the function body.


Please, Compiler? I promise I'm good for my function definition!

OK, fine, this time kid.

So we do this:

  #include <iostream>
  #include <string>
  using namespace std;
  
  // Function prototype for f
  double f (double x);
  
  int main () {
      double x = f(5.0);
      
      cout << x << endl;
  }
  
  double f (double x) {
      return x + x * x;
  }

So, now we get to the line in main double x = f(5.0); and although f is not yet defined, I see a function prototype (later defined after main) and so my code compiles.

Will the following code compile? If so, what will it output?

  #include <iostream>
  #include <string>
  using namespace std;
  
  // Function prototype for f
  double f (double x);
  
  int main () {
      double x = f(5.0);
      
      cout << x << endl;
  }

Click for answer

No! We broke our promise to the compiler that we'd define f later, so now we have to leave town or it will break our legs.


Now, as another note, there are certain things I can put into a function that just don't make sense.

If I tried to give my function f("functionsSuck") = x + x^2, it would neither be well defined nor very nice. My function f expects a number and outputs one as well.

Similarly, if I put in f(5.3) and got f(5.3) = "No you do!" in return, I would be both surprised and offended.

Just as implicit type conversions occur when I try to store a value of type A into a variable of type B, so do conversions happen with parameters and return types. Not all such conversions are legal.

Will the following code compile? If so, what will it output?

  #include <iostream>
  #include <string>
  using namespace std;
    
  // Function prototype for f
  double f (double x);
    
  int main () {
      string derp = "you wanted a string?";
      double x = f(derp);
        
      cout << x << endl;
  }
  
  double f (double x) {
      return x + x * x;
  }

Will the following code compile? If so, what will it output?

  #include <iostream>
  #include <string>
  using namespace std;
    
  // Function prototype for f
  double f (double x);
    
  int main () {
      // Function expects double parameter 0_o
      int i = 5;
      double x = f(i);
        
      cout << x << endl;
  }
  
  double f (double x) {
      return x + x * x;
  }

Will the following code compile? If so, what will it output?

  #include <iostream>
  #include <string>
  using namespace std;
    
  // Function prototype for f
  double f (double x);
    
  int main () {
      double x = f(5.5);
        
      cout << x << endl;
  }
  
  double f (double x) {
      // Function returns type double! 0_o
      bool result = x + x * x;
      return result;
  }

Although it might be obvious, whenever I call a function, once I return from that function, I resume my program from the point at which I called it.

Will the following code compile? If so, what will it output?

  #include <iostream>
  #include <string>
  using namespace std;
      
  // Function prototype for g
  int g (int x);
      
  int main () {
      cout << g(5) << endl;
      cout << g(-3.2) << endl;
      cout << g('c') << endl;
  }
    
  int g (int x) {
      return x + x * x;
  }

Above, whenever I called g from main, I return to the point in the main from which I called it after g returned an int.

After a function has returned, the code will resume execution from wherever that function was called.

Apropos, this means that functions can even call one another!


Finally, if we have a function that is not returning anything (can still have 0 or more parameters), we can specify a return type of void.

The void return type asserts that the function will not return anything. (though you can still stop the execution of a void function with return;)


What will the following code print out?

  #include <iostream>
  #include <string>
  using namespace std;
  
  double f (double x);
  void update();
  
  int main () {
      cout << f(2.0) << endl;
      cout << f(5.0) << endl;
  }
  
  double f (double x) {
      update();
      return x + x * x;
  }
  
  // Return type void means
  // I do NOT need to return
  // anything!
  void update () {
      cout << "hi :)" << endl;
  }

Lastly, if I promise my compiler that my function is going to return something of a certain type, I better deliver... or else...

Will the following code compile? If so, what will it output?

  #include <iostream>
  #include <string>
  using namespace std;
    
  string deliverGoods (string s);
    
  int main () {
      cout << deliverGoods("My goods, please.") << endl;
  }
  
  string deliverGoods (string s) {
      if (s == "Got the goods?") {
          return "Yeah I got the goods.";
      }
      // Uhh... err...
  }

Design two functions with the following signatures and purposes:

  1. isLameChar returns whether or not the character code of char c is less than int i.

  2. redeemLameChars returns a string result that is equal to input string s with all characters whose code is less than int i replaced with char r.


  #include <iostream>
  #include <string>
  #include <cassert>
  using namespace std;
  
  /*
   * Returns whether or not the character code if c
   * is less than i.
   */
  ??? isLameChar ( ??? ) {
      return c < i;
  }
  
  /*
   * Returns a string that is equal to s with all chars
   * whose code is less than i replaced with r
   */
  ??? redeemLameChars ( ??? ) {
      for (int j = 0; j < s.length(); j++) {
          // [!] If we meet our isLameChar criteria...
          if ( ??? ) {
              // ...then replace the current letter with r
              ???
          }
      }
      // Return that string with replacement
      ???
  }
  
  int main () {
      cout << redeemLameChars("abc", 1, '!') << endl;
      cout << redeemLameChars("abc", 100, '!') << endl;
      cout << redeemLameChars("C00L", '9', '!') << endl;
  }


Scope & Functional Scope

The long awaited lecture on scope has finally arrived, you may now stop holding your breath.

Scope refers to the names of variables, functions, identifiers, and other objects that are visible to your program in a particular region (block) of the code.


What do I mean by "a particular region of the code?"

Blocks are any regions of code enclosed by the curly brackets {...}. Each block contains its own scope.


Some examples:

  if (conditional) {
    // Everything in here is part of
    // the "if block"
  } else {
    // Everything in here is part of
    // the "else block"
  }
  while (conditional) {
    // Everything in here is part of
    // the "while block"
  }
  int main () {
    // Everything in here is part of
    // the "main block," which is really
    // just a "function block"
  }
  {
    // This is just a plain ole block!
  }
  
  // This is a chip off the ole block
  string chip = "-_-";

So what do blocks mean and what do they do for us? Well, first we need to know what identifiers are:

Identifiers are just the names we assign to variables, functions, templates, classes, namespaces, etc. E.g., int x = 5; x is an identifier.


Blocks allow us to keep identifiers in a "container" so that we can reuse them in different parts of the code and always know what identifier we're referring to when we use one.

Blocks act like envelopes such that blocks inside of other blocks will have access to their ancestors' identifiers.

So, let's take the below code and display how the blocks are nested:

  #include <iostream>
  #include <string>
  using namespace std;
    
  int main () {
      string s = "I'm in the main block!";
      cout << "main s: " << s << endl;
    
      if (s != "") {
          string s = "I'm in the if :(";
          cout << "if s: " << s << endl;
      }
    
      for (int i = 0; i < 3; i++) {
          string s = "I'm in the loop!";
          cout << "for s: " << s << endl;
          
          if (i == 2) {
              string s = "I'm special :)";
              cout << "for-if s: " << s << endl;
          }
      }
  }

Here's a graphical representation of the blocks above:

Global Space

Main Block

s = "I'm in the main block!"


if Block

s = "I'm in the if :("

for Block

s = "I'm in the loop!"


for-if Block

s = "I'm special :)"


So taking variables for example, how do I know which variable name is in scope?

The general idea in C++ is this:

  1. First, look in the current block and see if that identifier is defined. If it is, use that definition, otherwise...

  2. Look for a definition of that identifier in the next block up. If it is defined there, then use that definition. Keep looking in the next enclosing block, and if I don't find it in the global space...

  3. I get a compile error, because that identifier isn't defined anywhere in the code.

NOTE: I say this is how scope works in C++ (and a variety of other languages), but not all languages in general!

Alright, let's look at some examples and trace the blocks and scopes.

Will the following code compile? If so, draw the block nesting and say what it will output?

  /*
   * I call this example:
   * "Snakes on a Main"
   */
  #include <iostream>
  #include <string>
  using namespace std;
  
  bool snake = true;
  
  int main () {
      string samuel = "I'm tired of these darn snakes. ";
  
      if (samuel != "") {
          cout << samuel << snake << endl;
      }
      
      {
        string snake = "I hope there are no snakes in here...";
        cout << snake << endl;
      }
  }

Click for answer.

Global Space

snake = true;

Main Block

samuel = "I'm tired of these darn snakes. "


if Block

// variables samuel and snake referenced, but
// not in block... look back

Plain Block

snake = "I hope there are no snakes in here..."


One important note: although a variable may eventually live in a particular block, if it is referenced before it's defined, then we'll get a compile error.

Will the following code compile? If so, draw the block nesting and say what it will output?

  #include <iostream>
  #include <string>
  using namespace std;
  
  bool snake = true;
  
  int main () {
      if (samuel != "") {
          cout << samuel << snake << endl;
      }
      
      // I'm down here now!
      string samuel = "I'm tired of these darn snakes. ";
      
      {
        string snake = "I hope there are no snakes in here...";
        cout << snake << endl;
      }
  }

Click for answer.

Global Space

snake = true;

Main Block

if Block

// variables samuel and snake referenced, but
// not in block... look back

samuel = "I'm tired of these darn snakes. "


Plain Block

snake = "I hope there are no snakes in here..."


No! Doesn't compile; even though samuel is technically defined in the main function, by the time I want to use it, it hasn't been defined yet.


So how does all of this jazz about scope work with functions? Well, pretty much exactly how you'd expect.

What will the output be of the following code?

  #include <iostream>
  #include <string>
  using namespace std;
    
  bool snakesHere (string s);
  string snakes = "No Snakes";
  
  int main () {
      cout << snakesHere(snakes) << endl;
      string snakes = "SNAAAKES!";
      cout << snakesHere(snakes) << endl;
  }
  
  bool snakesHere (string snakes) {
      return snakes == "SNAAAKES!";
  }

Click for answer.

Global Space

snakes = "No Snakes"

Main Block

snakes = "SNAAAKES!"

snakesHere Block

// Designates that snakes is defined as a parameter
@param snakes


So, we get 0 and 1 output, respectively.


As a final note, we should talk about how to override scope, viz. what to do in the following scenario:

  #include <iostream>
  #include <string>
  using namespace std;
  
  int getMe = 0;
  
  int main () {
      bool getMe = 1;
      // How do I print out the int getMe
      // that's in the global namespace?
      cout << getMe << endl;
  }

How do I print out the global getMe? Well, the easiest way would be to rename one of the variables...

But say I insisted upon it being called getMe.

Scope access is done using the :: operator.


If I wanted to access a variable named x in the imANamespace namespace, I would say imANamespace::x

If I wanted to access a variable named x in the global namespace (and x was already defined in my scope), I would say ::x



References

We all make "references" in English all the time. "The Golden State" is a reference to California. "Nick" is a reference to some guy named Nickolas.

References are just different names for the same objects.

In C++, references are aliases for an object, in that they can be treated as actually being the object they're referencing. They are simply different names for the same object in memory. The syntax for a reference is:
<type>& <name>


Here's a simple example:

  #include <iostream>
  #include <string>
  using namespace std;
    
  int main () {
    double trouble = 123.45;
    double& issue = trouble;
  
    cout << "issue is: " << issue << endl;
  
    // Now, change issue...
    issue = 0.0;
    // ...and see what happens to trouble
    cout << "trouble is: " << trouble << endl;
    cout << "issue is: " << issue << endl;
  }

So, when I change issue above, trouble changes also... which isn't surprising because issue is just another name for trouble.

So why have references? Well, one illustration is how function parameters work in C++.

By default, function parameters are taken in what is known as "pass by value."

Pass by value means that parameter values are copied whenever a function is called, and do not refer to the exact argument variables.

What are the values of x and result that get printed?

  #include <iostream>
  #include <string>
  using namespace std;
  
  double f (double x);
  
  int main () {
      double x = 5.0,
             result = f(x);
      
      cout << result << endl;
      cout << x << endl;
  }
  
  // Receiving x by value
  double f (double x) {
      x = x + x * x;
      return x;
  }

Notice how I passed in x (by value) in main, but x in main did not get modified after I called f(x).

Pass by reference means that parameters are references (aliases, nicknames, etc.) to the arguments; they refer to the same variable in memory.

To indicate that a parameter is passed by reference, we place the ambserand (&) after the type in the parameter list.

  #include <iostream>
  #include <string>
  using namespace std;
  
  // Note, I also had to put
  // the & after double in the
  // function prototype
  double f (double& x);
  
  int main () {
      double x = 5.0,
             result = f(x);
      
      cout << result << endl;
      cout << x << endl;
  }
  
  // Receiving x by reference
  // Note the & after double
  double f (double& x) {
      x = x + x * x;
      return x;
  }


Practice & Project 3 Help

Let's give you the tools you need for your homework, hmm?

We should talk a little bit about some more things you can do with strings:

Substring is a string class method that returns a string equal to a sub-sequence of the calling string. The syntax is:


  string s = "some string";
  s.substr(start, length);

In s.substr(start, length), start and length are integers designating the following:

  • start defines the index at which you'd like to start collecting characters in the calling string (including the character at the start index).

  • length defines how many characters from that start index you'd like to collect

What will the following code print out?

  #include <iostream>
  #include <string>
  using namespace std;
  
  int main () {
      string s = "stringz";
      // Print out the first 3
      // characters of s
      cout << s.substr(0, 3) << endl;
      
      // Print out 3 characters
      // of s following index 2, including
      // the character at index 2
      cout << s.substr(2, 3) << endl;
      
      // Print out all characters
      // of s
      cout << s.substr(0, s.length()) << endl;
      
      // Print NOTHING FROM s
      cout << s.substr(2, 0) << endl;
      
      // Notice, none of this
      // changed s
      cout << s << endl;
  }

Because the substring returns a string, I can easily concatenate their results with the + operator.

Try some examples?

Design a function called excitementThief of return type void that takes in a string by reference and then removes all exclamation points from it.

  #include <iostream>
  #include <string>
  using namespace std;
  
  /*
   * Takes in a string by reference and then removes all exclamation
   * points from it.
   */
  void excitementThief ( ??? ) {
      for (int i = 0; i < s.length(); i++) {
          // [!] If the current character is an exclamation
          // point...
          if ( ??? ) {
              // [!] ...then remove that character using the
              // the substr function
              s = s.substr( ??? ) + s.substr( ??? );
              
              // [!] We need to do one more thing for this
              // to work below...
              ???
          }
      }
  }
  
  int main () {
      string s = "test!ing",
             t = "tes!!!t!i!ng",
             r = "!!!";
  
      excitementThief(s);
      excitementThief(t);
      excitementThief(r);
      
      // "testing"
      cout << s << endl;
      // "testing"
      cout << t << endl;
      // ""
      cout << r << endl;
  }


Debugging & Testing

Some general guidelines for project development and debugging:

  • When developing functions, always test them individually to make sure you're getting what you expect!

  • If your code is still behaving unexpectedly, you may want to do some simple cout statements to make sure variables have the values you expect.

  • If your code is STILL behaving unexpectedly, you may want to use an IDE's debugger.


We begin by describing unit tests and then will talk about the debugger.


Unit Tests

Unit tests verify that each individual piece of a program are operating as intended so that we can be confident that the whole program is correct.


Generally, this means we are individually testing the functions that constitute our program as a whole, verifying that every input generates their expected output.

Where do we perform unit tests? Typically we can devote a function to this task, even though we may not carry these tests into our final product.

First, let's look at a couple of ways to construct individual unit tests.

An individual unit test compares the expected output of a function to the actual output. When they match, that test is a success.


There are a variety of tools at our disposal for this definition, including the #include <cassert> library's assert statement.

The assert statement takes in a boolean value and, if false, will terminate the program with an error relating to the line of code on which an assertion failed.


This looks like:

  #include <iostream>
  #include <string>
  #include <cassert>
  using namespace std;
  
  int main () {
      int a = 5,
          b = 3;
  
      assert(true);
      assert(a == b);
      assert(a > b);
  }

We see that any false assertion will stop our code, and we can examine what expression violated our expectations.

Suppose we wanted to abstract that into a test suite using, of course, functions!

  #include <iostream>
  #include <string>
  #include <cassert>
  using namespace std;
  
  // ...some function definitions here
  // (assuming that someFunc is well defined)
  
  void runTests () {
      assert(someFunc(someParam1) == "test");
      assert(someFunc(someParam2) == "test2");
      // ...
  }
  
  int main () {
      // Now, if I want to run tests, I just have to
      // keep this line in main, otherwise, I can comment
      // it out!
      runTests();
  }

This is a really clean way of running unit tests.

The only issue with the assert statement is that it stops execution of your code immediately, and you don't get to see the results of all tests.

Better, is to make a custom test function that will alert you if a test succeeds and print out what it got and what it was expecting.

Here's an example one I made for string comparisons (feel free to use it yourself).

  #include <iostream>
  #include <string>
  #include <cassert>
  using namespace std;
  
  /*
   * Dumb function that removes the
   * first letter from a string and
   * returns a copy of that string
   */
  string stringyFunc (string s) {
      if (!s.length()) {
          return s;
      }
      return s.substr(1, s.length());
  }
  
  /*
   * Compares two strings: an actual (gotten from
   * from the function I'm testing) and an expected
   * (coded by the programmer), which will output
   * what test failed if a test were to fail
   * Returns true if a test succeeds
   */
  bool testString (string actual, string expected) {
      bool passed = actual == expected;
      if (!passed) {
          cerr << "[X] Test failed; EXPECTED: "
               << expected
               << " GOT: "
               << actual
               << endl;
      }
      return passed;
  }
  
  /*
   * Put all my unit tests in here!
   */
  void runTests () {
      testString(stringyFunc("test"), "est");
      testString(stringyFunc("t"), "");
      testString(stringyFunc(""), "");
  }
  
  int main () {
      // Run those tests here!
      runTests();
  }

The Debugger

A debugger allows you to take baby-steps through your code, and assess the value of each variable or output without having to run it all at once.

A breakpoint is a point you set in your code at which you would like the debugger to stop for you to assess the state of your program.

For example, input the following program into your favorite IDE and insert breakpoints where instructed. Then, run with debugging and see how the breakpoints operate.

  #include <iostream>
  #include <string>
  using namespace std;
  
  int main () {
      int i = 3;
      string snake = "hiss";
      // Insert breakpoint below
      
      while (i > 0) {
          snake += "s";
          cout << snake << endl;
          // Insert breakpoint below
          i--;
      }
  }

For more informatin on debuggers, consult our course page, which has videos and slides.