Team Cod: C++ Programming Language

Exploring C++

Our Team

Description and History

Carter

Introduction to C++

C++ is a general-purpose, middle-level language originally developed by Danish computer scientist, Bjarne Stroustrup, in 1979 at Bell Laboratories USA. It was designed as an extension of the C language, adding features such as classes, inheritance, abstraction, and polymorphism.

Carter

History of C++

Early Development

C++ can be traced back to 1979 when Bjarne Stroustrup was working on his Ph.D. thesis at Bell Labs. He aimed to create a language that combined low-level capabilities with high-level abstractions. This was influenced by his experience with a language called Simula, which was designed for simulations but too slow for practical use.

Birth of C++

Initially known as "C with classes," C++ was developed as a superset of the C language. Stroustrup's goal was to bring object-oriented programming to C without sacrificing speed or low-level functionality. The first C++ compiler, called Cfront, was created to translate C++ code into universal C.

Renaming to C++

In 1983, the language's name was changed to C++. The "++" operator in C signifies incrementing a variable, reflecting Stroustrup's view of improving upon C. This period saw the introduction of significant features like virtual functions, function overloading, references with the const keyword, and single-line comments.

Standardization and Growth

In 1985, Stroustrup published "The C++ Programming Language," and C++ was established as a commercial product. The language continued to evolve with the addition of features like protected and static members and inheritance from multiple classes. In 1990, "The Annotated C++ Reference Manual" was released.

Standardization and Beyond

In 1998, the first international standard for C++, known as C++98, was published. Subsequent revisions included C++03 and C++11, the latter introducing many new features. C++14 and C++17 followed with more enhancements, and C++20 extended the language's capabilities further.

Tyler

Importance of C++

Applications

C++ is widely used in various industries for systems programming, gaming, finance, scientific computing, and robotics. It offers low-level access to hardware resources, making it suitable for system-level software and high-performance numerical libraries.

Advantages

C++ excels in building complex software systems where performance, efficiency, and reliability are crucial. It is popular for developing device drivers, operating systems, and embedded systems. The language's support for scientific computing and simulation tools is also notable.

Comparison With Other Popular Programming Languages

Carter

C++ vs. Java

C++ is statically typed, while Java is dynamically typed. Memory management in C++ is manual, whereas Java relies on the JVM for garbage collection. C++ is generally faster but less portable than Java. Java has a larger ecosystem of libraries.

Feature C++ Java
Compiled or Interpreted Compiled Compiled (but bytecode executed by JVM)
Memory Management Manual (with pointers) Automatic (garbage collected)
Performance High performance with direct memory access Good performance with JIT compilation
Platform Independence Platform-dependent Platform-independent (runs on JVM)
Community and Libraries Large community and extensive libraries Large community and rich standard libraries
Language Paradigm Multi-paradigm (Associated with procedural, object-oriented, and generic programming) Primarily Object-Oriented
- - -
Mike

C++ vs. Python

C++ has a complex syntax and is statically typed, while Python has a simpler syntax and is dynamically typed. Python is highly portable and interpreted, while C++ needs separate compilation for each platform. C++ is faster but requires manual memory management, while Python manages memory automatically.

Feature C++ Python
Typing Statically typed Dynamically typed
Syntax Complex syntax Simple syntax
Portability Platform-dependent (needs compilation) Platform-independent (interpreted)
Memory Management Manual memory management Automatic memory management (garbage collected)
- - -
Peter

C++ vs. C

C++ is an extension of the C programming language and shares many similarities with it. However, there are also some key differences:

Feature C++ C
Paradigm Multi-paradigm (supports procedural, object-oriented, and generic programming) Procedural
Abstraction Supports high-level abstractions with classes and objects Low-level language with limited support for abstractions
Memory Management Supports both manual and automatic memory management (with features like RAII) Manual memory management (malloc and free functions)
Code Reusability Encourages code reusability through object-oriented principles Code reuse is limited to functions and libraries
Performance Can achieve high performance with optimizations, but may have some overhead due to abstractions Offers fine-grained control over performance but lacks high-level abstractions
- - -
Peter

Conclusion

In conclusion, the history of C++ is both fascinating and influential. From its early days as "C with classes" to its current status as a widely-used programming language, C++ has undergone significant evolution and development.

C++ stands out as a versatile and powerful tool in the world of software development. Its combination of flexibility, efficiency, and support for object-oriented programming makes it well-suited for a wide range of applications. From system-level programming to gaming, finance, scientific computing, and robotics, C++ continues to play a vital role in various industries.

While C++ may pose a learning curve for novice programmers due to its complex syntax and manual memory management, it rewards those who master it with the ability to create high-performance, efficient, and reliable software systems.

Looking ahead, C++ remains a relevant and important language in today's programming landscape. With ongoing updates and new features introduced in each version, C++ continues to evolve to meet the changing demands of modern software development. Its legacy is secured, and its future looks promising as it adapts to new challenges and technologies.

Sources:

  • History Of C++
  • C++ History
  • Mike

    Useful Resources

    Tyler

    Translators and Installation

    Here are some options for getting started with C++:

    Download and Install a Compiler

    If you prefer to work on your local machine, you can download and install a C++ compiler like GCC. Here are the steps:

    1. Visit the GCC website: https://gcc.gnu.org/
    2. Download the installer for your operating system.
    3. Follow the installation instructions provided on the website.

    Online Compiler

    If you want a quick and hassle-free way to start coding in C++, you can use an online compiler. We recommend the Programiz Online C++ Compiler. Here's how to get started:

    1. Visit the online compiler at Programiz Online C++ Compiler.
    2. Write or paste your C++ code into the editor.
    3. Click the "Run Code" button to compile and execute your program.

    Simple Programs With Build/ Run Steps

    Section that contains small examples

    Carter

    Hello World Example

    Here's a simple "Hello, World!" program in C++:

    #include <iostream> int main() { std::cout << "Hello, World!" << std::endl; return 0; } ===== OR ===== #include <iostream> using namespace std; int main() { cout << "Hello World"; return 0; }

    To compile and run the program:

    1. Save the code to a file with a ".cpp" extension, e.g., "HelloWorld.cpp".
    2. Open a terminal or command prompt.
    3. Navigate to the directory containing the file.
    4. Compile the program: g++ HelloWorld.cpp -o HelloWorld
    5. Run the program: ./HelloWorld
    Tyler

    Palindrome Example

    Here is a simple palindrome example in C++:

    #include <iostream> #include <string> #include <cctype> bool isPalindrome(const std::string& str) { int start = 0; int end = str.length() - 1; while (start < end) { while (start < end && !isalpha(str[start])) start++; while (start < end && !isalpha(str[end])) end--; if (tolower(str[start]) != tolower(str[end])) { return false; } start++; end--; } return true; } int main() { std::string input; std::cout << "Enter a String: "; std::getline(std::cin, input); if (isPalindrome(input)) { std::cout << "The String is a palindrome.\n"; } else { std::cout << "The String is not a palindrome.\n"; } return 0; }

    To compile and run the program:

    1. Save the code to a file with a ".cpp" extension, e.g., "palindrome_checker.cpp".
    2. Compile the program: g++ palindrome_checker.cpp -o palindrome_checker
    3. Run the program: ./palindrom_checker
    4. Enter a string when prompted.

    More Complex Examples

    Here are some more complex C++ examples:

    Mike

    Multiple Inheritance and Templates Example

    Here is a simple example of using multiple inheritance in C++:

    #include <iostream> #include <string> // Define BodySoap Class class BodySoap { public: void clean() { std::cout << "Cleaning your body with body soap." << std::endl; } }; // Define Shampoo Class class Shampoo { public: void clean() { std::cout << "Cleaning your hair with shampoo." << std::endl; } }; // Define Conditioner Class class Conditioner { public: void clean() { std::cout << "Softening your hair with conditioner." << std::endl; } }; //Class Template for combinedProduct template <typename T1, typename T2> class combinedProduct : public T1, public T2{ public: void useProduct() { T1::clean(); T2::clean(); std::cout << "Using combined product for a full shower routine." << std::endl; } }; int main() { combinedProduct<BodySoap, Shampoo> myProduct; myProduct.useProduct(); // Using the combined functionality combinedProduct<Conditioner, BodySoap> myOtherProduct; myOtherProduct.useProduct(); return 0; }

    To compile and run the program:

    1. Save the code to a file with a ".cpp" extension, e.g., "inheritance_soap.cpp".
    2. Compile the program: g++ inheritance_soap.cpp -o inheritanceSoap
    3. Run the program: ./inheritanceSoap
    Peter

    Value Category Example

    Every C++ expression has a type, and belongs to a value category. C++17 defines the expression value categories as: glvalue, prvalue, xvalue, lvalue, and rvalue.

    Value Categories in C++
    #include <iostream> #include <utility> // This is how Microsoft defines the value categories: // glvalue: An expression identifying an object, bit-field, or function. // prvalue: An expression used to initialize an object or compute a value. (has no address accessible by program) // xvalue: A glvalue that represents an object whose resources can be reused. (has an address that is no longer accessible by program but can be used to initialize an rvalue reference) // lvalue: A glvalue that is not an xvalue. (has an address accessible by program) // rvalue: Either a prvalue (temporary value) or an xvalue (reusable resource). int main() { // glvalue example (lvalue) int x = 5; // 'x' is an lvalue because it is a named variable that refers to a specific location in memory. std::cout << "lvalue (x): " << x << std::endl; // glvalue example (xvalue) int&& x_rvalue_ref = std::move(x); // 'std::move(x)' produces an xvalue, indicating 'x' is ready to be moved. std::cout << "xvalue (std::move(x)): " << x_rvalue_ref << std::endl; // rvalue example (xvalue) int z = std::move(x_rvalue_ref); // 'std::move(x_rvalue_ref)' is an rvalue (xvalue), ready for moving. std::cout << "rvalue (std::move(x_rvalue_ref)): " << z << std::endl; // rvalue example (prvalue) int i = 12; // '12' is an rvalue (prvalue), a temporary value not associated with a specific memory location. std::cout << "rvalue (12): " << i << std::endl; return 0; }

    To compile and run the program:

    1. Save the code to a file with a ".cpp" extension, e.g., "value_categories.cpp".
    2. Compile the program: g++ value_categories.cpp -o valueCategories
    3. Run the program: ./valueCategories
    Carter

    Tic Tac Toe - Online Compiler Example

    This example shows Tic Tac Toe in C++ working within an Online Compiler

    
    #include <iostream>
    #include <vector>
    
    using namespace std;
    
    // Function to display the Tic-Tac-Toe board
    void displayBoard(const vector<vector<char>>& board) {
        // Loop through rows
        for (int i = 0; i < 3; i++) {
            // Loop through columns within each row
            for (int j = 0; j < 3; j++) {
                // Output the symbol at the current position in the board
                cout << board[i][j];
                // Add a separator "|" if not at the last column in the row
                if (j < 2) cout << " | ";
            }
            // Move to the next line after completing each row
            cout << endl;
            // Add a horizontal line "---------" if not at the last row
            if (i < 2) cout << "---------" << endl;
        }
    }
    
    // Function to check if a player has won
    bool checkWin(const vector<vector<char>>& board, char player) {
        for (int i = 0; i < 3; i++) {
            // Check rows and columns using loops
            if ((board[i][0] == player && board[i][1] == player && board[i][2] == player) ||
                (board[0][i] == player && board[1][i] == player && board[2][i] == player)) {
                return true;
            }
        }
    
        // Check diagonals
        if ((board[0][0] == player && board[1][1] == player && board[2][2] == player) ||
            (board[0][2] == player && board[1][1] == player && board[2][0] == player)) {
            return true;
        }
        return false;
    }
    
    // Function to check if the board is full (a tie)
    bool isBoardFull(const vector<vector<char>>& board) {
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                // If an empty cell is found, the board is not full
                if (board[i][j] == ' ') {
                    return false;
                }
            }
        }
        // If no empty cells are found, the board is full
        return true;
    }
    
    int main() {
        // Initialize an empty 3x3 board using vectors
        vector<vector<char>> board(3, vector(3, ' '));
        char currentPlayer = 'X';
    
        cout << "Tic-Tac-Toe Game" << endl; // Display the message "Tic-Tac-Toe Game"
    
        while (true) {
            displayBoard(board);
    
            // Player's move
            int row, col;
            // Prompt the current player to enter their move (row and column)
            cout << "Player " << currentPlayer << ", enter your move (row and column): ";
            cin >> row >> col;
    
            // Check if the move is valid (within the board and the cell is not occupied)
            if (row < 1 || row > 3 || col < 1 || col > 3 || board[row - 1][col - 1] != ' ') {
                cout << "Invalid move! Try again." << endl;
                continue;
            }
    
            // Make the move by updating the board with the player's symbol
            board[row - 1][col - 1] = currentPlayer;
    
            // Check if the current player wins by calling the checkWin function
            if (checkWin(board, currentPlayer)) {
                displayBoard(board);
                cout << "Player " << currentPlayer << " wins! Congratulations!" << endl;
                break;
            }
    
            // Check for a tie (all cells are occupied) by calling the isBoardFull function
            if (isBoardFull(board)) {
                displayBoard(board);
                cout << "It's a tie! The game ends in a draw." << endl;
                break;
            }
    
            // Switch to the other player for the next turn (X -> O or O -> X)
            currentPlayer = (currentPlayer == 'X') ? 'O' : 'X';
        }
    
        return 0;
    }
                      

    To compile and run the program:

    1. Save the code to a file with a ".cpp" extension, e.g., "TicTacToe.cpp".
    2. Open a terminal or command prompt.
    3. Navigate to the directory containing the file.
    4. Compile the program: g++ TicTacToe.cpp -o TicTacToe
    5. Run the program: ./TicTacToe
    OR

    Copy and Paste Code into Online Compiler:

    Programiz Online C++ Compiler




    Expanding Complex Problems

    Addressing a Complex Challenges with C++

    Carter

    Problem Description

    Our team has taken on the task of solving a multitude of complex problems using the C++ programming language. One of the problems we are addressing are taking the Java banking application we've seen in our previous homeworks and converting it to C++. The second problem we addressed was the Double Ended Queue Ported from C.

    Solution Development

    Tyler & Mike

    Banking Application Ported From Java

    This example shows the C++ version of the Java banking application we've seen in our previous homeworks.

    In C++, header (`.h`) and source (`.cpp`) files are used to organize our code. Header files are typically used for declaration and source files are usually used for the implementation/definition. In this banking application the Customer, CheckingAccount, and SavingAccount classes are simple enough to be fully defined in their header files. Also, a function defined in the body of a class declaration is implicitly an inline function. Regular function calling can cause overhead when the program control is transfered to the called function and back to the main program. Inline functions elimate these extra steps by inserting the function's code directly into the caller's code, which can make them more efficient.

    The Account and Bank classes have their own source files as their toString methods are more complex so they should be implemented in the source file, not the header file. Then main.cpp is used as the main method that ties the whole application together.

    One especially cool feature of this C++ version is the use of smart pointers for managing memory. Smart pointers help ensure that the memory used by the program is handled properly. For example, a std::unique_ptr makes sure only one pointer points to that piece of memory at a time. Once this pointer is done with the memory, it automatically frees it up for us. This makes it a lot more simple then using manual pointers and manually freeing memory.

    Customer.h

    //include guards prevent multiple inclusions #ifndef CUSTOMER_H #define CUSTOMER_H #include <string> class Customer { private: std::string name; public: Customer(std::string n) : name(n) {} std::string toString() const { return name; } }; #endif // CUSTOMER_H

    CheckingAccount.h

    #ifndef CHECKINGACCOUNT_H #define CHECKINGACCOUNT_H #include <memory> #include "Account.h" //inherits from Account class class CheckingAccount : public Account { public: CheckingAccount(std::string number, std::shared_ptr<Customer> customer, double balance) : Account(number, customer, balance) {} //override Account accrue function void accrue(double rate) override {} }; #endif // CHECKINGACCOUNT_H

    SavingAccount.h

    #ifndef SAVINGACCOUNT_H #define SAVINGACCOUNT_H #include <memory> #include "Account.h" class SavingAccount : public Account { private: double interest = 0; public: SavingAccount(std::string number, std::shared_ptr<Customer> customer, double balance) : Account(number, customer, balance) {} //override Account accrue function void accrue(double rate) override { interest += balance * rate; balance += balance * rate; } }; #endif // SAVINGACCOUNT_H

    Account.h

    #ifndef ACCOUNT_H #define ACCOUNT_H #include <string> #include <memory> // Forward declaration class Customer; //abstract class since it has at least one pure virtual function class Account { protected: std::string number; std::shared_ptr<Customer> customer; // Shared pointer for Customer double balance; public: Account(std::string num, std::shared_ptr<Customer> c, double b) : number(num), customer(c), balance(b) {} //pure virtual function. must be implemented in derived classes virtual void accrue(double rate) = 0; double getBalance() const { return balance; } void deposit(double amount) { balance += amount; } void withdraw(double amount) { balance -= amount; } virtual std::string toString() const; }; #endif // ACCOUNT_H

    Account.cpp

    #include "Account.h" #include "Customer.h" std::string Account::toString() const { return number + ":" + customer->toString() + ":" + std::to_string(balance); }

    Bank.h

    #ifndef BANK_H #define BANK_H #include <set> #include <string> #include <memory> #include "Account.h" class Bank { private: // Unique pointers for Accounts. Ensures exclusive ownership of Account. std::set<std::unique_ptr<Account>> accounts; public: void add(std::unique_ptr<Account> account) { // std::move for transfer of pointer ownership. accounts.insert(std::move(account)); } void accrue(double rate) { // keyword 'auto' automatically detects and assigns the type of a variable. // When/How to use auto? // 'auto&&': Universal reference. Used for modifying or moving elements. // Works with both lvalues and rvalues. // Is read-only with const containers. // 'auto&': Lvalue reference. Used to directly modify elements in the container. // 'auto const&': Constant lvalue reference. Used for read-only access. // 'auto': Creates copies of elements for (auto& account : accounts) { //auto& is used to modify each account in the accounts container without making unnecessary copies. account->accrue(rate); } } std::string toString() const; }; #endif // BANK_H

    Bank.cpp

    #include "Bank.h" std::string Bank::toString() const { std::string r; for (const auto& account : accounts) { r += account->toString() + "\n"; } return r; }

    main.cpp

    #include <iostream> #include <memory> #include "Bank.h" #include "Customer.h" #include "CheckingAccount.h" #include "SavingAccount.h" int main() { Bank bank; // Shared Customer object. shared_ptr is a smart pointer that can share ownership with other shared_ptrs. auto c = std::make_shared<Customer>("Ann"); // make_unique is used to create unique_ptr instances bank.add(std::make_unique<CheckingAccount>("01001", c, 100.00)); bank.add(std::make_unique<SavingAccount>("01002", c, 200.00)); bank.accrue(0.02); std::cout << bank.toString(); return 0; }

    Java Code

    Customer.java

    public class Customer { private String name; public Customer(String name) { this.name = name; } public String toString() { return name; } }

    CheckingAccount.java

    public class CheckingAccount extends Account { public CheckingAccount(String number, Customer customer, double balance) { this.number = number; this.customer = customer; this.balance = balance; } public void accrue(double rate) {} }

    SavingAccount.java

    public class SavingAccount extends Account { private double interest = 0; public SavingAccount(String number, Customer customer, double balance) { this.number = number; this.customer = customer; this.balance = balance; } public void accrue(double rate) { interest += balance * rate; balance += balance * rate; } }

    Account.java

    public abstract class Account { protected String number; protected Customer customer; protected double balance; public abstract void accrue(double rate); public double balance() { return balance; } public void deposit(double amount) { balance += amount; } public void withdraw(double amount) { balance -= amount; } public String toString() { return number + ":" + customer + ":" + balance; } }

    Bank.java

    import java.util.*; public class Bank { private Set<Account> accounts = new HashSet<Account>(); public void add(Account account) { accounts.add(account); } public void accrue(double rate) { for (Account account : accounts) account.accrue(rate); } public String toString() { String r = ""; for (Account account : accounts) r += account + "\n"; return r; } public static void main(String[] args) { Bank bank = new Bank(); Customer c = new Customer("Ann"); bank.add(new CheckingAccount("01001", c, 100.00)); bank.add(new SavingAccount("01002", c, 200.00)); bank.accrue(0.02); System.out.println(bank); } }
    1. Save the header files in an "include" directory and source files in a "src" directory.
    2. Compile the program using a command that includes all source files, e.g., g++ src/*.cpp -I include -o BankingApp
    3. Run the program: ./BankingApp
    4. You can also run the program using valgrind to check memory leaks: valgrind --leak-check=full ./BankingApp

    Output

    01001:Ann:100.000000 01002:Ann:204.000000 HEAP SUMMARY: in use at exit: 0 bytes in 0 blocks total heap usage: 11 allocs, 11 frees, 74,146 bytes allocated All heap blocks were freed -- no leaks are possible
    Carter & Peter

    Double Ended Queue Ported from C

    This example shows the C++ version of the C source code implementation of a Double Ended Queue.

    deq.hpp

    
    #ifndef DEQ_HPP
    #define DEQ_HPP
    
    struct Data {
        int value;
        Data(int val) : value(val) {} 
        Data() : value(0) {}          
    };
    
    enum End { Head, Tail, Ends };
    
    class deq {
    public:
    
        //constructor/deconstructor
        deq();
        ~deq();
    
    
        //previously static functions
        void put(End e, const Data& d);
        Data* ith(End e, int i);
        Data* get(End e);
        Data* rem(End e, const Data& d);
    
        //previous extern functions
        void deq_head_put(const Data& d);
        Data* deq_head_get();
        Data* deq_head_ith(int i);
        Data* deq_head_rem(const Data& d);
    
        void deq_tail_put(const Data& d);
        Data* deq_tail_get();
        Data* deq_tail_ith(int i);
        Data* deq_tail_rem(const Data& d);
    
        //C++: const keyword indicates read-only ->memory protection
        //C:varaibles that cannot be changed after intialization
        void deq_str() const;
        int deq_length() const {return len;}
    
    private:
        struct Node {
            Node* np[Ends];
            Data* data;
            Node(Data* d) : data(d), np{nullptr, nullptr} {}
        };
    
        Node* ht[Ends];
        int len;
    
        void deq_delete();
    };
    
    #endif   
                            

    deq.cpp

    
    #include <iostream>
    #include <stdexcept>
    #include "deq.hpp"
    
    deq::deq() : ht{nullptr, nullptr}, len(0) {}
    
    deq::~deq() {
        deq_delete();
    }
    
    
    void deq::put(End e, const Data& d) {
        Data* newData = new Data(d);
        Node* curr = new Node(newData);
    
        if (!ht[Head] && !ht[Tail]) {
            ht[Head] = ht[Tail] = curr;
        } else {
            if (e == Head) {
                curr->np[Tail] = ht[Head];
                if (ht[Head]) ht[Head]->np[Head] = curr;
                ht[Head] = curr;
            } else { //Tail
                curr->np[Head] = ht[Tail];
                if (ht[Tail]) ht[Tail]->np[Tail] = curr;
                ht[Tail] = curr;
            }
        }
        len++;
    }
    
    
    
    Data* deq::ith(End e, int i) {
        if (i < 0 || i >= len) throw std::out_of_range("Index Out of Bounds");
    
        Node* temp = (e == Head) ? ht[Head] : ht[Tail];
        for (int j = 0; j < i; ++j) {
            temp = temp->np[e == Head ? Tail : Head];
        }
        return temp->data;
    }
    
    
    Data* deq::get(End e) {
        if (!ht[e]) throw std::runtime_error("Null Pointer");
    
        Node* node = ht[e];
        Data* value = node->data;
        ht[e] = node->np[e == Head ? Tail : Head];
    
        if (ht[e]) {
            ht[e]->np[e == Head ? Head : Tail] = nullptr;
        } else {
            ht[Tail] = ht[Head] = nullptr;
        }
    
        delete node;
        len--;
        return value;
    }
    
    
    
    Data* deq::rem(End e, const Data& d) {
        Node* curr = ht[e];
        Node* prev = nullptr;
    
        while (curr) {
            if (curr->data->value == d.value) {
                if (prev) {
                    prev->np[e == Head ? Tail : Head] = curr->np[e == Head ? Tail : Head];
                } else {
                    ht[e] = curr->np[e == Head ? Tail : Head];
                }
    
                if (ht[e]) {
                    ht[e]->np[e == Head ? Head : Tail] = prev;
                } else {
                    ht[Tail] = ht[Head] = nullptr;
                }
    
                Data* value = curr->data;
                delete curr;
                len--;
                return value;
            }
            prev = curr;
            curr = curr->np[e == Head ? Tail : Head];
        }
        return nullptr;
    }
    
    
    void deq::deq_head_put(const Data& d) {
        put(Head, d);
    }
    
    Data* deq::deq_head_get() {
        return get(Head);
    }
    
    Data* deq::deq_head_ith(int i) {
        return ith(Head, i);
    }
    
    Data* deq::deq_head_rem(const Data& d) {
        return rem(Head, d);
    }
    
    void deq::deq_tail_put(const Data& d) {
        put(Tail, d);
    }
    
    Data* deq::deq_tail_get() {
        return get(Tail);
    }
    
    Data* deq::deq_tail_ith(int i) {
        return ith(Tail, i);
    }
    
    Data* deq::deq_tail_rem(const Data& d) {
        return rem(Tail, d);
    }
    
    
    void deq::deq_str() const {
        const Node* current = ht[Head];
        std::cout << "ORDER(H->T): ";
        while (current != nullptr) {
            std::cout << current->data->value << " ";
            current = current->np[Tail];
        }
        std::cout << std::endl;
    }
    
    
    
    
    void deq::deq_delete() {
        Node* current = ht[Head];
        while (current != nullptr) {
            Node* temp = current;
            current = current->np[Tail];
    
            delete temp->data; 
            delete temp; 
        }
        ht[Head] = nullptr;
        ht[Tail] = nullptr;
        len = 0;
    }
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
                            

    main.cpp

    
    #include <iostream>
    #include "deq.hpp"
    
    int main() {
        deq q;
    
    
    //==============================TESTS================================================
        std::cout << "" << std::endl;
        std::cout << "====TEST 1: Put and Get functions for both Tail and Head====" << std::endl;
        std::cout << "" << std::endl;
    
    //deq_head_put()
        //EXPECTED ORDER: 5 10 15 20
        std::cout << "Head put: 20" << std::endl;
        std::cout << "Head put: 15" << std::endl;
        std::cout << "Head put: 10" << std::endl;
        std::cout << "Head put: 5" << std::endl;
        q.deq_head_put(Data(20));
        q.deq_head_put(Data(15));
        q.deq_head_put(Data(10));
        q.deq_head_put(Data(5));
        q.deq_str();
        std::cout << "" << std::endl;
    
    //deq_tail_put
        //EXPECTED ORDER: 5 10 15 20 25 30 35 40
        std::cout << "Tail put: 25" << std::endl;
        std::cout << "Tail put: 30" << std::endl;
        std::cout << "Tail put: 35" << std::endl;
        std::cout << "Tail put: 40" << std::endl;
        q.deq_tail_put(Data(25));
        q.deq_tail_put(Data(30));
        q.deq_tail_put(Data(35));
        q.deq_tail_put(Data(40));
        q.deq_str();
        std::cout << "" << std::endl;
    
    //deq_head_get()
        //remove half with deq_head_get()
        std::cout << "Head get: " << q.deq_head_get()->value << std::endl;
        std::cout << "Head get: " << q.deq_head_get()->value << std::endl;
        std::cout << "Head get: " << q.deq_head_get()->value << std::endl;
        std::cout << "Head get: " << q.deq_head_get()->value << std::endl;
        q.deq_str();
        std::cout << "Length: " << q.deq_length() << std::endl;
        std::cout << "" << std::endl;
    
    //deq_tail_get()
        std::cout << "Tail get: " << q.deq_tail_get()->value << std::endl;
        std::cout << "Tail get: " << q.deq_tail_get()->value << std::endl;
        std::cout << "Tail get: " << q.deq_tail_get()->value << std::endl;
        std::cout << "Tail get: " << q.deq_tail_get()->value << std::endl;
        q.deq_str();
        std::cout << "Length: " << q.deq_length() << std::endl;
        std::cout << "" << std::endl;
    
    
    
        std::cout << "====TEST 2: Ith and Rem functions for both Tail and Head====" << std::endl;
        std::cout << "" << std::endl;
    
        //ORDER: 5 10 15 20 25 30 35 40
        q.deq_head_put(Data(20));
        q.deq_head_put(Data(15));
        q.deq_head_put(Data(10));
        q.deq_head_put(Data(5));
        q.deq_tail_put(Data(25));
        q.deq_tail_put(Data(30));
        q.deq_tail_put(Data(35));
        q.deq_tail_put(Data(40));
        q.deq_str();
        std::cout << "" << std::endl;
    
    
    //deq_head_ith
        std::cout << "Head Ith: " << q.deq_head_ith(0)->value << std::endl;
        std::cout << "Head Ith: " << q.deq_head_ith(1)->value << std::endl;
        std::cout << "Head Ith: " << q.deq_head_ith(2)->value << std::endl;
        std::cout << "Head Ith: " << q.deq_head_ith(3)->value << std::endl;
        std::cout << "Head Ith: " << q.deq_head_ith(4)->value << std::endl;
        std::cout << "" << std::endl;
    
    //deq_tail_ith
        std::cout << "Tail Ith: " << q.deq_tail_ith(0)->value << std::endl;
        std::cout << "Tail Ith: " << q.deq_tail_ith(1)->value << std::endl;
        std::cout << "Tail Ith: " << q.deq_tail_ith(2)->value << std::endl;
        std::cout << "Tail Ith: " << q.deq_tail_ith(3)->value << std::endl;
        std::cout << "Tail Ith: " << q.deq_tail_ith(4)->value << std::endl;
        std::cout << "" << std::endl;
    
    
    //deq_head_rem
        q.deq_str();
        std::cout << "Head Rem: " << q.deq_head_rem(5)->value << std::endl;
        std::cout << "Head Rem: " << q.deq_head_rem(10)->value << std::endl;
        std::cout << "Head Rem: " << q.deq_head_rem(15)->value << std::endl;
        std::cout << "Head Rem: " << q.deq_head_rem(20)->value << std::endl;
        q.deq_str();
        std::cout << "" << std::endl;
    
        std::cout << "Tail Rem: " << q.deq_tail_rem(25)->value << std::endl;
        std::cout << "Tail Rem: " << q.deq_tail_rem(30)->value << std::endl;
        std::cout << "Tail Rem: " << q.deq_tail_rem(35)->value << std::endl;
        std::cout << "Tail Rem: " << q.deq_tail_rem(40)->value << std::endl;
        q.deq_str();
        std::cout << "" << std::endl;
    
        return 0;
    }
    
                                
                            

    deq.h

    
    #ifndef DEQ_H
    #define DEQ_H
    
    typedef void *Deq;
    typedef void *Data;
    
    extern Deq deq_new();
    extern int deq_len(Deq q);
    
    extern void deq_head_put(Deq q, Data d);
    extern Data deq_head_get(Deq q);
    extern Data deq_head_ith(Deq q, int i);
    extern Data deq_head_rem(Deq q, Data d);
    
    extern void deq_tail_put(Deq q, Data d);
    extern Data deq_tail_get(Deq q);
    extern Data deq_tail_ith(Deq q, int i);
    extern Data deq_tail_rem(Deq q, Data d);
    
    typedef char *Str;
    typedef void (*DeqMapF)(Data d);
    typedef Str  (*DeqStrF)(Data d);
    
    extern void deq_map(Deq q, DeqMapF f);
    extern void deq_del(Deq q, DeqMapF f);
    extern Str  deq_str(Deq q, DeqStrF f);
    
    #endif
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
                            

    deq.c

    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #include "deq.h"
    #include "error.h"
    
    typedef enum {Head,Tail,Ends} End;
    
    typedef struct Node {
        struct Node *np[Ends];
        Data data;
    } *Node;
    
    
    typedef struct {
        Node ht[Ends];
        int len;
    } *Rep;
    
    static Rep rep(Deq q) {
        if (!q) ERROR("zero pointer");
        return (Rep)q; 
    }
    
    
    static void put(Rep r, End e, Data d) {
        if((e==0)||(e==1)) {
        Node curr = (Node)malloc(sizeof(*curr));
        curr->data = d;
    
        if((r->ht[0] == NULL) && (r->ht[1] == NULL)) {
            curr->np[0] = NULL;
            curr->np[1] = NULL;
            r->ht[0] = curr;
            r->ht[1] = curr;
        } else {
    
            if(e == 0) {
            curr->np[0] = NULL;
            curr->np[1] = r->ht[0];
            r->ht[0]->np[0] = curr;
            r->ht[0] = curr;
            } 
    
            else if(e == 1) {
            curr->np[1] = NULL;
            curr->np[0] = r->ht[1];
            r->ht[1]->np[1] = curr;
            r->ht[1] = curr;
            }
            }
        r->len++;
        }
    }
    
    
    static Data ith(Rep r, End e, int i) {
        if((e==0)||(e==1)) {
        if((i >= (r->len)) || (i<0)) {
            ERROR("Index Out of Bounds");
            exit(EXIT_SUCCESS);
        }
    
        Node temp = NULL;
        
        if(e==0) {
            temp = r->ht[0];
            
            if(i == 0) {
            return temp->data;
            }
    
            for(int j=0; jnp[1];
            }
    
        } else {
            temp = r->ht[1];
    
            if(i == 0) {
            return temp->data;
            }
    
            for(int j=0; jnp[0];
            }
        } 
        return temp->data;
    
        } else {
        ERROR("Incorrect End Specified");
        exit(EXIT_SUCCESS);
        }
    }
    
    
    
    static Data get(Rep r, End e) {
        if (e == 0) {
    
        if (r->ht[0] == NULL) {
            ERROR("NULL HEAD POINTER");
            exit(EXIT_SUCCESS);
        }
    
        Node head = r->ht[0];
        Data value = head->data;
        Node next = head->np[1];
    
        if (next != NULL) {
            next->np[0] = NULL;
        }
    
        free(head);
        r->len--;
    
        if (r->len == 0) {
            r->ht[0] = NULL;
            r->ht[1] = NULL;
    
        } else {
            r->ht[0] = next;
        }
    
        return value;
    
        } else if (e == 1) {
    
        if (r->ht[1] == NULL) {
            ERROR("NULL TAIL POINTER");
            exit(EXIT_SUCCESS);
        }
    
        Node tail = r->ht[1];
        Data value = tail->data;
        Node prev = tail->np[0];
    
        if (prev != NULL) {
            prev->np[1] = NULL;
        }
    
        free(tail);
        r->len--;
        if (r->len == 0) {
            r->ht[0] = NULL;
            r->ht[1] = NULL;
    
        } else {
            r->ht[1] = prev;
        }
        return value;
    
        } else {
        ERROR("Incorrect End Specified");
        exit(EXIT_SUCCESS);
        }
    }
    
    
    
    
    
    static Data rem(Rep r, End e, Data d) { 
        Data value = NULL;
    
        if(e==0 || e==1) {
        Node curr = r->ht[e];
        while(curr != NULL) {
    
            if(curr->data == d) {
            value = curr->data;
    
            if(curr->np[0] != NULL) {
                curr->np[0]->np[1] = curr->np[1]; 
            } else {
    
                r->ht[0] = curr->np[1];
            }
    
            if (curr->np[1] != NULL) {
                curr->np[1]->np[0] = curr->np[0];
            } else {
    
                r->ht[1] = curr->np[0];  
            }
    
            free(curr);
            r->len--;
            return value;
            }
            if(e == 0) {
            curr = curr->np[1];
            } else {
            curr = curr->np[0];
            }
        }
    
        } else {
        ERROR("Incorrect End Specified");
        exit(EXIT_SUCCESS);
        }
        return value; 
    }
    
    
    extern Deq deq_new() {
        Rep r=(Rep)malloc(sizeof(*r));
        if (!r) ERROR("malloc() failed");
        r->ht[Head]=0;
        r->ht[Tail]=0;
        r->len=0;
        return r;
    }
    
    extern int deq_len(Deq q) { return rep(q)->len; }
    
    
    extern void deq_head_put(Deq q, Data d) {        put(rep(q),Head,d); }
    extern Data deq_head_get(Deq q)         { return get(rep(q),Head); }
    extern Data deq_head_ith(Deq q, int i)  { return ith(rep(q),Head,i); }
    extern Data deq_head_rem(Deq q, Data d) { return rem(rep(q),Head,d); }
    
    extern void deq_tail_put(Deq q, Data d) {        put(rep(q),Tail,d); }
    extern Data deq_tail_get(Deq q)         { return get(rep(q),Tail); }
    extern Data deq_tail_ith(Deq q, int i)  { return ith(rep(q),Tail,i); }
    extern Data deq_tail_rem(Deq q, Data d) { return rem(rep(q),Tail,d); }
    
    
    extern void deq_map(Deq q, DeqMapF f) {
        for (Node n=rep(q)->ht[Head]; n; n=n->np[Tail])
        f(n->data);
    }
    
    extern void deq_del(Deq q, DeqMapF f) {
        if (f) deq_map(q,f);
        Node curr=rep(q)->ht[Head];
        while (curr) {
        Node next=curr->np[Tail];
        free(curr);
        curr=next;
        }
        free(q);
    }
    
    extern Str deq_str(Deq q, DeqStrF f) {
        char *s=strdup("");
        for (Node n=rep(q)->ht[Head]; n; n=n->np[Tail]) {
        char *d=f ? f(n->data) : n->data;
        char *t; asprintf(&t,"%s%s%s",s,(*s ? " " : ""),d);
        free(s); s=t;
        if (f) free(d);
        }
        return s;
    }
                                
                            

    main.c

    
    #include <stdio.h>
    #include <stdlib.h>
    
    #include "deq.h"
    
    int main() {
    //================================TESTS================================================
        Deq q=deq_new();
    
    //deq_head_put()  
    
        //EXPECTED ORDER: 1
        printf("Head put: 1");
        deq_head_put(q, "1");
        char *a=deq_str(q,0);
        printf("PRINT: %s\n",a);
    
    
    //deq_tail_put() 
        //EXPECTED ORDER: 1 2 3 4
        printf("Tail put: 2");
        printf("Tail put: 3");
        printf("Tail put: 4");
        deq_tail_put(q, "2");
        deq_tail_put(q, "3");
        deq_tail_put(q, "4");
        char *b=deq_str(q,0); 
        printf("PRINT: %s\n",b);
    
        free(a);
        free(b);
        printf("\n");
    
    
    //deq_head_rem()
    //deq_tail_rem()
    
        //EXPECTED OUTPUT: 
        printf("\n\nREM TEST\n");
        deq_head_rem(q,"1");
        deq_head_rem(q,"2");
        deq_tail_rem(q,"3");
        deq_tail_rem(q,"4");
        char *c=deq_str(q,0);        
        printf("PRINT: %s\n",c);    
        free(c);
    
    //deq_head_ith()
    //deq_tail_ith()
        deq_head_put(q, "4");
        deq_head_put(q, "3");
        deq_head_put(q, "2");
        deq_head_put(q, "1");
    
        printf("\n\nINDEX TEST\n");
    
        //Check index value when starting from either side
        int index1 = 3;
        printf("index %d:  value: %s\n", index1, deq_head_ith(q, index1)); //EXPECTED: 4
        printf("index %d:  value: %s\n", index1, deq_tail_ith(q, index1)); //EXPECTED: 1
    
        int index2 = 0;
        printf("index %d:  value: %s\n", index2, deq_head_ith(q, index2)); //EXPECTED: 1
        printf("index %d:  value: %s\n", index2, deq_tail_ith(q, index2)); //EXPECTED: 4
    
    //deq_head_get()
    //deq_tail_get()
        char *e=deq_str(q,0);        
        printf("PRINT: %s\n",e);    //EXPECTED: PRINT: 1 2 3 4
        free(e);
    
        //check removes head
        deq_head_get(q);
        char *f=deq_str(q,0);        
        printf("PRINT: %s\n",f);    //EXPECTED: PRINT: 2 3 4
        free(f);
    
        //check removes tail
        deq_tail_get(q);
        char *g=deq_str(q,0);        
        printf("PRINT: %s\n",g);    //EXPECTED: PRINT: 2 3
        free(g);
    
        deq_head_rem(q,"2");
        deq_head_rem(q,"3");
    
        deq_del(q,0);
        return 0;
    }    
                            

    error.h

    
    #ifndef ERROR_H
    #define ERROR_H
    
    #include 
    #include 
    
    #define WARNLOC(file,line,kind,args...) do {  \
        fprintf(stderr,"%s:%d: ",file,line);        \
        fprintf(stderr,"%s: ",kind);                \
        fprintf(stderr,args);                       \
        fprintf(stderr,"\n");                       \
        fflush(stderr);                             \
    } while (0)
    
    #define ERRORLOC(file,line,kind,args...) do { \
        WARNLOC(file,line,kind,args);               \
        exit(1);                                    \
    } while (0)
    
    #define WARN(args...) WARNLOC(__FILE__,__LINE__,"warning",args)
    #define ERROR(args...) ERRORLOC(__FILE__,__LINE__,"error",args)
    
    #endif
                                
                            

    To compile and run the C++ program:

    1. Save the code to a file with the correct extension. Either ".cpp" or ".hpp" extension depending on if it's a C++ source file or C++ header file. For example: "deq.cpp" or "deq.hpp".
    2. Open a terminal or command prompt.
    3. Navigate to the directory containing the file.
    4. Compile the program: g++ -o myQ main.cpp deq.cpp
    5. Run the program: ./myQ

    Output

    
    ====TEST 1: Put and Get functions for both Tail and Head====
    Head put: 20
    Head put: 15
    Head put: 10
    Head put: 5
    ORDER(H->T): 5 10 15 20 
    
    Tail put: 25
    Tail put: 30
    Tail put: 35
    Tail put: 40
    ORDER(H->T): 5 10 15 20 25 30 35 40 
    
    Head get: 5
    Head get: 10
    Head get: 15
    Head get: 20
    ORDER(H->T): 25 30 35 40 
    Length: 4
    
    Tail get: 40
    Tail get: 35
    Tail get: 30
    Tail get: 25
    ORDER(H->T): 
    Length: 0
    
    ====TEST 2: Ith and Rem functions for both Tail and Head====
    
    ORDER(H->T): 5 10 15 20 25 30 35 40 
    
    Head Ith: 5
    Head Ith: 10
    Head Ith: 15
    Head Ith: 20
    Head Ith: 25
    
    Tail Ith: 40
    Tail Ith: 35
    Tail Ith: 30
    Tail Ith: 25
    Tail Ith: 20
    
    ORDER(H->T): 5 10 15 20 25 30 35 40 
    Head Rem: 5
    Head Rem: 10
    Head Rem: 15
    Head Rem: 20
    ORDER(H->T): 25 30 35 40 
    
    Tail Rem: 25
    Tail Rem: 30
    Tail Rem: 35
    Tail Rem: 40
    ORDER(H->T):