diff --git a/.gitignore b/.gitignore index 3e13231..3ab2d1f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *.o a.out .DS_Store - mylist.c +*cpp_demo +!cpp_demo/ diff --git a/M-cpp/cpp_demo/Makefile b/M-cpp/cpp_demo/Makefile new file mode 100644 index 0000000..8daaf45 --- /dev/null +++ b/M-cpp/cpp_demo/Makefile @@ -0,0 +1,23 @@ +CC = g++ +CXX = g++ +CXXFLAGS = -std=c++11 -g -Wall $(INCLUDES) +LDFLAGS = -g + +.PHONY: default + +default: cpp_demo + +cpp_demo: matrix.o + +cpp_demo.o: matrix.hpp + +matrix.o: matrix.hpp + +.PHONY: clean + +clean: + rm -f cpp_demo *.o + +.PHONY: all + +all: clean default diff --git a/M-cpp/cpp_demo/README.md b/M-cpp/cpp_demo/README.md new file mode 100644 index 0000000..9884b78 --- /dev/null +++ b/M-cpp/cpp_demo/README.md @@ -0,0 +1,14 @@ +# Matrix Demo + +This demo shows how a user can define a matrix class in C++. +Similar to Jae's `MyString`, `Matrix` class defined in `matrix.hpp` encapsulates all the memory management as seen in the constructor and destructor. +We added a few more functionalities such as overloading `operator+` to show how we can also implement addition of two matrices. +Note that the user of this class `Matrix` does not need to know **how** memory management or addition are done, +but only that such functionalities exist. +This is one of the hallmarks of object-oriented programming (OOP). + +### Matrix Addition +See the following [link](https://en.wikipedia.org/wiki/Matrix_(mathematics)#Addition,_scalar_multiplication,_and_transposition) for an explanation and an example of how to add two matrices. + +## Run +Type `make` to build the executable and run `./cpp_demo`. diff --git a/M-cpp/cpp_demo/cpp_demo.cpp b/M-cpp/cpp_demo/cpp_demo.cpp new file mode 100644 index 0000000..01b66f0 --- /dev/null +++ b/M-cpp/cpp_demo/cpp_demo.cpp @@ -0,0 +1,101 @@ +#include +#include "matrix.hpp" + +static const std::string BARS = "==================="; + +// Prints testname formatted as such: +// ===================testname test=================== +void print_test(const std::string& testname) +{ + std::cout << BARS << testname << " test" << BARS << std::endl; +} + +// Creates a matrix of size 3 x 2 and initializes entries. +// Returns the created matrix. +Matrix make_matrix() +{ + Matrix M(3, 2); + M(0,0) = 1; M(0,1) = 3; + M(1,0) = 5; M(1,1) = -2; + M(2,0) = 9; M(2,1) = -5; + return M; +} + +// Tests constructor. +// Prints the contents of a constructed and initialized matrix. +void test_ctor() +{ + print_test("constructor"); + Matrix M(1, 2); + M(0,0) = 6; M(0,1) = 9; + std::cout << "Printing M:" << std::endl; + M.print(); // prints the contents of M +} + +// Tests copy constructor. +// Creates a copy constructed Matrix object from return value of make_matrix(). +// Prints the contents of copy constructed object. +void test_cctor() +{ + print_test("copy constructor"); + Matrix M = make_matrix(); // copy constructed + std::cout << "Printing copy constructed matrix:" << std::endl; + M.print(); +} + +// Tests copy assignment. +// Creates Matrix M from return value of make_matrix(), Matrix A, and copy assigns M to A. +// Prints the contents of M and the contents of A before and after copy assignment. +void test_cass() +{ + print_test("copy assignment"); + Matrix M = make_matrix(); + Matrix A(1, 2); + A(0,0) = 10; A(0,1) = -3; + std::cout << "Printing A:" << std::endl; + A.print(); + std::cout << "Printing M:" << std::endl; + M.print(); + std::cout << "Printing A after copy assigned from M:" << std::endl; + A = M; // copy assignment + A.print(); +} + +// Tests adding Matrix M with a nontrivial matrix. +// We chose the nontrivial matrix to be +// [ +// 0, 1 +// 1, 2 +// 2, 3 +// ] +void test_add() +{ + print_test("add"); + Matrix M = make_matrix(); + Matrix N(3, 2); + N(0,0) = 0; N(0,1) = 1; + N(1,0) = 1, N(1,1) = 2; + N(2,0) = 2; N(2,1) = 3; + + Matrix res = M + N; + + std::cout << "Printing M:" << std::endl; + M.print(); + + std::cout << "Printing N:" << std::endl; + N.print(); + + std::cout << "Printing M + N:" << std::endl; + res.print(); +} + +int main() +{ + // run tests + test_ctor(); + test_cctor(); + test_cass(); + test_add(); + + return 0; +} diff --git a/M-cpp/cpp_demo/matrix.cpp b/M-cpp/cpp_demo/matrix.cpp new file mode 100644 index 0000000..2131dfb --- /dev/null +++ b/M-cpp/cpp_demo/matrix.cpp @@ -0,0 +1,94 @@ +#include +#include +#include "matrix.hpp" + +Matrix::Matrix(int nrows, int ncols) + // good practice to initialize members regardless of constructor body + : nrows_(nrows), ncols_(ncols), data_(nullptr) +{ + data_ = new int[nrows_ * ncols_]; +} + +Matrix::~Matrix() +{ + delete[] data_; +} + +Matrix::Matrix(const Matrix& m) +{ + this->copy_from(m); +} + +Matrix& Matrix::operator=(const Matrix& m) +{ + // If m is the same object as current object, + // there is nothing to do but return reference to current object. + // If this check were not done and continued with code, + // we would free this->data_ and copy contents of m.data_, + // but m.data_ points to freed memory since m and *this are the same objects! + if (this == &m) { + return *this; + } + + // MUST free existing data before reassigning + // Otherwise, memory leak! + delete[] this->data_; + this->copy_from(m); + + return *this; +} + +void Matrix::copy_from(const Matrix& m) +{ + this->nrows_ = m.nrows_; + this->ncols_ = m.ncols_; + this->data_ = new int[this->nrows_ * this->ncols_]; + std::copy(m.data_, m.data_ + m.nrows_ * m.ncols_, this->data_); +} + +int& Matrix::operator()(int i, int j) +{ + assert(i < nrows_ && j < ncols_); + return data_[i * ncols_ + j]; +} + +const int& Matrix::operator()(int i, int j) const +{ + // Both idiomatic and safer to use const_cast to cast away constness + // than performing C-style casting like ((Matrix&) *this). + // Be very careful with const_cast; this is one of the few times that its usage is considered acceptable. + return const_cast(*this)(i, j); +} + +Matrix Matrix::operator+(const Matrix& m) const +{ + // assert same sizes + assert(this->nrows_ == m.nrows_); + assert(this->ncols_ == m.ncols_); + + Matrix res(this->nrows_, this->ncols_); + + for (int i = 0; i < this->nrows_; ++i) { + for (int j = 0; j < this->ncols_; ++j) { + res(i,j) = (*this)(i,j) + m(i,j); + } + } + + return res; +} + +void Matrix::print() const +{ + // Print dimensions of matrix + std::cout << nrows_ << " x " << ncols_ << std::endl; + + // Print contents of matrix + std::cout << "[" << std::endl; + for (int i = 0; i < nrows_; ++i) { + for (int j = 0; j < ncols_; ++j) { + std::cout << (*this)(i,j) << ' '; + } + std::cout << std::endl; + } + std::cout << "]" << std::endl; +} diff --git a/M-cpp/cpp_demo/matrix.hpp b/M-cpp/cpp_demo/matrix.hpp new file mode 100644 index 0000000..916c3a6 --- /dev/null +++ b/M-cpp/cpp_demo/matrix.hpp @@ -0,0 +1,62 @@ +// pragma once is the C++ way of include guards. +// This guarantees that header files will be included at most once. +#pragma once + +// Matrix is a class that represents a matrix of integers. +class Matrix +{ +public: + Matrix() =default; // tells compiler to generate default constructor. + // Note that if user provides a constructor (as shown in the next line of code), + // compiler will not generate a default constructor! + // Any declarations such as: + // Matrix M; + // will raise a compiler error since there is no default constructor. + // Sometimes you may not want your Matrix to have this default constructor. + // In this case, you may omit this line, or + // as good practice, replace =default with =delete to be more explicit. + Matrix(int nrows, int ncols); // constructor + Matrix(const Matrix&); // copy constructor + ~Matrix(); // destructor + Matrix& operator=(const Matrix&); // copy assignment operator + + // operator() returns the row i, column j element of the matrix. + // For simplicity, it is defined such that if i >= number of rows or j >= number of columns, + // program crashes; in practice, it is best to raise an error. + // We return a reference to an internal data (int) so that we may write code as such: + // + // M(1, 2) = 69; + // + // which assigns 69 to row 1, column 2 entry of matrix M. + // If operator() returns an int (no reference) the above code does not even compile. + int& operator()(int i, int j); + const int& operator()(int i, int j) const; // const version + + // operator+ returns a new matrix that is the matrix addition of current matrix and matrix M. + // If the sizes do not match, program crashes; in practice, it is best to raise an error. + Matrix operator+(const Matrix& M) const; + + // Prints contents of current matrix. + void print() const; + + // Returns the number of rows. + int nrows() const + { + return nrows_; + } + + // Returns the number of columns. + int ncols() const + { + return ncols_; + } + +private: + // Helper function that deep copies another matrix into current matrix. + // This exact logic is used in copy constructor and copy assignment. + void copy_from(const Matrix&); + + int nrows_; // number of rows + int ncols_; // number of columns + int* data_; // pointer to array containing data +};