CDCDAC Prep
PGCP-ACCMCE (CCE Modules Exam)•~18 min read

C++ Programming Study Notes

Topic 1 (Free Preview)

C++ Program Structure, Translation and Language Fundamentals

Topic 1 notes are 100% free. Topics 2–15 are available on the CDACPrep app.

Open in App

Study Notes

Free high-yield revision notes for C++ Program Structure, Translation and Language Fundamentals

C++ Program Structure, Translation and Language Fundamentals

C++ is a compiled, statically typed language designed for direct control of resources as well as high-level abstraction. A correct C++ program depends on more than syntax: declarations must agree across files, definitions must satisfy the one-definition rule, object files must be linked and runtime operations must remain within the language's defined behaviour. This chapter builds that complete model from source text to executable program.

1. What C++ is

C++ supports several complementary styles:

  • procedural programming organizes work into functions and control flow;
  • object-oriented programming combines state and behaviour in classes and uses encapsulation and polymorphism;
  • generic programming expresses algorithms in terms of type requirements through templates;
  • resource-oriented programming ties resource lifetime to object lifetime through RAII;
  • functional techniques use immutable values, lambdas, algorithms and composable operations.

These styles can coexist. A class may own a file resource, a template may operate on that class and an ordinary function may coordinate the operation.

C++ evolved from C but has its own type, initialization, overload, lifetime, exception and template rules. Valid C code is not automatically good or even valid C++. Prefer C++ abstractions such as containers, strings, smart pointers and deterministic objects over copying manual C patterns without considering object lifetime.

History of C++

  • 1979: Bjarne Stroustrup at Bell Labs starts "C with Classes", adding classes to C for large systems programs.
  • 1983: the language is renamed C++ (++ is C's increment operator: "one step beyond C"). Virtual functions, overloading and references appear.
  • 1985: the first commercial compiler and the book The C++ Programming Language are released.
  • 1998: the first ISO standard, C++98, adds templates, exceptions and the Standard Template Library. C++03 is a bug-fix revision.
  • 2011 onwards: C++11 modernizes the language (auto, lambdas, move semantics, smart pointers); C++14, C++17 (the version used in this course), C++20 and C++23 follow on a three-year cycle.

Why C++ is needed

C gives speed and direct hardware access but offers few tools for organizing large programs: data and the functions that act on it are separate, every resource is released by hand and code must be rewritten for each data type. C++ keeps C's efficiency while adding:

  • classes and encapsulation, so data and its operations form one unit with controlled access;
  • inheritance and polymorphism, so related types share code and can be used through a common interface;
  • templates, so one algorithm or container works for many types;
  • constructors, destructors and RAII, so resources are released automatically;
  • exceptions, so errors are reported without checking every return code;
  • a large standard library (strings, streams, containers and algorithms).

C++ versus C

AspectCC++
ParadigmProceduralProcedural, object-oriented and generic
Data and functionsSeparate (struct holds data only)Combined in classes with access control
OverloadingNot supportedFunction and operator overloading
ReferencesNo (pointers only)References and pointers
Dynamic memorymalloc/calloc/realloc/freenew/delete (which construct and destroy objects), plus the C functions
Error handlingReturn codes, errnoExceptions, plus return codes
Generic codeMacros and void*Templates
NamespacesNoYes, with the standard library in std
Standard I/Oprintf, scanf (<stdio.h>)Type-safe streams std::cin, std::cout (<iostream>)
void* conversionConverts implicitly to any object pointerRequires an explicit cast

Most C code compiles as C++, but not all: C++ reserves extra keywords such as class and new, forbids the implicit void* conversion and requires every function to be declared before it is called.

Setting up a development environment

A C++ toolchain needs a compiler (which also drives the linker) and an editor or IDE:

  • Linux: install GCC (g++) or Clang through the distribution's package manager, for example the build-essential package on Ubuntu.
  • Windows: install MinGW-w64 (commonly through MSYS2) to obtain g++, or install Visual Studio with the "Desktop development with C++" workload to obtain the MSVC compiler.
  • macOS: install the Xcode Command Line Tools, which provide Clang; the g++ command on macOS is Clang.
  • Editors and IDEs: Visual Studio Code with a C++ extension, Code::Blocks, CLion or Visual Studio. An IDE runs the same compile and link steps described later in this chapter.

Check the installation and build a first program from a terminal:

bash
g++ --version
g++ -std=c++17 -Wall -Wextra hello.cpp -o hello
./hello

On Windows the program is run as hello.exe. If g++ is not found, the compiler's bin folder is missing from the PATH environment variable.

2. A minimal hosted program

cpp
#include <iostream>

int main() {
    std::cout << "Hello, C++\n";
    return 0;
}

#include <iostream> makes declarations for standard stream facilities available in the translation unit. main is the entry function of a hosted program and has return type int. std::cout is the standard output stream.

Reaching the closing brace of main is equivalent to returning zero. Zero conventionally reports successful termination to the host environment; a nonzero value conventionally reports failure. EXIT_SUCCESS and EXIT_FAILURE from <cstdlib> express portable status intent.

The implementation initializes standard facilities before entering main and performs normal static-duration cleanup after it returns.

First practice programs: reading input

std::cin >> variable reads a whitespace-separated value into a variable of matching type. This program reads values, performs a calculation and prints the result, the pattern behind the introductory lab exercises (adding numbers, area of a rectangle, power of a number, compound interest, swapping values):

cpp
#include <cmath>
#include <iostream>

int main() {
    double principal = 0.0, rate = 0.0;
    int years = 0;
    std::cout << "Principal, annual rate (%) and years: ";
    if (!(std::cin >> principal >> rate >> years)) {
        std::cerr << "Invalid input\n";
        return 1;
    }

    double amount = principal * std::pow(1.0 + rate / 100.0, years);
    std::cout << "Amount: " << amount << '\n';
    std::cout << "Compound interest: " << amount - principal << '\n';

    int a = 3, b = 7;
    int temporary = a;  // swap two numbers through a third variable
    a = b;
    b = temporary;
    std::cout << "a = " << a << ", b = " << b << '\n';
}

For the input 10000 10 2 it prints an amount of 12100 and compound interest of 2100, followed by a = 7, b = 3. std::pow comes from <cmath>. The if (!(std::cin >> ...)) test detects input that cannot be converted to the requested types.

Binary numbers and characters are also integers, so the same + operator adds them:

cpp
#include <bitset>
#include <iostream>

int main() {
    int first = 0b1011;   // binary literal: 11
    int second = 0b0110;  // 6
    int sum = first + second;
    std::cout << sum << ' ' << std::bitset<8>(sum) << '\n'; // 17 00010001

    char letter = 'A';
    std::cout << letter + 1 << ' ' << static_cast<char>(letter + 1) << '\n'; // 66 B
}

std::bitset<8>(value) prints the lowest 8 bits as 0s and 1s. Adding to a char promotes it to int, so letter + 1 prints the character code 66; converting back to char prints B (in the usual ASCII encoding).

3. Source character categories and tokens

Translation first turns source into preprocessing tokens and later language tokens. Important token categories are:

  • identifiers: programmer-defined names such as total;
  • keywords: reserved words such as class, return and constexpr;
  • literals: values such as 42, 3.5, 'A' and "text";
  • operators: symbols such as +, == and ::;
  • punctuators: structural symbols including braces, commas and semicolons.

Identifiers are case-sensitive, so count, Count and COUNT are different. An identifier cannot be a keyword. Names beginning with reserved underscore patterns belong to the implementation and should not be invented in application code.

Whitespace separates tokens where necessary. Single-line comments begin with //; block comments use /* ... */ and do not nest.

4. Expressions, statements and blocks

An expression computes a value, designates an object or produces side effects:

cpp
price * quantity
counter++
std::cout << message

A statement is a complete unit of execution. Expression statements usually end in a semicolon. Compound statements group statements in braces and introduce a block scope:

cpp
if (quantity > 0) {
    const double total = price * quantity;
    std::cout << total << '\n';
}

total exists only within the block. A declaration is itself a statement in many contexts. An accidental semicolon after if (condition); creates an empty controlled statement and is a common defect.

5. Declarations and definitions

A declaration introduces a name and enough type information for later use:

cpp
double area(double radius); // function declaration
extern int active_users;    // declaration, normally not definition

A definition creates an entity or supplies its implementation:

cpp
double area(double radius) {
    return 3.14159 * radius * radius;
}

int active_users = 0;

Every definition is also a declaration, but not every declaration is a definition. A compiler can type-check a call from a declaration. The linker still needs the corresponding function or object definition when the program odr-uses it.

Declarations repeated across files must agree. A mismatched declaration can cause compile errors, unresolved symbols or undefined behaviour depending on how it differs.

6. Scope, storage and lifetime

Scope answers where a name can be used. Common scopes include block, function parameter, class, namespace and global namespace scope.

Storage duration answers how long an object's storage exists:

  • automatic: normally one block execution;
  • static: entire program;
  • thread: one object per thread for the thread's lifetime;
  • dynamic: controlled through allocation and ownership.

Lifetime is the interval during which an object exists as its type. Storage may exist before construction or after destruction without containing a live object. This distinction becomes critical with placement construction, unions, raw memory and manual allocation.

Name visibility, storage duration and object lifetime are related but not interchangeable.

7. Namespaces

Namespaces group names and prevent collisions:

cpp
namespace billing {
    double calculate_tax(double amount);
}

double tax = billing::calculate_tax(1000.0);

Standard-library names are in std. A using declaration introduces one selected name:

cpp
using std::cout;

A using directive such as using namespace std; makes all visible names candidates during lookup. It can cause ambiguity and should not appear in public headers. Explicit qualification communicates origin and scales better.

An unnamed namespace gives names internal linkage within one translation unit. Nested namespaces can be written compactly in modern C++: namespace company::module { ... }.

8. The preprocessing phase

The preprocessor handles directives before ordinary compilation. Common directives include:

  • #include for textual inclusion;
  • #define and #undef for macros;
  • #if, #ifdef, #ifndef and #endif for conditional inclusion;
  • #pragma for implementation-specific control.

#include conceptually inserts the named header's preprocessed content. Angle brackets normally search implementation-configured include locations; quotes normally search relative project locations first, followed by configured locations.

Macros perform token substitution without C++ type rules:

cpp
#define SQUARE(x) ((x) * (x))

SQUARE(i++) modifies i twice, demonstrating why constants, inline functions, templates and constexpr functions are safer than function-like macros.

9. Headers and inclusion guards

A header normally contains declarations, type definitions, templates and suitable inline definitions shared by translation units:

cpp
#ifndef GEOMETRY_CIRCLE_HPP
#define GEOMETRY_CIRCLE_HPP

double area(double radius);

#endif

The guard prevents the same header content from being processed repeatedly within one translation unit. #pragma once is widely supported but historically implementation-specific.

Headers should be self-contained: including one should provide everything required to compile its contents. Do not rely on another unrelated header having been included first.

Avoid non-inline function definitions and ordinary non-inline global variable definitions in headers because every including source file can produce a definition.

10. Translation units

A translation unit is roughly one source file after preprocessing has expanded its includes and conditional directives. Each source file is compiled separately:

text
main.cpp + included headers → main translation unit → main.o
math.cpp + included headers → math translation unit → math.o

Separate compilation improves build scalability but means the compiler usually sees only one translation unit at a time. Headers communicate declarations and definitions that must be visible, especially templates.

Changing a source file typically requires recompiling that file. Changing a widely included header can require recompiling every dependent translation unit.

11. The translation and build pipeline

A practical pipeline is:

  1. preprocessing handles directives and inclusion;
  2. compilation parses, type-checks and translates;
  3. assembly creates machine-code object files;
  4. linking combines objects and libraries and resolves symbols;
  5. loading maps the executable and required libraries into a process;
  6. execution begins and eventually enters main.

Compilers often expose several steps through one driver command:

bash
g++ -std=c++17 -Wall -Wextra -Wpedantic main.cpp math.cpp -o app

-std=c++17 selects the language mode. Warnings reveal suspicious but accepted code. Treating warnings seriously catches conversions, shadowing, missing returns and unused results before they become defects.

Files may be built separately:

bash
g++ -std=c++17 -Wall -Wextra -c main.cpp
g++ -std=c++17 -Wall -Wextra -c math.cpp
g++ main.o math.o -o app

-c creates an object file without linking. The final command resolves references and produces the executable.

If main.cpp calls a correctly declared function whose definition was never linked, compilation can succeed and linking fails with an undefined reference or unresolved external symbol. If two object files provide forbidden multiple definitions, linking usually reports duplication.

13. The one-definition rule

The one-definition rule (ODR) governs how many definitions an entity may have across a program. Ordinary non-inline functions and externally linked variables must have one program definition when odr-used.

Classes, templates and inline functions may appear identically in several translation units, normally through headers. Their definitions must satisfy the ODR's equivalence requirements.

C++17 inline variables allow a header definition intended to denote one program entity:

cpp
inline constexpr int maximum_retries = 3;

inline is primarily an ODR and linkage facility in modern C++; it does not command the optimizer to substitute a function body.

14. Linking and libraries

A static library contributes selected object code to the executable at link time. A shared or dynamic library is loaded as a separate binary dependency, often at program start.

Link order can matter with some static-library toolchains because unresolved symbols are processed in sequence. Binary compatibility also matters: compiler ABI, library version, architecture, debug/runtime settings and build flags must be compatible.

A header supplies compile-time declarations. A library supplies compiled definitions. Having one without the other is insufficient when code uses the facility.

15. Diagnostics by phase

Classify failures before fixing them:

FailureTypical example
PreprocessingMissing included file, unmatched conditional
CompilationSyntax error, type mismatch, inaccessible member
LinkingMissing definition, duplicate external definition
LoadingRequired shared library unavailable
Runtime exception/errorExplicit exception or OS failure
Logic defectProgram runs but computes the wrong defined result
Undefined behaviourProgram violates a rule with no required outcome

The compiler is not required to diagnose every rule violation. Link success also does not prove defined runtime behaviour.

16. Defined, unspecified, implementation-defined and undefined

Defined behaviour has requirements prescribed by the standard. Implementation-defined behaviour lets an implementation choose and document one possibility, such as aspects of fundamental type sizes. Unspecified behaviour permits one of several possibilities without requiring documentation. Undefined behaviour imposes no requirements on that execution.

Examples of undefined behaviour include out-of-bounds access, dereferencing an invalid pointer, signed integer overflow and using an object outside its lifetime.

Undefined behaviour does not mean “random value” or “exception.” An optimizer may assume it never occurs, so observed debug output cannot establish a portable result. Sanitizers can detect many violations during testing but cannot prove their absence.

17. Standard streams

<iostream> declares standard stream objects:

  • std::cin: standard input;
  • std::cout: normal output;
  • std::cerr: diagnostic output, commonly unbuffered or promptly flushed;
  • std::clog: buffered diagnostic output.
cpp
std::cout << "value = " << value << '\n';

'\n' inserts a newline. std::endl inserts a newline and flushes the stream. Unnecessary flushing can reduce performance, so use endl only when a flush is intended.

Stream insertion is type-aware and can be overloaded for user-defined types. Always check input state before relying on extracted values.

18. Language standards and portability

C++ evolves through published standards. C++17 introduced features including structured bindings, if constexpr, inline variables, fold expressions, std::optional, std::variant and filesystem support.

cpp
auto [key, value] = pair;

if constexpr (std::is_integral_v<T>) {
    // compiled only for matching instantiations
}

Select the standard explicitly in builds and continuous integration. A compiler version and language mode are separate facts: a new compiler may default to an older mode, while an old compiler may only partly implement a requested standard.

Portable C++ also avoids assumptions about byte width, integer sizes, endianness, filesystem layout and extensions unless the target environment documents them.

19. Program organization

A small multi-file project might use:

text
project/
├── include/
│   └── geometry/circle.hpp
├── src/
│   ├── circle.cpp
│   └── main.cpp
└── CMakeLists.txt

Headers publish interfaces. Source files hold implementation that need not be visible. Build systems track dependencies, compilation options, include paths, libraries and platform differences.

Minimize header dependencies with forward declarations where a complete type is not needed, but do not sacrifice clarity. Private implementation techniques can reduce rebuild coupling for stable library interfaces.

20. C++ and C interoperability

C++ overloads names and commonly uses implementation-specific name mangling. A C-compatible declaration can request C language linkage:

cpp
extern "C" int legacy_calculate(int);

This affects linkage, not type safety, object lifetime or memory ownership. C interfaces cannot directly express C++ overloads, templates, exceptions or classes.

Do not let exceptions cross a C ABI boundary. Define who allocates, who frees, which encoding is used and how failures are reported.

Common mistakes

MistakeCorrect approach
Putting using namespace std; in a headerQualify names or use narrow declarations
Defining ordinary globals in headersUse one source definition or a valid inline variable
Assuming inclusion compiles a libraryLink the required definitions
Treating all failures as compiler errorsIdentify preprocessing, compile, link, load or runtime phase
Using macros for typed calculationsPrefer functions, templates or constexpr
Assuming undefined behaviour has a predictable outputTreat the execution as invalid
Using std::endl for every lineUse newline unless flushing is required
Relying on compiler defaultsSelect standard and warnings explicitly

Worked multi-file trace

circle.hpp declares double area(double);. circle.cpp includes the header and defines the function. main.cpp includes the same header and calls it.

The preprocessor gives each source file the declaration. Each source compiles independently. The main object file contains a reference to area; the circle object file contains its definition. The linker connects them. Omitting circle.o leaves the reference unresolved even though both source files were individually valid.

Ready to test what you learned?

Practice 20 MCQs with Instant Explanations

Solve exam-level questions for C++ Programming with scoring, option checking, and detailed answers.

Go to Practice MCQs