C++ from the ground up

Values, types and const

Naming values in C++: the everyday types, what whole-number division does, why a const is a promise you make to the compiler, and what auto is for.

Part 2 of 12

Beginner15 min readBack to Courses

Every value has a type

C++ makes you say what a value is before you name it: int for a whole number, double for one with a fraction, bool for true or false, char for a single character, and std::string for text. The type is not paperwork. It decides how much space the value takes, what arithmetic means, and whether the compiler can catch a mistake.

A statement that names a value is a declaration, and it ends in a semicolon. You may give a value at the same time, and you should: a number you never set holds whatever was in that memory before, which is a famously confusing thing to debug.

Whole numbers and fractions, two worlds

Integer arithmetic stays whole. 7 / 2 is 3, and the leftover is thrown away rather than rounded. That surprises everyone once, and then it becomes the reason you write (double) — a cast, which asks the compiler to treat one value as another type so the division happens in the fractional world.

Mixing the two types is allowed and the integer quietly becomes a fraction, which is why pages / 4 and pages / 4.0 are different programs. Writing the cast makes the change visible to whoever reads it next.

const, and letting the compiler work it out

Put const in front of a declaration and the value can never be assigned again. That is worth typing even when you think you are sure, because it turns a slip into a compile error at the line where it happened, rather than a wrong number three functions later.

auto asks the compiler to take the type from the value you give: auto price = 12.5; makes a double. It saves little on a number and a lot on the long iterator and lambda types in later chapters. Use it when the type is obvious from the right side, and write the type out when it is not.

A string literal is not a std::string

Text in double quotes — "C++ Primer" — is not a std::string. It is an array of characters stored with the program, and it converts to a std::string when one is asked for. That is why std::string title = "C++ Primer"; works, and why "a" + "b" does not: adding two pointers is not joining text, and the compiler refuses it.

The conversion is one-way. A std::string knows its own length and can grow; a literal does not. When a literal starts a concatenation, make the first operand a string — std::string("a") + "b" — and the rest follows.

Where a name lives

A name is visible from the line that declares it to the closing brace of its block. Declare a value inside an if and it is gone when the branch ends; declare it at the top of main and the whole function can use it. Two blocks can each have their own count without interfering, and an inner one can hide an outer one, which compiles and is a reliable source of confusion.

That is the argument for declaring a value where it is first needed rather than all at the top. The scope then matches the use, the name cannot be read before it holds a sensible value, and a reader does not have to hold six names in mind while looking for the one that matters.

A page of types

#include <iostream>
#include <string>

int main() {
    int pages = 320;
    double price = 12.5;
    bool read = false;
    std::string title = "C++ Primer";
    char initial = 'C';

    const int weeks = 4;

    std::cout << title << ": " << pages << " pages\n";
    std::cout << "Price " << price << '\n';
    std::cout << "Initial " << initial << '\n';
    std::cout << "Read? " << read << '\n';

    std::cout << "Per week: ";
    std::cout << pages / (double)weeks << '\n';
    return 0;
}

A small book record

Run it and match each line of output to the declaration behind it. Then add a fourth input, change weeks to 5 and watch the pages-a-week line change, and try printing pages / weeks without the cast.

Output
Click "Run" to execute your code

A bool printed with cout comes out as 0 or 1, not true or false. The stream has no idea what the number means; telling it is a job for an if statement or a format flag.

What the type decides

The type fixes three things at once: how many bytes the value occupies, which operations are legal, and what those operations mean. int division drops the remainder, double division keeps it, and char arithmetic works on character codes. The compiler chooses the machine instruction from the type, so the type is part of the program rather than a note about it.

It also sets the range. A 32-bit int holds about two billion in each direction, and going past that is undefined behaviour for a signed integer rather than a reliable circle. A double trades exactness for range: most decimal fractions cannot be stored exactly, which is why money is often counted in whole cents.

Three habits prevent most type bugs: initialise every value (int total = 0;), never assume a double holds an exact decimal, and avoid mixing signed and unsigned values in one comparison. An unsigned subtraction that would go negative wraps to a huge number instead, and the loop that depended on it may never end.

Try these next

  • Declare a double price and an int count, compute a total, and print it with two decimals.
  • Store the same number in an int and a double, then print both and note which one kept the fraction.
  • Make a const double rate and try to assign to it; read the compiler error and put the line back.

A coffee budget

Run it and check the arithmetic by hand. Then change the price and the number of cups, and add a line that prints the total cost of six cups.

Output
Click "Run" to execute your code

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

What is 7 / 2 when both values are int?