Variables and Basic Types
2.1 Primitive Built-in Types
Type: Defines what kind of data a value represents and what operations can be performed on it.
Meaning: A type determines how a value is stored and how it can be used.
1) Arithmetic Types
Arithmetic Types: Represent boolean values, characters, integers, and floating-point numbers.
void: Represents the absence of a value and is mainly used as the return type of functions that do not return a value.
Arithmetic Type Categories
| Category | Types |
|---|---|
| Boolean | bool |
| Character | char, char8_t, wchar_t, char16_t, char32_t |
| Integer | short, int, long, long long |
| Floating-point | float, double, long double |
Integral Types: Include boolean, character, and integer types.
Floating-point Types: Represent finite approximations of real-number values.
Type Size
Type Size: The amount of storage used by a type.
sizeof(char) is always 1, but the number of bits in a byte and the sizes of most other built-in types depend on the implementation.
#include <iostream>
int main()
{
std::cout << sizeof(char) << '\n';
std::cout << sizeof(int) << '\n';
std::cout << sizeof(double) << '\n';
}
Character Types
char: Basic character type.
char8_t: Represents UTF-8 code units.
wchar_t: Represents implementation-defined wide-character code units.
char16_t: Represents UTF-16 code units.
char32_t: Represents UTF-32 code units.
char c = 'A';
char8_t utf8 = u8'A';
char16_t utf16 = u'A';
char32_t utf32 = U'A';
wchar_t wide = L'A';
Integer Types
Integer Types: Represent whole-number values.
The minimum ordering of their storage capabilities is:
short <= int <= long <= long long
short s = 10;
int i = 100;
long l = 1000L;
long long ll = 10000LL;
Floating-point Types
float: Floating-point type with the lowest minimum precision.
double: Has precision at least as great as float.
long double: Has precision at least as great as double.
float f = 3.14f;
double d = 3.14;
long double ld = 3.14L;
Signed and Unsigned Types
Signed Type: Can represent negative values, positive values, and zero.
Unsigned Type: Represents only values greater than or equal to zero.
int signed_value = -10;
unsigned int unsigned_value = 10;
short, int, long, and long long are signed by default.
unsigned int u1 = 42;
unsigned u2 = 42;
Unsigned Wraparound
Unsigned integer arithmetic wraps modulo the range of the type.
unsigned int u = 0;
--u;
After the decrement, u becomes the maximum value representable by unsigned int.
An unsigned value can never be less than zero.
unsigned int u = 10;
// Wrong: always true for unsigned values
while (u >= 0)
{
--u;
}
A safer countdown checks the value before decrementing.
unsigned int u = 10;
while (u > 0)
{
--u;
std::cout << u << '\n';
}
2) Type Conversion
Type Conversion: Converts a value from one type to another.
Arithmetic to bool
Zero becomes false.
Nonzero values become true.
bool a = 0; // false
bool b = 42; // true
bool c = -1; // true
bool to Arithmetic Type
false becomes 0.
true becomes 1.
int a = false; // 0
int b = true; // 1
Floating-point to Integer
The fractional part is discarded.
double d = 3.99;
int i = d;
// i == 3
Integer to Floating-point
An integer can be converted to a floating-point value, although sufficiently large values may lose precision.
int i = 42;
double d = i;
// d == 42.0
Negative Value to Unsigned
A negative value converted to an unsigned type wraps into the unsigned range.
unsigned char c = static_cast<unsigned char>(-1);
For an 8-bit unsigned char, the resulting value is 255.
Signed and Unsigned Expressions
Expressions containing signed and unsigned values use the usual arithmetic conversions to determine a common type.
int i = -1;
unsigned int u = 1;
auto result = i + u;
Mixing signed and unsigned values should therefore be done carefully.
3) Literals
Literal: A value written directly in source code.
Integer Literals
int decimal = 20;
int octal = 024;
int hexadecimal = 0x14;
These literals represent the same numerical value using different bases.
Floating-point Literals
double a = 3.14;
double b = 3.14e2;
float c = 3.14f;
long double d = 3.14L;
Character Literals
Character literals use single quotes.
char c = 'A';
String Literals
String literals use double quotes.
const char* text = "Hello";
A null character \0 is automatically appended to the end of a string literal.
Adjacent string literals are concatenated.
const char* text =
"Hello "
"World";
This is equivalent to:
const char* text = "Hello World";
Escape Sequences
Escape Sequence: A backslash-based notation used to represent special characters.
char newline = '\n';
char tab = '\t';
std::cout << "Hello\nWorld\n";
Common escape sequences include:
| Escape | Meaning |
|---|---|
\n | New line |
\t | Tab |
\\ | Backslash |
\" | Double quote |
\' | Single quote |
\0 | Null character |
Literal Prefixes and Suffixes
| Category | Prefix or Suffix | Example |
|---|---|---|
| UTF-8 | u8 | u8"text" |
| UTF-16 | u | u"text" |
| UTF-32 | U | U"text" |
| Wide character | L | L"text" |
| Unsigned integer | u, U | 42u |
| Long integer | l, L | 42L |
| Long long | ll, LL | 42LL |
float | f, F | 3.14f |
double | none | 3.14 |
long double | l, L | 3.14L |
Boolean Literals
bool running = true;
bool finished = false;
Pointer Literal
nullptr represents a null pointer.
int* p = nullptr;
2.2 Variables
Variable: An object or reference introduced by a declaration.
Object: A region of storage that has a type and a lifetime.
1) Variable Definitions
Variable Definition: Creates a variable by specifying its type and name.
int value;
double price;
bool running;
A definition may also provide an initial value.
int value = 10;
double price = 19.95;
bool running = true;
Initialization
Initialization: Gives an object its initial value when the object is created.
int value = 10;
Assignment: Changes the value of an already existing object.
int value = 10;
value = 20;
Initialization and assignment are different operations.
List Initialization
List Initialization: Initializes an object using braces {}.
int a{10};
double b{3.14};
List initialization prevents narrowing conversions that may lose information.
double d = 3.14;
// Error: narrowing conversion
// int i{d};
Default Initialization
A variable defined without an initializer is default initialized.
int value;
A non-static local built-in variable should not be read before it is given a valid value.
int main()
{
int value;
// Do not use value here before assigning it.
value = 10;
std::cout << value;
}
Static-storage built-in objects are zero-initialized when no explicit initializer is provided.
int global_value;
int main()
{
static int static_value;
// global_value == 0
// static_value == 0
}
2) Variable Declarations and Definitions
Declaration: Makes a name and its type known to the program.
Definition: Creates or fully defines the entity.
extern
extern declares a variable that is defined elsewhere.
// file1.cpp
int counter = 0;
// file2.cpp
extern int counter;
void increment()
{
++counter;
}
A variable can be declared multiple times but generally has one definition.
An extern declaration with an initializer is itself a definition.
extern int counter = 0;
3) Identifiers
Identifier: A name used to identify variables, functions, classes, and other program entities.
int count;
double average_price;
bool is_running;
Identifiers are case-sensitive.
int value = 10;
int Value = 20;
value and Value are different identifiers.
Identifiers cannot begin with a digit.
int value2;
// Invalid
// int 2value;
Reserved Identifiers
User-defined identifiers should not use names reserved by the language or implementation.
Avoid:
int __value;
int _Value;
Prefer descriptive names.
int student_count;
double average_price;
bool window_open;
4) Scope of a Name
Scope: A region of the program in which a name can be used.
Global Scope
int global_value = 10;
int main()
{
std::cout << global_value;
}
Block Scope
int main()
{
int value = 10;
{
int other = 20;
std::cout << value;
std::cout << other;
}
// other is not visible here
}
Nested Scope and Name Hiding
An inner declaration can hide an outer declaration with the same name.
int value = 10;
int main()
{
int value = 20;
std::cout << value; // 20
std::cout << ::value; // 10
}
::value accesses the name from global scope.
2.3 Compound Types
Compound Type: A type defined in terms of another type.
Important compound types include references and pointers.
1) References
Reference: An alternative name for an existing object.
An lvalue reference uses &.
int value = 10;
int& ref = value;
A reference must be initialized.
int value = 10;
int& ref = value;
The reference becomes an alias for the object.
int value = 10;
int& ref = value;
ref = 20;
// value == 20
Assigning through a reference changes the original object.
A reference remains bound to the same object after initialization.
int a = 10;
int b = 20;
int& ref = a;
ref = b;
This assigns b's value to a; it does not make ref refer to b.
2) Pointers
Pointer: A value that can store the address of an object.
A pointer uses * in its declaration.
int* p = nullptr;
Address-of Operator
& obtains the address of an object.
int value = 10;
int* p = &value;
Dereference Operator
* accesses the object pointed to by a pointer.
int value = 10;
int* p = &value;
std::cout << *p;
A dereferenced pointer can also modify the object.
int value = 10;
int* p = &value;
*p = 20;
// value == 20
Null Pointer
A pointer that does not refer to an object should normally be initialized with nullptr.
int* p = nullptr;
A pointer can be tested in a condition.
if (p)
{
std::cout << *p;
}
A null pointer evaluates to false.
A non-null pointer evaluates to true.
Pointer Assignment
Pointer assignment changes the address stored in the pointer.
int a = 10;
int b = 20;
int* p = &a;
p = &b;
Dereferenced assignment changes the pointed-to object.
int value = 10;
int* p = &value;
*p = 30;
void*
void* can hold a converted pointer to an object without knowing the object's specific type.
int value = 10;
void* p = &value;
The pointed-to value cannot be accessed directly through void*.
// Invalid
// std::cout << *p;
3) Understanding Compound Type Declarations
* and & apply to individual declarators.
int* p1;
int* p2;
int value;
In the following declaration:
int* p1, p2;
only p1 is a pointer.
p2 is an int.
For clarity, separate declarations are often easier to read.
int* p1;
int p2;
Pointer to Pointer
A pointer can point to another pointer.
int value = 10;
int* p = &value;
int** pp = &p;
Two dereferences access the final object.
**pp = 20;
// value == 20
Reference to Pointer
A reference can alias a pointer object.
int value = 10;
int* p = &value;
int*& ref = p;
A pointer to a reference cannot be defined because a reference is not an object.
2.4 const Qualifier
const: Prevents modification of an object through a const-qualified access path.
const int value = 10;
A const object must be initialized.
const int max_count = 100;
Modification through the const object is not allowed.
const int value = 10;
// Error
// value = 20;
A const object can be used to initialize a non-const object.
const int a = 10;
int b = a;
b = 20;
Changing b does not change a.
1) References to const
A reference to const cannot modify the object through the reference.
const int value = 10;
const int& ref = value;
// Error
// ref = 20;
A non-const reference cannot normally bind to a const object.
const int value = 10;
// Error
// int& ref = value;
A reference to const may bind to a non-const object.
int value = 10;
const int& ref = value;
The underlying object may still be modified directly.
int value = 10;
const int& ref = value;
value = 20;
// ref now observes 20
A reference to const can also bind to a temporary value.
const int& ref = 42;
2) Pointers and const
Pointer to const
A pointer to const cannot modify the pointed-to object through the pointer.
const int value = 10;
const int* p = &value;
// Error
// *p = 20;
A pointer to const may also point to a non-const object.
int value = 10;
const int* p = &value;
The original object can still be modified directly.
value = 20;
Const Pointer
A const pointer cannot change the address it stores.
int value = 10;
int* const p = &value;
The pointer cannot be redirected.
int other = 20;
// Error
// p = &other;
But the pointed-to object can still be modified.
*p = 30;
Const Pointer to const
Both the pointer and the pointed-to object are treated as const through the pointer.
const int value = 10;
const int* const p = &value;
3) Top-Level const
Top-Level const: The object itself is const.
const int value = 10;
For a pointer:
int value = 10;
int* const p = &value;
p itself is const.
Low-Level const
Low-Level const: A compound type refers or points to a const-qualified object.
const int value = 10;
const int* p = &value;
Here the pointed-to int is const through p.
Both Levels
const int value = 10;
const int* const p = &value;
p cannot change, and *p cannot be modified through p.
Copying and const
Top-level const is ignored when copying a value.
const int a = 10;
int b = a;
Low-level const must normally be preserved.
const int value = 10;
const int* p = &value;
// Error
// int* q = p;
4) constexpr and Constant Expressions
Constant Expression: An expression that satisfies the requirements for compile-time evaluation.
constexpr int size = 10;
constexpr
A constexpr variable must be initialized using a constant expression.
constexpr int width = 10;
constexpr int height = 20;
constexpr int area = width * height;
A constexpr variable is implicitly const.
constexpr int value = 10;
// Error
// value = 20;
Runtime const vs constexpr
A const value may be initialized at run time.
int get_value();
const int value = get_value();
A constexpr value requires constant-expression initialization.
constexpr int value = 42;
constexpr Function
A constexpr function may be evaluated at compile time when used with suitable arguments.
constexpr int square(int x)
{
return x * x;
}
constexpr int result = square(5);
The same function can also execute at run time.
int value;
std::cin >> value;
int result = square(value);
constexpr Pointer
For a pointer variable, constexpr makes the pointer itself constant.
constexpr int* p = nullptr;
2.5 Dealing with Types
Complex types can become difficult to write and understand directly.
C++ provides type aliases, auto, and decltype to simplify type handling.
1) Type Aliases
Type Alias: A name that represents another type.
typedef
typedef unsigned long ulong;
ulong value = 10;
using
Modern C++ commonly uses alias declarations.
using ulong = unsigned long;
ulong value = 10;
Pointer Type Alias
using IntPtr = int*;
IntPtr p = nullptr;
An alias represents a complete type.
using IntPtr = int*;
int value = 10;
IntPtr const p = &value;
p is a const pointer to int.
It is equivalent to:
int* const p = &value;
2) The auto Type Specifier
auto: Lets the compiler deduce a variable's type from its initializer.
auto i = 10;
auto d = 3.14;
auto c = 'A';
The deduced types are approximately:
int i = 10;
double d = 3.14;
char c = 'A';
An auto variable requires an initializer.
// Error
// auto value;
auto and Top-Level const
auto normally removes top-level const.
const int value = 10;
auto copy = value;
copy is an ordinary int.
copy = 20;
const auto
Top-level const can be added explicitly.
const int value = 10;
const auto copy = value;
auto&
A reference can be deduced using auto&.
int value = 10;
auto& ref = value;
ref = 20;
const auto&
A reference to const can be written as:
const auto& ref = value;
It can also bind to a temporary.
const auto& ref = 42;
Pointer Deduction
const int value = 10;
const int* p = &value;
auto q = p;
q preserves the low-level const and therefore has type:
const int*
3) The decltype Type Specifier
decltype: Determines a type from an expression without evaluating that expression.
int value = 10;
decltype(value) other = 20;
other has type int.
Type Preservation
Unlike ordinary auto deduction, decltype preserves the declared type in its special variable form.
const int value = 10;
decltype(value) other = 20;
other has type:
const int
References and decltype
int value = 10;
int& ref = value;
decltype(ref) other_ref = value;
other_ref is also an int&.
Dereference Expression
Dereferencing a pointer produces an lvalue.
int value = 10;
int* p = &value;
decltype(*p) ref = value;
decltype(*p) is:
int&
Parenthesized Variables
There is an important difference between:
decltype(value)
and:
decltype((value))
For an ordinary variable:
int value = 10;
decltype(value) a = 20;
decltype((value)) b = value;
The types are:
// decltype(value)
int
// decltype((value))
int&
The extra parentheses cause the general expression rules to be used.
2.6 Defining Our Own Data Structures
Data Structure: Groups related data together.
Class: A user-defined type containing data and related behavior.
struct can be used to define a class type.
1) Defining the Sales_data Type
#include <string>
struct Sales_data
{
std::string book_no;
unsigned units_sold = 0;
double revenue = 0.0;
};
A class definition ends with a semicolon.
Data Members
Data Member: A variable contained inside a class object.
struct Sales_data
{
std::string book_no;
unsigned units_sold = 0;
double revenue = 0.0;
};
Each Sales_data object contains its own copies of these non-static data members.
Sales_data item1;
Sales_data item2;
item1 and item2 contain separate values.
In-class Initializers
Members can receive default values directly in the class definition.
struct Sales_data
{
std::string book_no;
unsigned units_sold = 0;
double revenue = 0.0;
};
2) Using the Sales_data Class
Objects are created using the class name.
Sales_data item;
Members are accessed using the dot operator ..
item.book_no = "978-0-0000";
item.units_sold = 5;
item.revenue = 100.0;
Reading Data
#include <iostream>
#include <string>
struct Sales_data
{
std::string book_no;
unsigned units_sold = 0;
double revenue = 0.0;
};
int main()
{
Sales_data item;
double price = 0.0;
std::cin
>> item.book_no
>> item.units_sold
>> price;
item.revenue =
item.units_sold * price;
}
Adding Two Sales_data Objects
Two transactions should refer to the same ISBN before their values are combined.
if (item1.book_no == item2.book_no)
{
unsigned total_count =
item1.units_sold + item2.units_sold;
double total_revenue =
item1.revenue + item2.revenue;
}
Calculating Average Price
double average_price = 0.0;
if (total_count != 0)
{
average_price =
total_revenue / total_count;
}
Complete Example
#include <iostream>
#include <string>
struct Sales_data
{
std::string book_no;
unsigned units_sold = 0;
double revenue = 0.0;
};
int main()
{
Sales_data item1;
Sales_data item2;
double price1 = 0.0;
double price2 = 0.0;
std::cin
>> item1.book_no
>> item1.units_sold
>> price1;
std::cin
>> item2.book_no
>> item2.units_sold
>> price2;
item1.revenue =
item1.units_sold * price1;
item2.revenue =
item2.units_sold * price2;
if (item1.book_no == item2.book_no)
{
unsigned total_count =
item1.units_sold + item2.units_sold;
double total_revenue =
item1.revenue + item2.revenue;
double average_price =
total_count != 0
? total_revenue / total_count
: 0.0;
std::cout
<< item1.book_no << ' '
<< total_count << ' '
<< total_revenue << ' '
<< average_price << '\n';
}
}
3) Writing Our Own Header Files
Header File: Stores declarations and definitions that need to be shared between source files.
A class definition is commonly placed in a header.
// Sales_data.hpp
#ifndef SALES_DATA_HPP
#define SALES_DATA_HPP
#include <string>
struct Sales_data
{
std::string book_no;
unsigned units_sold = 0;
double revenue = 0.0;
};
#endif
A source file can include the header.
#include "Sales_data.hpp"
int main()
{
Sales_data item;
}
#include
#include inserts the contents of another file during preprocessing.
#include <iostream>
#include <string>
#include "Sales_data.hpp"
Header Guards
Header Guard: Prevents the same header contents from being processed more than once in one translation unit.
#ifndef SALES_DATA_HPP
#define SALES_DATA_HPP
struct Sales_data
{
// ...
};
#endif
The basic pattern is:
#ifndef UNIQUE_NAME
#define UNIQUE_NAME
// header contents
#endif
The guard name should be unique.
Essential Study Checklist
Remember these concepts before moving on:
- Built-in types determine how values are represented and used.
- Signed and unsigned integer behavior is different.
- Implicit type conversions can change values or lose information.
- Initialization and assignment are different operations.
- List initialization prevents narrowing conversions.
- A declaration introduces a name; a definition creates or fully defines an entity.
- Scope determines where a name can be used.
- A reference is an alias for another object.
- A pointer stores an address and is dereferenced with
*. nullptrrepresents a null pointer.const int*means pointer to const.int* constmeans const pointer.- Top-level and low-level
constdescribe different const qualifications. constexpris used for values intended to participate in constant evaluation.usingcreates type aliases.autodeduces a type from an initializer.decltypedetermines a type from an expression.structdefines a user-defined class type.- Data members are accessed with the dot operator
.. - Header guards prevent repeated header processing.