.. highlight:: c++ Chapter 3 Notes =============== Please read chapter 3 of the textbook. True Tables for Boolean Expressions ----------------------------------- boolean expressions are important in C++ because they appear in if-statements and loops recall that a boolean expression is an expression that evaluates to either ``true`` or ``false`` here are some examples :: true // evaluates to true false // evaluates to false 5 == 5 // evaluates to true 4 == 2 // evaluates to false 5 != 5 // evaluates to false 4 != 2 // evaluates to false 2 <= 3 // evaluates to true 5 <= 1 // evaluates to false true && true // evaluates to true (x < 0) || (x > 1) // value depends upon x !(x == y) // value depends upon x and y (a == 0 && b == 0) || (a != 0 && b != 0) // value depends upon a and b as with regular arithmetic expressions, parentheses can be used to change the order of evaluation of sub-expressions Truth Tables ------------ the logical operators ``&&`` (and), ``||`` (or), and ``!`` (not) are often defined using truth tables see p. 114 of the textbook Operator Precedence ------------------- C++ operators have different levels of precedence in ``1 + 2 * 3``, ``*`` is evaluated before ``+`` because ``*`` has higher precedence than ``+`` all C++ operators have a precisely defined precedence level here are the precedence levels of a few important operators :: highest precedence (done first) unary operators: + - ++ -- ! binary arithmetic operators: * / % binary arithmetic operators: + - boolean operators: < > <= >= boolean operators: == != boolean operators: && boolean operators: || lowest precedence (done last) for example, consider the expression ``x + 1 > 2`` ``+`` has a higher precedence than ``>``, so ``x + 1`` is evaluated first then the result of ``x + 1`` is compared to 2 you could re-write the expression equivalently as ``(x + 1) > 2`` Short-Circuit Evaluation ------------------------ C++ uses a common trick to speed up evaluation of some expressions consider the boolean expression ``true || E``, where ``E`` is some other boolean expression (although we don't know exactly what it is, or what it evaluates to) no matter the value of ``E``, the expression ``true || E`` must be ``true`` because it starts with ``true`` so in this expression C++ does not even bother to evaluate ``E``! similarly, an expression of the form ``false && E`` is always ``false``, and so C++ does not evaluate ``E`` Short-Circuit Evaluation ------------------------ in general, when C++ evaluates the expression ``E1 || E2`` it first evaluates ``E1`` if ``E1`` is ``true`` then it stops and returns ``true`` for the entire expression otherwise, if ``E1`` is ``false``, then it evaluates ``E2`` similarly, when C++ evaluates the expression ``E1 && E2`` it first evaluates ``E1`` if ``E1`` is ``false`` then it stops and returns ``false`` for the entire expression otherwise, if ``E1`` is ``true``, then it evaluates ``E2`` Short-Circuit Evaluation ------------------------ short-circuit evaluation can speed up the evaluation of some boolean expressions it also forces the order in which the sub-expressions of boolean expressions are checked Integers as Boolean Values -------------------------- C++ lets you use integers as boolean values this is how things were traditionally done in C (and C++ inherits almost everything from C) the integer 0 is considered to be ``false``, and every other non-0 integer is considered to be ``true`` so you can write expressions like this :: (0 && -6) || 5 // same as (false && true) || true !52 // same as !true however, while this is allowed in C++, it is rarely a good idea to do this using integers as booleans can be quite confusing it makes your code harder to understand, which in turn makes your code harder to debug and modify Enumeration Types ----------------- an enumeration type is way to create your own data type enumeration types are useful when you want a type for a small, finite series of values :: // C++11-style enumerations enum class Day { Sun, Mon, Tue, Wed, Thu, Fri, Sat }; Day today = Day::Mon; // must put Day:: at start of Day values C++ also supports older, C-style enumerations, e.g. :: enum Direction { NORTH = 0, SOUTH = 1, EAST = 2, WEST = 3 }; C-style enumerations pretty much just give names to integers in C++11, you should almost always prefer the ``enum class`` enumerations they are usually clearer, and help catch more errors than C-style enumerations Enumeration Types ----------------- the use of ``Day::`` to access the values of ``Day`` seems a bit inconvenient but it allows different enumeration types to use values with the same name, e.g. :: // enum.cpp #include "cmpt_error.h" #include using namespace std; // C++11-style enumerations enum class Day { sun, mon, tue, wed, thu, fri, sat }; enum class Solar_obj {sun, moon, earth}; int main() { Day today = Day::sun; // must put Day:: at start of Day values if (today == Day::sun) { cout << "Sunday\n"; } else if (today == Day::mon) { cout << "Monday\n"; } else if (today == Day::tue) { cout << "Tuesday\n"; } else if (today == Day::wed) { cout << "Wednesday\n"; } else if (today == Day::thu) { cout << "Thursday\n"; } else if (today == Day::fri) { cout << "Friday\n"; } else if (today == Day::sat) { cout << "Saturday\n"; } // There is no confusion between Day::sun and Solar_obj::sun. Solar_obj obj = Solar_obj::sun; // Must put Solar_obj:: at start // of Solar_obj values if (obj == Solar_obj::sun) { cout << "The Sun\n"; } else if (obj == Solar_obj::moon) { cout << "The Moon\n"; } else if (obj == Solar_obj::earth) { cout << "The Earth\n"; } } // main Multi-way if-statements ----------------------- one common use of if-statements is to check the value of a variable and then to do different things depending upon that value :: // simple_arith.cpp #include "cmpt_error.h" #include using namespace std; int main() { while (true) { // loop forever (infinite loop) cout << "eval> "; double x, y; char op; cin >> x >> op >> y; if (op == '+') { cout << x + y; } else if (op == '-') { cout << x - y; } else if (op == '*') { cout << x * y; } else if (op == '/') { cout << x / y; } else { cout << "Sorry, I don't know the operator " << op; } cout << '\n'; } // while } Multi-way if-statements ----------------------- if there's only one statement in the body of an if-statement, then the braces are not required :: if (op == '+') cout << x + y; else if (op == '-') cout << x - y; else if (op == '*') cout << x * y; else if (op == '/') cout << x / y; else cout << "Sorry, I don't know the operator " << op; this is nicer to read but don't forget to add the braces back in when you add new statements! :: if (op == '+') cout << x + y; else if (op == '-') cout << x - y; else if (op == '*') cout << x * y; else if (op == '/') cout << x / y; else cout << "Sorry, I don't know the operator " << op; cout << "\nI only know +, -, *, and /\n"; don't be fooled by the indentation! remember that C++ doesn't care about indentation when it comes to determining blocks of code the new ``cout`` statement is **not** part of the ``else`` body instead, the new ``cout`` statement is the first statement after the entire if-else-if statement, and so it *always* executed the correct way to write this is with braces :: if (op == '+') cout << x + y; else if (op == '-') cout << x - y; else if (op == '*') cout << x * y; else if (op == '/') cout << x / y; else { cout << "Sorry, I don't know the operator " << op; cout << "\nI only know +, -, *, and /\n"; } because of this problem, it's usually wise to *always* uses braces even if they are not necessary then you have one less problem to worry about in your code! :: if (op == '+') { cout << x + y; } else if (op == '-') { cout << x - y; } else if (op == '*') { cout << x * y; } else if (op == '/') { cout << x / y; } else { cout << "Sorry, I don't know the operator " << op; cout << "\nI only know +, -, *, and /\n"; } The switch Statement -------------------- the switch can be useful to make decisions based on integers or characters :: int main() { cout << "eval> "; double x, y; char op; cin >> x >> op >> y; switch (op) { case '+': cout << x + y; break; case '-': cout << x - y; break; case '*': cout << x * y; break; case '/': cout << x / y; break; default: cout << "Sorry, I don't know the operator " << op; } cout << '\n'; } notice the use of ``break`` --- that's important! if you forget ``break`` in a ``switch`` statement, then the flow of control "falls through" to the next case (!) ``switch`` statements only work with integer types, such as ``int`` and ``char`` we will mostly avoid them in this course they are sometimes a little more readable than if-statements, and possibly a little more efficient, but they are less flexible and tend to be more error- prone The switch Statement -------------------- ``switch`` statements can be used with enumerated types :: enum class Day { sun, mon, tue, wed, thu, fri, sat }; Day today = Day::sun; switch (today) { case Day::sun: cout << "Sunday\n"; break; case Day::mon: cout << "Monday\n"; break; case Day::tue: cout << "Tuesday\n"; break; case Day::wed: cout << "Wednesday\n"; break; case Day::thu: cout << "Thursday\n"; break; case Day::fri: cout << "Friday\n"; break; case Day::sat: cout << "Saturday\n"; break; } Loops ----- as we've seen, loops execute a block of code 0 or more times we saw some examples of ``while`` loops earlier C++ also has for-loops and do/while loops for-loops come in two flavours: regular, and ranged ranged for-loops are new in C++11, while regular for-loops are essentially a tidier version of a while-loop do/while loops are just like while-loops, except the condition is checked at the bottom instead of the top we won't use do/while loops much, if at all, in this course While Loops ----------- this program prints the square of the numbers from 1 to 10 :: int main() { int i = 1; // i is the loop index variable while (i < 11) { cout << i << " * " << i << " = " << i * i << "\n"; ++i; } cout << "done\n"; } notice that ``i`` is initialized to 1 that's because we decided this program should start at 1 notice that the condition for the loop is ``i < 11`` that's because we want 10 to be the last value of ``i`` that is squared in other words, as long as ``i`` is less than 11, the loop body should be repeated we could have written the condition like this: ``i <= 10`` the statement ``++i`` adds 1 to ``i`` it's called the loop's increment statement without it you'd have an **infinite loop** that runs forever While Loops ----------- while loops typically take this general form :: initialization_statement; while (condition) { statement_1; statement_2; // ... statement_n; increment; } the exact details will vary depending upon the purpose of the loop and somtimes a while-loop will not take this form but in general, this is a good pattern to follow when writing loops Increment Operators ------------------- the expression ``++i`` adds 1 to the variable ``i`` ``++`` is called the increment operator ``--`` is called the decrement operator they are short and convenient and occur all the time in C++ code you can use them inside expressions, e.g. :: int i = 1; int a = 5; cout << "i = " << i << "\n" << "a = " << a << "\n"; // i = 1 // a = 5 a = a + ++i + 2; cout << "i = " << i << "\n" << "a = " << a << "\n"; // i = 2 // a = 9 Increment Operators ------------------- the expression ``++i`` is called **pre**-increment that's because ``++i`` first increments ``i``, and then returns the value of ``i`` the expression ``i++`` is called **post**-increment that's because ``i++`` first increments ``i``, and then returns the value of ``i - 1`` (i.e. the value before the increment) when used as statements, there's no difference between post-increment and pre- increment :: int i = 0; ++i; // i is now 1 i++; // i is now 2 Increment Operators ------------------- but used in expressions, there are serious problems often, the results are undefined, e.g. :: int i = 0; cout << ++i << "\n" << i << "\n"; this does not compile using our (strict) course makefile the problem is that C++ cannot guarantee what, exactly, the value of ``i`` will be many C/C++ programmers ignore this problem but it can be a serious source of hard-to-find bugs **rule of thumb**: **never** use increment (or decrement) operators **in an expression** in this course ambiguous use of increment/decrement operators will usually be caught by our strict compiler options this is a good thing that will stop you from writing buggy programs! For Loops --------- recall this general while-loop template :: initialization_statement; while (condition) { statement_1; statement_2; // ... statement_n; increment; } this happens frequently enough that C++ provides a special loop for this case this is called a for-loop here's the basic template :: for(initialization_statement; condition; increment) { statement_1; statement_2; // ... statement_n; } this behaves the same way as the above while-loop but the advantage is that all the loop-control information --- the initialization condition, and increment --- are in one place at the top of the loop For Loops --------- :: int main() { cout << "How many iterations do you want? "; int n = 0; cin >> n; for(int i = 0; i < n; ++i) { cout << " i = " << i << "\n"; } // cout << "i = " << i << "\n"; cout << "Done!\n"; } compare this to an equivalent version using a for-loop :: int main() { cout << "How many iterations do you want? "; int n = 0; cin >> n; int i = 0; while (i < n) { cout << " i = " << i << "\n"; ++i; } // cout << "i = " << i << "\n"; cout << "Done!\n"; } there is subtle difference here in the for-loop, the scope of the ``i`` variable is within the loop only but in the while-loop version, you can access ``i`` after the loop For Loops --------- the parts of the for-loop header are optional :: cout << "How many iterations do you want? "; int n = 0; cin >> n; int i = 0; for(; i < n; ) { cout << " i = " << i << "\n"; ++i; } cout << "Done!\n"; an idiomatic way to write an infinite loop (a loop that never stops) is this :: for(;;) { // infinite loop cout << "chugga chugga toot toot!\n"; } type ctrl-C to end this! For Loops --------- there's often multiple ways to write the same loop for example, suppose you want to print a count-down from 10 to 1 for a rocket :: for(int i = 10; i > 0; --i) { cout << "... " << i << "\n"; } cout << "Blast off!!\n"; here's another solution :: for(int i = 10; i >= 1; --i) { cout << "... " << i << "\n"; } cout << "Blast off!!\n"; or even this :: for(int i = 0; i < 10; ++i) { cout << "... " << (10 - i) << "\n"; } cout << "Blast off!!\n"; try to use the method that you find clearest and simplest clear, simple code is almost always better than complicated and tricky code! For Loops --------- the condition for a for-loop can be more complex that checking the value of the loop-control variable as an example, lets write a program that takes a positive ``int`` as input and prints all the *factors* of n like this :: Please enter a positive integer: 12 12 = 1 * 12 12 = 2 * 6 12 = 3 * 4 an important requirement here is that the left factor must be less than, or equal to, the right factor so a line like "12 = 4 * 3" should **not** be printed :: // factors.cpp #include "cmpt_error.h" #include using namespace std; int main() { cout << "Please enter a positive integer: "; int n; cin >> n; if (n <= 0) { cout << n << "isn't big enough!\n"; } else { for(int factor = 1; factor * factor <= n; ++factor) { if (n % factor == 0) { // test if n is a multiple of factor int a = factor; int b = n / factor; cout << n << " = " << a << " * " << b << "\n"; } // if } // for } // if } // main notice how we use comments to label all the ``}``\ s at the end of the program this helps ensure that the close braces are paired correctly with their open braces we also can't easily predict how many lines this program will print! the for-loop condition handles it correctly, though Example: 2-digit Numbers Divisible by their Digit Sum ----------------------------------------------------- loops can appear with in loops this can be quite confusing, so use nested loops with care for example, suppose you want to solve this mathematical problem what are all the 2-digit integers, i.e. 11 to 99, that are divisible by the sum of their digits? one solution to this problem is this :: int main() { int count = 0; for(int a = 1; a < 10; ++a) { for(int b = 1; b < 10; ++b) { int n = 10 * a + b; if (n % (a + b) == 0) { cout << n << " is divisible by the sum of its digits\n"; ++count; } } } cout << "count = " << count << "\n"; } // main this code has a for-loop within a for-loop the variables ``a`` and ``b`` each range over all possible digits which we can use to create all numbers 11 to 99 nested loops are tricky in general! Example: Sum of the Digits of a Positive int -------------------------------------------- Write a program that prints all the digits of a positive ``int``, along with their sum. Use only basic C++ operators in your answer. Don't use strings or any functions from other files (like ``cmath``). For example, given the int 38722, your programs should print:: 2 2 7 8 3 sum = 22 Sample solution:: #include using namespace std; int main() { cout << "What is n? "; int n = 0; cin >> n; cout << "n=" << n << "\n\n"; if (n == 0) { cout << 0 << "\n"; cout << "sum = 0\n"; } else { int sum = 0; // n = 1234 while (n > 0) { // get the ones digit int ones = n % 10; // print it cout << ones << "\n"; sum += ones; // remove ones digit from n n = (n - ones) / 10; } // while cout << "sum = " << sum << "\n"; } // if } Example: Printing a Triangle of Stars ------------------------------------- Write a program that reads a positive ``int``, n, from the user, and prints a triangle of stars, with 1 star in the first two, 2 in the second, and so on. For example, if n=4, then your program should print this:: * ** *** **** Sample solution:: // stars.cpp #include "cmpt_error.h" #include using namespace std; int main() { cout << "How many rows do you want? "; int num_rows = 0; cin >> num_rows; for(int i = 0; i < num_rows; ++i) { int num_stars = i + 1; for(int j = 0; j < num_stars; ++j) { cout << '*'; } cout << "\n"; } } // main Example: Sum of Numbers Entered by the User ------------------------------------------- Write a program that prints the sum of ``double``\_s entered by the user. Use the number -1 to indicate the end of the numbers. For example:: Please enter some numbers: 1.2 -1.2 3 0 4.5 -1 sum = 7.5 The -1 is *not* included in the sum. Sample solution (while loop):: #include using namespace std; int main() { cout << "Enter some numbers (-1 to end): "; double x = 0.0; double sum = 0.0; while (x != -1) { sum += x; cin >> x; } cout << "sum = " << sum << "\n"; } Sample solution (for loop):: #include using namespace std; int main() { cout << "Enter some numbers (-1 to end): "; double sum = 0.0; for(double x = 0.0; x != -1; cin >> x) { sum += x; } cout << "sum = " << sum << "\n"; } Example: All 3-letter Strings ----------------------------- Write a program that prints all 17,576 3-letter strings consisting of lower- case letters a to z. The first few strings are:: aaa aab aac aad ... Sample solution:: // three_letters.cpp #include "cmpt_error.h" #include using namespace std; int main() { int count = 0; for(char a = 'a'; a <= 'z'; ++a) { for(char b = 'a'; b <= 'z'; ++b) { for(char c = 'a'; c <= 'z'; ++c) { cout << a << b << c << "\n"; count++; } } } cout << "count = " << count << "\n"; } // main Ranged for-loops ---------------- when we get to vectors and arrays, we'll see an alternate form of for-loop called a ranged for-loop ranged for-loops are a more convenient way to loop over the data in an array or vector What Kind of Loop to Use? ------------------------- we've seen while loops, do/while loops, and for-loops do/while loops occasionally have their uses, but you probably just avoid them the problem with do/while loops is that they test their condition at the bottom and that's rarely what you want and even when you do think you need a do/while loop, you often end up adding if-statements near the top to make it simulate a regular while-loop! so a good rule of thumb is: never use do/while loops What Kind of Loop to Use? ------------------------- so should you use for-loops or while-loops? if you can only pick one, use a for-loop a for-loop does everything a while-loop does, but it is more readable because all the loop-control information is one place sometimes, though, a while-loop will result in easier-to-read code one kind of case is when the increment, or initialization, of the loop is complex and needs more than one statement The break Statement ------------------- we've already seen the ``break`` statement inside of a ``switch`` a ``break`` statement can also be used to make the flow of control immediately jump out of a loop :: int sum = 0; int count = 0; for (;;) { cout << "Please enter a positive integer (0 to quit): "; int n = 0; cin >> n; if (n == 0) { break; // jump out of the loop } else if (n < 0) { cout << n << " is not a positive integer!\n"; break; // jump out of the loop } else { sum += n; ++count; } } cout << "Done!\n"; double avg = double(sum) / count; cout << "You entered " << count << " positive integers.\n"; cout << "Their sum is " << sum << ".\n"; cout << "Their average is " << avg << ".\n"; generally, using ``break`` like this is a bad idea it usually makes loops harder to understand, especially if you have more than one ``break`` it's better if the only way to get out of a loop is because its condition is false that will help you understand what the loop is doing, which aids in debugging The break Statement ------------------- here is one alternative that avoids ``break`` :: int sum = 0; int count = 0; bool done = false; while (!done) { // while "done is not false" do the following .... cout << "Please enter a positive integer (0 to quit): "; int n = 0; cin >> n; if (n == 0) { done = true; // jump out of the loop } else if (n < 0) { cout << n << " is not a positive integer!\n"; done = true; // jump out of the loop } else { sum += n; ++count; } } cout << "Done!\n"; double avg = double(sum) / count; cout << "You entered " << count << " positive integers.\n"; cout << "Their sum is " << sum << ".\n"; cout << "Their average is " << avg << ".\n"; this uses a flag variable called ``done`` the idea is to set ``done`` to true when the loop is finished it's initially ``false`` to ensure the loop iterates at least once flag variables like ``done`` are a usually technique to remember when writing loops they can be useful when a loop has a complex stopping condition that doesn't easily fit into the loop-condition Designing Loops --------------- mastering loops is hard because there are so many ways to write them but you always need to think about, at least, these three things with any loop - what is the "setup" for the loop, i.e. what variables does it need and what are their initial values - what work should be done on each iteration of the loop - when, exactly, does the loop stop in general, writing loops is one of the more challenging parts of programming they are hard to visualize, and thus hard to debug as we continue in this course, we will learn standard patterns for writing loops