diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..67f6ac7 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,46 @@ +# Apply to ALL headers (empty = no restriction) +HeaderFilterRegex: '.*' + +# Treat warnings as errors (optional but recommended for CI) +# WarningsAsErrors: '*' +WarningsAsErrors: '' + +Checks: > + -*, + clang-diagnostic-*, + modernize-*, + -modernize-use-trailing-return-type, + -modernize-use-auto, + cppcoreguidelines-*, + -cppcoreguidelines-owning-memory, + -cppcoreguidelines-pro-type-vararg, + -cppcoreguidelines-avoid-magic-numbers, + bugprone-*, + performance-*, + readability-*, + -readability-magic-numbers, + -readability-identifier-length, + misc-*, + -misc-unused-parameters + +CheckOptions: + - key: readability-identifier-naming.NamespaceCase + value: lower_case + + - key: readability-identifier-naming.ClassCase + value: CamelCase + + - key: readability-identifier-naming.StructCase + value: CamelCase + + - key: readability-identifier-naming.FunctionCase + value: lower_case + + - key: readability-identifier-naming.VariableCase + value: lower_case + + - key: readability-identifier-naming.MemberCase + value: lower_case + + - key: modernize-use-nullptr.NullMacros + value: 'NULL' \ No newline at end of file diff --git a/.github/workflows/cpp-build-test-coverage.yml b/.github/workflows/cpp-build-test-coverage.yml index 4f168bb..f8180a1 100644 --- a/.github/workflows/cpp-build-test-coverage.yml +++ b/.github/workflows/cpp-build-test-coverage.yml @@ -57,7 +57,7 @@ jobs: --inline-suppr \ --error-exitcode=1 \ ./src ./include - + # ------------------------------------------------------- # Step 3: Configure and build the project # ------------------------------------------------------- @@ -97,3 +97,22 @@ jobs: cd build echo "## Test Coverage Summary" >> $GITHUB_STEP_SUMMARY lcov --summary coverageFiltered.info >> $GITHUB_STEP_SUMMARY + + # ------------------------------------------------------- + # Step 7: Run Clang-Tidy (experimenting , not exit) + # - Modern C++ static analysis + # - Enforces best practices + # - Add `--warnings-as-errors=*` to exit code 1 + # ------------------------------------------------------- + - name: Run clang-tidy + # run: | + # echo "Running clang-tidy..." + # clang-tidy \ + # -checks='clang-analyzer-*,modernize-*,performance-*,readability-*' \ + # -p build \ + # $(find ./src -name '*.cpp') + run: | + echo "Running clang-tidy using .clang-tidy options" + clang-tidy \ + -p build \ + -header-filter='^src/.*' $(find src -name "*.cpp") \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index a7e11f1..1456339 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,7 @@ set(PROJECT_NAME_TEST ${PROJECT_NAME}_unit_test) # name for the unit-test execut # Require at least C++17 for GoogleTest and modern C++ features set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Enable output of compile commands for clang-tidy # Optionally enforce warnings (good for learning/debugging) add_compile_options(-Wall -Wextra -Wpedantic) @@ -123,6 +124,8 @@ set(APP_SOURCES "src/patterns/creational/Builder.cpp" "src/patterns/creational/Prototype.cpp" "src/core/datatypes/class/Relationship.cpp" + "src/core/datatypes/class/VirtualBase.cpp" + "src/core/datatypes/class/Binding.cpp" ## Exceptions "src/core/exception/BasicHandle.cpp" "src/core/exception/ThrowNoexcept.cpp" @@ -131,6 +134,8 @@ set(APP_SOURCES "src/core/filehandle/StringStream.cpp" "src/core/filehandle/FileIO.cpp" "src/core/filehandle/Directory.cpp" + "src/core/filehandle/OutputFormatting.cpp" + "src/core/filehandle/BinaryFileHandling.cpp" ## Container "src/core/datatypes/container/sequence/Array.cpp" "src/core/datatypes/container/sequence/Vector.cpp" diff --git a/Dockerfile b/Dockerfile index ee108f7..80d4f69 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,6 +14,8 @@ RUN \ apt-get install -y gcc g++ && \ # install cppcheck apt-get install -y cppcheck && \ + # install clang tidy + apt-get install -y clang-tidy && \ # install lcov apt-get install -y lcov diff --git a/README.md b/README.md index 3caa08f..9571397 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -## 1. OVERVIEW +## 1. Overview **Project Structure** ``` includes/ → Header files (.h, .hpp) src/ → Source files (.cpp) tests/ → GoogleTest test cases ``` -## 2. DEPENDENCIES +## 2. Dependencies Make sure the following tools are installed before building the project: - **g++ / gcc** - **CMake** @@ -14,12 +14,12 @@ Make sure the following tools are installed before building the project: - **cppcheck** (for static analysis) - **Optional:** Install `clang-format` to format C++ code. -Linux +**Linux** ```bash sudo apt install clang-format find . -regex '.*\.\(cpp\|h\|hpp\)' -exec clang-format -i {} \; ``` -Windows +**Windows** ```bash # Install clang-format via Chocolatey choco install clang-format @@ -27,55 +27,79 @@ choco install clang-format # Apply clang-format recursively to .cpp, .h, .hpp files Get-ChildItem -Recurse -Include *.cpp, *.h, *.hpp | ForEach-Object { clang-format -i $_.FullName } ``` -## 3. SETUP -* Setup the Local Test Environment - * 1.Using your own Ubuntu system - * Install `gcc`, `cmake`, `git`, and `pthread` (Skip this step if you already install) - ``` - $ sudo apt-get update - $ sudo apt-get install g++ - $ sudo apt-get install lcov - $ sudo apt-get install cmake - $ sudo apt-get install git - $ sudo apt-get install cppcheck - ``` - * Build the application and the tests - ``` - $ cd build - $ cmake .. - $ cmake --build . - ``` - * Run the application and the test - ``` - $ ./cpp_lab_project - $ ./cpp_lab_project_test - ``` - * (Optional) Run static analysis - ``` - $ sudo apt-get install cppcheck - $ cppcheck "folder" / "file" - ``` - * 2.Using **Docker** - * Build the Docker image - ``` - docker build --tag sample-ci-cpp . - ``` - * Run an interactive container - ``` - docker run -it sample-ci-cpp:latest /bin/bash - ``` - * Inspect the environment - ``` - printenv - ``` - * *Notes:* - * Use the -t or --tag flag to set the name of the image to be created. (the full name is actually sample-ci-cpp:latest, since latest is the default tag) - * Opening an interactive shell inside your Docker container to explore, test, or debug the environment built from your image. - * docker run to start a new container. - * -it → run it interactively: - * -i = keep STDIN open (so you can type commands) - * -t = allocate a terminal (TTY) - * sample-ci-cpp:latest → the image you built earlier. - * /bin/bash → the command to execute inside the container (opens a Bash shell). +## 3. Setup +### 3.1. Setup the Local Test Environment +- **Ubuntu system** + * Install `gcc`, `cmake`, `git`, and `pthread` (Skip this step if you already install) + ``` + $ sudo apt-get update + $ sudo apt-get install g++ + $ sudo apt-get install lcov + $ sudo apt-get install cmake + $ sudo apt-get install git + $ sudo apt-get install cppcheck + ``` + * Build the application and the tests + ``` + $ cd build + $ cmake .. + $ cmake --build . + ``` + * Run the application and the test + ``` + $ ./cpp_lab_project + $ ./cpp_lab_project_test + ``` + * (Optional) Run static analysis - cppcheck + ``` + $ sudo apt-get install cppcheck + $ cppcheck "folder" / "file" + ``` + * (Optional) Run static analysis - clang-tidy + ``` + $ sudo apt-get install -y clang-tidy + $ clang-tidy -p build -header-filter='^src/.*' $(find src -name "*.cpp") + ``` +- **Docker** + * Update `Dockerfile` + * Build the Docker image + ``` + $ cd build + $ docker build --tag cpp-lab . + ``` + * Run an interactive container + ``` + $ docker run -it cpp-lab:latest /bin/bash + ``` + * Inspect the environment + ``` + $ printenv + ``` + * *Notes:* + * Use the `-t` or `--tag` flag to set the name of the image to be created. (the full name is actually `cpp-lab:latest`, since latest is the default tag) + * Opening an interactive shell inside your Docker container to explore, test, or debug the environment built from your image. + * docker run to start a new container. + * `-it`: run it interactively: + * `-i`: keep STDIN open (so you can type commands) + * `-t`: allocate a terminal (TTY) + * `cpp-lab:latest`: the image you built earlier. + * `/bin/bash`: the command to execute inside the container (opens a Bash shell). -## 4. DOCUMENTATIONS \ No newline at end of file +## 5. Update Docker Image +```bash +# Navigate to the project that contain your Dockerfile +cd cpp-lab + +# Build the project by running the following command, swapping out DOCKER_USERNAME with your username. +docker build -t DOCKER_USERNAME/cpp-lab . + +# Verify the image exists locally +docker image ls + +# To push the image +docker push DOCKER_USERNAME/cpp-lab +``` + +## 6. TROUBLESHOOTING +1. `push access denied, repository does not exist or may require authorization: server message: insufficient_scope: authorization failed` +=> docker login / Docker Desktop login diff --git a/src/core/datatypes/TypeConVersions.cpp b/src/core/datatypes/TypeConVersions.cpp index 9c5ae3e..b41eb94 100644 --- a/src/core/datatypes/TypeConVersions.cpp +++ b/src/core/datatypes/TypeConVersions.cpp @@ -1,3 +1,4 @@ +// cppcheck-suppress-file [unreadVariable] #include using namespace std; @@ -12,6 +13,11 @@ class Derived : public Base { void show() override { cout << "Derived class\n"; } }; +class DerivedX : public Base { + public: + void show() override { cout << "DerivedX class\n"; } +}; + void implicitConversion() { cout << "\n--- Implicit Type Conversion ---\n"; // *1. Numeric promotion (safe, no data loss) @@ -39,48 +45,59 @@ void explicitConversion() { double pi = 3.14159; - // *1. C-style cast + // *1. C-style cast - not safe int pi_c = (int)pi; cout << "C-style cast: " << pi_c << "\n"; - // *2. static_cast - compile-time type checking + // **2. static_cast - type-safe relationship + compile-time type checking** int pi_static = static_cast(pi); cout << "static_cast: " << pi_static << "\n"; - // object -> object + // object -> object Derived derived{}; - [[maybe_unused]] Base baseObj = static_cast(derived); - // object -> reference + Base baseObj = static_cast(derived); + // DerivedX derivedx =static_cast(derived); // ERROR + + // object -> reference const Base& baseRef = static_cast(derived); // object -> ptr - [[maybe_unused]] const Base* base_ptr = static_cast(&derived); + const Base* base_ptr = static_cast(&derived); - // *3. const_cast: const_cast adds or removes the const qualifier + // **3. const_cast: const_cast adds or removes the const qualifier** const double c_pi = 2.71828; const double* pConst = &c_pi; const double* pNonConst = const_cast(pConst); // remove const cout << "const_cast: " << *pNonConst << " (removed const)\n"; - // *4. reinterpret_cast: reinterpret memory (unsafe) + // **4. reinterpret_cast: reinterpret memory (unsafe)** + // It is used to convert a pointer of some data type into a pointer of another data type, + // even if the data types before and after conversion are different. (#static,dynamic) + // It does not check if the pointer type and data pointed by the pointer is same or not. const void* pVoid = reinterpret_cast(&pi); cout << "reinterpret_cast: address of pi = " << pVoid << "\n"; - // ** Use case: Memory-Mapped I/O ** - // C + // ******************** Use case: Memory-Mapped I/O ****************************** + // C =============================================== // #define REG_ADDR 0x000fff01 // volatile uint8_t* reg = reinterpret_cast(REG_ADDR); // *reg = 0xF; // *reg = 0x1; - // C++ + // C++ =============================================== // #include // constexpr std::uintptr_t REG_ADDR = 0x000fff02; // auto* const reg = reinterpret_cast(REG_ADDR); // *reg = 0xF; - // *reg = 0x1; + // ******************************************************************************** + + // **5. dynamic_cast: safe cast between related classes - type-safe relationship + run-time type checking** + // RTTI (RunTime Type Information) check + // #include + // if(typeid(*basePtr) == typeid(Derived)){ + // Derived* derivedPtr = dynamic_cast(basePtr) + // } - // *5. dynamic_cast: safe cast between related classes (runtime checked) Base* basePtr = new Derived(); const Derived* derivedPtr = dynamic_cast(basePtr); if (derivedPtr) @@ -102,11 +119,11 @@ void typeAliases() { // *1. using - preferred using MyDouble = double; - [[maybe_unused]] const MyDouble a = 3.14; + const MyDouble a = 3.14; // *2. typedef - old style typedef double OldDouble; - [[maybe_unused]] OldDouble b = 2.718; + OldDouble b = 2.718; // *3. Function pointer alias using FuncType = int (*)(double, char); diff --git a/src/core/datatypes/class/Binding.cpp b/src/core/datatypes/class/Binding.cpp new file mode 100644 index 0000000..30b7080 --- /dev/null +++ b/src/core/datatypes/class/Binding.cpp @@ -0,0 +1,78 @@ +// cppcheck-suppress-file [functionStatic,duplInheritedMember] +#include +#include "ExampleRegistry.h" +namespace { +namespace EarlyBinding { +using namespace std; + +class Animal { + public: + virtual ~Animal() = default; + void speak() { // NOT virtual + cout << "Animal speaks\n"; + } +}; + +class Dog : public Animal { + public: + void speak() { // Hides Animal::speak() + cout << "Dog barks\n"; + } +}; + +void print(int x) { + cout << "int\n"; +} + +void print(double x) { + cout << "double\n"; +} + +void run() { + std::cout << "\n---EarlyBinding---\n"; + Animal* a = new Dog(); + a->speak(); // Early binding: Non-virtual member function + + print(5); // Early binding: int version chosen at compile time + delete a; +} +} // namespace EarlyBinding + +namespace LateBinding { +using namespace std; + +class Animal { + public: + virtual ~Animal() = default; + virtual void speak() { // Virtual! + cout << "Animal speaks\n"; + } +}; + +class Dog : public Animal { + public: + void speak() override { cout << "Dog barks\n"; } +}; + +void run() { + std::cout << "\n---LateBinding---\n"; + Animal* a = new Dog(); + a->speak(); // Late binding + delete a; +} +} // namespace LateBinding +} // namespace + +class Binding : public IExample { + std::string group() const override { return "core/class"; }; + + std::string name() const override { return "Binding"; }; + std::string description() const override { return "Binding examples"; }; + + void execute() override { + EarlyBinding::run(); + LateBinding::run(); + }; +}; + +REGISTER_EXAMPLE(Binding, "core/class", "Binding"); \ No newline at end of file diff --git a/src/core/datatypes/class/README.md b/src/core/datatypes/class/README.md index 289a91e..5b58415 100644 --- a/src/core/datatypes/class/README.md +++ b/src/core/datatypes/class/README.md @@ -264,4 +264,48 @@ public: void start(); }; }; +``` + +## Type Cast +- C-style cast is unsafe, so we should use dynamic_cast or static_cast instead. For example: +```cpp +#include +using namespace std; + +class Car { +public: + virtual ~Car() {} +}; + +class PassCar : public Car { +private: + int passengers = 5; +public: + void display() { + cout << passengers << endl; + } +}; + +class Truck : public Car { +private: + int a =1; +public: + void setAxle1s() { + cout << "Truck axles set1\n"; + } + void setAxles() { + cout << "Truck axles set\n"; + } +}; + +int main() { + Car* car = new Truck(); + + // -style cast + PassCar* p = (PassCar*)car; + + p->display(); + + delete car; +} ``` \ No newline at end of file diff --git a/src/core/datatypes/class/VirtualBase.cpp b/src/core/datatypes/class/VirtualBase.cpp new file mode 100644 index 0000000..c72c06a --- /dev/null +++ b/src/core/datatypes/class/VirtualBase.cpp @@ -0,0 +1,126 @@ +// cppcheck-suppress-file [functionStatic] +#include +#include "ExampleRegistry.h" +namespace { + +namespace Problem { +class PoweredDevice { + public: + PoweredDevice() { std::cout << "Powered Device created\n"; } + ~PoweredDevice() { std::cout << "Powered Device destroyed\n"; } +}; + +class Scanner : public PoweredDevice { + public: + Scanner() { std::cout << "Scanner created\n"; } + ~Scanner() { std::cout << "Scanner destroyed\n"; } + + void start() { std::cout << "Scanner started\n"; } +}; + +class Printer : public PoweredDevice { + public: + Printer() { std::cout << "Printer created\n"; } + ~Printer() { std::cout << "Printer destroyed\n"; } + + void start() { std::cout << "Printer started\n"; } +}; + +class Copier : public Scanner, public Printer { + public: + Copier() { std::cout << "Copier created\n"; } + ~Copier() { std::cout << "Copier destroyed\n"; } +}; + +void run() { + std::cout << "\n---Problem---\n"; + Copier c{}; + std::cout << "\n"; + c.Scanner::start(); + c.Printer::start(); + std::cout << "\n"; + // Output: + // Powered Device created <-- multiple "instances" of the Powered Device appearing in an inheritance hierarchy + // Scanner created + // Powered Device created <-- + // Printer created + // Copier created + + // Scanner started + // Printer started + // Copier destroyed + // Printer destroyed + // Powered Device destroyed + // Scanner destroyed + // Powered Device destroyed +} + +} // namespace Problem + +namespace VirtualBaseClasses { +class PoweredDevice { + public: + PoweredDevice() { std::cout << "Powered Device created\n"; } + ~PoweredDevice() { std::cout << "Powered Device destroyed\n"; } +}; + +class Scanner : public virtual PoweredDevice { + public: + Scanner() { std::cout << "Scanner created\n"; } + ~Scanner() { std::cout << "Scanner destroyed\n"; } + + void start() { std::cout << "Scanner started\n"; } +}; + +class Printer : public virtual PoweredDevice { + public: + Printer() { std::cout << "Printer created\n"; } + ~Printer() { std::cout << "Printer destroyed\n"; } + + void start() { std::cout << "Printer started\n"; } +}; + +class Copier : public Scanner, public Printer { + public: + Copier() { std::cout << "Copier created\n"; } + ~Copier() { std::cout << "Copier destroyed\n"; } +}; + +void run() { + std::cout << "\n---Solved by Virtual Base Class---\n"; + // The most derived class is responsible for constructing the virtual base class (Copier) + Copier c{}; + std::cout << "\n"; + c.Scanner::start(); + c.Printer::start(); + std::cout << "\n"; + // Output: + // Powered Device created <-- there is only one base object + // Scanner created + // Printer created + // Copier created + + // Scanner started + // Printer started + + // Copier destroyed + // Printer destroyed + // Scanner destroyed + // Powered Device destroyed +} +} // namespace VirtualBaseClasses +} // namespace + +class VirtualBase : public IExample { + std::string group() const override { return "core/class"; }; + + std::string name() const override { return "VirtualBase"; }; + std::string description() const override { return "VirtualBase examples"; }; + + void execute() override { + Problem::run(); + VirtualBaseClasses::run(); + }; +}; + +REGISTER_EXAMPLE(VirtualBase, "core/class", "VirtualBase"); \ No newline at end of file diff --git a/src/core/filehandle/BinaryFileHandling.cpp b/src/core/filehandle/BinaryFileHandling.cpp new file mode 100644 index 0000000..3a86333 --- /dev/null +++ b/src/core/filehandle/BinaryFileHandling.cpp @@ -0,0 +1,121 @@ +//cppcheck-suppress-file [invalidPointerCast] +#include +#include +#include +#include "ExampleRegistry.h" + +namespace { + +// Important: Do not use sizeof() to dump the raw memory of an object if it contains std::string. +class Account { + friend std::ostream& operator<<(std::ostream&, const Account&); + + private: + int code; + std::string name; + double balance; + + public: + explicit Account(int c = 0, const std::string& n = "", double b = 0.0) + : code(c), name(n), balance(b) {} + + int getCode() const { return code; } + + std::ostream& write(std::ostream&) const; + std::istream& read(std::istream&); +}; + +std::ostream& Account::write(std::ostream& os) const { + os.write(reinterpret_cast(&code), sizeof(code)); + os.write(reinterpret_cast(&balance), sizeof(balance)); + + std::size_t length = name.size(); + os.write(reinterpret_cast(&length), sizeof(length)); + os.write(name.data(), length); + + return os; +} + +std::istream& Account::read(std::istream& is) { + is.read(reinterpret_cast(&code), sizeof(code)); + is.read(reinterpret_cast(&balance), sizeof(balance)); + + std::size_t length = 0; + is.read(reinterpret_cast(&length), sizeof(length)); + + name.resize(length); + is.read(&name[0], length); + + return is; +} + +std::ostream& operator<<(std::ostream& os, const Account& acc) { + os << "Code : " << acc.code << "\n"; + os << "Name : " << acc.name << "\n"; + os << "Balance: " << acc.balance << "\n"; + return os; +} + +void run() { + + // ====== WRITE ====== + Account* accounts[2]; + accounts[0] = new Account(1, "Whitney Elizabeth Houston", 2500); + accounts[1] = new Account(2, "Michael Jackson", 5000); + + std::ofstream outf("data.bin", std::ios::binary | std::ios::trunc); + + if (!outf) { + std::cerr << "Cannot open file for writing\n"; + return; + } + + for (int i = 0; i < 2; ++i) { + if (!accounts[i]->write(outf)) + std::cerr << "Error writing account " << i << "\n"; + } + + outf.close(); + std::cout << "Finish writing.\n"; + + // ====== READ ACCOUNT #2 ====== + std::ifstream inf("data.bin", std::ios::binary); + + if (!inf) { + std::cerr << "Cannot open file for reading\n"; + } else { + + Account* temp = new Account(); + + while (inf.peek() != EOF) { + temp->read(inf); + + if (temp->getCode() == 2) { + std::cout << "\nAccount #2\n"; + std::cout << *temp << std::endl; + break; + } + } + + delete temp; + } + + inf.close(); + + // ====== CLEAN MEMORY ====== + delete accounts[0]; + delete accounts[1]; + + std::cout << "Finish reading.\n"; +} +} // namespace + +class BinaryFileHandling : public IExample { + public: + std::string group() const override { return "core/filehandle"; } + std::string name() const override { return "BinaryFileHandling"; } + std::string description() const override { return "Binary file handling"; } + void execute() override { run(); } +}; + +REGISTER_EXAMPLE(BinaryFileHandling, "core/filehandle", "BinaryFileHandling"); \ No newline at end of file diff --git a/src/core/filehandle/OutputFormatting.cpp b/src/core/filehandle/OutputFormatting.cpp new file mode 100644 index 0000000..399a98e --- /dev/null +++ b/src/core/filehandle/OutputFormatting.cpp @@ -0,0 +1,66 @@ +#include +#include +#include "ExampleRegistry.h" + +namespace { +void run() { + std::ios oldState(nullptr); + oldState.copyfmt(std::cout); // Save state + + // std::ios::boolalpha / noboolanpha + std::cout << true << ' ' << false << '\n'; // 0 + std::cout.setf(std::ios::boolalpha); + std::cout << false << ' ' << true << '\n'; // true false + + // std::ios::showpos / noshowpos + std::cout << 5 << ' ' << -5 << '\n'; // 5 -5 + std::cout.setf(std::ios::boolalpha); + std::cout << 5 << ' ' << -5 << '\n'; // +5 -5 + + // std::ios::upercase / no + std::cout << 12345678.9 << '\n'; // 1.23457e+07 + std::cout.setf(std::ios::uppercase); + std::cout << 12345678.9 << '\n'; // 1.23457E+07 + + // std::ios::basefield + // std::ios::dec + // std::ios::hex + // std::ios::oct + std::cout << 11 << '\n'; // 11 + std::cout.setf(std::ios::hex, std::ios::basefield); + std::cout << 11 << '\n'; // B + + // std::fixed - use dec notation + std::cout << std::fixed << '\n'; + std::cout << std::setprecision(5) << 123.456 << '\n'; // 123.45600 + + // std::scientific + std::cout << std::scientific << '\n'; + std::cout << std::setprecision(5) << 123.456 << '\n'; // 1.23456e+002 + + // reset ======================================================== + std::cout.copyfmt(oldState); // Restore state + + // std::setw() - set the filed width for input and output + // std::left/right/internal - left/right justifies - Left-justifies the sign of the number, and right-justifies the value + std::cout << -12345 << '\n'; + std::cout << std::setw(10) << -12345 << '\n'; + std::cout << std::setw(10) << std::internal << -12345 << '\n'; + + // std::fill(char) set the fill char + std::cout.fill('*'); + std::cout << std::setw(10) << std::internal << -12345 << '\n'; + + std::cout.copyfmt(oldState); // Restore state +} +} // namespace + +class OutputFormatting : public IExample { + public: + std::string group() const override { return "core/filehandle"; } + std::string name() const override { return "OutputFormatting"; } + std::string description() const override { return ""; } + void execute() override { run(); } +}; + +REGISTER_EXAMPLE(OutputFormatting, "core/filehandle", "OutputFormatting"); \ No newline at end of file diff --git a/src/core/filehandle/README.md b/src/core/filehandle/README.md new file mode 100644 index 0000000..07dcf86 --- /dev/null +++ b/src/core/filehandle/README.md @@ -0,0 +1,322 @@ +## 1. I/O Streams +- It is a part of the STL. +- I/O is implemented with `streams`. +
+ +- **stream** is a sequence of bytes that can be accessed sequentially. It may produce or consume amounts data over time. + - **input stream**: used to hold input data from a data producer. + - **output stream**: used to hold output data for a particular data consumer. + - *e.g.* when writing/input data to an device, the device may not be ready to accept that data, so the data will sit in the stream. + - [C++] <=> streams <=> [os] +
+ +- **I/O in C++**: we can use the STL classes to deal with streams + - **input stream**: `istream` & extraction operation (`>>`) is used to remove values from the stream + - **output stream**: `ostream` & insertio operation (`<<`) is used to put vlaues in the stream.\ + - `iostream` can handle both i & o +
+ +- **Standard streams**: + - `cin`: an `istream` object tied to the standard input + - `cout`: an `ostream` object tied to the standard output + - `cerr`: an `ostream` object tied to the standard error - unbuffered output + - `clog`: an `ostream` object tied to the standard error - buffered output + +## 2. Input with istream +- Use `extraction operator (>>)` to read information from an input stream. It skips **whitespace (blanks, tabs, and newlines)**. Use `get(), getLine()` to not discard the whitespace. +- `manipulator` is an object that is used to modify a stream when applied with the `extraction (>>)` or `insertion (<<)` operators. + +## 1.3. Output with ostream +- There are two ways to change the formatting options: + - `flags`: as boolean variable can be turned on and off e.g. `std::ios::showpos`, which live in the `std::ios` class + - `manipulators`: are objects placed in a stream that affect the way things are input and output. e.g. `std::boolalpha`, which are live in the `std namespace` of the `` class + - We also can use the `member functions` in `std::ostream` class +- To switch a flag on, use `self()` function. +- e.g. Refer to the `OutputFormatting.cpp` and [28.3 — Output with ostream and ios +](https://www.learncpp.com/cpp-tutorial/output-with-ostream-and-ios/) + +## 1.4. Stream classes for string +- String streams provide a buffer to hold data but are not connected to an I/O channel. +- The primary uses of the string stream: + - Display data later then or proccess i/o data. + - Get data into a string stream + - Get data from a string stream + - Convertion strings/numbers + - Clear a string stream + +- e.g: +```cpp +#include +#include +int main() { + std::stringstream os{}; + // input + os << "0xF"; + std::cout << os.str(); + + os.str("0x1 0x2"); + std::cout << os.str(); + + // output + std::string bytesStr = os.str(); + std::cout << bytesStr; + + os.str("0x0 0xF 0xE 0x2"); + os >> bytesStr; + std::cout < +- There are three basics file I/O classes: + - `ifstream`: derived from `istream` + - `ofstream`: derived from `ostream` + - `fstream`: derived from `stream` +
+ +- **important:** We have to explicitly setup to use file streams: + - 1. **Open a file: instantiate an object of file I/O class, with the name of the file as a param** + - 2. **Use the insertion (<<) or extraction (>>) to r/w** + - 3. **Close a file: call the close() or let file I/O go out of scope.** + - 4. **To delete a file, simply use the remove() function.** +
+ +- **File output:** +```cpp +#include +#include +#include + +static void fileOutput() { + std::ofstream outfile{"grs_bytes.csv"}; + if (!outfile || !outfile.is_open()) { // !error || !open + std::cerr << "[E] Cannot create the output file"; + return; + } + + // Put bytes data to the file + // put string + std::string elfBytes{ + R"""( + time_s, + gsr_value 0.0, 45.27761157741693 0.005, 41.69912812397066 0.01, + 38.13110177547114 0.015, 35.32162785580394 0.02, + 31.75617843363382 0.025, 28.352875321528607 0.03, + 25.23210282654006 0.035, 21.769688905641132 0.04, + 17.99031153059391 0.045, 15.073732055666543 0.05, + 15.13550182371759 0.055, 14.69547985289048 0.06, + 14.867397107985468 0.065, 14.982082556093832 0.07, + 14.893751010484861 0.075, 14.877034044202343 0.08, + 14.820590581790071 0.085, 15.1065350504897 0.09, + 15.152287796727098 0.095, 14.764395300078201 0.1, + 15.118189654760348 0.105, 15.473255351586635 0.11, + 14.913896402347113 + \n )"""}; + outfile << elfBytes; + + elfBytes = + "0x0 0x0" + "0x0 0x0" + "0x0 0x0"; + outfile << elfBytes; + + elfBytes = + "0xF 0xA \ + 0xE 0xB \ + 0x0 0x0"; + + outfile << elfBytes; + + outfile.put('E'); // put char + outfile.close(); +} +``` +
+ +- **File input:** +```cpp +static void fileInput() { + std::ifstream inFile{"grs_bytes.csv"}; + if (!inFile.is_open()) { + std::cerr << "Error to open file"; + } + + std::string inputStr{}; + std::cout << "========" << std::endl; + while ( + inFile >> + inputStr) { // Note that ifstream returns a 0 if we’ve reached the end of the file (EOF) + std::cout << inputStr; + } + std::cout << "========" << std::endl; + + // not skip whitespace + inFile.close(); + inFile.open("grs_bytes.csv"); // explicitly call open() + inputStr.clear(); + while (std::getline(inFile, inputStr)) { + std::cout << inputStr << std::endl; + } + std::cout << "========" << std::endl; + + inFile.close(); +} +``` +
+ +- **File Mode:** + - `app`: opens the file in append mode + - `ate`: seeks to the end of the file before reading/writing + - `binary`: opens the file in binary mode (instead of text mode) + - `in`: opens the file in read mode (default for ifstream) + - `out`: opens the file in write mode (default for ofstream) + - `trunc`: erases the file if it already exists +
+ +## 1.7 Ramdom File I/O +- **File pointer:** Each file stream class contains a file pointer that is used to keep track of the current read/write position within the file +- **Ramdom access** with `seekg() & seekp()` +- **IOS seek flags:** + - `beg`: the offset is relative to the beginning of the file (default) + - `cur`: the offset is relative to the current location of the file pointer + - `end`: the offset is relative to the end of the file + +> In programming, a newline (‘\n’) is actually an abstraction. +On Windows, a newline is represented as sequential CR (carriage return) and LF (line feed) characters (thus taking 2 bytes of storage). +On Unix, a newline is represented as a LF (line feed) character (thus taking 1 byte of storage). + +> // assume iofile is an object of type fstream +iofile.seekg(iofile.tellg(), std::ios::beg); // seek to current file position +
+ +## 1.8 Binary File +- e.g. +```cpp +#include +#include +#include + +using namespace std; + +/* + * Class Account + * Represent a bank account with: + * - code : account ID + * - name : account holder name + * - balance : current balance + */ +class Account +{ + // Allow operator<< to access private members + friend ostream& operator<<(ostream&, const Account&); + +private: + int code; // Account ID + string name; // Account holder name + double balance; // Account balance + +public: + // Constructor with default values + Account(int c = 0, const string& n = "", double b = 0) + : code(c), name(n), balance(b) {} + + // Getter for account code + int getCode() const { return code; } + + // Write object to binary file + ostream& write(ostream&) const; + + // Read object from binary file + istream& read(istream&); +}; + +/* + * Write entire object to binary stream + * WARNING: + * This method is unsafe because std::string contains dynamic memory. + * Writing raw memory of the object is not portable and not recommended + * for real-world applications. + */ +ostream& Account::write(ostream& os) const +{ + os.write(reinterpret_cast(this), sizeof(Account)); + return os; +} + +/* + * Read entire object from binary stream + */ +istream& Account::read(istream& is) +{ + is.read(reinterpret_cast(this), sizeof(Account)); + return is; +} + +/* + * Overload << operator to print Account info + */ +ostream& operator<<(ostream& os, const Account& acc) +{ + os << "Code : " << acc.code << endl; + os << "Name : " << acc.name << endl; + os << "Balance: " << acc.balance << endl; + return os; +} + +int main() +{ + + // Create two accounts dynamically + Account* accounts[] = { + new Account(1, "Whitney Elizabeth Houston", 2500), + new Account(2, "Michael Jackson", 5000) + }; + + // Open binary file for append + fstream outf("data.bin", ios::out | ios::app | ios::binary); + + // Write both accounts to file + for (int i = 0; i < 2; ++i) + { + if (!accounts[i]->write(outf)) + cerr << "Error in writing!" << endl; + } + + outf.close(); + + cout << "Account #2" << endl; + + + // Read account with code = 2 + Account temp(2); + + fstream inf("data.bin", ios::in | ios::binary); + + // Move read pointer to correct position + // (code - 1) * sizeof(Account) + inf.seekg((temp.getCode() - 1) * sizeof(Account)); + + if (!temp.read(inf)) + cerr << "Error in reading!" << endl; + else + cout << temp << endl; + + inf.close(); + + // Free allocated memory + delete accounts[0]; + delete accounts[1]; + + return 0; +} +``` \ No newline at end of file