Object-Oriented Programming with C++

Chapter 1
Programming and Modern C++ Foundations

Bachelor Program in Mechatronics Engineering
Object-Oriented Programming with C++   •   Bachelor

Programming and Modern C++ Foundations

Introduction

Mechatronic systems combine mechanical structures, sensors, actuators, electronics, computation, and control. Software is the component that transforms measured data into decisions and commands. C++ is particularly important because it combines abstraction mechanisms associated with object-oriented programming with direct control over memory, timing, and hardware interfaces. It is used in embedded controllers, robotics, computer vision, simulation, industrial automation, and real-time systems.

This chapter establishes the foundations required for later study of classes, inheritance, polymorphism, templates, data structures, and embedded software design. The emphasis is not merely on memorizing syntax. Good C++ programming requires understanding how source code becomes an executable program, how data is represented, how control decisions are expressed, and how interfaces can be designed safely.

Learning Objectives

After completing this chapter, you should be able to:

  • distinguish major programming paradigms and explain the role of object-oriented programming;
  • describe preprocessing, compilation, linking, and execution;
  • organize a program into source and header files;
  • use namespaces, fundamental types, variables, constants, and operators correctly;
  • construct conditional and iterative control flow;
  • define and call functions using values, references, pointers, and const correctness;
  • identify common type, lifetime, ownership, and interface errors;
  • select and use an appropriate C++ development toolchain.

1. Programming Paradigms and Computational Thinking

A programming paradigm is a general style for expressing computation. A paradigm influences how a programmer decomposes a problem, represents data, and controls operations. Real programs often combine several paradigms.

Imperative programming describes computation as a sequence of commands that change program state. A variable may be assigned a new value, and a loop may repeatedly update that value. This style maps naturally to low-level hardware operations and is important in embedded control.

Procedural programming organizes imperative instructions into procedures or functions. Functions reduce repetition and provide boundaries for testing and reuse. A motor-control program, for example, might contain functions named readEncoder, computeControlSignal, and writePwm.

Object-oriented programming organizes software around objects that combine state and operations. A TemperatureSensor object may contain calibration data and provide a readCelsius operation. Encapsulation protects representation details; abstraction exposes only useful operations; inheritance can express specialized relationships; polymorphism allows a common interface to support different implementations.

Generic programming expresses algorithms independently of specific data types. C++ templates support this approach. A sorting algorithm can be written once and used with integers, floating-point values, or user-defined types, provided the required operations are available.

Functional programming emphasizes expressions, transformation of values, and avoidance of unintended state changes. C++ is not purely functional, but lambdas, standard algorithms, and immutable data can support functional techniques.

These paradigms are complementary. A robotics application may use objects to represent sensors, procedures to initialize hardware, templates for reusable mathematical operations, and functional algorithms to process measurement collections.

Core Problem-Solving Model

A useful engineering programming process is:

  1. state the problem and system constraints;
  2. identify inputs, outputs, and relevant state;
  3. design an algorithm or model;
  4. select data representations;
  5. implement the design;
  6. compile and test incrementally;
  7. verify behavior against requirements;
  8. maintain and document the system.

For a control application, constraints may include sampling period, memory capacity, numerical precision, safety response time, and hardware failure modes. Correct syntax is therefore necessary but not sufficient: the program must also satisfy functional and physical requirements.

2. From Source Code to Executable Program

A C++ program passes through several stages before it runs. The exact implementation differs between toolchains, but the conceptual pipeline is stable.

C++ build pipeline Source.cpp / .hpp Preprocessexpanded text Compileobject code Linkexecutable The operating system or embedded loader then starts execution.
C++ build pipeline from source files to executable program
Figure: C++ build pipeline from source files to executable program

Figure: Conceptual C++ build pipeline.

Preprocessing

The preprocessor handles directives such as #include and conditional compilation. An include directive makes declarations from another file available to the current translation unit. Modern C++ generally favors header guards or #pragma once and prefers declarations in headers with definitions in source files. The preprocessor performs textual transformation; it does not understand C++ types in the same way as the compiler.

Compilation

Each source file is compiled into an object file. The compiler checks syntax and many semantic rules, such as type compatibility and whether a referenced function has been declared. A translation unit consists of a source file after included headers and preprocessing have been applied.

Linking

The linker combines object files and libraries. It resolves references such as a function call in one source file to a definition in another. A missing definition commonly produces an undefined-reference or unresolved-external error. Multiple incompatible definitions may produce a multiple-definition error.

Compilation errors concern individual translation units; linking errors concern the construction of the complete program. Runtime errors occur after successful linking and may include invalid memory access, incorrect calculations, or failures to meet timing requirements.

3. Source Organization and Namespaces

A small program can be placed in one file, but professional systems normally separate interfaces from implementations.

// sensor.hpp
#pragma once
namespace plant {
    double convertToCelsius(double voltage);
}
// sensor.cpp
#include "sensor.hpp"
namespace plant {
    double convertToCelsius(double voltage) {
        return (voltage - 0.5) * 100.0;
    }
}
// main.cpp
#include <iostream>
#include "sensor.hpp"
int main() {
    std::cout << plant::convertToCelsius(0.73) << '\n';
}

The header declares an interface, while the source file defines the function. The main function is the usual entry point of a hosted C++ program and returns an integer status to the operating system.

A namespace prevents unrelated identifiers from colliding. The qualified name plant::convertToCelsius explicitly identifies the function in namespace plant. using namespace std; is discouraged in headers because it can introduce many names into every file that includes the header. Prefer qualified names such as std::cout and narrowly scoped using declarations when appropriate.

The One Definition Rule requires that entities have an appropriate number of definitions across a program. Header files should normally contain declarations, templates, inline definitions, or class definitions designed for inclusion. Non-inline ordinary function definitions placed in a widely included header can cause multiple-definition errors.

4. Types, Objects, and Variables

A type specifies the set of values an object can represent and the operations permitted on those values. An object is a region of storage with a type and a lifetime. A variable is a named object or reference.

Common fundamental types include:

CategoryExamplesTypical use
Booleanboollogical conditions
Characterchar, char8_tcharacter or byte-oriented data
Integershort, int, long, long longcounts and discrete values
Floating pointfloat, double, long doublemeasured or continuous quantities
No valuevoidfunctions that return no value

The exact size and range of integer types are implementation-dependent, although minimum guarantees exist. Use std::int32_t or related types from <cstdint> when an exact-width integer is required and the implementation provides it. Use std::size_t for sizes and indexing of standard containers, while remembering that it is unsigned.

Initialization is preferable to leaving objects uninitialized:

int count{0};
double gain{2.5};
bool enabled{true};

Brace initialization helps prevent narrowing conversions. For example, assigning a floating-point value to an integer with braces is rejected because information could be lost.

A declaration introduces a name and type. A definition also creates the object or supplies a function body. Scope describes where a name can be used. Lifetime describes how long the object exists. These concepts are related but not identical.

Conversions and Type Safety

C++ permits implicit conversions, but they can conceal errors. Converting a double to an int discards the fractional part; converting a signed integer to an unsigned integer can produce surprising results for negative values. Use explicit casts only when the conversion is understood and justified:

double ratio{static_cast<double>(samples) / duration};

In engineering software, physical units should be made visible through naming, documentation, or dedicated types. A variable named period_ms communicates more than a variable named p. For high-assurance applications, strong unit libraries can prevent the accidental addition of incompatible quantities.

5. Operators and Expressions

An expression produces a value or causes an effect. Arithmetic operators include +, -, *, /, and %; comparison operators include ==, !=, <, >, <=, and >=; logical operators include &&, ||, and !.

The expression a / b performs integer division if both operands are integers. For example, 5 / 2 produces 2, whereas 5.0 / 2.0 produces 2.5. Division by zero is invalid. The remainder operator applies to integral operands and is useful for cyclic counters.

Operator precedence determines grouping, but parentheses improve readability and prevent mistakes:

bool safe{(temperature < limit) && sensorOnline};

The assignment operator = changes an object; the equality operator == compares values. Confusing them is a common defect. The increment operators ++i and i++ both increment i, but their expression values differ: the prefix form yields the incremented value, while the postfix form yields the previous value.

The conditional operator selects between two expressions:

double command{enabled ? requestedCommand : 0.0};

Use it for short, clear selections. A complicated conditional expression should generally be replaced by an if statement or a named function.

6. Control Flow

Selection

An if statement executes code conditionally:

if (speed > maximumSpeed) {
    speed = maximumSpeed;
} else {
    applyNormalControl();
}

Multiple mutually exclusive cases can use else if. A switch statement is suitable when selecting among discrete integral or enumeration values. Include a default branch when an unexpected value requires explicit handling.

Iteration

A while loop is appropriate when the number of iterations is determined by a condition. A for loop is convenient for a counter-controlled process, and a range-based for loop is often clearest for traversing a collection:

for (const auto& measurement : measurements) {
    process(measurement);
}

The reference avoids copying each element, and const prevents modification. Every loop must make progress toward termination or intentionally operate forever, as in a real-time service loop. In safety-critical systems, loop bounds and timing behavior should be explicit.

break exits a loop, while continue skips to the next iteration. Excessive use can obscure control flow; extracting a function or expressing the loop condition clearly is usually preferable.

7. Functions and Interfaces

A function packages a computation behind a name and parameter interface:

double clamp(double value, double lower, double upper) {
    if (value < lower) return lower;
    if (value > upper) return upper;
    return value;
}

This function assumes lower ≤ upper. If that condition is not guaranteed by the surrounding design, the interface should define how invalid bounds are handled, perhaps by validation or an exception.

Parameters passed by value are copied or moved into the function. This is appropriate for small scalar values and when a function needs an independent local value. A reference parameter aliases an existing object. A pointer represents an address and may be null. A const reference permits read-only access without copying a potentially large object.

void calibrate(const SensorData& data);
void resetController(Controller& controller);
void configure(Hardware* hardware);

The first function promises not to modify data through that reference. The second may modify the caller's controller. The third can represent an optional or absent object, so it must check for null before dereferencing.

Functions should have one clear responsibility and should expose preconditions, postconditions, units, ownership expectations, and error behavior. A function that returns a pointer should document who owns the pointed-to object. Prefer return-by-value for newly created values; modern C++ can avoid unnecessary copying through move semantics and return-value optimization.

8. References, Pointers, and Memory Safety

A reference is an alias to an existing object. It normally must be initialized and cannot be reseated to refer to a different object. A pointer stores an address and can be null or changed to point elsewhere.

int value{10};
int& alias{value};
int* address{&value};

alias = 20;       // changes value
*address = 30;    // changes value through the pointer

The address-of operator & obtains an address, and the dereference operator * accesses the object at a valid address. Dereferencing a null pointer, a dangling pointer, or an invalid address produces undefined behavior. A dangling pointer refers to an object whose lifetime has ended.

Automatic local objects are destroyed when their scope ends. Dynamic allocation with new and delete is error-prone because every successful allocation must have a matching and appropriate release. Modern C++ therefore favors resource-owning library types such as std::unique_ptr, std::shared_ptr, and standard containers. This design principle is called Resource Acquisition Is Initialization, or RAII: an object's constructor acquires a resource and its destructor releases it.

Use raw pointers and references mainly as non-owning access mechanisms unless a different ownership convention is explicitly documented. std::unique_ptr expresses exclusive ownership; std::shared_ptr expresses shared ownership but introduces reference-counting overhead and can create cycles. Ownership should be designed rather than inferred from syntax.

9. Const Correctness

const communicates that an object should not be modified through a particular name or interface:

const double nominalVoltage{24.0};
void print(const std::string& name);

A pointer can have several forms:

const int* p1;       // pointer to const int
int* const p2 = &x;  // const pointer to int
const int* const p3 = &x; // const pointer to const int

Const correctness improves reliability because it makes unintended mutation harder and documents interfaces. A member function declared const promises not to modify the observable state of its object, allowing it to be called on const objects. In later object-oriented programming, this becomes an important part of class design.

const does not make an object universally immutable if another non-const alias exists, and it does not automatically make pointed-to data safe from concurrent access. Thread safety requires additional synchronization or a design that avoids data races.

10. Development Toolchains

A C++ toolchain commonly includes a compiler, linker, standard library, debugger, build system, formatter, static analyzer, package manager, and version-control system. Examples of compilers include GCC, Clang, and Microsoft Visual C++. Build systems such as CMake generate platform-specific build files and help manage dependencies.

A typical command-line workflow might be:

g++ -std=c++20 -Wall -Wextra -Wpedantic -c sensor.cpp -o sensor.o
g++ -std=c++20 -Wall -Wextra -Wpedantic -c main.cpp -o main.o
g++ sensor.o main.o -o application

The first two commands compile source files into object files; the final command links them. Warning options do not replace testing, but they expose suspicious constructs early. A debugger permits breakpoints, stepping, inspection of variables, and call-stack analysis. Sanitizers can detect classes of memory and undefined-behavior defects during testing.

A professional workflow should use reproducible builds, compiler warnings, automated tests, code review, version control, and continuous integration. Select the language standard deliberately, document compiler versions, and avoid relying on undefined or implementation-specific behavior without a justified platform constraint.

Worked Example: A Safe Motor Command Interface

The following illustrative example shows several foundational ideas:

#include <algorithm>
#include <iostream>

namespace drive {

double limitCommand(double requested, double maximum) {
    if (maximum < 0.0) {
        return 0.0;
    }
    return std::clamp(requested, -maximum, maximum);
}

void report(const double command) {
    std::cout << "Command: " << command << "\n";
}

}

int main() {
    const double requested{1.25};
    const double maximum{1.0};
    const double command{drive::limitCommand(requested, maximum)};
    drive::report(command);
}

The namespace prevents naming collisions. Braced initialization is used throughout. const communicates that the values are not reassigned. The function limits the command to the interval from −maximum to +maximum, provided maximum is nonnegative. If requested = 1.25 and maximum = 1.0, the output command is 1.0. The function also defines behavior for an invalid negative limit by returning zero; a production interface might instead report an error because silently changing a safety parameter may be inappropriate.

Case Study: Illustrative Conveyor Monitoring Controller

Background

A small conveyor transports parts through a mechatronics laboratory. An encoder supplies speed measurements, a photoelectric sensor detects parts, and a motor controller accepts a normalized command between −1.0 and 1.0.

Problem

An early prototype used global variables, implicit conversions, duplicated limit checks, and a pointer that was sometimes null. Operators observed inconsistent behavior when the sensor disconnected and when the requested speed exceeded the motor rating.

Analysis

The defects involved both software structure and system requirements. The program did not clearly distinguish measured speed, requested speed, and motor command. It also failed to document whether a pointer represented optional hardware or required hardware. These ambiguities made testing difficult.

Approach and Solution

The redesigned interface used named types and units in variable names, placed related operations in a namespace, passed measurement records by const reference, and used a dedicated limiting function. Initialization was explicit, warnings were enabled, and tests covered normal operation, disconnected sensing, zero limits, and saturation at the command boundary. A null hardware pointer was handled as a fault state rather than dereferenced.

Outcome

The revised design made invalid states more visible, reduced duplicated logic, and allowed the command-limiting function to be tested independently. It did not by itself guarantee physical safety: emergency stopping, electrical interlocks, watchdog behavior, and validation of the complete control system remained necessary.

Learning Points

Good C++ foundations support engineering reliability. Clear interfaces, explicit units, const correctness, defined ownership, and independent tests reduce defects. However, software correctness must be combined with hardware safeguards, hazard analysis, and appropriate verification procedures.

Challenges, Limitations, and Professional Practice

C++ offers performance and expressive power, but that flexibility creates risks. Undefined behavior can result from out-of-bounds access, invalid lifetime assumptions, data races, signed overflow in certain contexts, and incorrect pointer use. Complex build systems can also produce configuration-dependent failures.

For mechatronics applications, numerical precision, timing, interrupt behavior, concurrency, and hardware variation deserve special attention. Floating-point values represent approximations; equality comparisons may be inappropriate when measurements contain tolerance. Real-time systems may restrict dynamic allocation or exceptions in time-critical paths. Such restrictions should be derived from system requirements rather than applied mechanically.

Ethically, engineers must not claim that a program is safe merely because it compiles or passes a few tests. Safety-related software requires traceable requirements, documented assumptions, independent review, fault handling, and evidence appropriate to the risk. Security also matters: unvalidated inputs, unsafe update mechanisms, and exposed debug interfaces can affect physical equipment and human safety.

Contemporary Developments

Modern C++ emphasizes safer abstractions without abandoning performance. Standard containers, algorithms, smart pointers, concepts, ranges, and modules can reduce common errors and improve expressive clarity. Modules aim to provide a more structured alternative to extensive textual inclusion, although toolchain support and project migration practices vary.

AI-assisted coding tools can suggest code, tests, and explanations, but generated code must be reviewed, compiled, tested, and checked against licensing, security, safety, and confidentiality requirements. An AI tool does not establish correctness or responsibility. In engineering practice, human verification remains essential.

Chapter Summary

Programming paradigms provide different ways to structure computation; C++ supports imperative, procedural, object-oriented, generic, and selected functional techniques. A C++ program is preprocessed, compiled into translation-unit object code, and linked with other objects and libraries. Headers normally describe interfaces, source files provide implementations, and namespaces prevent name collisions.

Types define values and permitted operations, while variables represent named objects with scopes and lifetimes. Explicit initialization, careful conversions, and meaningful units improve reliability. Operators form expressions, and control-flow statements express decisions and repetition. Functions create reusable interfaces whose parameters should communicate copying, mutation, optionality, and ownership.

References provide aliases, pointers provide address-based access, and both require lifetime discipline. Const correctness documents and restricts mutation. RAII and standard resource-owning types are preferred to unmanaged dynamic allocation. Finally, a professional toolchain combines compiler diagnostics, build automation, debugging, testing, static analysis, version control, and review. These foundations are essential for safe and maintainable mechatronics software.

Key Terms

Programming paradigm: A general approach to organizing and expressing computation.

Imperative programming: Programming based on commands that change program state.

Object-oriented programming: A paradigm that combines state and operations in objects.

Encapsulation: Restricting direct access to representation details through an interface.

Abstraction: Representing essential behavior while hiding unnecessary implementation detail.

Namespace: A named scope used to organize identifiers and avoid collisions.

Translation unit: A source file after preprocessing and inclusion of headers.

Compiler: A tool that translates source code into object or machine code and diagnoses many errors.

Linker: A tool that combines object files and resolves cross-file references.

Type: A specification of possible values, storage representation, and valid operations.

Object: A region of storage with a type and a lifetime.

Reference: An alias to an existing object.

Pointer: An object that stores an address or a null value.

Const correctness: The disciplined use of const to prevent unintended modification.

Scope: The region of program text in which a name can be used.

Lifetime: The interval during which an object exists.

RAII: A resource-management technique in which object lifetime controls resource ownership.

Undefined behavior: Program behavior for which the C++ standard imposes no requirements.

Toolchain: The compiler, linker, libraries, debugger, build tools, and related development utilities.

Review Questions

Short Answer Questions

  1. What is the difference between compilation and linking?
  2. Why are namespaces useful in a multi-file C++ project?
  3. Distinguish an object’s scope from its lifetime.
  4. When is a const reference preferable to pass-by-value?
  5. What problem does RAII address?

Long Answer Questions

  1. Explain how object-oriented, procedural, generic, and functional techniques can coexist in a mechatronics application.
  2. Describe the organization of a header file, source file, and main function in a small C++ project.
  3. Discuss the differences among values, references, pointers, and ownership.
  4. Explain why implicit conversions can be dangerous in engineering software and propose practices for controlling them.
  5. Describe a professional C++ development workflow from editing through testing and deployment.

Analytical / Application Questions

  1. Design a function interface for a sensor-reading operation that may fail. Explain your choices of return type, parameters, const, and error handling.
  2. A motor command is requested as 1.4, while the permitted normalized range is −1.0 to 1.0. Propose an implementation and identify assumptions, boundary behavior, and tests.
  3. A program works when all code is in one file but produces an unresolved-reference error after being separated into controller.hpp, controller.cpp, and main.cpp. Analyze likely causes and give a systematic debugging procedure.

Multiple Choice Questions with Answers

Question 1

Which stage combines object files and resolves references between separately compiled source files?

A. Preprocessing

B. Compilation

C. Linking

D. Execution

Correct Answer: C. Linking

Question 2

What is the primary purpose of a namespace?

A. To allocate dynamic memory

B. To prevent identifier collisions

C. To convert floating-point values

D. To start program execution

Correct Answer: B. To prevent identifier collisions

Question 3

What value does the expression 5 / 2 produce when both operands are of integer type?

A. 2

B. 2.5

C. 3

D. 0

Correct Answer: A. 2

Question 4

Which parameter form allows read-only access without copying a potentially large object?

A. T

B. T&

C. const T&

D. T* without any checks

Correct Answer: C. const T&

Question 5

What is a principal danger of dereferencing a null pointer?

A. Guaranteed integer rounding

B. Undefined behavior

C. Automatic namespace creation

D. A linking optimization

Correct Answer: B. Undefined behavior

Question 6

Which declaration represents a pointer to a const integer?

A. int* const p

B. const int* p

C. int& p

D. const int p*

Correct Answer: B. const int* p

Question 7

Which C++ feature is most directly associated with exclusive ownership of a dynamically managed object?

A. std::unique_ptr

B. std::weak_ptr

C. A raw integer

D. A namespace

Correct Answer: A. std::unique_ptr

Question 8

What does a range-based loop using const auto& generally achieve when traversing a collection?

A. It modifies every element without restriction

B. It avoids element copies and prevents modification through the loop variable

C. It deletes the collection after each iteration

D. It converts every element to an integer

Correct Answer: B. It avoids element copies and prevents modification through the loop variable

Question 9

In the motor-command example, what is the result of limiting a requested command of 1.25 with a maximum of 1.0?

A. −1.25

B. 0.0

C. 1.0

D. 2.25

Correct Answer: C. 1.0

Question 10

Which practice best supports reproducible professional C++ development?

A. Ignoring compiler warnings

B. Using undocumented implementation behavior

C. Combining version control, documented builds, testing, and review

D. Placing all resources in unmanaged global variables

Correct Answer: C. Combining version control, documented builds, testing, and review

Object-Oriented Programming with C++
Chapter 1 of 14