Types, auto and const

[types]

Work with whole numbers, decimals, characters, true/false and text; see why the type decides the answer; let the compiler pick a type; and protect values with const.

Lesson 2 of 20/8 checks passed

In Lesson 1 you met three types: std::string for text, int for whole numbers and double for decimal numbers. A variable’s type decides two things: what values it can hold, and what you can do with them. In this lesson you will use the most common types, see where they behave differently, let the compiler work out a type for you, and protect a value so it can’t be changed by mistake.

Use Compiler Explorer as in Lesson 1, with Execute the code ticked and the Output pane open. For each step, replace everything in the left pane with the new program.

Step 1: Whole numbers

An int holds a whole number: no decimal point, but it can be negative, like -12. Run this program:

#include <print>

int main() {
    int apples = 7;
    int people = 2;
    std::println("Total: {}", apples + people);
    std::println("Difference: {}", apples - people);
    std::println("Product: {}", apples * people);
    std::println("Each person gets: {}", apples / people);
    std::println("Left over: {}", apples % people);
    std::println("2 + 3 * 4 = {}", 2 + 3 * 4);
    std::println("(2 + 3) * 4 = {}", (2 + 3) * 4);
}

Most of the results are what you would expect. Three are worth a closer look:

  • Dividing two ints gives an int. 7 divided by 2 is 3.5, but an int can’t hold the .5, so it is dropped and you get 3. The result is not rounded; the fraction is simply removed.
  • % gives the remainder after dividing. 7 apples shared between 2 people is 3 each with 1 left over, so 7 % 2 is 1. It is often read as “modulo”.
  • Multiplication and division happen before addition and subtraction, as in ordinary arithmetic. So 2 + 3 * 4 is 2 + 12 = 14. Parentheses change the order: (2 + 3) * 4 is 5 × 4 = 20.

The things a program calculates with, like + and %, are called operators.

Check 2.1 Whole-number arithmetic

The output is exactly:

Total: 9
Difference: 5
Product: 14
Each person gets: 3
Left over: 1
2 + 3 * 4 = 14
(2 + 3) * 4 = 20

Step 2: Decimal numbers

A double holds a number that can have a fractional part, like 3.5 or -0.25. Run this program:

#include <print>

int main() {
    double distance = 7.0;
    double hours = 2.0;
    std::println("Speed: {}", distance / hours);
    std::println("Whole numbers only: {}", 7 / 2);
    std::println("With a decimal number: {}", 7 / 2.0);
    std::println("Rounded: {:.1f}", 10.0 / 3);
}
  • distance / hours divides two doubles, so the answer keeps its fraction: 3.5.
  • 7 / 2 is written with two whole numbers, so it is whole-number division again, giving 3.
  • 7 / 2.0 gives 3.5. Writing 2.0 instead of 2 makes it a decimal number. When a calculation mixes an int with a double, the int is turned into a double first, so the fraction is kept.
  • 10.0 / 3 is 3.3333… and goes on forever. {:.1f}, from Lesson 1, shows it with one digit after the point.

The rule to remember: if you want a fraction in the answer, at least one number in the division must be a double.

Check 2.2 Decimal division

The output is exactly:

Speed: 3.5
Whole numbers only: 3
With a decimal number: 3.5
Rounded: 3.3

Step 3: Single characters and true or false

Two more types you will use constantly:

  • char holds exactly one character, written between single quotes: 'A', '7', '?'. Double quotes are for strings, which can hold any amount of text.
  • bool holds one of two values: true or false. The name comes from George Boole, a mathematician who worked on the logic of true and false.

Run this program:

#include <print>

int main() {
    char grade = 'A';
    bool passed = true;
    bool late = false;
    std::println("Grade: {}", grade);
    std::println("Passed: {}", passed);
    std::println("Late: {}", late);
    std::println("Is 5 greater than 3? {}", 5 > 3);
    std::println("Is 5 equal to 3? {}", 5 == 3);
}

The last two lines compare two numbers. A comparison is a question whose answer is a bool:

  • 5 > 3 asks “is 5 greater than 3?” The answer is true.
  • 5 == 3 asks “is 5 equal to 3?” The answer is false.

Notice the two equals signs. A single = gives a variable a value; a double == asks whether two values are equal. Mixing them up is one of the most common mistakes in C++. The other comparisons are != (not equal), < (less than), <= (less than or equal) and >= (greater than or equal). In Lesson 3, comparisons like these will let your programs make decisions.

Check 2.3 char and bool

The output is exactly:

Grade: A
Passed: true
Late: false
Is 5 greater than 3? true
Is 5 equal to 3? false

Step 4: Working with text

Run this program:

#include <print>
#include <string>

int main() {
    std::string first = "Grace";
    std::string last = "Hopper";
    std::string full = first + " " + last;
    std::println("Full name: {}", full);
    std::println("Characters: {}", full.size());
}
  • With strings, + joins text together. first + " " + last joins three pieces: the first name, a string holding one space, and the last name. Without the space you would get GraceHopper.
  • full.size() asks the string how many characters it holds. The dot means “ask this variable to do something”; the parentheses () mean “do it now”. The space counts as a character, so Grace Hopper has 12.

The same operator can do different jobs depending on the types it works with: + adds numbers but joins strings. This is one reason the type of every variable matters.

Check 2.4 Joining strings

The output is exactly:

Full name: Grace Hopper
Characters: 12

Try putting your own first and last name in, and check the character count by counting the letters and the space yourself.

Step 5: Changing a variable’s value

Programs constantly update a variable based on its current value: adding to a score, counting items, taking away a life. Run this program:

#include <print>

int main() {
    int score = 10;
    std::println("Start: {}", score);
    score = score + 5;
    std::println("After score = score + 5: {}", score);
    score += 3;
    std::println("After score += 3: {}", score);
    score -= 4;
    std::println("After score -= 4: {}", score);
    ++score;
    std::println("After ++score: {}", score);
}
  • score = score + 5; looks odd as maths, but in C++ it is an instruction, read from right to left: work out score + 5 using the current value (10 + 5 = 15), then store the answer back in score.
  • score += 3; is a shorter way to write score = score + 3;. There is also -=, *= and /=.
  • ++score; adds exactly 1. It is used so often that it has its own operator. --score; takes 1 away.

Each line works with whatever value score has at that moment, so follow the program from top to bottom to see how it gets to 15.

Check 2.5 Updating a variable

The output is exactly:

Start: 10
After score = score + 5: 15
After score += 3: 18
After score -= 4: 14
After ++score: 15

Step 6: Let the compiler work out the type

When a variable is given a value straight away, the compiler can see what type that value is. Writing auto in place of the type asks the compiler to work it out for you. Run this program:

#include <print>

int main() {
    auto count = 7;     // 7 is a whole number, so count is an int
    auto price = 2.5;   // 2.5 has a decimal point, so price is a double
    auto ready = true;  // true is a bool, so ready is a bool
    std::println("{} {} {}", count, price, ready);
    std::println("count / 2 = {}", count / 2);
    std::println("price / 2 = {}", price / 2);
}

The comments say which type the compiler chooses, and why. The type is decided once, when the variable is created, and never changes: count is an int for the rest of the program.

That has a consequence you can see in the output. count / 2 is whole-number division, giving 3, because count is an int. price / 2 gives 1.25 because price is a double. With auto, the value you start with decides the type, so 7 and 7.0 give you different variables.

A good rule while you are learning: use auto when the type is obvious from the value, and write the type out when it helps the reader. For text, keep writing std::string; a later lesson explains why.

Check 2.6 auto picks the type

The output is exactly:

7 2.5 true
count / 2 = 3
price / 2 = 1.25

Step 7: Values that must not change

Some values should stay the same for the whole program: the number of lives a game starts with, the number of days in a week. Putting const (short for constant) in front of the type makes the compiler refuse any attempt to change the variable. Run this program:

#include <print>

int main() {
    const int max_lives = 3;
    int lives = max_lives;
    lives = lives - 1;
    std::println("Lives: {} of {}", lives, max_lives);
}

It prints Lives: 2 of 3. lives is an ordinary variable, so it can change; max_lives is const, so it can’t.

Now add this line just below lives = lives - 1;, making it line 7:

    max_lives = 5;

The compiler stops with an error:

<source>:7:15: error: assignment of read-only variable 'max_lives'
    7 |     max_lives = 5;
      |     ~~~~~~~~~~^~~

Read-only means it can be read but not changed. The compiler caught the mistake before the program could run, which is the point: a const value can’t be changed by accident anywhere in your program, however long it gets. A good habit is to make a variable const whenever you don’t need to change it.

Delete the line you added so the program compiles again.

Check 2.7 const blocks changes

With the extra line, the compiler reports assignment of read-only variable 'max_lives' on line 7. After deleting it, the program compiles and prints Lives: 2 of 3.

Step 8: Your turn

This time, write the program yourself. It should convert a temperature from Celsius to Fahrenheit. The formula is:

fahrenheit = celsius × 9 ÷ 5 + 32

Your program should:

  1. Create a const double variable called celsius holding 21.5.
  2. Create a const double variable called fahrenheit using the formula. In C++, × is written * and ÷ is written /.
  3. Print both, showing Fahrenheit with one digit after the decimal point, in the form shown in the check below.

Start from an empty left pane. Remember the #include line, main, the curly braces, and a semicolon at the end of each statement. If the compiler reports an error, read the first message and look at the line it names and the one before.

Show a solution
#include <print>

int main() {
    const double celsius = 21.5;
    const double fahrenheit = celsius * 9 / 5 + 32;
    std::println("{} C is {:.1f} F", celsius, fahrenheit);
}

The order of operations from Step 1 works in your favour here: celsius * 9 / 5 is worked out first, then 32 is added, so no parentheses are needed. Because celsius is a double, the division keeps its fraction.

Check 2.8 Temperature converter

Your program prints exactly:

21.5 C is 70.7 F

If you see 70.69999… or similar, add :.1f to the second placeholder. If you see 70.0 F or another round number, check that celsius is a double and not an int.

What you can do now

  • Choose between int, double, char, bool and std::string for a value.
  • Calculate with + - * / %, predict whole-number division, and use parentheses to control the order.
  • Compare values with == != < > <= >= and read the true or false result.
  • Join strings with + and count their characters with .size().
  • Update a variable with =, +=, -= and ++.
  • Let the compiler choose a type with auto, and protect a value with const.
  • Write a short program from a description, without copying one.

So far, every program runs each line once, from top to bottom. In Lesson 3 your programs start making decisions: using comparisons to choose which lines run.

Stuck on a check? Ask in the forum or see questions about this lesson.