02 Variables, Types, and Operators
A progressive guide to C++ variables, built-in types, initialization, scope, conversions, and the operators used to build expressions and conditions.
How C++ represents values
A C++ program manipulates values stored in objects. Every object and expression has a type, and the type determines both the values it can represent and the operations that are valid. A is a named object whose value may change during execution.
C++ uses static typing: most type-related rules are checked by the compiler before the program runs. This helps detect invalid operations early, such as attempting an operation that is not supported for a particular type.
Core ideas
A type describes the kind of value involved.
An object stores a value during its lifetime.
A gives a name to an object whose value can change.
An expression produces a value, and that value also has a type.
Takeaway: Always ask what type an expression has, what values that type can represent, and whether the intended operation is valid.
Built-in types and literals
The most common are grouped by the values they represent:
boolrepresentstrueorfalse.char,signed char, andunsigned charrepresent characters or small integer values.short,int,long,long long, and their unsigned forms represent whole numbers.float,double, andlong doublerepresent numbers with fractional parts.voidrepresents the absence of a value, commonly for functions that do not return one.std::nullptr_tis the type ofnullptr.
Signed integer types can represent negative and positive values. Unsigned integer types represent zero and nonnegative values and can represent a larger maximum value for the same storage size. The exact size of an integer type is implementation-defined, so portable code should not assume that every implementation gives int the same number of bits.
A literal is a value written directly in source code. For example, 42 is an integer literal, 42LL is a long long literal, 3.14 is a double literal, 3.14F is a float literal, true is a Boolean literal, and 'A' is a character literal. Digit separators such as 1'000'000 improve readability without changing the value.
Takeaway: Choose a type that expresses the intended kind of value, and avoid relying on implementation-specific type sizes when portability matters.
Declaring and initializing variables
A declaration gives a name and type to an object. An initializer supplies its starting value. Common forms include int score;, int lives = 3;, int level(2);, and int points{100};.
A should be initialized before it is read. A local built-in declared without an initializer does not receive a useful automatic value, so reading it before assigning a value can produce an invalid result. Initialization at the point of declaration makes the intended starting state explicit.
uses braces, as in int count{10};. It is a useful default because it provides consistent syntax and rejects many narrowing conversions. For example, int truncated{3.14}; is rejected because the fractional part would be lost. Assignment-style initialization, such as int truncated = 3.14;, is allowed and produces the integer value 3, but the loss of information is easier to overlook.
Prefer one declaration per statement when that improves clarity. For example, separate declarations for width and height make each 's type and initial value easy to see.
Takeaway: Initialize variables immediately, and prefer braces when you want narrowing conversions to be diagnosed.
Constants and compile-time values
A cannot be modified after initialization, so it must receive a value when it is declared. Values such as a maximum number of attempts or a mathematical constant are good candidates for when they should not change.
A is intended to be usable in constant expressions, meaning its value can be determined during compilation when the surrounding context permits it. Every is constant, but a is not necessarily a compile-time constant.
Use the narrowest promise that matches the design:
Use
when a value must not be changed after initialization.Use
when the value should participate in compile-time evaluation.
Trying to assign a new value to either kind of object is an error.
Takeaway: Immutability communicates intent, while additionally signals compile-time use.
and name visibility
A name's is the region in which the name can be used. Common scopes include namespace , block , and function-parameter . A name declared inside a block is generally unavailable after execution leaves that block.
An inner declaration can hide an outer declaration with the same spelling. For example, a local named value can hide a namespace- also named value. Although this is permitted, unnecessary shadowing makes code harder to read because the same spelling refers to different objects in different regions.
describes visibility, not how long an object exists. Names declared inside a function generally refer to objects with automatic storage duration, which normally exist while execution is within the relevant .
Takeaway: Keep scopes as small as practical, avoid unnecessary shadowing, and distinguish where a name is visible from how long its object exists.
Conversions and numeric precision
A changes a value from one type to another. Conversions can be implicit, performed automatically by the compiler, or explicit, requested by the programmer.
When arithmetic expressions combine different numeric types, C++ converts operands according to its conversion rules before performing the operation. For example, multiplying an int by a double converts the integer operand to a floating-point type. Converting a floating-point value to an integer discards its fractional part, so converting 9.8 to int produces 9.
Conversions can lose information or produce surprising results. Potentially risky cases include converting a signed value to an unsigned type or converting a value to a type with a smaller range. The preferred general-purpose cast for an intentional numeric conversion is . For example, <int>(measurement) makes the conversion visible, but it does not make the conversion inherently safe.
A cast should be accompanied by confidence that the destination type can represent the intended result. When the goal is fractional division, convert before dividing. If total and count are both integers, total / count performs integer division; converting total to double first allows a fractional result.
Takeaway: Make intentional conversions explicit and check whether the destination type can preserve the information you need.
Arithmetic operators
Arithmetic operators calculate numeric results:
+adds values.-subtracts values or negates one value.*multiplies values./divides values.%produces the remainder for integral operands.++increments a value by one.--decrements a value by one.
With integer operands, division discards the fractional part. Thus, 7 / 2 produces 3, while 7.0 / 2 produces 3.5. The remainder operation gives 7 % 2 as 1.
Prefix and postfix increment both increase a , but their expression values differ. In int first = ++x;, x is incremented before its value is used, so first receives the new value. In int second = y++;, the old value is used for second, and y is incremented afterward.
Do not divide by zero. Also keep signed integer calculations within the representable range of their types; signed overflow is not a reliable method for obtaining a larger value.
Takeaway: Know whether an operation uses integer or floating-point operands, and use increment forms only when their evaluation order is clear.
Comparisons and logical conditions
Comparison operators produce Boolean results:
==tests equality.!=tests inequality.<tests whether the left value is less than the right value.<=tests whether the left value is less than or equal to the right value.>tests whether the left value is greater than the right value.>=tests whether the left value is greater than or equal to the right value.
Do not confuse equality with assignment. score == 100 compares score with 100, while score = 100 stores 100 in score.
Direct equality comparisons can be unreliable for floating-point values produced by calculations. A tolerance-based comparison is often more appropriate, such as checking whether std::abs(a - b) is less than a small chosen tolerance.
Logical operators combine or invert Boolean conditions:
&&means logical AND.||means logical OR.!means logical NOT.
controls whether the right side of a logical expression is evaluated. In A && B, B is evaluated only if A is true. In A || B, B is evaluated only if A is false. This can prevent invalid operations, such as dereferencing a pointer after first checking that it is not nullptr.
Takeaway: Comparisons create conditions, logical operators combine them, and short-circuiting can make those conditions safer.
Assignment and updates
The assignment operator = stores a value in an already-existing modifiable object. Initialization creates an object's initial state, while assignment changes the state afterward. For example, int count = 0; initializes count, and count = 5; assigns a new value to it.
Compound-assignment operators combine an operation with assignment:
score += 5is equivalent in intent to adding5toscoreand storing the result.score -= 2subtracts and stores.score *= 3multiplies and stores.score /= 2divides and stores.score %= 4computes a remainder and stores.
Assignments associate from right to left, so a = b = c = 0 is grouped as a = (b = (c = 0)). Although assignment expressions have values and can be chained, separate statements are often clearer.
The left operand must be modifiable. A object cannot receive a new assigned value.
Takeaway: Distinguish initialization from later assignment, and use compound assignment when it makes the update easier to read.
Precedence and putting ideas together
determines how an expression is grouped, while associativity determines how operators with equal precedence are grouped. Multiplication and division are grouped before addition and subtraction. Comparisons are grouped before logical AND and logical OR, and assignment has relatively low precedence.
For example, 2 + 3 * 4 is grouped so that multiplication happens first, producing 14. Parentheses change the grouping: (2 + 3) * 4 produces 20.
Precedence determines grouping, not necessarily the order in which every subexpression is evaluated. Avoid complicated expressions that modify the same multiple times. Use parentheses when they communicate intent, especially when arithmetic, comparisons, and logical operators appear together.
A small integrated pattern is to store input values, compare each value with a requirement, and combine the resulting Boolean values. A score can be checked against a passing threshold, completed work can be checked against a minimum, and && can require both conditions before selecting a passing outcome.
Takeaway: Learn the basic precedence rules, but prefer explicit parentheses and simple expressions when readability or evaluation behavior could be misunderstood.