Review of last time

Operator(s) Name Arity Pre/Post Assoc.
- + Unary minus/plus Unary Pre. N/A
! Logical NOT Unary Pre. N/A
* / % Times, div, mod Bin. Left
+ - Plus, minus Bin. Left
<< >> Ins/Ext, Shift Bin. Left
< > <= >= Comparison Bin. Left
!= == (In)equality Bin. Left
&& Logical AND Bin. Left
|| Logical OR Bin. Left

Assignment operators

The simplest assignment operator is =:

int x;
x = 1;      // Value stored in x becomes 1
x = x + 1;  // Value stored in x becomes 2

int y;
cin >> y;
x = y; // Value stored in x becomes whatever the user entered

Operations like x = x + 1 are common enough that there are shortcut assignment operators for them: x += 1 is exactly the same as x = x + 1. The shortcut operators are

x += y;    // Same as x = x + y
x -= y;    // x = x - y
x *= y;    // x = x * y
x /= y;    // x = x / y

Like all assignment operators, these are right associative. This means that you must fully compute the right-hand input to the assignment before performing it. This means that

x -= 1 + 2;

is interpreted as x = x - (1 + 2) and not as x = x - 1 + 2. Don’t forget the implicit parentheses!

x += 1 and x -= 1 are common enough that there are even shortcuts available for them:

++x; // Value stored in x increases by one
--x; // Value stored in x decreases by one

These are called the increment and decrement operators. They are available in both prefix (++x) and postfix (x++) forms, although the difference is only apparent when they are used in larger expressions, not as a single statement. If you’re curious, try running this code:

int x = 1;
cout << "++x = " << ++x << endl;
cout << "x   = " <<   x << endl;

int y = 1;
cout << "y++ = " << ++y << endl;
cout << "y   = " <<   y << endl;

and see what it prints.

Today

More about boxes (variables)

Proper name for a box is “variable”. It’s unfortunate that we’ve “borrowed” the name from algebra, because just as = in C++ is very different from in algebra, a “variable” in C++ is very different from an algebraic variable. As we’ve seen, a variable holds a value, but that value can change over the lifetime of our program. (As opposed to algebra, where it’s value may be unknown, but is always the same in a particular system of equations.)

One thing C++ does have in common with algebra is that the type of a variable remains the same over its lifetime. Thus, we can talk about the type of a variable/box, or the type of the value within it interchangeably, they are guaranteed to be the same.

Speaking of the “lifetime” of a variable, how long does a variable live? (Note, as we’ll see later, this isn’t necessarily the same question as how long the value in it lives!) In this program

int main() {
  // ...some stuff
  int a;
  // ...more stuff
  return 0;
}

it is an error to try to access a in the “some stuff” section. We call the region of the program’s source code where the name of a variable can be used the scope of that variable. There’s a very basic rule that governs the scope of variables:

The scope of a variable extends from its declaration to the end of the current block.

Blocks

What’s a block? A block is a kind of “compound statement” consisting of zero-or-more statements enclosed in curly-braces. This means that the “body” of main is in fact a block, but we can use a block anywhere where a single statement is expected:

int main() {
  cout << "Hello";
  {
    cout << "World";
  }
  cout << "Goodbye";
  return 0;
}

Blocks don’t change the order in which things happen; we still move from one statement to the next, moving inside and out of blocks as necessary. Note that the empty block is a thing, and it’s sometimes useful:

{ }

You can think of the empty block as a statement that doesn’t do anything. Sometimes C++ will tell you you must put a statement somewhere; if you don’t want anything to happen, you can use an empty block.

Variable scoping and blocks

This means that variables are actually local to the block that contains their declaration. That is, a variable’s box is accessible from the point in the program where we say int a (or whatever) to the closing curly brace that ends the block it’s in. For example, in this program:

int main() {
  int a = 1;
  {
    int b = 2;
    ...
  }
  int b = 3;
  ...
  return 0;
}

Normally it’s an error to have two variables with the same name in the same scope, but here the two b‘s are in different scopes. Let’s see how this works out (annotate text with scope starting and endings).

In particular, the “inner b”’s scope ends before the outer b comes into existence, so there is no conflict.

There’s one more subtle feature of variables and scopes. If we do this, it’s an error (only one declaration of a name per-scope allowed):

{
  int a = 5;
  ...
  int a = 2;
  ...
}

but if we wrap the second declaration in a block, there’s no error!

{
  int a = 5;
  ...
  {
    int a = 2;
  }
  ... 
}

What’s going on here? Let’s add some couts and see what things look like inside:

#include <iostream>
using namespace std;

int main() {
  int a = 5;

  cout << "a outside = " << a << endl;
  {
    int a = 2;
    cout << "a inside = " << a << endl;
  }
  cout << "a outside = " << a << endl;

  return 0;
}

What’s happening is that the inner declaration of a “shadows” the outer one. This means that the outer a is temporarily hidden, a new box named a is created, and within the inner block, a refers to this new box. When we reach the end of this block, the new a goes away and the old a (which never really went anywhere) is available again.

It’s considered very bad form to intentionally shadow names like this, because it’s confusing. You can’t tell at a glance what a particular name is referring to.

The important thing to remember is that variables “die” at the end of their enclosing blocks. Right now we’re not using blocks, but later on we will, so you’ll need to remember that if you create a variable within a block, you won’t be able to use it outside the block.

When we declare a variable inside a function definition or block we call it a local variable and say that it has local scope. Remember that local scopes (except for nested blocks) are independent of each other

There is another kind of scope that lets us create variables which are shared by all definitions, whose scope in fact extends over the entire file. These are global variables.

To create a global variable, simply put its declaration outside of main, before it:

int x = 1;

int main() {
  cout << x; // x is accessible here

  return 0;
}

In fact, for a global variable, there is a single box labeled x for the entire program. (If you create an x within a function, it will shadow the global x, just as it does with scopes.) Global variables can be used not just by main but by other definitions that we create.

What is the scope of a global variable? It extends from the point of its declaration to the end of the file. (Later on, when we look at multi-file programs, we’ll see that this means that global variables from one file are not normally accessible from other files.) Note that this means, just like local variables, that you must declare a global variable before you use it anywhere.

What is the lifetime of a global variable’s value? It is the entire duration of your program: global variables come into existence even before main starts, and don’t go away until after main returns. This is necessary because otherwise it would not be safe for main to use global variables. E.g., imagine if we had

int version = 113;

int main() {
  cout << "My Program (c) " << version << endl;
}

If version is not properly setup before main starts, it would not be safe for main to print it out!

To summarize, we now have two kinds of scopes:

More types of boxes

Just to reiterate, what does this do:

int weight = 2;

(Creates a box called weight, that can hold an int, and sets the value in it to 2.)

The only two types we’ve seen so far are int (for integers) and bool (for true/false). Here is the next type: float is the type of “floating point values”, I.e., numbers that have a decimal point in them. When we write 1 in our program, C++ knows that it’s an int, but if we write 1.0 then C++ knows that it’s a float.

If we want to our BMI program to not round things off, all we need to do is change the boxes to floats. Note that we don’t change the int before main, because that determines what kind of output main will give.

(Demonstrate)

Note that we can also enter values with a decimal point now, when prompted for height/weight. cin now knows that we are expecting a floating point value, will accept “1.0” as an input (if we were extracting into an int, it would read 1 and then stop.)

Question: what happens if we leave height/weight as int, and just change bmi to being a float? Is that sufficient to make the result into a decimal value?

(Demonstrate)

No, in fact, it is not. This is because the calculation for BMI is still composed entirely of ints, so C++ will still round it off. It doesn’t know where the result of that calculation will be stored until after it has completed it and rounded off. If we want to make the result actually be a float, we have to ensure that the intermediate results are floats also.

One easy way to make this happen is to write

703.0 * weight / (height * height)

Just adding a decimal point to the constant causes everything else to be upgraded to a float.

This is the result of something called “promotion”. C++ has a hierarchy of number types. For now, all you need to know is that if you mix floats and ints, the result will be a float. For example, 3 / 2 is totally int and will be rounded off; 3 / 2.0 has one float in it, so both sides will be implicitly “promoted”, as if you had written 3.0 / 2.0.

int is promoted to float because float is “bigger”. int can only hold whole numbers, but float can hold both whole numbers and fractions. So we can always store an int-value in a float box without losing anything, but the opposite is not necessarily true.

Some weirdness with floats

The name “floating point” comes from how floats are stored in the computer: as a certain number of significant digits (around 7 errors start creeping in), and then, separately, the position of the decimal point as an exponent. This means that we can represent very large, or very small numbers, just by moving the decimal pointer very far to the right or the left (demonstrate), but we can’t really store more than 7-8 actualy digits before information starts to get lost.

E.g., try doing this:

float f = 1.234567891233455;
cout << f;

you’ll find that it’s been “shortened” to 1.23457; the remaining digits were lost. C++ will always chop off the least significant digits, because they represent the part of the number closest to 0. Notice that this happens when we try to add a very small and very large number together:

float f = 123456.0 + 0.0000123456;
cout << f;

The output is 123456; the smaller digits were discarded completely.

There are other quirks to dealing with floats, but they are beyond this scope (ha!) of this class. If you’re going to use them in some kind of scientific application, you’ll want to be aware of all their weird quirks and limitations.

Expressions vs. Statements

We’ve seen statements and expressions, so let’s take a look at how things work when they are combined:

cout << 1 + 2;

1 + 2 is an expression, and the whole thing is a statement, but what are the steps within the statement that will be taken?

  1. Evaluate the expression 1 + 2 to get 3.

  2. Execute the statement cout << 3.

It’s important to distinguish expressions from statements, because expressions actually come up a lot, probably even more than statements!

What kinds of expressions are there? Here are the ones we’ve seen so far:

The interesting thing about expressions is how we can combine them to build larger expressions. With statements, the only ways we have of putting them together is to string them together in a straight line, or put some inside a block. Either way, they still get executed from beginning to end. With an expression, we can build it out of subexpressions. This means that in something like _ + _, both the left and right sides can be other expressions. We can build quite complex expressions by starting with simple expressions (literals, variables) and then joining them with operators into larger and larger expressions.

The only reason I mention this is so that you don’t think the expressions I show you are somehow special. Pretty much any place where I use a variable name, or a literal value, you could put in an expression of the same type.

Expressions have types, just like variables. The type of an expression generally depends on the operation, and on the inputs to it. For example, we’ve seen that the type of 1 / 2 is int, but the type of 1.0 / 2 is float. The conversion expressions can be used to manually convert between types, and some conversions are done in the background, implicitly (as in float a = 1;) But sometimes the types don’t make sense, and that’s when you’ll get an error.

Strings and characters

We mentioned that "..." is called a string literal and that C++ just stores it’s contents verbatim (hence the name, literal). There’s not much we can do with string literals, besides print them; to do more, we need the string type. This type is not built-in, we have to bring it in via

#include <string>

This lets us do

string name = "Your Name Here";

i.e., string is another kind of variable (box) we can create. What can we do with strings? (Note that I don’t expect you to memorize all these, though you should write them down. I’m just trying to show you all the interesting things you can do with strings.) (All of these should be drawn out, using the example name = "Andy Clifton").

For a complete list of all the things strings can do, see http://www.cplusplus.com/reference/string/string/.

Although I said that string literals treat everything between the quotation marks literally, that’s not strictly true. A number of escapes are allowed to encode special characters. For example, if you want to write a string that has double-quotes in it, you’d do

string line = "The word is \"potato\"!";

Within a string literal, \" will not end the literal, but rather will insert a single double-quote. The backslash (NOT a forward slash) 'escapes’ the double-quote. Other escapes include:

One final note: the empty string "" is a thing! It has length 0, and contains no characters.

Converting between strings and ints

Suppose we have a string that consists of a number, e.g., "12". How can we read this into an int? We’ll see a more general way later, but for now, we can use stoi:

int i = stoi("23"); // i = 23

Expression: stoi(s) gives the int that is represented by the numeric digits in s.

stoi is part of #include <string>.

The string given must at least start with something that looks like a number. E.g., stoi("101 dalmations") will evaluate to 101. If no number can be recognized, stoi will produce an error.

We can convert an int (or float, double, bool, etc.) to a string using to_string:

string s = to_string(12); // s == "12"

What are strings made of? Characters

The individual letters, numbers, and symbols that make up a string are called characters, and there’s a type for them, too. It’s char:

char c = 101; // c = capital A

As this shows, chars can be numbers, but they can also be symbols. A “char literal” is the symbol form, between single quotes. This is equivalent to the above:

char c = 'A';

A single symbol, written between single-quotes, is a char literal, whose value is the ASCII value of that symbol. The semi-complete ASCII chart can be found here: https://en.wikipedia.org/wiki/ASCII#Printable_characters.

(Note that all of the above string escapes can also be used as single-character literals: \n is the character corresponding to pressing Enter. The escape \' is allowed so you can represent the single-quote character.)

If you print a char, you’ll get the symbol version, not the numeric version:

char c = 101;
cout << c;

This will print A. If you want to see the numeric code, you have to convert it to an int:

cout << (int)('A'); // Prints 101

Similarly, since characters are numbers, you can do math on them, provided you remember to convert them back into characters:

cout << (char)('A' + 1); // Prints B

You can compare characters using ==, <, etc. although for < and friends, the order is a little weird:

'A' < 'Z' < 'a' < 'z'
'0' < '9'

That is, uppercase and lowercase letters are “in order”, but all uppercase characters are “less than” all lowercase letters. Similarly, numeric characters compare “in order”.

If we have a string, we can use at() to access the individual characters at particular positions:

string name = "Andy Clifton";
cout << name.at(5); // Prints 'C'

Expression: s.at(n) gives the char in the string s at position (int) n. While s.at is an expression, it can also be used on the left side of an assignment statement:

We can even use this to replace a single character:

name.at(5) = 'B'; // my cousin, Andy Blifton

How does this work, doesn’t the thing on the left of an = have to be a variable (i.e., a box in which to store something)? Well yes, but at() actually looks inside the string and gives us the char-sized box that’s holding the particular character we specify.

Now, suppose I had a string whose contents I did not know, and I want to find the word “peach” in it and replace the lower-case ‘p’ with an upper-case one. How would I do this?

string text = ...;
???

Obviously, we’re going to have to use text.at(...):

string text = ...;
text.at(...) = 'P';

what should we give to at? (Use find to find the string we are looking for).

The dot operator

The dot before .at is actually another operator:

name.substr(0,5);
name.length();
name.erase(0,3).append("Potato");

The dot operator is special in that its left operand must be a variable (box), and its operand must name a member of that box. String-type boxes have members substr, length, append, etc. The dot operator associates to the left, so

name.erase(0,3).append("Potato"); // means
(name.erase(0,3)).append("Potato");

Note that this works because name.erase() evaluates to name, which is still a variable (box).

IO for strings and characters

How can we read a single character from cin? If you do

char c;
cin >> c;

it will read a single character (not its numeric code). But note that cin’s normal behavior, where it will skip over spaces, will still apply. What if you want to read the next character, regardless of whether it’s a letter, a symbol, a space, or whatever (even Enter is a character). For this you need get:

char c;
cin.get(c);

At this point, you can press any key that will generate a character (some, like Ctrl, do not, while others, like Insert, generate more than one!) and it will be stored in c.

We can use this to construct a program which reads a single character and then prints out its code:

#include <iostream>
using namespace std;

int main() {
  cout << "Enter a character: ";

  char c;
  cin.get(c);

  cout << "The ASCII code for '" << c << "' is " << (int)(c) << endl;

  return 0;
}

There is some surprising behavior around get, however. Suppose I do this:

int i;
cin >> i;
char c;
cin.get(c);

What happens if we type something like “221B Baker St.” at the first prompt?

cin uses what’s called buffered input, which means that it reads what you type into a buffer. Subsequent accesses will continue to use the buffer, until it is empty; then it will prompt you to enter more text. So in this case, i will be set to 221 but we will not be prompted to enter a character for c! It will be set to the next character in the buffer, which is ‘B’.

If you want to throw away whatever is left in the buffer (e.g., to make sure that the get prompts the user and reads a single character) use ignore:

int i;
cin >> i;
cin.ignore(10000, '\n');
char c;
cin.get(c);

What this says to do is ignore up to 10000 characters from the buffer, but stop if we see a \n. The \n will be left over after we press Enter after typing the int, so this will remove it and then be ready to receive new input.

Another thing we can do with cin is “peek” one character ahead. We do this by reading it (with get) and then putting it back:

char c;
cin.get(c);
// Examine c...
cin.putback(c);

What if we want to read an entire line of text, all the way up to the point where the user pressed Enter, including spaces? We can do this with getline, which comes from #include <string>:

string s;
getline(cin,s); 

This will store the entire line entered by the user (not including the Enter character at the end, however) into s, where we can then use all our string manipulation tricks on it.

Note that the above about throwing away whatever is left in the buffer applies. If you do:

int i; 
string s;
cin >> i;

getline(cin, s);

then C++ will read an int, leave the \n in the buffer, and then when getline look at it it will see (and “get”) a blank line! If you want getline to wait for a full line, again, we have to ignore:

int i; 
string s;
cin >> i;
cin.ignore(10000, '\n');
getline(cin, s);