본문으로 건너뛰기

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

CategoryTypes
Booleanbool
Characterchar, char8_t, wchar_t, char16_t, char32_t
Integershort, int, long, long long
Floating-pointfloat, 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:

EscapeMeaning
\nNew line
\tTab
\\Backslash
\"Double quote
\'Single quote
\0Null character

Literal Prefixes and Suffixes

CategoryPrefix or SuffixExample
UTF-8u8u8"text"
UTF-16uu"text"
UTF-32UU"text"
Wide characterLL"text"
Unsigned integeru, U42u
Long integerl, L42L
Long longll, LL42LL
floatf, F3.14f
doublenone3.14
long doublel, L3.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:

  1. Built-in types determine how values are represented and used.
  2. Signed and unsigned integer behavior is different.
  3. Implicit type conversions can change values or lose information.
  4. Initialization and assignment are different operations.
  5. List initialization prevents narrowing conversions.
  6. A declaration introduces a name; a definition creates or fully defines an entity.
  7. Scope determines where a name can be used.
  8. A reference is an alias for another object.
  9. A pointer stores an address and is dereferenced with *.
  10. nullptr represents a null pointer.
  11. const int* means pointer to const.
  12. int* const means const pointer.
  13. Top-level and low-level const describe different const qualifications.
  14. constexpr is used for values intended to participate in constant evaluation.
  15. using creates type aliases.
  16. auto deduces a type from an initializer.
  17. decltype determines a type from an expression.
  18. struct defines a user-defined class type.
  19. Data members are accessed with the dot operator ..
  20. Header guards prevent repeated header processing.