Command Palette

Search for a command to run...

MTH 4300

Lecture 2

Types in C++

Types

In Python, types are inferred from the value or are hinted.

For example, this was how we could initialize variable with and without types.

Python variable initialization
name = "morty"
another_name: str = "rick"

Modern C++ actually works in a similar manner! Except we don't consider typing a variable a hint. Providing the type is actually the default behavior

C++ variable initialization
#include <string>

auto name = "morty"
std::string another_name = "rick";

Note that the auto keyword allows us to infer the type from the right-hand value. These days, it's considered a best practice to use the auto keyword especially if you're initializing the variable.

Declaration vs initialization

Something that wasn't prevalent in Python is the idea of declarations. In C++, you're allowed to declare a variable without assigning it a value.

Declaration vs initialization
int declared_variable;
int initialized_variable = 10;

Declarations are a way to reserve space for a variable without needing it to have a value right away.

The different ways we learn how to reserve or request space for data in C++ is part of the memory control that we're exposed to with the language. The distinction will become a lot more obvious in the future once we demystify the distinction between stack and heap allocations

Data types in C++

Numbers

When we were in Python, we had two types for numbers: int and float. Complexities around overflows and size of the number were abstracted away into something that just works.

C++ uncovers this layer of abstraction for us by forcing us to choose the type for our number.

int

You would declare your variable to be an int if you wanted to store whole numbers. However, like I mentioned before, our choice of type is driven by how large of a number we expect to store in this variable. When you choose to use an int, you are guaranteed that the int is at least 16 bits wide.

Different hardware and compilers can choose a larger size as long as it meets the minimum. You'll see that in practice,

Platformsizeof(int)
x86-64, ARM644 bytes (32 bits)
Old 16-bit embedded systems2 bytes (16 bits)
Some historical systems8 bytes (64 bits)

In most cases, this means that int will have a size of 32 bits. So the range of values that fit within an int fall between [231,2311][-2^{31}, 2^{31} - 1]

We also have something called an unsigned int which has the same size as int, but does not allow for negative numbers. This means that the range of values it falls within actually doubles on the positive side. Therefore, the range of values for an unsigned int within a 32-bit system is [0,2321][0, 2^{32} - 1]

Examples
int a, b, c;

int signed_integer = 10;
unsigned int index = 0;

long long / long long int

You would use long long if you wanted to store whole numbers that are at least 64 bits wide. Because we know that it's at least 64 bits wide, then the range of values should fall from [263,2631][-2^{63}, 2^{63} - 1]

There is also a long type, but because of its cross-platform behavior, it's recommended to use long long if we want to guarantee at least 64 bits.

  • By cross-platform behavior, I meant to say that Windows will have a different size for long depending on whether you're using a 32-bit or 64-bit system. MacOS and Linux both treat long as something that's at least 64 bits wide.
Examples
// A common thing to do is create a type alias for long long
typedef long long ll;

ll a_number = 100000;
long long result = 0;

float / double

If you need real numbers, then float and double are the types you'll need.

The difference between the two types is the amount of precision you have with the decimal places.

  • float has about ~6-7 significant decimal digits
  • double has about ~15-16 significant decimal digits

In most cases, you'll want to use double unless memory or performance is a hard constraint.

Examples
float a = 1.234567;
double b = 1.234567891;

Booleans

For booleans, there are two types of values: true and false.

Note that these are spelled with lowercase t and f unlike the Python equivalents.

bool

The sizeof(bool) is guaranteed to be at least 1 byte. It can't be smaller than a byte because a byte is the smallest addressable unit as you can't take the address of a single bit

Examples
bool a_true_value = true;
bool a_false_value = false;
What happens when we declare a boolean? Is there a default value?

Not always. Depending on where you initialize it, the value could be indeterminate! That being said, it's a best practice to always initialize explicitly

Characters

char

A char stores a single character and is always exactly 1 byte (8 bits).

Under the hood, a char is just a small integer — each character is mapped to a number via the ASCII encoding standard. For example, 'A' is 65 and 'a' is 97.

Examples
char letter = 'A';
char digit  = '5';
char newline = '\n'; // escape sequence for a newline

Note that character literals use single quotes ('), not double quotes.

Because char is really an integer, you can do arithmetic with it:

Char arithmetic
char c = 'a';
c = c + 1; // c is now 'b'

Like int, char can also be unsigned. A plain char may be signed or unsigned depending on the platform, so if you care about the sign, prefer signed char or unsigned char explicitly.

TypeRange (typical)
char (signed)−128 to 127
unsigned char0 to 255

std::string

For text longer than a single character, C++ provides std::string in the <string> header.

Examples
#include <string>

std::string name = "morty";
std::string greeting = "Hello, " + name; // "Hello, morty"

Some operations you'll use frequently:

Common string operations
#include <string>

std::string s = "hello";

s.length();       // 5 — number of characters
s.size();         // same as length()
s[0];             // 'h' — index into the string
s + " world";     // concatenation → "hello world"
s.substr(1, 3);   // "ell" — substring starting at index 1, length 3
s.find("ell");    // 1 — index where "ell" starts (std::string::npos if not found)
std::string vs C-style strings

C also has a string type: a null-terminated char array like char name[] = "morty". You'll still see these in older code and when interfacing with C libraries, but for new C++ code always prefer std::string. It manages its own memory and is much safer to work with.

Type Casting

Sometimes you need to convert a value from one type to another. This is called type casting.

Implicit casting

C++ will automatically convert between compatible types in certain situations. This is called an implicit cast (or coercion).

Implicit cast examples
int   a = 5;
double b = a;    // int → double, safe: 5.0
double c = 3.14;
int    d = c;    // double → int, truncates: 3 (compiler may warn)

When going from a larger or more precise type to a smaller one (e.g. doubleint), you lose information. This is called a narrowing conversion and is a common source of bugs.

Explicit casting with static_cast

The preferred C++ way to cast explicitly is static_cast<TargetType>(value). It makes your intent clear and lets the compiler catch mistakes:

static_cast examples
double pi = 3.14159;
int truncated = static_cast<int>(pi);       // 3

int total = 7, count = 2;
double average = static_cast<double>(total) / count; // 3.5, not 3

The second example is a very common pattern. Without the cast, 7 / 2 is integer division and gives 3. Casting one operand to double forces floating-point division.

Avoid C-style casts

You may see C-style casts like (int)pi in older code. They work but bypass some of the compiler's safety checks. Prefer static_cast in new C++ code.

I/O operations

C++ handles input and output through the <iostream> header.

Output with std::cout

std::cout (character output) writes to the terminal. The << operator chains values together:

Output examples
#include <iostream>

int main() {
    std::cout << "Hello, world!" << "\n";

    int x = 42;
    std::cout << "The answer is: " << x << "\n";

    return 0;
}

You may also see std::endl used to end a line. It works, but it flushes the output buffer every time which is slower. Prefer "\n" unless you specifically need a flush.

Input with std::cin

std::cin (character input) reads from the terminal. The >> operator extracts into a variable:

Input examples
#include <iostream>
#include <string>

int main() {
    int age;
    std::cout << "Enter your age: ";
    std::cin >> age;
    std::cout << "You are " << age << " years old.\n";

    return 0;
}

std::cin >> skips leading whitespace and stops at the next whitespace, which means it reads one word at a time. To read an entire line including spaces, use std::getline:

Reading a full line
#include <iostream>
#include <string>

int main() {
    std::string full_name;
    std::cout << "Enter your full name: ";
    std::getline(std::cin, full_name);
    std::cout << "Hello, " << full_name << "!\n";

    return 0;
}
Mixing cin >> and getline

If you use std::cin >> before std::getline, the leftover newline in the buffer will cause getline to immediately return an empty string. Fix this by calling std::cin.ignore() between them.

Operators in C++

Arithmetic operators

These are the standard math operations. They work on numeric types (int, double, etc.):

OperatorMeaningExampleResult
+Addition3 + 25
-Subtraction3 - 21
*Multiplication3 * 26
/Division7 / 23 (integer!)
%Modulo (remainder)7 % 21

The most important thing to keep in mind is that / on two integers performs integer division — it discards the remainder. Use static_cast<double> on one operand if you need a decimal result.

% (modulo) only works on integers and returns the remainder after division. It's useful for things like checking if a number is even (n % 2 == 0).

Increment and decrement operators

These are shorthand for adding or subtracting 1 from a variable:

Increment and decrement
int i = 5;
i++;  // i is now 6 (post-increment)
i--;  // i is now 5 (post-decrement)
++i;  // i is now 6 (pre-increment)
--i;  // i is now 5 (pre-decrement)

The difference between pre (++i) and post (i++) matters when the expression is used as a value:

  • Post-increment (i++) returns the old value, then increments.
  • Pre-increment (++i) increments first, then returns the new value.
Pre vs post difference
int a = 5;
int b = a++;  // b = 5, a = 6
int c = ++a;  // c = 7, a = 7

In a standalone statement (not embedded in a larger expression), both behave identically.

Comparison operators

Comparison operators evaluate to a bool (true or false):

OperatorMeaningExampleResult
==Equal to3 == 3true
!=Not equal to3 != 4true
<Less than3 < 4true
>Greater than3 > 4false
<=Less than or equal3 <= 3true
>=Greater than or equal4 >= 5false
== vs =

A very common mistake is using = (assignment) instead of == (comparison) inside an if condition. if (x = 5) assigns 5 to x and is always truthy — it does not check if x equals 5.

Assignment operators

Beyond the basic =, C++ provides compound assignment operators that combine an arithmetic operation with assignment:

OperatorEquivalent toExampleResult (if x = 10)
=x = 5x is 5
+=x = x + nx += 3x is 13
-=x = x - nx -= 3x is 7
*=x = x * nx *= 2x is 20
/=x = x / nx /= 2x is 5
%=x = x % nx %= 3x is 1

Logical operators

Logical operators combine or negate boolean expressions:

OperatorMeaningExampleResult
&&Logical ANDtrue && falsefalse
||Logical ORtrue || falsetrue
!Logical NOT!truefalse

&& and || use short-circuit evaluation: if the result is determined by the left operand alone, the right operand is never evaluated.

Short-circuit example
int x = 0;
if (x != 0 && 10 / x > 1) { // safe: right side never evaluated when x == 0
    // ...
}

Exercises

Q1.

For each value below, choose the most appropriate C++ type (int, unsigned int, long long, float, double, bool, char, std::string) and briefly explain why:

  1. The number of students enrolled in a course (never negative, fits in tens of thousands).
  2. A student's GPA (e.g. 3.75).
  3. Whether a student has passed the course.
  4. A single letter grade ('A', 'B', …).
  5. A student's full name.
  6. The total number of grains of sand on Earth (~7.5×10187.5 \times 10^{18}).

Q2.

Without running the code, determine what each std::cout statement prints. Then verify by compiling and running.

#include <iostream>

int main() {
    int a = 7, b = 2;

    std::cout << a / b << "\n";
    std::cout << a % b << "\n";
    std::cout << static_cast<double>(a) / b << "\n";

    int x = 4;
    int y = x++;
    int z = ++x;
    std::cout << x << " " << y << " " << z << "\n";

    bool result = (a > b) && (b == 3);
    std::cout << result << "\n";

    return 0;
}

Q3.

Write a complete C++ program that does the following:

  1. Prompts the user to enter their first name and last name on a single line (use std::getline).
  2. Prompts the user to enter their age as an integer.
  3. Prompts the user to enter their GPA as a double.
  4. Prints a formatted summary, for example:
Name: Ada Lovelace
Age:  27
GPA:  3.92

Make sure to #include the necessary headers and handle the cin >> age / getline mixing issue.

#include <iostream>
#include <string>

int main() {
    // TODO: declare variables for name, age, and GPA

    // TODO: prompt for and read the full name (use std::getline)

    // TODO: prompt for and read the age

    // TODO: prompt for and read the GPA

    // TODO: print the formatted summary

    return 0;
}

Q4.

Each snippet below contains a subtle bug or unexpected behavior related to types and casting. Identify what goes wrong in each case, explain why, and write a corrected version.

#include <iostream>

int main() {
    // (a) — what is the actual value of average?
    int total = 5, count = 2;
    double average = total / count;
    std::cout << average << "\n";

    // (b) — does this always print "Sufficient funds" when it shouldn't?
    unsigned int balance = 100;
    int withdrawal = 200;
    if (balance - withdrawal > 0) {
        std::cout << "Sufficient funds\n";
    }

    // (c) — why might cents not equal 999?
    double price = 9.99;
    int cents = price * 100;
    std::cout << cents << "\n";

    return 0;
}

Practice Problems