1.
Question 1:
a. Encapsulation is the bundling of relevant attributes and methods within the same
container, like a class or struct. Data hiding is the act of making certain attributes
and methods that operate on internal data of the container inaccessible to client
code; instead, the client can only access specific data through the interface that
the container provides. We hide the data for two reasons: first, we want to make
sure that the process of using the code as streamlined and intuitive as possible
by hiding all complex background work from the users; second, we want to avoid
user meddling with the data, either intentionally or unintentionally, which can
seriously affect the performance and behavior of our code.
Example: Consider a TV remote. We bundle together the electronic circuits that
make the remote work and the buttons that we use to control the TV into one
convenient handheld device; this is encapsulation. Next, we hide away the
complex wiring and internal workings of the remote, so users need only to
understand how to press buttons to use the remote; this is data hiding.
b. Let’s say we have two variables A and B, with variable A pointing to some data
called D. In OOP, shallow copying data from A to B essentially gives B a
reference to the data A points to. In other words, if D is lost, both A and B lose
their data. Deep copy, on the other hand, means creating a copy of data D,
called D’, and points B to it. Therefore, B can continue to refer to its unique data
D’ even when D is destroyed.
Example: when copying an array from pointer A to B, we can either perform
shallow copying, which gives B the same address as A’s, or deep copying, which
creates an entirely new array and gives its address for B to hold.
c. In inheritance:
i. Public/protected members in Base class can be used in the Derived class
as well.
ii. Private members in Base class stay inaccessible, and the Derived class
can only access them through specified functions Base provides.
Example: Consider a national reserve with three types of fund: one for external
lenders (public), one to strengthen its own regional reserves (protected), one for emergency use
(private). Subordinate reserves, which can also be regarded as an external lender, can access
the first two types of funds, while the last is restricted to only the national reserve.
d. We need to turn a virtual function into a pure virtual function when we need the
subclass to explicitly enforce the virtual members of the base class. When a
class has at least one pure virtual function, it becomes an abstract class, which
cannot be instantiated directly but instead can only be a pointer or reference to
its subclasses.
Example: Consider an Animal class, with an Eat() function. Each animal has a
different way of eating, so we cannot implement any exact logic for Eat() in the
base class. Instead, we mark it as purely virtual, so that each subclass, or each
species, needs to implement the Eat() function with their own logic. This Animal
class becomes abstract, as is the concept of animal in real life.
2. Question 2:
a. Code:
class MyString {
public:
MyString(); //default constructor
MyString(char* ptr, int size); //custom constructor
~MyString(); //destructor to delete the pointer array
MyString operator+(const MyString& other);
bool operator==(const MyString& other);
bool operator!=(const MyString& other);
bool operator>(const MyString& other);
bool operator<(const MyString& other);
MyString operator=(const MyString& other);
char operator[](const int index);
char operator[](const int index) const;
private:
char* data[];
int length;
}
b. Code:
MyString operator+(const MyString& other) {
int newSize = this->size + other.size;
if (newSize > INT_MAX || newSize <= 0)
throw invalid_argument(“invalid size”);
char* newPtr[newSize];
for (int i = 0; i < this->size; i++) {
newPtr[i] = this->data[i];
}
for (int i = this->size; i < other.size; i++) {
newPtr[i] = other.data[i];
}
return MyString(newPtr, newSize);
}
c. Code:
MyString& operator=(const MyString& other) {
if (this != other) {
delete this->data;
this->size = other.size;
char* newData[size];
for (int i = 0; i < this->size; i++) {
newData[i] = other.size[i];
}
this->data = newData;
return *this;
}
d. Code:
ostream& operator<<(ostream& os, const MyString& str) {
for (int i = 0; i < str.size; i++) os << str[i] << “ “ << endl;
return os;
}
3. Printed lines:
Create d1:
B::Constr
Inter::Constr
D::Constr
Calling:
D::CopyConstr
Inter::CopyConstr
B::CopyConstr
byValue
B::Show
Inter::Show
byRef
D::Show
End of main
B::Destr
B::Destr
Inter::Destr
Inter::Destr
D::Destr
D::Destr
4. Problem 4
a.
b. Code:
class Library {
private:
int size;
vector<Book> books
public:
void add(Book book);
void checkoutByISBN(string isbn);
}
class Book {
protected:
string title;
string author;
string ISBN;
bool isCheckout;
int daysCheckout;
string userIDcheckout;
public:
void checkout();
void checkout(int days);
void checkout(string user_id, int days);
void checkout(string user_id);
void checkout_part(int start, int end);
}
class printedBook : protected Book {
private:
int pages;
public:
checkout_part(int startPage, int endPage) override;
}
class eBook : protected Book {
private:
long long fileSize;
public:
checkout_part(int, int) override;
}
class audioBook : protected Book {
private:
int duration;
public:
checkout_part(int startTime, int endTime) override;
}
c. Code:
void add(Book book) {
if (books.size() < size) {
books.push_back(book);
size++;
} else {
cerr << “Library is full” << endl;
}
}
void checkoutByISBN(string isbn) {
for (int i = 0; i < books.size(); i++) {
Book tmp = books[i];
if (tmp.ISBN = isbn) {
books.erase(i);
cout << “Checkout completed” << endl;
return;
}
}
}