Symbolic Constants

Symbolic Constants: Constants are "variables" (in quotes because constants don't vary) that, once declared, cannot change value anywhere in the scope. They are declared as:
const <type> <name> = <value>;


Note, when we declare a constant variable, we name them (by convention) in ALL CAPITAL LETTERS, usually with words separated by underscores.

Why might we use constants? Doesn't it seem like we're just putting restrictions on ourselves?

Remember the One Change, One Place philosophy whereby we need only change one variable when we want to replace a value throughout our code. Forcing constant variables to be unable to change protects us from committing errors by changing values that weren't meant to be changed.


Think of type and const declarations as a "contract" between you and the compiler or you and other programmers working on the same project.

You want the compiler to honor your wishes of keeping a const value static just as much as you want coworkers to not touch your damn constants.

Here is an example of some const variables:

  #include <iostream>
  #include <string>
  using namespace std;
  
  int main () {
      const int X = 6;
      const double LAME_E = 2.718;
      const string STUFF = "YELLING";
      // const comment = good tea;
  }

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

  #include <iostream>
  using namespace std;
  
  int main () {
      const int DER = 6;
      DER = 5;
  }

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

  #include <iostream>
  using namespace std;
  
  int main () {
      const int DER = 6;
      // Assigning the SAME value, 
      // i.e., value isn't changing
      DER = 6;
  }

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

  #include <iostream>
  using namespace std;
  
  // All consts should be named this
  const int CANT_TOUCH_THIS = 5;
  
  int main () {
      int CANT_TOUCH_THIS = 6;
      cout << CANT_TOUCH_THIS << endl;
  }


Some More Notes on Conditionals

When dealing with conditionals, what we as humans know as "true" and "false" can be represented in a variety of C++ expressions.
We say that C++ expressions that evaluate to true are truthy and those that evaluate to false are falsy.


We've discussed that conditionals evaluate to 1 if they're true and 0 if they're false, but there's also another C++ built-in type that is very similar.

Type bool: Type bool, short for Boolean, are variables that hold only the value true or the value false. These are still represented as 1 and 0 respectively.


Let's take a look at an example of bools:

  #include <iostream>
  using namespace std;
  
  int main () {
      bool b = false,
           c = true,
           d = b || c,
           e = 1 == c;

      cout << true << endl;
      cout << d << endl;
      cout << e << endl;
  }

So why have bools at all if they're representing 0 and 1 still?

Well, maybe we want to make sure that something truthy is always equal to 1:

  #include <iostream>
  #include <string>
  using namespace std;
    
  int main () {
      bool iLikeNums = 50 * 20,
           iLikeStrings = "test";
  
      cout << iLikeNums << endl;
      cout << iLikeStrings << endl;
  }

What are some values (that we've discussed) that are truthy and what are some values that are falsy in C++?

Truthy:

  • Non-zero numbers

  • true (technically still just 1)

Falsy:

  • Zero

  • false (technically still just 0)


Because 0 and 1 are the main truthy and falsy values, we know we can store comparison results in variables too:

  #include <iostream>
  using namespace std;
  
  int main () {
      int conditionOutcome,
          e = 2,
          n = 4;
      
      // Comparisons can be stored
      // as ints
      conditionOutcome = e > n;
      cout << conditionOutcome << endl;
      conditionOutcome = e < n;
      cout << conditionOutcome << endl;
  }

We can also use conditionals within assignments:

  #include <iostream>
  using namespace std;
  
  int main () {
      int x = 5,
          y = (x >= 5) * 6,
          z = (x == 4) < 1;
      
      cout << y << endl;
      cout << z << endl;
  }

Note: You can run into trouble when you compare two different types.

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

  #include <iostream>
  #include <string>
  using namespace std;
  
  int main () {
      int i = 5;
      string kindaI = "5";
      
      if (i == kindaI) {
          cout << "Hey, we're equal." << endl;
      } else {
          cout << "Aww, unequal." << endl;
      }
  }

What are examples of different types that can be safely compared?

You can safely compare between doubles, ints, and bools, for example.


Switch Statements

We use switch statements when we want to concisely compare equivalence cases of ints (only ints!).

Switch Statements
The syntax for a switch statement goes something like this:

  switch (intBeingCompared) {
      case 1:
          // Whatever you want to happen if
          // intBeingCompared is equal to 1
          break;
          // Use break to end that case
      case 42:
          // Whatever you want to happen if
          // intBeingCompared is equal to 42
      case 2:
          // I didn't include a break above,
          // so whenever intBeingCompared is
          // EITHER 2 or 42, this block will run
          break;
      default:
          // This will run if intBeingCompared
          // matches none of the cases above
          break;
  }

What will the following code output?

  #include <iostream>
  using namespace std;
  
  int main () {
      int x = 5;
      
      switch (x) {
          case 1:
              cout << "I'm the one" << endl;
              break;
          case 5:
              cout << "I'm 5 years old";
              break;
          case 23:
              cout << endl << "No... really.";
              cout << endl;
              break;
          default:
              break;
      }
  }

What will the following code output?

  #include <iostream>
  using namespace std;
  
  int main () {
      int x = 5;
      
      switch (x) {
          case 1:
              cout << "I'm the one" << endl;
              break;
          case 5:
              cout << "I'm 5 years old";
          case 23:
              cout << endl << "No... really.";
              cout << endl;
              break;
          default:
              break;
      }
  }


Some Notes on Logic

Rule of thumb: Unless you group conditionals (using parentheses), your code will evaluate conditionals in this order:

  • Logical Binary Operators (e.g. <, ==, etc.)

  • Logical AND ( && )

  • Logical OR ( || )


Notice, logical AND will be evaluated before logical OR.

To what will the following conditional evaluate?

  #include <iostream>
  using namespace std;
  
  int main () {
      int x = 1,
          y = 2,
          z = 3;
      
      // NB: You'll usually want to use grouping
      // to break apart a conditional of this
      // complexity--even just for clarity's
      // sake
      if (x == y || x < 3 && z < y || z != y) {
          cout << "You're in here!" << endl;
      } else {
          cout << "You're in there!" << endl;
      }
  }

Reducing Conditionals

Sometimes, our control flow gets complicated quickly, and we end up accidentally repeating ourselves unnecessarily.

There are a couple of quick tricks that might help you design your flow more... elegantly.

Tip 1: Keep Conditionals DRY (Don't Repeat Yourself):
If you have some similar component between two or more conditions in an if-ladder, you might be able to condense them into a single parent condition.


Here's an example below where we see the condition i > 5 listed in two of our if-ladder's rungs.

  if (i > 5 && j < 10) {
      // Action A
  } else if (i > 5 && j > 15) {
      // Action B
  }

How can we simplify the above conditional?

  if (i > 5) {
      // Everything inside here implies that
      // i is greater than 5
      if (j < 10) {
          // Action A
      } else if (j > 15) {
          // Action B
      }
  }

Tip 2: Keep Actions DRY (Don't Repeat Yourself):
If you have the same action as a result of two separate conditionals, you can condense them into a single conditional using the logical OR.

Here's an example below where we see the same Action A repeated in two of our conditionals.

  if (i > 5 && j < 10) {
      // Action A
  } else if (i > 8 && j > 15) {
      // Action A
  }

How can we simplify the above conditional?

  if ((i > 5 && j < 10) || (i > 8 && j > 15)) {
      // Action A
  }


More Notes on Iteration

So far, we've discussed three main tools for iteration: the while loop, the for loop, and the do-while loop.

We'll begin with a review of their syntax:

While LoopThe while loop executes its code block over and over until its condition is false.


  while (condition) {
      // Code block
  }

The while loop follows these steps:

  1. Check Condition: If the conditional is true, we execute the code block, otherwise, we break out of the loop.

  2. Execute Code Block: If the conditional was true in (1), we will execute everything in the code block, then go back to (1).


For Loop Just like a while loop, except we initialize the iterator, define the conditional, and define the post-iteration behavior all in the signature. The syntax is:


  for (int i = value; conditional; post_loop_action) {
      // Code block
  }

The for loop follows these steps:

  1. Declare Iterator: Typically, we declare and initialize our iterator in the first part of the for syntax. This step is performed ONLY ONCE.

  2. Check Condition: If the conditional is true, we execute the code block, otherwise, we break out of the loop.

  3. Execute Code Block: If the conditional was true in (2), we will execute everything in the code block.

  4. Perform Post Loop Action: If we executed the code block in (3), we perform the post loop action, then go back to (2).


You should know: variables declared in the for loop declaration are defined within their closest scope.

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

  #include <iostream>
  #include <string>
  using namespace std;
  
  int main () {
    for (int i = 0; i < 3; i++) {
          cout << "Outer i: "
               << i << endl;
               
          // Don't ever name your nested
          // iterator the same as
          // another above it :(
          for (int i = 3; i > 0; i--) {
              cout << "Inner i: "
                   << i << endl;
          }
      }
  }

NB for Masochists: You can omit any portion of the for syntax and it will still loop. Disclaimer: Please don't do this :(

  #include <iostream>
  using namespace std;
    
  int main () {
      int i = 0;
      // It looks like it's crying because
      // it's so sad ;;
      for (;;) {
          if (i >= 3) {
              break;
          }
          cout << i << endl;
          i++;
      }
  }

Complete the highly useful code fragment below that prints out a giant letter 'N' that is SIZE x SIZE in dimension.

  #include <iostream>
  #include <string>
  using namespace std;
    
  /*
   * Design a program that writes 
   * a 5 by 5 capital letter N 
   * made up of the character 'N'.
   *
   * Output should be:
   * N   N
   * NN  N
   * N N N
   * N  NN
   * N   N
  */
  int main () {
      // Assume SIZE must be odd
      const int SIZE = 5;
        
      // For loop that goes through
      // SIZE rows...
      for (int i = 0; i < SIZE; i++) {
          // And loop that goes through
          // SIZE columns
          for (int j = 0; j < SIZE; j++) {
              // [!] Condition for printing 'N'
              // HINT: 3 cases for printing one!
              if ( ??? ) {
                  cout << "N";
              
              // Otherwise, print a space
              } else {
                  cout << " ";
              }
          }
            
          cout << endl;
      }
  }

Do-While Loop Just like a while loop, except we always execute the code block before checking the continuation condition.


  do {
      // Code block
  } while (condition);

The do-while loop follows these steps:

  1. Execute Code Block: Execute everything in the code block, and check the continuation condition at the end.

  2. Check Condition: If the conditional is true, we execute the code block again, otherwise, we break out of the loop.


We'll do one practice problem with the do-while now and more iteration in the coming section.

Complete the code fragment below for playing a number guessing game with your computer!

  #include <iostream>
  #include <string>
  #include <cmath>
  #include <time.h>
  using namespace std;
  
  /*
   * Try to guess the number your computer is thinking of!
   * We first generate a random number between 0 and MAX_CHOICES,
   * prompt the user for a guess, and tell them if they win,
   * are 1 away from the number, or are way off (2+ away).
   * Players have only MAX_TRIES turns to guess the number.
   */
  int main () {
      // Magic incantation: don't worry about for now
      srand(time(NULL));
      const int MAX_CHOICES = 5; // Guess 0 - 4
      const int MAX_TRIES = 3;   // Get 3 tries
      
      // Target is the random number we're guessing
      int target = rand() % MAX_CHOICES,
          counter = 0,
          guess,
          difference;
  
      string hint;
      
      do {
          cout << "Guess a number! ";
          // [!] Collect user's guess
          ???
          
          // abs == absolute value
          difference = abs(target - guess);
          
          // [!] Give the following hint for whatever
          //     the value of difference is:
          // 0:  "You win!" (and then return)
          // 1:  "Off by 1!"
          // 2+: "Way off... just give up."
          ???
  
          cout << hint << endl;
          counter++;
      
      // [!] Keep going as long as the user has more tries
      } while ( ??? );
      
      cout << "YOU LOSE! YOU GET NOTHING! GOOD DAY SIR/MADAME!" << endl;
  }


More String Operations

We begin our discussion of strings by clarifying an oft-confused difference between C++ strings and C strings.

Let's start by defining some of the words we've been slinging around.

Characters are single letters or symbols like 'a', or '|', and include special characters like the new line character '\n'. We use single quotes to designate characters.

Strings consist of some n characters that are "strung" together, where n >= 0. We use double quotes to designate strings.

C Strings are arrays of characters that possess a terminating 0 byte designating the end of the string.

C++ Strings are objects representing arrays of characters, and do NOT possess the terminating 0 byte.


Because C++ strings are not 0-terminated, we know that we can access characters from a string's 0th to its (length - 1)th element.

Today, we'll dive more into C++ strings and characters, and talk about C strings in later lectures.

Characters are a native type in C++ that are defined by using the reserved word char.

Because strings are arrays of characters, we can store individual string characters into char variables.

  #include <iostream>
  #include <string>
  using namespace std;
    
  int main () {
      string bean = "bean";
      char know = 'a',
           your = 'b',
           abcs = 'c',
           
           // Assign the char beanAt1 the
           // character located at index 1
           // in string bean
           beanAt1 = bean[1];
    
      cout << know << your << abcs << endl;
      cout << beanAt1 << endl;
  }

It works the other way around too! You can assign characters to positions of a string that already have a character in them!

  #include <iostream>
  #include <string>
  using namespace std;
  
  int main () {
      string s = "Andrew = Cool";
  
      s[9] = 'F';
  
      // :(
      cout << s << endl;
  }

Warning! Don't try to assign or access characters that are beyond a string's index!

  #include <iostream>
  #include <string>
  using namespace std;
  
  int main () {
      string s = "Andrew = Cool";
  
      s[200] = 'F';
  
      // 0_o
      cout << s << endl;
  }

Characters are interesting because they are each represented by a unique integer value.

  #include <iostream>
  #include <string>
  using namespace std;
  
  int main () {
      int iBeC = 'C',
          iBec = 'c',
          iBeNewline = '\n';
      
      char iBeT = 84;
      
      cout << "C == " << iBeC << endl;
      cout << "c == " << iBec << endl;
      cout << "\\n == " << iBeNewline << endl;
      cout << "84 == " << iBeT << endl;
  }

There are a variety of operations we can use with characters in the cctype library.

Character Classification:

  • isalpha asks if this character is a letter.

  • isdigit asks if this character is a number.

  • isalnum asks if this character is alphanumeric. (is it a number or a letter?)

  • isupper asks if this character is an uppercase letter.

  • islower asks if this character is a lowercase letter.


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

  #include <iostream>
  #include <string>
  #include <cctype>
  using namespace std;
    
  int main () {
    char numInChar = '5';
  
    cout << isalnum(numInChar) << endl;
  }

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

  #include <iostream>
  #include <string>
  #include <cctype>
  using namespace std;
    
  int main () {
    string stringyNum = 'hi5';
  
    cout << isalnum(stringyNum[2]) << endl;
  }

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

  #include <iostream>
  #include <string>
  #include <cctype>
  using namespace std;
    
  int main () {
    char numInChar = '5';
  
    cout << isupper(numInChar) << endl;
  }

There are also character conversion functions that we now have at our disposal:

Character Conversions:

  • tolower converts the given character to a lowercase version of itself.

  • toupper converts the given character to an uppercase version of itself.

Write a program that queries the user for a string, swaps all lowercase letters for uppercase ones (and vice-versa), and then prints out the result.

  #include <iostream>
  #include <string>
  #include <cctype>
  using namespace std;
    
  /*
  * Write a program that queries the user for a string,
  * swaps all lowercase letters for uppercase ones (and 
  * vice versa), and then prints out the result.
  */
  int main () {
      string s;
      char c;
        
      // Ask for a string
      cout << "Gimme dat string: ";
      // [!] Collect user's input
      ???
      
      // [!] Iterate through each string character
      for (int i = 0; ???; i++) {
          // [!] If it's uppercase, convert to lower, and vice versa
          // We'll use the ternary conditional!
          c = ( ??? ) ? ??? : ???;
  
          // Print out current value of c
          cout << c;
      }
      cout << endl;
  }


Practice

Here are a few practice problems to get you exam-ready.


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

  #include <iostream>
  #include <string>
  using namespace std;
  
  int main () {
      int i = 0;
  
      if (i = 1) {
          cout << "i be truthy" << endl;
      } else {
          cout << "i be falsy" << endl;
      }
  }

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

  #include <iostream>
  #include <string>
  using namespace std;
  
  int main () {
      int i = 3;
  
      switch (i) {
          case 1
              cout << "1" << endl;
          case 2
              cout << "2" << endl;
          case 3
              cout << "3" << endl;
          default
              cout << "DEFAULT!" << endl;
      }
  }

OK OK... what about now?

  #include <iostream>
  #include <string>
  using namespace std;
  
  int main () {
      int i = 3;
  
      switch (i) {
          case 3:
              cout << "3" << endl;
          case 2:
              cout << "2" << endl;
          case 1:
              cout << "1" << endl;
          default:
              cout << "DEFAULT!" << endl;
      }
  }

What could possibly go wrong in the following code?

  #include <iostream>
  #include <string>
  using namespace std;
  
  int main () {
      string s;
      cout << "Enter a string! ";
      getline(cin, s);
  
      int i = 0;
      do {
          i += 2;
          cout << s[i] << endl;
      } while (i < s.length());
  }


Midterm

Note: although I've seen the format of the past midterms, that may or may not be indicative of future formats!

Studying tips:

  • Know your syntax, know your syntax, know your syntax. Can't write a for-loop or a switch from memory? Hit the books!

  • Loops, conditionals, and switch statements are perhaps the largest portions of the exam.

  • Try to do the practice examples by hand... you won't have an IDE to tell you that you missed a semi-colon!

  • Be comfortable spotting bugs! Pay careful attention to uninitialized variables and syntax errors!

You MUST sign up for a midterm time on the course website under "Announcements."

The midterm covers everything in the past weeks up until strings and loops. Reviewing my lecture notes and doing the practice examples will set you on the right path.

You are allowed to bring a single 8.5" x 11" sheet of notes, double sided, printed or hand-written into the midterm exam. (double check on this -- it's been true in previous years)

I will have office hours at the usual times the day of the exam, up until the actual exam! I will let you know which I'm proctoring for that exact OH end-time...

I will compose a practice exam with questions on all the big topics, plus any that you give me... now. I'll try to get this out by Sunday at the latest.