Chapter 12 - Worked Exercises 1

Exercise 1 - Vector

Preamble

C++ provides to us in the standard library a vector class, which is a dynamically sized array that can grow in size as we add elements to it.

In this example we'll learn how to implement our own vector class that mimics some of the behaviour of the std::vector class.

Part 1

Create a class VectorInt that is like a vector class that stores a collection of ints.

The class should have a private member variable for a dynamic array of ints, call this member variable values.

The class should also have two member variables of type int:

  • One called capacity, representing the size of the values array.

  • One called size, representing the number of elements currently stored by the VectorInt.

Note that capacity and size serve different roles. A vector may have a capacity of 100, but its size may only be 10, meaning that only 10 of the 100 slots in the array are currently being used. Adding to this vector would add an element at index 10, then incrementing the size to 11.

Part 2

Provide a default constructor that creates a dynamic array for 50 elements.

Provide a constructor with one int argument for the number of elements in the initial dynamic array.

Provide member functions capacity, size that simply return the capacity and size of the vector.

Provide a destructor to ensure that all dynamically allocated memory is cleaned up when the vector is destroyed.

Part 3

Create a member function push_back which accepts an integer value, and adds it to the end of the values array. The size variable should also be updated to account for this.

Part 4

When there is no more room in the values array, then a new dynamic array with twice the capacity of the old dynamic array is created.

The values of the old array are copied into the new dynamic array, then the element is added to the end of it.

The size and capacity variables should be updated accordingly.

Part 5

Implement a copy constructor, and a suitable overloading of the assignment operator to ensure that deep copies of the vector are made when these are invoked.

Provide two examples in your main function showing when each of these copy methods are invoked.

Part 6

Create a const char* member variable called contents_str.

Now create a conversion operator that converts the VectorInt into a const char* type. You will need to use the member variable contents_str to store the string (character array) that is to be returned by the function.

The conversion operator should return the contents of the VectorInt in the following format:

[ a, b, c, d ... ]

where a, b, c, d are elements of the VectorInt.

This should allow you to pipe a VectorInt object to std::cout, do this in your main function.

Exercise 1 - Rational Numbers

In this exercise we will explore why operator overloading is useful. You will first create a class without operator overloading, where the convenience of operator overloading will later become apparent.

Preamble

In mathematics, a rational number is a number that can be expressed as a fraction of two integers, a numerator and a denominator.

For example, the numbers 1/2, 2/3, 15/32, 65/4, 16/5 are all rational.

Part 0

Create a Rational class that will be used to represent rational numbers. The class is composed of two integers representing the numerator and denominator respectively.

In each of the subsequent parts below, implement a driver main function to test the functionality of your implementation of the Rational class.

Part 1

Provide a constructor to create a Rational object from two int parameters corresponding to the numerator and denominator respectively.

Since all integers are also rational numbers (as in 2/1 or 17/1), provide a constructor that takes int parameter called whole_number, and constructs a Rational object with a denominator of 1.

Provide a default constructor that initialises an object to value 0 (i.e. 0/1).

Part 2

Provide member functions add, sub, mul and div that takes another Rational object as a parameter, and returns a new value of type Rational.

You will find the following formulas useful for defining your functions:

  • Addition: a/b + c/d = (a * d + b * c) / (b * d)

  • Subtraction: a/b - c/d = (a * d - b * c) / (b * d)

  • Multiplication: (a/b) * (c/d) = (a * c) / (b * d)

  • Division: (a/b) / (c/d) = (a * d) / (c * b)

Provide member functions less and equals that takes another Rational object as a parameter, and returns a bool value.

You will find the following formulas useful for defining your functions:

  • Less than: (a/b) < (c/d) means (a * d) < (c * b)

  • Equals to: (a/b) == (c/d) means (a * d) == (c * b)

Let any sign be carried by the numerator, keeping the denominator positive.

Part 4

Overload the increment and decrement operators (both prefix and postfix) such that it increments or decrements the rational number by 1.

For decrementing, this would be subtracting the numerator by the denominator. For example, decrementing 1 from 15/8 would result in 7/8.

Conversely, incrementing is adding denominator to the numerator.

Part 5

Using the member functions you have created in Part 2, overload all the following operators so that they correctly apply to the type Rational:

  • ==

  • <

  • <=

  • >

  • >=

  • +

  • -

  • *

  • /

Detour - Greatest Common Divisor

The greatest common divisor (GCD) of two number is the largest number that divides them both.

For example, common divisors of 20 and 15 are 1, 3, and 5. Hence, the GCD is 5, as it is the largest of all the divisors.

Create a function that returns the GCD of two numbers:

int gcd(int x, int y) {
   // your code here
}

Part 6

You may notice that our current Rational class does not attempt to simplify its fraction. For example multiplying Rational(1, 2) with Rational(2, 1) yields Rational(2, 2) instead of Rational(1, 1).

Although the answers are semantically the same, this means that doing many operations over may result in seemingly large fractions, and may even lead to overflow.

Use the gcd function you have created in the previous section to simplify the fraction in the constructor of the Rational class.

Show an example in your main function that implementing the above step solves the issue mentioned.

Exercise 3 - Freeing Memory

For each of the below examples, write appropriate code that would free all memory that was dynamically allocated. If no freeing is required, state so.

Question 1

int* a = new int(10);
int b = 5;

Question 2

int* a = new int;
int* b = new int;

Question 3

int* a = new int[10];
int* b = new int[12];

for (int i = 0; i < 10; i++) {
    a[i] = 4;
}

Question 4

int** a = new int*[50];

Question 5

int** a = new int*[5];

for (int i = 0; i < 5; i++) {
    a[i] = 0;
}

Question 6

int** a = new int*[16];

for (int i = 0; i < 16; i++) {
    a[i] = new int(2);
}

Question 7

int* a = 0;
int* b = 0;
int c = 10;

Question 8

int* a = new int(5);
int* b = a;
int* c = new int(*b);

Exercise 4 - String

Preamble

Much like the VectorInt class that you have made in Exercise 1, you will now implement a String class that behaves similar to the std::string class of the standard library.

Remember that the point of the exercise is to not use any standard libraries, unless explicitly specified.

Part 0

Refresh your memory by reviewing the C-style Character Strings section of the textbook.

You are allowed and suggested to use the following standard library functions found in the <string.h> header in your code:

Part 1

Create a String class that stores a string as a character array (char*) in a member variable called contents.

Create a constructor that accepts a const char* parameter, and copies the contents from said parameter into its contents member variable.

  • The character array for the contents member variable should be dynamically allocated.

  • You will find the functions strcpy() and strlen() helpful.

Create a suitable destructor that frees the memory allocated by contents.

Part 2

Implement a conversion operator that would convert a String object into a const char*. This would be simply returning its contents member variable.

Explain why this allows you to now do the following:

String myStr = "Hello World!"
std::cout << myStr << std::endl;  // Prints "Hello World!"

Part 3

Overload the addition operator (operator+) so that you are able to concatenate two String objects together.

String myStr = "Hello World!";
String myStr2 = " Goodbye!";
String myStr3 = myStr + myStr2;
std::cout << myStr3 << std::endl;  // Prints "Hello World! Goodbye!"

Overload the addition operator (operator+) so that you are able to concatenate a String object with a const char* object together.

String myStr4 = "Good ";
String myStr5 = myStr + " morning!";
std::cout << myStr5 << std::endl;  // Prints "Good morning!"

Part 4

Implement a suitable copy constructor for the String class. Show in your main program an example of an issue that is addressed by the copy constructor that you've implemented.

Implement a suitable copy assignment operator for the String class. Show in your main program an example of an issue that is addressed by the copy constructor that you've implemented.

Solution

https://replit.com/@chowder2/AbsoluteTightHexagon#main.cpp

Last updated

Was this helpful?