Types, Variables, and Constants
Variables are named placeholders for data stored in your computer’s RAM (Random Access Memory). C++ is a strictly typed programming language, meaning each piece of data in a C++ program has an associated data type.
Table of Contents
- Variables Identifiers
- Identifier Naming Conventions
- Variable Definition and Assignment
- Variable Scope
- Data Types
- Booleans
- Characters
- Escape Sequences
- Strings
- Whole Numbers
- Unsigned Whole Numbers
- Integer Literals
- Floating Point Numbers
- Float Literals
- Void
- Auto
- Constants
- Reference Variables
- Type Conversion
- Sizes Can Be Different
- Further Reading
Variables Identifiers
The names we give our variables in C++ are called identifiers.
- Identifiers are composed of letters, numbers, underscores, and most other Unicode characters.
- The first character of an identifier must not be a number.
- Identifiers are case-sensitive.
Identifier Naming Conventions
Although the C and C++ standard libraries use snake_case
as a naming convention, the openFrameworks community tends to use camelCase
naming.
For this reason, we will use camelCase
for our variable, constant, and function identifiers and PascalCase
for our class names.
Variable Definition and Assignment
Before we can use a variable it must first be defined with a data type and an identifier in a statement structured like this: type identifier;
int userAge;
int grinsPerHour;
int numberOfApples;
An optional initial value can be provided using an =
, parenthesis, or curly braces:
int userAge = 24; // Copy Initialization
int grinsPerMinute(43); // Direct Initialization
int numberOfApples{ 6 }; // List Initialization
💡 Best Practice:
Initialize your variables on definition and use list initialization whenever possible.
Variable Scope
Scope defines where in a program a variable is accessible.
In C++, variables are scoped to the {}
blocks in which they are defined.
int existingGemCount{ 3 };
int requestedGems = askUser("How many gems did you find?");
if (requestedGems > 0) { // block starts here
int releasedGems{ gemOracle(requestedGems) }; // releasedGems is only scoped to this block.
} // block ends here
existingGemCount += releasedGems // Error: "releasedGems" variable is out of scope.
☝️ Assumes that askUser()
and gemOracle()
have been defined elsewhere to return integers.
Data Types
We cannot define a variable without first declaring its data type. C++ supports user-defined data types, but we will start by looking at the built-in primitive data types.
Data types in C++ fall into the following categories:
- Booleans –
bool
- Characters –
char
- Whole Numbers –
short
,int
andlong
- Floating Point Numbers –
float
,double
andlong double
- Void –
void
Booleans
The name for the Boolean type in C++ is bool
. Variables of type bool
can hold one of two values:
true
false
Boolean variables are typically stored using 1 byte of RAM.
🎵 Note:
C++ stores variables in “byte-sized” chunks, so bool
consumes 8 bits rather than 1.
Characters
Character data is stored using the char
type.
A char
stores a single ASCII character using 1 byte of RAM.
🎵 Note:
C++11 added the char16_t
and char32_t
data types for improved Unicode characters.
Escape Sequences
There are certain special characters that can be used in C++ that are encoded using escape sequences. These sequences all begin with a backslash character. The most common of these special characters are:
\n
- New Line\t
- Tab\'
- Single Quote\"
- Double Quote\\
- Backslash
Strings
Strings are not primitive types, but we’ll treat them as such for now.
To use variables of type std::string
you must #include
the <string>
header file.
#include <string>
#include <iostream>
int main() {
std::string simpsonsQuote{ "It was the best of times, it was the blurst of times." };
int amount{ 12 };
std::string woodChuck{ "The woodchuck chucked " + std::to_string(amount) + " pieces of wood." };
std::cout << simpsonsQuote << "\n" << woodChuck;
}
⏳ Wait For It:
We’ll learn a lot more about strings in the standard collection types module.
Whole Numbers
The most common data type for whole numbers in C++ is the int
, which stands for integer.
Although the minimum size specified in the standard for an int
is 16 bits (2 bytes), most modern architectures will use 32 bits (4 bytes) for each integer.
There are other types of data types for whole numbers. Each one supports a specific range of possible numbers:
short
-32,768 to +32,767 (2 bytes)int
-2,147,483,648 to +2,147,483,647 (Typically 4 bytes.)long
-2,147,483,648 to +2,147,483,647 (Same asint
since both typically use 4 bytes.)long long
-9,223,372,036,854,775,808 to +9,223,372,036,854,775,807 (8 bytes)
⏳ Wait For It:
Integers have undefined behaviour for values outside these ranges. See Basic Math.
Unsigned Whole Numbers
It is possible to use the whole number types in the last section as unsigned numbers.
Unsigned numbers can only store positive values, but the range of numbers is doubled.
unsigned short
0 to 65,535unsigned int
0 to 4,294,967,295unsigned long
0 to 4,294,967,295unsigned long long
0 to 18,446,744,073,709,551,615
⚡ Warning:
Using unsigned numbers is not recommends. What’s the unsigned answer to 8 - 10
?
Integer Literals
We can use different prefixes to write integers in different numeral base systems:
int d{ 990 }; // Base 10 Decimal 990. No prefix.
int o{ 0520 }; // Base 8 Octal 520. Prefixed with: 0
int h{ 0x2A }; // Base 16 Hex 2A. Prefixed with: 0x
int b{ 0b10 }; // Base 2 Binary 10. Prefixed with: 0b
We can use the l
and ll
suffixes to denotes numbers that are long
or long long
. Big numbers can be made easier to read by using single quote characters in the thousands positions:
long grainsOfSand{ 2'000'555'000l };
long long maxLongLong{ 9'223'372'036'854'775'807ll };
Floating Point Numbers
We use floating point numbers to store values that include fractional components, or for numbers that are large than the maximum integer.
There are three floating point types in C++:
float
±1.17 x 10⁻³⁸ to ±3.40 x 10³⁸ (4 bytes)double
±2.22 x 10⁻³⁰⁸ to ±1.79 x 10³⁰⁸ (8 bytes)long double
±2.22 x 10⁻³⁰⁸ to ±1.79 x 10³⁰⁸ (4 to 8 bytes)
⚡ Warning:
Depending on the architecture, long double
may be equivalent to double
.
Float Literals
C++ will automatically type a numeric literal as a double if it includes a decimal point. We can a f
or a l
suffix to write float
or long double
literals. In most cases the suffixes are not required, but they can help clarify your code.
float energyLevel{ 0.2f };
double height{ 4.3 }; // No suffix defaults to double.
long double funFactor{ 123456789l }; // Literal long double.
Really large floating point values can be defined using scientific notation.
float avogadrosNumber{ 6.02214179e23f }; // Scientific notation:6.02214179 x 10²³
⏳ Wait For It:
C++ floating point numbers are only approximations. See Basic Math.
Void
In C++ we use the type void
to mean “no type”. Variables cannot be defined with a type of void
, only functions can.
void logNumber(int number) {
// Function is void so we don't use a "return" statement.
}
⏳ Wait For It:
When we learn about pointers we’ll see that void
has another use there.
Auto
The auto
keyword allows us to define variables with their types inferred based on their initialization.
char newline() {
return '\n';
}
int main() {
auto answer{ 42 }; // Type int due to integer literal.
auto temperature{ 12.23 }; // Type double due to double literal.
auto combo{ answer + temperature }; // Type double due to expression.
auto enter{ newline() }; // Type char due to return type.
}
💡 Best Practice?
Some folks in the community follow the “AAA Style” meaning “Almost Always Auto”.
There’s also the AAAA camp: Almost Always Avoid Auto 🤣 Further Reading
Constants
You can make your code more error proof by marking variables that shouldn’t be modified as constants with const
and constexpr
.
We use constexpr
for things whose value is known at compile time:
constexpr double pi{ 3.1415926 };
pi = 3; // Error. Cannot redefine a constant.
We use const
to mark variables as immutable even if their value isn’t known at compile time;
const double temperature{ someTemperatureFunction() };
temperature = 1.2; // Error. Cannot redefine a constant.
💡 Best Practice:
An important best practice when writing C++ code is const correctness.
Reference Variables
A reference is a type of variable that acts as an alias to another variable.
References are declared using an ampersand &
after the name of the type:
#include <iostream>
int main() {
int theAnswer = 42;
int& referenceToTheAnswer{ theAnswer };
std::cout << theAnswer << "\n"; // 42
std::cout << referenceToTheAnswer << "\n"; // 42
referenceToTheAnswer = 0; // Change the value of the referenced variable.
std::cout << theAnswer << "\n"; // 0
}
References can also be declared as const
which prevents the referenced variable from being changed by way of the reference:
int theAnswer = 42;
const int& immutableAnswer{ theAnswer };
immutableAnswer = 0; // Compiler Error: Cannot assign to const-qualified reference.
⏳ Wait For It:
References allow us to avoid the performance cost associated with copying data:
- Using references when passing arrays to functions.
- Using references when passing vectors to functions.
Type Conversion
In many cases, the C++ compiler will implicitly convert one primitive type to another in a process called coercion.
float myFloat{ 1.12f };
double myDouble{ myFloat }; // The float value will be promoted to a double.
functionWithOneArgumentOfTypeDouble(myFloat); // Again the float will be promoted.
Sometimes conversions like this can lead to the possible loss of data:
float myFloat{ 1.12f };
int myInt{ myFloat }; // Error! Float cannot be narrowed to int.
If you are fine with the loss of data, you can force the issue with a static_cast<>
:
float myFloat{ 1.12f };
int myInt{ static_cast<int>myFloat }; // The float will be truncated to 1
Sizes Can Be Different
As mentioned a few times above, the size and allowed range for certain types can be architecture or implementation dependent.
Note the difference when running this program in Compiler Explore and then in Visual Studio on 64bit Windows:
#include <iostream>
int main()
{
std::cout << "bool:\t\t" << sizeof(bool) << " bytes\n";
std::cout << "char:\t\t" << sizeof(char) << " bytes\n";
std::cout << "short:\t\t" << sizeof(short) << " bytes\n";
std::cout << "int:\t\t" << sizeof(int) << " bytes\n";
std::cout << "long:\t\t" << sizeof(long) << " bytes\n";
std::cout << "long long:\t" << sizeof(long long) << " bytes\n";
std::cout << "float:\t\t" << sizeof(float) << " bytes\n";
std::cout << "double:\t\t" << sizeof(double) << " bytes\n";
std::cout << "long double:\t" << sizeof(long double) << " bytes\n";
}
/*
Windows 11 64bit Output:
bool: 1 bytes
char: 1 bytes
short: 2 bytes
int: 4 bytes
long: 4 bytes
long long: 8 bytes
float: 4 bytes
double: 8 bytes
long double: 8 bytes
*/