Chapter 2: Basic Syntax and Fundamentals
Program Structure
Every C++ program has a standard structure:
// Preprocessor directives (before compilation)
#include <iostream> // Standard library headers use <>
#include "myheader.h" // Local headers use ""
// Namespace to avoid std:: prefix (optional, often avoided)
// using namespace std;
// Main function - program entry point
int main() {
// Your code here
return 0; // 0 = success, non-zero = error
}
The main() Function
// Basic form
int main() {
return 0;
}
// With command-line arguments
int main(int argc, char* argv[]) {
// argc = argument count
// argv = argument values (array of C-strings)
for (int i = 0; i < argc; i++) {
std::cout << "Arg " << i << ": " << argv[i] << std::endl;
}
return 0;
}
Data Types
Fundamental Types
#include <iostream>
#include <cstdint> // For fixed-width integers
int main() {
// Boolean
bool flag = true; // or false
// Characters
char c = 'A'; // 1 byte, ASCII
wchar_t wc = L'Ω'; // Wide character
char16_t c16 = u'好'; // UTF-16
char32_t c32 = U'🎉'; // UTF-32
// Integers (size varies by platform)
short s = 32767; // At least 16 bits
int i = 2147483647; // At least 16 bits (usually 32)
long l = 2147483647L; // At least 32 bits
long long ll = 9223372036854775807LL; // At least 64 bits
// Fixed-width integers (guaranteed sizes)
int8_t i8 = 127; // Exactly 8 bits
int16_t i16 = 32767; // Exactly 16 bits
int32_t i32 = 2147483647; // Exactly 32 bits
int64_t i64 = 9223372036854775807LL; // Exactly 64 bits
// Unsigned variants
unsigned int ui = 4294967295U;
uint32_t u32 = 4294967295U;
// Floating point
float f = 3.14159f; // ~7 decimal digits precision
double d = 3.141592653589793; // ~15 decimal digits precision
long double ld = 3.14159265358979323846L;
// Size operator
std::cout << "Size of int: " << sizeof(int) << " bytes\n";
std::cout << "Size of double: " << sizeof(double) << " bytes\n";
return 0;
}
Type Inference with auto
auto x = 42; // int
auto y = 3.14; // double
auto z = 3.14f; // float
auto s = "hello"; // const char*
auto str = std::string("hello"); // std::string
Constants
// Compile-time constant (preferred)
constexpr int MAX_SIZE = 100;
// Runtime constant
const int size = getSize(); // Value determined at runtime
// Old C-style (avoid in new code)
#define OLD_MAX 100
Variables and Initialization
C++ has several initialization syntaxes:
int a; // Uninitialized (dangerous! Contains garbage)
int b = 5; // Copy initialization
int c(5); // Direct initialization
int d{5}; // Brace initialization (C++11, preferred)
int e = {5}; // Copy-list initialization
// Brace initialization prevents narrowing conversions
int x = 3.14; // Compiles (truncates to 3) - silent bug!
int y{3.14}; // ERROR: narrowing conversion - catches bugs
// For complex types
std::vector<int> v1 = {1, 2, 3}; // Initializer list
std::vector<int> v2{1, 2, 3}; // Same thing
Best Practice: Use brace initialization {} to catch type errors at compile time.
Operators
Arithmetic
int a = 10, b = 3;
int sum = a + b; // 13
int diff = a - b; // 7
int prod = a * b; // 30
int quot = a / b; // 3 (integer division truncates)
int rem = a % b; // 1 (modulo)
double x = 10.0, y = 3.0;
double div = x / y; // 3.333... (floating-point division)
// Compound assignment
a += 5; // a = a + 5
a -= 2; // a = a - 2
a *= 3; // a = a * 3
a /= 2; // a = a / 2
// Increment/Decrement
int i = 5;
i++; // Post-increment: use i, then add 1
++i; // Pre-increment: add 1, then use i
i--; // Post-decrement
--i; // Pre-decrement
Comparison and Logical
bool result;
// Comparison
result = (5 == 5); // true (equality)
result = (5 != 3); // true (inequality)
result = (5 < 10); // true
result = (5 <= 5); // true
result = (5 > 3); // true
result = (5 >= 5); // true
// Logical
result = true && false; // false (AND)
result = true || false; // true (OR)
result = !true; // false (NOT)
// Short-circuit evaluation
// Second operand not evaluated if result determined by first
if (ptr != nullptr && ptr->isValid()) {
// Safe: ptr->isValid() only called if ptr is not null
}
Bitwise
int a = 0b1010; // 10 in binary
int b = 0b1100; // 12 in binary
int and_result = a & b; // 0b1000 = 8 (AND)
int or_result = a | b; // 0b1110 = 14 (OR)
int xor_result = a ^ b; // 0b0110 = 6 (XOR)
int not_result = ~a; // Inverts all bits
int left = a << 2; // 0b101000 = 40 (left shift)
int right = a >> 1; // 0b0101 = 5 (right shift)
Control Flow
Conditionals
// if-else
if (condition) {
// code
} else if (another_condition) {
// code
} else {
// code
}
// With initialization (C++17)
if (auto result = compute(); result > 0) {
// result is in scope here
}
// result is out of scope here
// Ternary operator
int max = (a > b) ? a : b;
// switch
switch (value) {
case 1:
doSomething();
break;
case 2:
case 3:
doOtherThing(); // Handles both 2 and 3
break;
default:
doDefault();
}
// switch with initialization (C++17)
switch (auto x = getValue(); x) {
case 0: break;
case 1: break;
}
Loops
// for loop
for (int i = 0; i < 10; i++) {
std::cout << i << " ";
}
// Range-based for (C++11)
std::vector<int> nums = {1, 2, 3, 4, 5};
for (int n : nums) {
std::cout << n << " "; // Copy of each element
}
for (const int& n : nums) {
std::cout << n << " "; // Reference (no copy, read-only)
}
for (int& n : nums) {
n *= 2; // Modifiable reference
}
// while loop
int count = 0;
while (count < 10) {
std::cout << count++ << " ";
}
// do-while loop (executes at least once)
do {
std::cout << "Runs at least once\n";
} while (false);
// Loop control
for (int i = 0; i < 100; i++) {
if (i == 5) continue; // Skip rest of iteration
if (i == 10) break; // Exit loop entirely
}
Functions
Basic Functions
// Function declaration (prototype)
int add(int a, int b);
// Function definition
int add(int a, int b) {
return a + b;
}
// Void function (no return value)
void printMessage(const std::string& msg) {
std::cout << msg << std::endl;
}
// Default parameters
void greet(const std::string& name = "World") {
std::cout << "Hello, " << name << "!\n";
}
// greet(); // "Hello, World!"
// greet("Alice"); // "Hello, Alice!"
Function Overloading
// Same name, different parameters
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
std::string add(const std::string& a, const std::string& b) {
return a + b;
}
// Compiler chooses based on argument types
add(1, 2); // Calls int version
add(1.5, 2.5); // Calls double version
add("Hello", "!"); // Calls string version
Pass by Value vs Reference
// Pass by value (copy)
void modifyValue(int x) {
x = 100; // Only modifies local copy
}
// Pass by reference (modify original)
void modifyReference(int& x) {
x = 100; // Modifies original
}
// Pass by const reference (read-only, no copy)
void readOnly(const std::string& str) {
std::cout << str; // Efficient, no copy
// str = "new"; // ERROR: can't modify const
}
int main() {
int a = 5;
modifyValue(a); // a is still 5
modifyReference(a); // a is now 100
// For large objects, always use const reference for read-only
std::string bigString = "...";
readOnly(bigString); // No expensive copy
}
Inline Functions
// Suggests compiler to inline (replace call with body)
inline int square(int x) {
return x * x;
}
Lambda Functions (C++11)
// Basic lambda
auto add = [](int a, int b) { return a + b; };
int result = add(3, 4); // 7
// Lambda with capture
int multiplier = 10;
auto times = [multiplier](int x) { return x * multiplier; };
// [=] captures all by value
// [&] captures all by reference
// [x, &y] captures x by value, y by reference
// Lambda with explicit return type
auto divide = [](int a, int b) -> double {
return static_cast<double>(a) / b;
};
// Generic lambda (C++14)
auto generic_add = [](auto a, auto b) { return a + b; };
generic_add(1, 2); // Works with int
generic_add(1.5, 2.5); // Works with double
Input/Output
Console I/O
#include <iostream>
#include <string>
int main() {
// Output
std::cout << "Hello, World!" << std::endl; // endl flushes buffer
std::cout << "Hello, World!\n"; // \n is often preferred (faster)
// Chaining output
std::cout << "Value: " << 42 << ", Pi: " << 3.14 << "\n";
// Input
int number;
std::cout << "Enter a number: ";
std::cin >> number;
// String input (single word)
std::string word;
std::cin >> word; // Reads until whitespace
// Full line input
std::string line;
std::getline(std::cin, line); // Reads entire line
// Error handling
if (std::cin.fail()) {
std::cerr << "Invalid input!\n"; // stderr for errors
std::cin.clear(); // Clear error flags
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
return 0;
}
Formatting Output
#include <iostream>
#include <iomanip>
int main() {
double pi = 3.14159265358979;
std::cout << std::fixed << std::setprecision(2) << pi << "\n"; // 3.14
std::cout << std::scientific << pi << "\n"; // 3.14e+00
std::cout << std::setw(10) << 42 << "\n"; // Right-aligned in 10 chars
std::cout << std::left << std::setw(10) << 42 << "\n"; // Left-aligned
std::cout << std::hex << 255 << "\n"; // ff
std::cout << std::oct << 255 << "\n"; // 377
std::cout << std::dec << 255 << "\n"; // 255 (back to decimal)
std::cout << std::boolalpha << true << "\n"; // "true" instead of 1
}
Strings
C-Style Strings (avoid when possible)
// Null-terminated character arrays
char str1[] = "Hello"; // Array of 6 chars (includes \0)
const char* str2 = "World"; // Pointer to string literal
// Dangerous operations
char buffer[10];
strcpy(buffer, "Hello"); // Can overflow!
strcat(buffer, " World"); // Can overflow!
strlen(str1); // 5 (doesn't count \0)
C++ Strings (std::string)
#include <string>
int main() {
// Creation
std::string s1 = "Hello";
std::string s2("World");
std::string s3(5, 'a'); // "aaaaa"
// Operations
std::string combined = s1 + " " + s2; // Concatenation
s1 += "!"; // Append
// Access
char first = s1[0]; // No bounds checking
char safe = s1.at(0); // Throws if out of bounds
char last = s1.back();
char front = s1.front();
// Size
size_t len = s1.length(); // or s1.size()
bool empty = s1.empty();
// Comparison
if (s1 == s2) { /* ... */ }
if (s1 < s2) { /* lexicographic comparison */ }
// Searching
size_t pos = s1.find("llo"); // Position of substring
if (pos != std::string::npos) {
// Found
}
// Substrings
std::string sub = s1.substr(0, 3); // "Hel" (pos, length)
// Modification
s1.replace(0, 2, "Jell"); // "Jello!" (pos, length, new_str)
s1.erase(4, 1); // Remove 1 char at position 4
s1.insert(4, "y"); // Insert at position 4
// Conversion
std::string numStr = std::to_string(42);
int num = std::stoi("42");
double d = std::stod("3.14");
return 0;
}
String Views (C++17)
#include <string_view>
// Non-owning reference to string data (no allocation)
void printView(std::string_view sv) {
std::cout << sv << "\n";
}
int main() {
std::string str = "Hello, World!";
std::string_view view = str; // No copy
std::string_view literal = "Test"; // Works with literals
printView(str); // Works
printView("Hello"); // Works, no string construction
printView(view); // Works
// Can create substring views without allocation
std::string_view hello = view.substr(0, 5);
}
Arrays
C-Style Arrays
// Fixed size, stack allocated
int arr[5] = {1, 2, 3, 4, 5};
int arr2[] = {1, 2, 3}; // Size inferred (3)
int arr3[5] = {1, 2}; // Rest initialized to 0
// Access
int first = arr[0];
arr[1] = 10;
// Size (only works in same scope)
size_t size = sizeof(arr) / sizeof(arr[0]); // 5
// Decay to pointer when passed to function (loses size info)
void processArray(int* arr, size_t size); // Must pass size separately
std::array (C++11)
#include <array>
std::array<int, 5> arr = {1, 2, 3, 4, 5};
// Size is part of the type
size_t size = arr.size(); // 5
bool empty = arr.empty();
// Safe access
int val = arr.at(2); // Bounds-checked
// Works with algorithms
std::sort(arr.begin(), arr.end());
// Can be passed by reference without decay
void process(const std::array<int, 5>& arr);
std::vector (Dynamic Array)
#include <vector>
// Creation
std::vector<int> v1; // Empty
std::vector<int> v2(5); // 5 elements, default value (0)
std::vector<int> v3(5, 10); // 5 elements, all 10
std::vector<int> v4 = {1, 2, 3, 4, 5}; // Initializer list
// Size operations
v1.push_back(1); // Add to end
v1.pop_back(); // Remove from end
v1.resize(10); // Change size
v1.reserve(100); // Pre-allocate capacity (optimization)
size_t size = v1.size();
size_t cap = v1.capacity();
// Access
int first = v1[0]; // No bounds check
int safe = v1.at(0); // Bounds check
int& front = v1.front();
int& back = v1.back();
// Iteration
for (const auto& elem : v1) {
std::cout << elem << " ";
}
// Insert and erase
v1.insert(v1.begin() + 2, 99); // Insert 99 at position 2
v1.erase(v1.begin()); // Remove first element
v1.clear(); // Remove all elements
Exercises
Type Exploration
- Write a program that prints the size of all fundamental types on your system
- Test what happens with integer overflow
Calculator
- Create a simple calculator that reads two numbers and an operator (+, -, *, /)
- Handle division by zero gracefully
String Manipulation
- Write a function that reverses a string
- Write a function that counts vowels in a string
Vector Practice
- Read numbers from input until user enters -1
- Store in vector, then print: sum, average, min, max
Previous: 01 - Tooling and Setup Next: 03 - Memory Management