Skip to content

Search is only available in production builds. Try building and previewing the site to test it out locally.

3.1 C++ Introduction and Types

C++ is a powerful, object-oriented programming language that has played a significant role in software development since its inception. Developed by Bjarne Stroustrup at Bell Labs (now AT&T Labs) between 1983 and 1985, C++ built upon the foundations of the C language. C itself has a rich lineage, tracing back to the B language (1970) by Ken Thompson and ultimately to the BCPL language.

Initially known as “C with Classes,” C++ extended the C language by introducing object-oriented programming (OOP) features. These include the ability for engineers to define classes and create objects from these classes, facilitating a more organised and modular approach to software design. Beyond OOP, C++ also enhanced C with features like improved type checking.

C++ rapidly gained widespread adoption, largely due to its strong resemblance to C’s syntax. This familiarity allowed developers to integrate existing C code seamlessly, making it a natural progression for many projects. While C++ embraces an object-oriented organisational structure, it’s not a purely object-oriented language; it’s a hybrid language. This means it retains the efficiencies of C, such as direct access to types and pointers, while also offering the benefits of OOP.

To address inconsistencies between various C++ compilers, the ANSI/ISO (American National Standards Institute/International Organisation for Standardisation) committee adopted a worldwide uniform language specification in 1998 (ISO/IEC 14882). Unfortunately, even today, not all compilers fully support this standard, leading to potential compatibility issues where code developed with one compiler on a particular operating system might not compile on another.

However, the standardisation efforts continue, with significant updates like C++11 (approved in 2011) and C++14 (approved in 2014). The most recent version, C++23, was approved in February 2023, and discussions for C++26 are already underway, demonstrating the ongoing evolution of the language.

C++ in Embedded Systems: Bridging Low-Level Control and High-Level Abstraction

Section titled “C++ in Embedded Systems: Bridging Low-Level Control and High-Level Abstraction”

C++ significantly extends the capabilities of C by integrating OOP features and modern memory management mechanisms like smart pointers. This unique blend makes it a popular and powerful choice for contemporary embedded systems. Its core strength lies in its ability to bridge the gap between low-level hardware control, inherited from C, and the high-level abstractions provided by OOP and templates. This dual capacity allows developers to write highly efficient machine code that optimises system resources while maintaining a clean, modular design.

C++ is particularly effective for complex embedded applications, facilitating the development of scalable and maintainable code without compromising performance. Its strong compatibility with legacy C libraries further enhances its utility, allowing for the reuse of existing codebases within modern embedded ecosystems.

Practical Applications of C++ OOP in Smart Edge Environments

Section titled “Practical Applications of C++ OOP in Smart Edge Environments”

The object-oriented capabilities of C++ find numerous practical applications in smart edge environments:

  • Device Drivers: C++ is extensively used for writing device drivers, providing both the necessary low-level hardware control and the benefits of code abstraction through OOP. This combination results in efficient and flexible drivers for modern microcontrollers and peripherals. We discuss bit manipulation operations in this chapter.
  • RTOS Integration: Many Real-Time Operating Systems (RTOS) integrate effectively with C++, enabling embedded developers to manage concurrency, multithreading, and task scheduling efficiently. Features like RAII (Resource Acquisition Is Initialisation) and smart pointers are particularly valuable for robust resource management in RTOS-based projects, ensuring resources are properly acquired and released. We discuss this in Chapter 5.
  • Communication Protocols: C++ excels in implementing custom communication protocols and leveraging existing networking libraries. Its performance capabilities enable optimised message handling and error detection algorithms, which are crucial for efficient and reliable data transfer in connected edge systems. 
  • Graphical User Interfaces (GUIs): C++ and its object-oriented approach significantly simplifies the implementation of graphical user interfaces for embedded systems. Frameworks like Qt are prime examples of C++‘s strength in developing rich GUIs for embedded devices. Interesting, complex real-time 3D displays are becoming commonplace in edge applications such as modern vehicle navigation and parking support displays.
  • Industrial Automation & IoT: C++ plays a crucial role in large-scale IoT and industrial automation projects, where its OOP features enable the creation of highly modular and maintainable applications. Notably, C++ accounts for a large fraction of automotive embedded software, underscoring its importance in industries demanding precision and reliability.

While C++ offers substantial benefits, certain OOP features, if not managed carefully, can introduce performance or memory overheads in resource-constrained embedded environments. These considerations are crucial for optimising embedded system performance and are often discussed in detail in specialised literature. 

This chapter focuses on providing examples of programming in the C of C++. It covers the fundamentals of the language for functional programming and prepares the reader for the next chapter in which classes are introduced.

The first program in a new language should always be “Hello world!”. There is a surprising amount of knowledge required to understand this, the simplest of programs:

// Hello World Application
#include <iostream> // 1
using namespace std; // 2
int main() { // 3
cout << "Hello world!\n"; // 4
return 0; // 5
}
  1. The <iostream> header file is required for the cout call. The #include directive is a C++ pre-processor1 instruction that causes the compiler, at compile time, to insert the contents of the specified file into your program at that location. This allows you to import libraries of code into your application as needed. The overall size of your application will increase by the size of each included header file, but the inclusion of <iostream> is necessary to perform any input/output operations, such as printing to the screen in C++ (this is discussed further later).
  2. The using namespace statement is a feature of C++ that imports the header file into the appropriate namespace (we will explain this properly later). While convenient for small programs, using the entire std namespace in larger projects is often discouraged to avoid name collisions.
  3. The main() function is the entry point for all command-line C and C++ applications. In standard C++, a return type must always be specified; main() returns an int value.
  4. The cout call sends the string "Hello World!" to the standard output stream, typically the screen. The output stream << operator evaluates the parameter that follows it and places it onto the output stream. To end a line of output, you can use either << "\n" or << endl.
  5. The return 0 statement tells the function to return the integer value 0 to the calling process. This statement is not strictly necessary in main(), as it will return 0 by default if no return value is specified.
    The source code for this example is contained in a file called HelloWorld.cpp.

You might assume that this is the shortest possible C++ program. It is not. The shortest valid C++ program is :

int main(){}

In this case, no libraries are needed because there is no input or output. While historical versions of C allowed omitting the return type, modern C++ requires main to return int. This demonstrates C++’s flexibility in assuming default states and values — although this flexibility is also one of its greatest weaknesses.

In the upcoming and most modern agreed version of C++ (C++23), the Hello World application will be adapted slightly as follows:

#include <print>
int main() {
std::println("Hello World!");
}

Few compilers currently support C++23 and g++ only supports the standard with the -std=c++2b compiler setting in early releases. The previous code will still work fine.

Code Cloze
C++

Complete the Simple Hello World Program

Drag snippets from the pool into the blanks so the program produces the output shown, then click Submit.

1#include <·····>
2using namespace ·····;
3
4int main() {
5 ····· << "Hello, world!
6";
7 return ·····;
8}
Expected Output
Hello, world!

Available Snippets

std
printf
System
cout
NULL
iostream
0
void
stdio.h

While closely related in the software development process, a C/C++ compiler and an Integrated Development Environment (IDE) serve distinct purposes. A compiler is a specialised program that translates human-readable source code (written in C or C++) into machine code or other low-level code that a computer’s processor can directly understand and execute. An IDE, on the other hand, is a comprehensive software application that bundles various development tools into a single, cohesive environment. This typically includes a source-code editor, build automation tools (which utilise compilers), and a debugger, among other features like syntax highlighting and code completion, all designed to streamline the entire software development workflow and enhance developer productivity. In essence, the compiler is the engine that converts your code, while the IDE is the complete workshop that provides all the tools you need to build and refine your software.

Octo-Dog

Figure 1. A terrifying AI representation of Octo Dog!

There are several C++ compilers and Integrated Development Environments that you can use for this module. Once the compiler is ANSI compliant there should be no issue in using it with this module as we just require a standard non-windowing compiler. A Unix/Linux compiler will also work. The current recommended compiler is GCC (GNU Compiler Collection) and the recommended IDE is VS Code. Other compilers such as Clang/LLVM (default compiler on MacOS with XCode) and Microsoft Visual C++ (MSVC) are also fine but not all will be available during the examination.

Integrated Development Environments (IDEs)

Section titled “Integrated Development Environments (IDEs)”

Visual Studio Code (VS Code) is a lightweight, cross-platform source code editor widely used in modern C and C++ development. It offers powerful features such as intelligent code completion (IntelliSense), debugging, integrated terminal, and support for version control systems like Git. With extensions like the C/C++ extension from Microsoft, VS Code provides syntax highlighting, code navigation, refactoring tools, and seamless integration with build systems such as CMake and Make. Its flexibility, customisability, and active extension ecosystem make it a popular choice for both professional developers and students working on C/C++ projects across various platforms. See Figure 1.

Figure 2. VS Code (dark mode) for C/C++ Development.

Eclipse CDT (C/C++ Development Tooling) is a powerful, open-source IDE widely used for C and C++ development, particularly in embedded and enterprise environments. Built on the Eclipse platform, it offers advanced features such as code completion, static code analysis, refactoring tools, and a fully integrated debugger. Eclipse CDT supports a range of build systems, including Make, CMake, and managed builds, and integrates well with version control systems like Git through additional plugins. Its extensive plugin ecosystem and strong support for cross-compilation toolchains make it especially suitable for complex, large-scale, and embedded C/C++ projects.

695 Figure 3. The Eclipse CDT Integrated Development Environment (IDE)

Once you have your IDE in place you can use this simple example to test that it is working correctly:

// Hello World Application
#include<iostream>
using namespace std;
int main() {
cout << "Hello EEN1097!\n";
return 0;
}

A variable is a data item stored in a block of memory that has been reserved for it. The variable type defines the amount of memory reserved. This is illustrated in Figure 4. Figure 4. An example memory space with variables defined.

C++ supports many variable types, such as:

  • int integers ( -5, -1, 0, 1, 3 etc.)
  • char characters ( ‘a’, ‘b’, ‘c’, etc.) (typically 8-bits in size; the range 0-255 applies to an unsigned char)
  • float floating point numbers (4.5552324, 1.001, -4.5553 etc.)
  • double larger more accurate floating point numbers (i.e. to more decimal places or with a larger magnitude)
  • long (long int) larger integer value range than int if using 16-bit int values.
  • bool contains the true or false value (i.e. a 1 or 0 respectively).
  • short (short int) smaller integer value range than int
  • unsigned int 0,1,2,3, etc.
  • unsigned long 0,1,2,3,4, etc.
  • auto (C++11) tells the compiler to automatically deduce the type of a variable from its initialiser. (We will use this later)
  • register was used in older C/C++ to suggest storing a variable in a CPU register for speed. It is deprecated since C++11 and was effectively removed in C++17 (though the keyword remains reserved).

Variables may be defined using these types, as illustrated in Figure 3.

int main() {
float a = 25.0;
int b = 545;
double c = 123.0;
char d = 'A'; // ASCII value of 'A' is 65.
bool e = true;
}

The source for this test program is given in SizeofVariables.cpp. Using these variables we can assign values to them, modify them and print them to the output if required:

// Variables Application
#include<iostream>
using namespace std;
int main() {
int x = 7, y = 10; //1
x=2; // assign variable x a value of 2
x++; // increment x by 1
x+=2; // increment x by 2
cout << "x equals " << x << "\n"; //2
}

The source for this is in Variables1.cpp

  1. You can define multiple variables on one line.
  2. At this point, the program will result in an output of x equals 5

Notes about variables:

  • Variables can be introduced as required!
  • cin allows values to be read in.
  • C++ will usually complain if you assign a value of one type to a variable of another type of lower resolution.
  • Variables can be initialised as they are defined.
  • We can use the const keyword to protect the value of a variable from change. In embedded applications, const variables may be stored in ROM (rather than RAM) and will only be placed there once, no matter how many times they are used. As such, use const rather than #define whenever possible. In modern C++, constexpr is often preferred for constants that can be evaluated at compile time.
  • A volatile variable is one that can change outside of the control of the compiler, such as value changed by hardware, threading or interrupts. We use the volatile keyword to tell the compiler not to perform any form of optimisation on this data. We can also set this value as const volatile to prevent the programmer from changing this value — It can still change, but outside the control of the programmer

The lab below is pre-loaded with the textbook example: float a = 25.0;, int b = 545;, and double c = 123.0;. Drag any type from the palette onto the memory strip to allocate it. Click a placed variable to rename it or change its value.

C++ Memory Lab

Drag a type onto memory and watch the variable claim the bytes its size demands.

Memorybase = 0x1000
16 / 16 bytes used0 free
+0
+4
+8
+C
a
25.0
b
545
c
123.0
byte 0
4
8
12

Type Palette — drag onto memory

bool8-bit
char8-bit
short16-bit
int32-bit
unsigned int32-bit
long64-bit
unsigned long64-bit
float32-bit
double64-bit
integer types
floating point
character
boolean

Declared variables

TypeNameValueAddressSize
floata25.00x10000x10034 bytes (32-bit)
intb5450x10040x10074 bytes (32-bit)
doublec123.00x10080x100F8 bytes (64-bit)
Try this:Click Textbook example to load a, b, c. Then drag a char onto memory — notice it claims a single byte, same as a bool, while double claims eight. Click any placed variable to rename it or change its value. With 16 bytes of memory there's no room for all five textbook variables at once — that's the point: char and bool are cheap, double is expensive.

There are certain conversion rules for basic types:

// Using variables with automatic conversion
#include<iostream>
int main() {
int x,y; //(see 1)
x = 6.73; // x becomes 6
cout << "x = " << x << "\n";
char c = 'w'; // (see 2)
cout << "c = " << c << "\n";
x = c; // x becomes the integer
// equivalent of 'w' which is 119
cout << "x = " << x << "\n";
y = 2.110; // y becomes 2
double d; // (see 3)
d = y; // d becomes 2.0
cout << "d = " << d << "\n";
const float pi = 3.14159;
// (see 4)
//pi = 223.34; // would be an error if included
return 0;
}

The source for this is in Variables2.cpp

  1. x and y are being “declared” as variables. Variables in C++ are not automatically initialised to zero, so it would be better practice to use the statement int x=0, y=0;
  2. The variable c is initialised as it is defined.
  3. The variable d is introduced as required.
  4. The pi variable is defined as constant so that it cannot be modified without causing a compile-time error.

This program will output:

Terminal window
x = 6
c = w
x = 119
d = 2

The byte size of your variables matters and can have a significant impact on your code. For embedded applications floating point operations are very expensive, especially if you do not have a hardware floating point unit (what was once called a maths coprocessor). On the other hand, if you choose incorrect precision then strange things can happen. Take this very short segment of code:

#include <print>
int main(){
float balance = 200000000.0f;
balance = balance + 1.0f;
std::println("My balance is now €{:12.2f}", balance);
}

What will this output be? Well you might expect that the balance of my bank account would now be €200,000,001, but when compiled using a 32-bit compiler it is actually:

Terminal window
My balance is now €200000000.00

And I have lost €1. I can live with that, but if it was a ‘for loop’ with 100,000,000 x €1 lodgements, I would lose them too! Big numbers tend to consume small numbers on embedded devices.

Why does this happen? Well the precision of a 32-bit float is about 7 decimal places and since this number will actually be stored as 2.0e+8, it cannot represent 2.00000001e+8. We could easily solve this by using 64-bit floating point numbers in this case, but please remember that the same problem will recur, just with bigger numbers.

Code Output
C++

Integer Division vs. Floating Point

The scope of a variable is the area in a program where the variable is visible and valid. If we examine the code segment:

void someFunction() {
int y = 5;
x++; // invalid - x is not defined in someFunction()
y++; // valid - y now equals 6
}
int main() {
int x = 1;
x++; // valid - x now equals 2
y++; // invalid - y is not defined in main()
}

The variable y is defined in someFunction() and so is only valid in that function. x is defined in the main() function and so is only valid in that method.

A more complex case can be seen below:

main() {
int x = 7;
cout << "x = " << x << "\n";
{
cout << "x = " << x << "\n";
int x = 2;
cout << "x = " << x << "\n";
}
cout << "x = " << x << "\n";
}

This code segment will result in the output:

Terminal window
x = 7
x = 7
x = 2
x = 7

The first definition of x is initialised with the value 7. This first x is displayed first and next within the {}. As a new x is then defined within the {} it now has scope and is displayed on the next line with a value of 2. Once we go outside the {} then that x variable is destroyed and scope once again returns to the original x variable, resulting in an output of 7. Although the use of {} to create an inner level of scope might seem unusual it is only the general case of for(){}, if(){}, while(){} etc…

When working with embedded devices it can be important to define exactly what we mean when working with types that are machine dependent. For example int might be a 32-bit number on one platform and a 16-bit number on a different platform. Clearly, this could cause errors if you were trying to port a code library between two such devices. To assist with this there are standard types available in the <cstdint> (or <stdint.h>) header file, and are included by default on recent C++ compilers. These include:

SizeSignedUnsigned
8-bitint8_t (signed char)uint8_t (unsigned char)
16-bitint16_tuint16_t
32-bitint32_tuint32_t
64-bitint64_tuint64_t

Use these types whenever you require portability and readability.

We also sometimes need to give a variable type another name. We can use typedef to reduce the apparent complexity of the code, for example:

typedef unsigned char uchar;
typedef unsigned int cardinal;
typedef int integer;
//etc..

We can then just use this defined type as normal:

integer x;

You should use this carefully and only where the definition of a necessary data type is required. If you type define int as elephant, it may make your code more interesting, but it will make it difficult for another programmer to comprehend.

One side effect in C++ is that if you are defining:

int* a,b;

it does not create two pointers, rather one int pointer a and one int variable b as the * binds to the right. If you were to use a typedef for this then we would not have the same problem. For example,

typedef int* intPointer;
intPointer a,b;

declares two pointers a and b, both of dereference type int.

Modern Best Practice: Type Aliasing with using

Section titled “Modern Best Practice: Type Aliasing with using”

While typedef is still common, modern C++ (C++11 and later) prefers the using keyword for type aliasing. It is often considered more readable because it follows the Name = Value assignment pattern:

using uchar = unsigned char;
using cardinal = unsigned int;
using intPointer = int*;

The using keyword is also more flexible, as it can be used with templates (template aliases), which typedef cannot.

Compilers are designed to optimise your code and generate efficient program code, however this sometimes leads to unintended consequences. For example, take the following segment of code:

int a, b; //using global variables for emphasis
void function() {
a = 10;
b = a * 7;
if (a == 10) {
cout << "a has the value 10" << "\n";
}
else {
cout << "a does not have the value of 10" << "\n";
}
}

Since the value of the variable a does not change between when it is assigned and when it is compared, an optimising compiler might reduce this code to something like:

int a, b; //using global variables for emphasis
void function(){
a = 10;
b = a * 7;
// The compiler 'knows' a is 10, so it removes the if check
cout << "a has the value 10" << "\n";
}

Depending on your program and intention, this might not be correct or safe. For example, if you are working on a multithreaded embedded device, or a device where the variable a is actually mapped to an input/output hardware then it could indeed be possible that the variable a is modified external to the function between when the variable is created and the subsequent comparison. Therefore it might not be correct for you to allow the compiler to optimise this code.

The keyword volatile allows us to inform the compiler that a variable can be modified outside of the program it is compiling, and that it should not make assumptions about a variable that lead to incorrect optimisation. To fix this in the function above you can simply state:

volatile int a;
int b;
void function() {
}

Now, any code that uses the variable a will not optimise the code related to that variable. In embedded applications, this can also be useful for setting up delays that might be needed for serial communication. For example,

void delay() {
volatile int a = 0;
while (a++<100000) {}
}

This loop would likely be removed by an optimising compiler, but might be needed for an embedded application. Making the variable a (in this case) volatile prevents this removal.

The level of compiler optimisation is set at compile time. For example, with a simple C++ program:

Terminal window
molloyd@desktop:~/een1079$ g++ test.cpp -o test -O3

Where O3 sets the most aggressive optimisation level. The default, -O0 is the least aggressive option, which is suitable for code development and debugging and then O1 to O3 increase the level of optimisation by performing additional tests on the code, which increases compilation time. See the note above.

Find the line of code that causes a problem in this example, assuming that an optimising compiler is used to build the executable code. Click on the line of code that is causing the problem.

Code Check

Spot the Hardware Polling Bug

1int sensor_value = 0;
2
3void waitForSensor() {
4 // Wait until the hardware updates the sensor_value to 1
5 while(sensor_value == 0) {
6 // Do nothing, just wait for an external process to change sensor_value
7 }
8 std::cout << "Sensor triggered!" << std::endl;
9}

A struct (short for structure) is a user-defined data type that groups variables of different data types under a single name. Think of it as a container that allows you to create a logical bundle of related data. Unlike classes in C++, a basic struct in C is a simple data aggregate without member functions (methods) or access specifiers (like public or private). This makes them a fundamental feature in C and a lightweight alternative to classes in C++ when you only need to store data. We discuss classes in the next chapter.

Example: A 2D Point.

A common use case for a struct is to represent a simple object that has multiple properties. For example, a point in a 2D coordinate system has an x-coordinate and a y-coordinate. Instead of using two separate variables, x and y, you can bundle them into a single struct for clarity and convenience.

Here’s how you can define and use a struct in C/C++:

#include <iostream>
// Define the Point struct with x and y 'grouped'
struct Point {
int x;
int y;
};
int main() {
// Declare a variable of type Point
Point myPoint;
// Access and assign values to its members
myPoint.x = 10;
myPoint.y = 20;
// Print the values
std::cout << "The x-coordinate is: " << myPoint.x << "\n";
std::cout << "The y-coordinate is: " << myPoint.y << "\n";
return 0;
}

In this example, the Point struct is a blueprint for creating variables that each have an x and a y integer. We declare myPoint as a Point variable (note that the struct keyword is optional in C++) and then access its individual members using the dot operator (.). This makes the code more organised and readable, especially when dealing with complex data structures.

Enums in C/C++: Creating a Set of Named Constants

Section titled “Enums in C/C++: Creating a Set of Named Constants”

An enumeration or enum in C/C++ is a user-defined data type that consists of a set of named integer constants. It provides a way to assign names to integral values, making code more readable and self-documenting. Instead of using “magic numbers” like 0, 1, 2, and 3, you can use meaningful names such as FORWARD, BACKWARD, LEFT, and RIGHT. This is especially useful in robotics and embedded systems where you need to define a limited set of states, commands, or modes.

Example: Robot Movement Commands.

Let’s imagine we are programming a simple robot. The robot can only perform a specific set of movements. We can use an enum to define these valid commands.

Here’s how you can declare an enum:

enum RobotMovement {
FORWARD,
BACKWARD,
LEFT,
RIGHT,
STOP
};

Note: Modern C++ often uses enum class (scoped enums), which provides even better type safety and prevents name collisions by requiring the use of the RobotMovement:: prefix (e.g., RobotMovement::FORWARD).

By default, the compiler assigns integer values starting from 0. So, FORWARD is 0, BACKWARD is 1, LEFT is 2, RIGHT is 3, and STOP is 4. You can also manually assign specific values if needed, like so: enum Command { START = 1, PAUSE = 2, END = 4 };

One of the primary advantages of using an enum is type safety. It makes your code more robust by preventing you from passing invalid, out-of-range values to a function. A function that expects a RobotMovement enum can’t be accidentally called with an arbitrary integer that doesn’t correspond to a valid movement command.

Consider a function that controls the robot’s motors. Without enums, you might write a function that takes an int and then use a series of if-else or switch-case statements to handle the different values.

// This function is less safe and more error-prone
void moveRobot(int command) {
switch(command) {
case 0: // FORWARD
// ... code to move forward
break;
case 1: // BACKWARD
// ... code to move backward
break;
// ... and so on
default:
// Handle invalid command
break;
}
}

The problem here is that you can call moveRobot(99), and the function will just go to the default case, which might not be what you want.

With the enum, the compiler helps you by ensuring only valid RobotMovement values are used.

#include <iostream>
enum RobotMovement {
FORWARD,
BACKWARD,
LEFT,
RIGHT,
STOP
};
void moveRobot(RobotMovement command) {
switch(command) {
case FORWARD:
std::cout << "Moving robot forward." << "\n";
break;
case BACKWARD:
std::cout << "Moving robot backward." << "\n";
break;
case LEFT:
std::cout << "Turning robot left." << "\n";
break;
case RIGHT:
std::cout << "Turning robot right." << "\n";
break;
case STOP:
std::cout << "Stopping robot." << "\n";
break;
}
}
int main() {
moveRobot(FORWARD); // This is a valid call.
moveRobot(STOP); // This is a valid call.
// moveRobot(99); // This will cause a compile-time error!
return 0;
}

In this improved version, if a programmer tries to call moveRobot(99), the compiler will flag it as an error because 99 is not of the RobotMovement type. This prevents a whole class of bugs and makes the code more robust and maintainable. It clearly communicates the function’s expectations, making it easier for others to use and understand your code.

The modern way to write the above code is using an enum class. This forces you to use the scope operator (e.g., RobotMovement::FORWARD), which prevents the names from leaking into the surrounding code and potentially causing conflicts.

enum class RobotMovement {
FORWARD,
BACKWARD,
LEFT,
RIGHT,
STOP
};
void moveRobot(RobotMovement command) {
switch(command) {
case RobotMovement::FORWARD:
std::cout << "Moving forward." << "\n";
break;
// ... and so on
}
}
int main() {
moveRobot(RobotMovement::FORWARD); // Must use RobotMovement::
}
Knowledge Check

Which of the following statements about the evolution of C++ are true?

Code Cloze
C++

Modern C++23 Printing

Knowledge Check

In a standard C++ 'Hello World' program using iostream, which statements are correct?

Code Cloze
C++

Preventing Compiler Optimisation

Knowledge Check

What are the roles of the 'const' and 'volatile' keywords in embedded systems?

Code Cloze
C++

Portable Fixed-Width Integers

Knowledge Check

Why might adding 1.0 to a float variable with a value of 200,000,000 result in no change to the value?

Code Cloze
C++

Enumerated State Management

Knowledge Check

Which of the following are benefits of using 'structs' and 'enums'?

  1. The C++ preprocessor runs before compilation and processes directives such as #include, #define, and conditional compilation statements. It inserts code from header files, performs macro substitution, and controls compilation flow based on conditions. This is detailed in the next chapter.