Chapter 1
C++ Programming Foundations and Toolchain
Bachelors Program in Computer Science Engineering
Object-Oriented Programming (C++)
•
Bachelor
C++ Programming Foundations and Toolchain
Introduction
C++ is a compiled, statically typed, general-purpose programming language used in systems software, embedded systems, games, scientific applications, finance, browsers, and performance-critical services. Its importance in computer science engineering arises from the control it provides over data representation, memory, execution, and abstraction.
A C++ program is not normally executed directly from its human-readable source code. Instead, source files pass through a toolchain that transforms them into machine instructions. Understanding this transformation is essential because many programming errors are not caused by incorrect algorithms alone; they arise from incorrect file organization, missing declarations, incompatible types, linker failures, build configuration, or misunderstood runtime behavior.
This chapter introduces the language and professional tools required for subsequent object-oriented programming. It emphasizes the relationship among source code, preprocessing, compilation, linking, execution, debugging, and version control.
Learning Objectives
After studying this chapter, you should be able to:
Explain the stages of a C++ build process.
Organize declarations and definitions in source and header files.
Use preprocessing directives and namespaces appropriately.
Distinguish fundamental types, expressions, statements, and functions.
Apply control flow, references, pointers, and const correctness.
Perform basic formatted input and output.
Use a compiler, build system, debugger, and version-control system effectively.
Diagnose common compile-time, link-time, and run-time errors.
1. The Compiled Programming Model
A compiler translates source code into object code for a particular target architecture and operating system. A typical C++ build has four conceptual stages:
Preprocessing expands header inclusion and conditional directives.
Compilation checks syntax and semantics and produces an object file.
Linking combines object files and libraries into an executable.
Loading and execution place the program in memory and begin execution.
C++ build pipeline from source files to executable
Figure: C++ build pipeline from source files to executable
Figure: Conceptual C++ build pipeline.
A compiler command such as g++ -std=c++20 -Wall -Wextra main.cpp -o app requests the C++20 language standard, enables useful warnings, compiles main.cpp, and names the output app. Compiler options vary among GCC, Clang, and Microsoft Visual C++, but the underlying stages remain similar.
An important distinction is that compilation is usually performed separately for each source file. The linker then resolves references between the resulting object files and libraries. Thus, a missing function definition can produce a linker error even when every source file compiles successfully.
2. Source and Header Organization
A translation unit is the source file after preprocessing. Header files are commonly used to publish declarations shared by several translation units, while .cpp files contain definitions and implementation details.
A declaration tells the compiler that an entity exists; a definition supplies its implementation or storage. For example:
// temperature.hpp
#pragma once
double celsius_to_fahrenheit(double celsius);
The header uses #pragma once, a widely supported directive that prevents repeated inclusion in one translation unit. Traditional include guards, such as #ifndef TEMPERATURE_HPP, provide a portable alternative.
Angle brackets generally identify implementation or library headers, whereas quotation marks commonly identify project headers. The difference is a search-path convention, not a guarantee of origin.
A useful rule is: place interfaces in headers and non-inline implementation in source files. Avoid placing ordinary non-inline function definitions or non-inline global variables in headers included by multiple source files, because this can violate the one-definition rule.
3. Preprocessing and Namespaces
The preprocessor operates before ordinary C++ compilation. #include performs textual inclusion; #define creates macros; #if, #ifdef, and related directives support conditional compilation. Macros do not obey C++ scope and type rules, so constants and functions should usually be expressed using constexpr, const, templates, or ordinary functions instead.
A namespace prevents name collisions by grouping identifiers. The standard library is contained primarily in std:
#include <iostream>
namespace university {
int course_count = 6;
}
int main() {
std::cout << university::course_count << '\n';
}
The scope-resolution operator :: selects a name from a namespace or class scope. Although using namespace std; is convenient in small experiments, it is poor practice in headers and can make large programs ambiguous. Prefer qualified names such as std::vector and std::cout, or narrowly scoped declarations such as using std::cout; inside a function.
4. Types, Objects, and Initialization
A type determines the permitted values, operations, representation requirements, and conversions of an object. Fundamental types include bool, character types, integral types such as int and long long, and floating-point types such as float and double. Exact sizes are implementation-dependent, although the standard specifies minimum ranges and ordering relationships.
Common initialization forms include:
int a = 10; // copy initialization
int b(10); // direct initialization
int c{10}; // list initialization
int d{}; // value initialization, normally zero
List initialization is valuable because it rejects many narrowing conversions. For example, int x{3.7}; is ill-formed rather than silently discarding the fractional part.
The auto specifier asks the compiler to deduce a type from an initializer. It improves maintainability when the type is lengthy, but it does not make C++ dynamically typed. The type is still fixed at compile time.
A conversion may be implicit or explicit. In double result = 5 / 2;, both operands are integers, so integer division occurs before conversion and the result is 2.0. Writing 5.0 / 2.0 produces 2.5. This illustrates why programmers must understand operand types rather than relying on the destination type.
5. Expressions and Operators
An expression produces a value, designates an object, or performs an operation. C++ provides arithmetic, relational, logical, assignment, bitwise, conditional, and other operators. Operator precedence determines grouping, but parentheses should be used when they improve clarity.
The logical operators && and || short-circuit: the right operand is evaluated only when necessary. This supports safe conditions such as:
if (ptr != nullptr && *ptr > 0) {
// Dereferencing is reached only when ptr is non-null.
}
Do not modify the same scalar object repeatedly within one expression unless evaluation rules are fully understood. Clear, separate statements are generally safer than clever expressions.
6. Control Flow
Selection uses if, else if, else, and switch. Iteration uses while, do while, and for loops. A range-based for is often the clearest way to traverse a collection:
for (const int value : values) {
std::cout << value << '\n';
}
Use const int value when the loop should not modify elements. Use const int& value when copying is expensive and the elements need not be changed. break terminates the nearest loop or switch; continue skips to the next iteration. Excessive use of jumps can obscure control flow, so structured alternatives are preferred.
A switch generally selects among integral, enumeration, or compatible values. Each intended case should normally end with break, return, or another deliberate control transfer. Accidental fall-through is a frequent defect.
7. Functions and Interfaces
A function packages behavior behind a name and parameter list. Its declaration specifies the interface:
The const reference avoids copying and promises that the function will not modify the vector. A function can return a value, return void, or communicate through parameters. Prefer a return value for ordinary results; output parameters should be reserved for cases where they make the interface clearer.
Parameters may be passed by value, pointer, lvalue reference, or constant reference. Pass by value when an independent copy is appropriate; pass by reference when the function must access or modify the caller's object; pass by pointer when absence (nullptr) is meaningful or pointer semantics are required.
Function overloading permits several functions with the same name but different parameter lists. The return type alone cannot distinguish overloads. Default arguments should be used sparingly because they belong to the declaration visible at the call site.
A local variable has automatic storage duration and normally exists until its enclosing block ends. Returning a reference or pointer to a local variable is invalid because the local object has ceased to exist.
8. References, Pointers, and Memory Safety
A reference is an alias for an existing object and must normally be initialized when declared:
int score = 80;
int& alias = score;
alias = 90; // score becomes 90
A pointer stores an address and can be null:
int score = 80;
int* p = &score;
if (p != nullptr) {
*p = 90;
}
The address-of operator & obtains an address; the dereference operator * accesses the object at an address. Dereferencing a null, dangling, or invalid pointer is undefined behavior. A dangling pointer refers to an object whose lifetime has ended.
Modern C++ uses resource-managing types such as std::vector, std::string, and smart pointers rather than raw dynamic allocation. When exclusive ownership is required, std::unique_ptr is normally preferred; shared ownership should use std::shared_ptr only when ownership is genuinely shared. The principle of RAII—resource acquisition is initialization—binds resource lifetime to object lifetime and reduces leaks.
9. Const Correctness
const communicates non-modification and allows the compiler to detect violations:
void print(const std::string& text);
const int limit = 100;
A pointer to constant data, const int, cannot modify the pointed-to integer through that pointer. A constant pointer, int const, cannot be redirected after initialization. Both restrictions can be combined as const int* const.
Member functions can also be marked const; such a function promises not to modify the observable state of its object. Const correctness improves interfaces, enables use with const objects, and makes assumptions visible to readers and tools.
10. Basic Input and Output
The stream library provides formatted I/O. std::cout writes standard output, std::cin reads standard input, and std::cerr is commonly used for diagnostics.
#include <iostream>
#include <string>
int main() {
std::string name;
int age{};
std::cout << "Name: ";
std::getline(std::cin, name);
std::cout << "Age: ";
if (std::cin >> age) {
std::cout << name << " is " << age << " years old.\n";
} else {
std::cerr << "Invalid age.\n";
return 1;
}
}
operator>> skips leading whitespace and stops at whitespace for strings. std::getline reads an entire line. Mixing them requires attention to the newline remaining in the input buffer; a common solution is std::cin.ignore(...) before getline.
Input is external and therefore untrusted. Production programs should validate ranges, detect stream failure, and avoid assuming that users or files supply well-formed data.
11. Compilers, Build Systems, and Debuggers
A compiler should be invoked with a modern language standard and warnings. For example:
Warnings are not merely cosmetic: they often identify narrowing, unused variables, suspicious comparisons, and unreachable code. Treating warnings as errors can be appropriate in controlled builds, although third-party code may require separate configuration.
For multiple files, a build system records dependencies and rebuilds only what has changed. CMake is widely used because it generates build files for several platforms and IDEs. A minimal project might contain CMakeLists.txt, src/main.cpp, and include/project/name.hpp. Build systems also manage compiler flags, libraries, tests, and installation.
A debugger permits controlled execution. Breakpoints pause execution; stepping moves through statements; watch expressions inspect values; the call stack reveals how execution reached the current point. Debuggers are especially useful for locating invalid assumptions, incorrect state changes, and memory errors. They do not replace tests, code review, or static analysis.
Compilation errors concern source-language rules. Linker errors concern unresolved or multiply defined symbols. Runtime errors occur after successful linking and include invalid memory access, failed input, and violated program assumptions. Classifying the failure first makes diagnosis more efficient.
12. Version Control and Professional Practice
Git records changes to a project and supports collaboration. A practical workflow is to make small, coherent commits, write descriptive messages, review differences before committing, and keep generated build artifacts out of the repository. A .gitignore file commonly excludes build directories, editor metadata, and binaries.
Version control is also an experimental tool. A developer can create a branch, test an alternative implementation, compare behavior, and return to a known revision. It does not replace backups, testing, or secure handling of credentials; passwords and private keys must never be committed.
Applied Case: A Temperature-Conversion Utility
Background. A laboratory team needs a command-line utility that converts Celsius readings to Fahrenheit and rejects invalid input.
Problem. An initial prototype places all code in one file, performs integer arithmetic, and prints a misleading result for decimal readings.
Analysis. The conversion relationship is F = C × 9/5 + 32, where C is temperature in degrees Celsius and F is temperature in degrees Fahrenheit. The variables have temperature units; the ratio 9/5 and the offset 32 are part of the Celsius–Fahrenheit scale transformation. Floating-point operands are required when fractional readings must be preserved.
Approach. The team places the function declaration in temperature.hpp, its definition in temperature.cpp, and input/output logic in main.cpp. It compiles with warnings enabled, uses double, validates std::cin, and builds through CMake. A debugger verifies that a reading of 25.0 produces 77.0. Unit tests check 0.0 → 32.0, 100.0 → 212.0, and a fractional value.
Outcome. The modular design permits the conversion function to be reused by a graphical interface or sensor service. Compiler warnings expose accidental integer division during review, while Git preserves the corrected implementation and test history.
Learning points. Separate interfaces from implementation, choose types according to required precision, validate external input, automate builds, and test boundary and representative values.
Challenges, Limitations, and Best Practices
C++ provides substantial control but also permits unsafe operations. Undefined behavior means that the language imposes no requirements on the result; compiler optimizations may produce surprising behavior. Common causes include out-of-bounds access, use-after-lifetime, signed overflow, uninitialized reads, and invalid casts.
Recommended practices include using standard library containers, initializing objects, minimizing raw ownership, enabling warnings, applying sanitizers during testing, documenting ownership and lifetime, and keeping functions small enough to reason about. Security-sensitive software should use code review, dependency management, fuzz testing, and appropriate organizational standards.
C++ standards evolve. C++20 and C++23 add language and library facilities, but portability depends on compiler support and project constraints. A professional developer selects a standard deliberately, records toolchain versions, and tests on supported platforms rather than assuming all implementations behave identically.
Chapter Summary
C++ development is a pipeline from source and headers through preprocessing, compilation, linking, and execution. Headers publish declarations, source files provide definitions, and namespaces organize names. Types determine valid operations and conversions; expressions combine values; control-flow statements determine execution; functions package reusable behavior. References provide aliases, pointers represent addresses, and const expresses non-modification. Standard streams support basic I/O, while compilers, build systems, debuggers, sanitizers, and Git form the professional toolchain. Safe C++ programming depends on explicit lifetime management, validated input, warnings, testing, and disciplined organization.
Key Terms
Translation unit: Source text after preprocessing.
Preprocessor: Tool that handles directives such as #include and conditional compilation.
Compiler: Program that translates C++ into object code.
Linker: Tool that combines object files and libraries.
Header: File containing declarations and reusable interface information.
Namespace: Named scope used to organize identifiers.
Type: Set of values and operations permitted for an object or expression.
Initialization: Establishment of an object's initial value.
Expression: Construct that produces a value or performs an operation.
Reference: Alias bound to an existing object.
Pointer: Object that stores an address.
Dangling pointer: Pointer referring to an object whose lifetime has ended.
Const correctness: Consistent use of const to express non-modification.
RAII: Resource-management technique tying resource lifetime to object lifetime.
Undefined behavior: Execution for which the C++ standard imposes no requirements.
Build system: Tool that manages compilation, dependencies, and linking.
Debugger: Tool for controlled execution and inspection of program state.
Version control: System for recording and coordinating changes to files.
Review Questions
Short Answer Questions
What is the difference between compilation and linking?
Why are declarations commonly placed in header files?
Distinguish a reference from a pointer.
What problem does const help prevent?
Why can 5 / 2 produce a different result from 5.0 / 2.0?
Long Answer Questions
Explain the complete C++ build pipeline and identify typical failures at each stage.
Discuss how source files, headers, namespaces, and the one-definition rule support modular design.
Explain value, reference, pointer, and constant-reference parameter passing with appropriate use cases.
Describe how a debugger, compiler warnings, sanitizers, and tests complement one another.
Explain RAII and why modern C++ prefers standard containers and smart pointers to unmanaged dynamic memory.
Analytical / Application Questions
A program compiles successfully but reports an unresolved external function during linking. Classify the error and propose a systematic diagnosis.
Design the file organization and validation strategy for a command-line program that reads ten floating-point measurements and reports their average.
Analyze the risks in a function that returns a pointer to a local variable, and propose two safe alternatives.
Multiple Choice Questions with Answers
Question 1
Which stage combines object files and libraries into an executable?
A. Preprocessing
B. Compilation
C. Linking
D. Parsing
Correct Answer: C. Linking
Question 2
What is the primary purpose of a header file?
A. To store executable machine instructions
B. To publish declarations and interface information
C. To replace the linker
D. To start program execution
Correct Answer: B. To publish declarations and interface information
Question 3
What is the value of 5 / 2 when both operands are of type int?
A. 2
B. 2.5
C. 3
D. 5
Correct Answer: A. 2
Question 4
Which operator dereferences a pointer?
A. &
B. ::
C. *
D. ->>
Correct Answer: C. *
Question 5
What does a const reference parameter generally provide?
A. A copy that must be modified
B. Access without copying and without modification through that reference
C. A nullable ownership handle
D. Guaranteed dynamic allocation
Correct Answer: B. Access without copying and without modification through that reference
Question 6
Which tool is primarily used to pause execution and inspect program state?
A. Linker
B. Debugger
C. Preprocessor
D. Package manager
Correct Answer: B. Debugger
Question 7
Why is std::vector generally preferred to a raw dynamic array for ordinary collections?
A. It prevents all possible logic errors
B. It manages storage and provides a standard interface
C. It always uses less memory
D. It can contain only integers
Correct Answer: B. It manages storage and provides a standard interface
Question 8
Which statement about a dangling pointer is correct?
A. It always contains nullptr
B. It points to an object whose lifetime has ended
C. It is a pointer to constant data
D. It is required for RAII
Correct Answer: B. It points to an object whose lifetime has ended
Question 9
What does std::getline normally read?
A. One complete line, including spaces until the delimiter
B. Only the next integer
C. Only the next non-whitespace token
D. A compiler diagnostic
Correct Answer: A. One complete line, including spaces until the delimiter
Question 10
Which practice is most appropriate for a shared project repository?
A. Commit passwords for convenience
B. Commit generated binaries after every build
C. Use small coherent commits and exclude build artifacts
D. Avoid reviewing differences before committing
Correct Answer: C. Use small coherent commits and exclude build artifacts