diff --git a/exercises/basicTypes/CMakeLists.txt b/exercises/basicTypes/CMakeLists.txt new file mode 100644 index 00000000..16b2d5c8 --- /dev/null +++ b/exercises/basicTypes/CMakeLists.txt @@ -0,0 +1,15 @@ +# Set up the project. +cmake_minimum_required( VERSION 3.12 ) +project( basicTypes LANGUAGES CXX ) + +# Set up the compilation environment. +include( "${CMAKE_CURRENT_SOURCE_DIR}/../common.cmake" ) +set( CMAKE_CXX_STANDARD 20 ) + +# Create the user's executable. +add_executable( basicTypes PrintHelper.h basicTypes.cpp ) + +# Create the "solution executable". +add_executable( basicTypes.sol EXCLUDE_FROM_ALL PrintHelper.h solution/basicTypes.sol.cpp ) +target_include_directories( basicTypes.sol PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ) +add_dependencies( solution basicTypes.sol ) diff --git a/exercises/basicTypes/Makefile b/exercises/basicTypes/Makefile new file mode 100644 index 00000000..285197b9 --- /dev/null +++ b/exercises/basicTypes/Makefile @@ -0,0 +1,11 @@ +all: basicTypes +solution: basicTypes.sol + +clean: + rm -f *o *so basicTypes *~ basicTypes.sol + +% : %.cpp PrintHelper.h + $(CXX) -g -std=c++20 -Wall -Wextra -o $@ $< + +%.sol : solution/%.sol.cpp PrintHelper.h + $(CXX) -g -std=c++20 -Wall -Wextra -o $@ $< -I . diff --git a/exercises/basicTypes/PrintHelper.h b/exercises/basicTypes/PrintHelper.h new file mode 100644 index 00000000..ae38297a --- /dev/null +++ b/exercises/basicTypes/PrintHelper.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include +#include + +#ifdef __MSVC__ +std::string demangle(std::string_view input) { return input; } +#else +#include +std::string demangle(std::string_view input) { + int status; + return abi::__cxa_demangle(input.data(), NULL, NULL, &status); +} +#endif + +// This helper prints type and value of an expression +void printWithTypeInfo(std::string expression, auto const & t, bool useBitset = false) { + const auto & ti = typeid(t); + const std::string realname = demangle(ti.name()); + + std::cout << std::left << std::setw(30) << expression << " type=" << std::setw(20) << realname << "value="; + if (useBitset) { + std::cout << std::bitset<16>(t) << "\n"; + } else { + std::cout << std::setprecision(25) << t << "\n"; + } +} + +// This macro both prints and evaluates an expression: +#define print(A) printWithTypeInfo("Line " + std::to_string(__LINE__) + ": "#A, A); +#define printBinary(A) printWithTypeInfo("Line " + std::to_string(__LINE__) + ": "#A, A, true); + diff --git a/exercises/basicTypes/basicTypes.cpp b/exercises/basicTypes/basicTypes.cpp new file mode 100644 index 00000000..3a46116b --- /dev/null +++ b/exercises/basicTypes/basicTypes.cpp @@ -0,0 +1,79 @@ +#include "PrintHelper.h" + +/* ************************************* + * * Fundamental types and expressions * + * ************************************* + * + * Tasks: + * ------ + * - Compile the program and analyse the output of the different expressions + * - Discuss with other students or your tutor in case the result of an expression is a surprise + * - Fix the marked expressions by changing types such that they produce meaningful results + * - Answer the questions in the code + */ + +int main() { + std::cout << "Using literals of different number types:\n"; + print(5); + print(5/2); //FIXME + print(100/2ull); + print(2 + 4ull); + print(2.f + 4ull); + print(0u - 1u); // FIXME + print(1.0000000001f); // FIXME Why is this number not represented correctly? + print(1. + 1.E-18); // FIXME + + std::cout << "\nUsing increment and decrement operators:\n"; + int a = 1; + int b; + int c; + print(b = a++); // Q: What is the difference between a++ and ++a? + print(c = ++a); + print(a); + print(b); + print(c); + + std::cout << "\nCompound assignment operators:\n"; + int n = 1; + print(n *= 2); // Q: Is there a difference between this and the next line? + print(n *= 2.9); + print(n -= 1.1f); + print(n /= 4); // Q: Based on the results of these expressions, is there a better type to be used for n? + + std::cout << "\nLogic expressions:\n"; + const bool alwaysTrue = true; + bool condition1 = false; + bool condition2 = true; + print( alwaysTrue && condition1 && condition2 ); + print( alwaysTrue || condition1 && condition2 ); // Q: Why does operator precedence render this expression useless? + print( alwaysTrue && condition1 || condition2 ); + print(condition1 != condition1); // Q: What is the difference between this and the following expression? + print(condition2 = !condition2); + print( alwaysTrue && condition1 && condition2 ); + print( alwaysTrue || condition1 && condition2 ); + print( alwaysTrue && condition1 || condition2 ); + + std::cout << '\n'; + print( false || 0b10 ); // Q: What is the difference between || and | ? + print( false | 0b10 ); + printBinary( 0b1 & 0b10 ); + printBinary( 0b1 | 0b10 ); + printBinary( 0b1 && 0b10 ); // Q: Are the operators && and || appropriate for integer types? + printBinary( 0b1 || 0b10 ); + + std::cout << "\nPlay with characters and strings:\n"; + print("a"); // Q: Why is this expression two bytes at run time, the next only one? + print('a'); + + char charArray[20]; + char* charPtr = charArray; + charArray[19] = 0; // Make sure that our string is terminated with the null byte + + print(charArray); + print(charArray[0] = 'a'); + print(charArray); + print(charArray[1] = 98); + print(charArray); + print(charPtr); + // FIXME: Ensure that no unexpected garbage is printed above +} diff --git a/exercises/basicTypes/solution/basicTypes.sol.cpp b/exercises/basicTypes/solution/basicTypes.sol.cpp new file mode 100644 index 00000000..f8b94551 --- /dev/null +++ b/exercises/basicTypes/solution/basicTypes.sol.cpp @@ -0,0 +1,81 @@ +#include "PrintHelper.h" + +/* ************************************* + * * Fundamental types and expressions * + * ************************************* + * + * Tasks: + * - Compile the program and analyse the output of the different expressions + * - Discuss with other students or your tutor in case the result of an expression is a surprise + * - Fix the marked expressions by changing types such that they produce meaningful results + * - Answer the questions in the code + */ + +int main() { + std::cout << "Using literals of different number types:\n"; + print(5); + print(5/2.); //FIXME + print(100/2ull); + print(2 + 4ull); + print(2.f + 4ull); + print(0 - 1 ); // FIXME + print(1.0000000001 ); // FIXME Why is this number not represented correctly? + print(1.l+ 1.E-18); // FIXME + + std::cout << "\nUsing increment and decrement operators:\n"; + int a = 1; + int b; + int c; + print(b = a++); // Q: What is the difference between a++ and ++a? + print(c = ++a); // A: Whether it returns the previous or new value + print(a); + print(b); + print(c); + + std::cout << "\nCompound assignment operators:\n"; + float n = 1; + print(n *= 2); // Q: Is there a difference between this and the next line? + print(n *= 2.9); // A: Yes, the computation runs in float and is converted back to int + print(n -= 1.1f); + print(n /= 4); // Q: Based on the results of these expressions, is there a better type to be used for n? + // A: Probably yes, for example float + + std::cout << "\nLogic expressions:\n"; + const bool alwaysTrue = true; + bool condition1 = false; + bool condition2 = true; + print( alwaysTrue && condition1 && condition2 ); + print( alwaysTrue || condition1 && condition2 ); // Q: Why does operator precedence render this expression useless? + print( alwaysTrue && condition1 || condition2 ); // A: "true || " is evaluated last. The expression therefore is always true. + print(condition1 != condition1); // Q: What is the difference between this and the following expression? + print(condition2 = !condition2); // A: The first is a comparison, the second a negation with subsequent assignment + print( alwaysTrue && condition1 && condition2 ); + print( alwaysTrue || condition1 && condition2 ); + print( alwaysTrue && condition1 || condition2 ); + + std::cout << '\n'; + print( false || 0b10 ); // Q: What is the difference between || and | ? + print( false | 0b10 ); // A: a boolean operation vs. a bit-wise boolean operation + printBinary( 0b1 & 0b10 ); + printBinary( 0b1 | 0b10 ); + printBinary( 0b1 && 0b10 ); // Q: Are the operators && and || appropriate for integer types? + printBinary( 0b1 || 0b10 ); // A: Most likely not, because the integers are first converted to boolean + + std::cout << "\nPlay with characters and strings:\n"; + print("a"); // Q: Why is this expression two bytes at run time, the next only one? + print('a'); // A: Because the first one is a string, which is 0-terminated + + char charArray[20]; + // There are many ways to solve this, for example to use std::string and not manually manage the memory. + // However, if one really desires to manage a char array, one should at least initialise it with the 0 byte: + std::fill(std::begin(charArray), std::end(charArray), '\0'); + char* charPtr = charArray; + + print(charArray); + print(charArray[0] = 'a'); + print(charArray); + print(charArray[1] = 98); + print(charArray); + print(charPtr); + // FIXME: Ensure that no unexpected garbage is printed above +}