C++ Test. An application of type ConsoleApplication. The template class CArray

Test. An application of type ConsoleApplication. The template class CArray

Using this example, you can learn to develop your own template classes in which memory is allocated dynamically.


 Contents


Task

Develop a template class CArray – an array of data of an arbitrary type T and tests that demonstrate working with this class. Memory for the data array is allocated dynamically.

The following methods should be implemented:

  • default constructor;
  • copy constructor;
  • destructor;
  • method push_back (T _value) to add an _value element to the end of the array;
  • method erase(int _index) which removes an item from the array at the given index;
  • the method insert(int _index) which inserts an element into the array at the specified index;
  • method size() returns the size of the array;
  • method clear() that clears the array;
  • operator function operator[](), which overloads the operator [ ] of indexing array elements and returns the value of the array item at the specified index;
  • method print(), intended for displaying the contents of the array on the screen.

The solution should demonstrate the operation of the class using the following tests:

  • Work with numbers (int)
    • 1.1. Adding 20 random numbers in a cycle from 0 to 100.
    • 1.2. Sort the resulting set of numbers in ascending order.
    • 1.3. Removing every second element in the array.
    • 1.4. Inserting 10 random numbers ranging from 0 to 100 at random positions.
    • 1.5. Container cleaning.
  • Work with objects (std::string)
    • 2.1. Adding in a cycle of 15 randomly selected words consisting of lowercase Latin letters.
    • 2.2. Sort the resulting set of words in ascending order.
    • 2.3. Removing every word that includes any of the letters a, b, c, d, e.
    • 2.4. Inserting three randomly selected words into random positions.

 


Instructions

1. The text of the program

An implementation of a program like Console Application has the following form.

// The task about the template class
#include <iostream>
#include <new>
#include <vector>
#include <time.h>
using namespace std;

template <typename T>
class CArray
{
public:
  // default constructor
  CArray()
  {
    count = 0;
  }

  // copy constructor
  CArray(const CArray& _A)
  {
    try {
      // attempt to allocate memory for an array of type T
      A = new T[_A.count];
      count = _A.count;

      for (int i = 0; i < count; i++)
        A[i] = _A.A[i];
    }
    catch (bad_alloc e)
    {
      count = 0;
      cout << "Error. Cannot allocate memory." << endl;
      cout << e.what() << endl;
    }
  }

  // copy operator
  CArray& operator=(const CArray& _A)
  {
    // release previously allocated memory
    if (count > 0)
      delete[] A;

    // allocate memory
    try {
      A = new T[_A.count];
      count = _A.count;

      for (int i = 0; i < count; i++)
        A[i] = _A.A[i];
    }
    catch (bad_alloc e)
    {
      count = 0;
      cout << "Error. Cannot allocate memory." << endl;
      cout << e.what() << endl;
    }
    return *this;
  }

  // destructor
  ~CArray()
  {
    // release previously allocated memory
    if (count > 0)
      delete[] A;
  }

  // add item to the end of array
  void push_back(T value)
  {
    T* A2; // additional pointer to array

    // attempt to allocate memory
    try {
      A2 = new T[count + 1];

      // if the memory is allocated, then increase by 1 the number of items
      count++;

      // copy array A to array A2
      for (int i = 0; i < count - 1; i++)
        A2[i] = A[i];

      // add item value
      A2[count - 1] = value;

      // release the memory allocated for the array A
      if ((count - 1) > 0)
        delete[] A;

      // redirect pointer A to array A2
      A = A2;
    }
    catch (bad_alloc e)
    {
      cout << "Error. Cannot allocate memory." << endl;
      cout << e.what() << endl;
    }
  }

  // method that outputs array A - needed for testing
  void print()
  {
    cout << "Array A: " << endl;

    if (!count)
    {
      cout << "Array is empty." << endl;
      return;
    }

    for (int i = 0; i < count; i++)
      cout << A[i] << " ";
    cout << endl;
  }

  // add an item to the array at a given index
  void insert(int _index, const T& _value)
  {
    // validation of the index
    if (!CheckIndex(_index))
    {
      cout << "Error. Bad index. ";
      return;
    }

    T* A2;

    A2 = Alloc(count + 1); // attempt to allocate memory
    if (A2 == nullptr)
    {
      cout << "Error. Method insert(). Cannot allocate memory." << endl;
      return;
    }

    // copy A to A2 before the _index position
    for (int i = 0; i < _index; i++)
      A2[i] = A[i];

    // insertion at _index position
    A2[_index] = _value;

    // copy A to A2 after position _index
    for (int i = _index + 1; i < count + 1; i++)
      A2[i] = A[i - 1];

    // release preallocated memory for array A
    if (count > 1)
      delete[] A;

    count++; // increase the number of array items by 1

    // redirect pointer A to array A2
    A = A2;
  }

  // method erase() - removes an item from the array at a given index
  void erase(int _index)
  {
    // checking if the index is correct
    if (!CheckIndex(_index))
    {
      cout << "Bad index." << endl;
      return;
    }

    // the loop to delete from an array of an item in position _index
    for (int i = _index; i < count - 1; i++)
      A[i] = A[i + 1];

    count--; // reduce the number of array items by 1

    // reallocate memory
    T* A2 = Alloc(count);

    // copy A to A2
    for (int i = 0; i < count; i++)
      A2[i] = A[i];

    // release the memory allocated for the array A
    if (count > 0)
      delete[] A;

    // redirect pointer A to A2
    A = A2;
  }

  // method returning array size
  int size()
  {
    return count;
  }

  // array clearing method
  void clear()
  {
    if (count > 0)
    {
      delete[] A;
      count = 0;
    }
  }

  // operator function operator[], overloads the array indexing operator
  T& operator[](int _index)
  {
    if (CheckIndex(_index))
      return A[_index];
    else
    {
      T value = 0;
      cout << "Bad index." << endl;
      return value;
    }
  }

protected:
  T* A; // data array
  int count; // the number of items in the array

  // internal method that checks if index value is within acceptable limits
  // it returns true if the index is correct
  bool CheckIndex(int _index)
  {
    if ((_index < 0) || (_index >= count))
      return false;
    return true;
  }

  // a method that allocates memory for an array of type T,
  // method returns a pointer to the allocated array
  T* Alloc(int _count)
  {
    T* A2 = nullptr;
    try {
      A2 = new T[_count];
    }
    catch (bad_alloc e)
    {
      cout << e.what() << endl;
      return nullptr;
    }
    return A2; // return the pointer to the selected fragment
  }
};

// Tests to demonstrate class work
// 1.1. Add 20 random numbers in the loop from 0 to 100
void Test_1_1(CArray<int>& A)
{
  int i, number;

  cout << "Test 1.1. Form the array with 20 random numbers." << endl;
  srand(time(NULL));

  for (i = 0;i < 20;i++)
  {
    // generate a random number from 0 to 100
    number = rand() % 100;

    // add random number to the end of the array
    A.push_back(number);
  }
}

// 1.2. Sorting a set of numbers in ascending order
void Test_1_2(CArray<int>& A)
{
  int i, j;
  int t;

  cout << "Test 1.2. Sorting the array." << endl;

  // insertion sorting
  for (i=0;i<A.size()-1;i++)
    for (j=i; j>=0; j--)
      if (A[j] > A[j + 1])
      {
        t = A[j];
        A[j] = A[j + 1];
        A[j + 1] = t;
      }
}

// 1.3. Delete every second item
void Test_1_3(CArray<int>& A)
{
  CArray<int> B; // a new array

  cout << "Test 1.3. Deleting every secont item." << endl;

  B.clear();
  for (int i = 0; i < A.size();i++)
  {
    if (i % 2 == 1)
      B.push_back(A[i]);
  }
  A = B;
}

// 1.4. Insertion 10 random numbers ranging from 0 to 100 at random positions
void Test_1_4(CArray<int>& A)
{
  int i;
  int number, pos;
  int t;

  cout << "Test 1.4. Inserting 10 random numbers to random positions" << endl;

  // snap to the current time when generating random numbers
  srand(time(NULL));

  t = 0; // variable that determines the number of added numbers

  for (i = 0; i < 10; i++)
  {
    number = rand() % 100; // random number
    pos = rand() % (10 + t); // random position (0..10+t)
    A.insert(pos, number);
    t++; // number of added numbers
  }
}

// 1.5. Container cleaning
void Test_1_5(CArray<int>& A)
{
  cout << "Test 1.5. Clear the array." << endl;
  A.clear();
}

// Demonstration of the class work with int numbers
void DemoInt()
{
  CArray<int> A; // array to be tested

  // Test 1.1
  Test_1_1(A);
  A.print(); // display array to the screen

  // Test 1.2
  Test_1_2(A);
  A.print();

  // Test 1.3
  Test_1_3(A);
  A.print();

  // Test 1.4
  Test_1_4(A);
  A.print();

  // Test 1.5
  Test_1_5(A);
  A.print();
}

// 2. Work with objects (std::string)
// 2.1. Adding 15 randomly selected words consisting of Latin letters in lowercase in a loop
void Test_2_1(CArray<string>& A)
{
  int i, j;
  int len; // the length of the word
  string word;
  char symbol;

  cout << "Test 2.1. Adding 15 new words." << endl;
  srand(time(NULL));

  // word formation loop
  for (i = 0; i < 15; i++)
  {
    len = rand() % 6 + 1; // random word length from 1 to 6

    // generate a random word
    word = "";
    for (j = 0;j < len;j++)
    {
      // get a random symbol
      symbol = 'a' + rand() % ((int)'z' - (int)'a' + 1);
      word += symbol; // add symbol to the word
    }
    A.push_back(word); // add a random word to array
  }
}

// 2.2. Sort words in ascending order
void Test_2_2(CArray<string>& A)
{
  int i, j;
  string t;

  cout << "Test 2.2. Sorting words." << endl;

  // insertion sorting
  for (i = 0;i < A.size() - 1;i++)
  {
    for (j=i; j>=0; j--)
      if (A[j] > A[j + 1])
      {
        t = A[j];
        A[j] = A[j + 1];
        A[j + 1] = t;
      }
  }
}

// 2.3. Удаление каждого слова, включающего в себя любую из букв a,b,c,d,e
void Test_2_3(CArray<string>& A)
{
  // additional variables
  int i, j;
  string word;
  CArray<string> B;
  bool f_delete;

  cout << "Test 2.3. Deleting words wit characters a..z " << endl;

  // loop to create a new array B that does not contain words
  for (i = 0; i < A.size(); i++)
  {
    word = A[i];
    f_delete = false;

    for (j=0; j<word.length(); j++)
      if (('a' <= word[j]) && (word[j] <= 'e')) // if the symbol is within 'a' .. 'e'
      {
        f_delete = true; // it can be deleted
        break;
      }

    // add a word to array B that does not contain 'a' .. 'e'
    if (!f_delete)
      B.push_back(word);
  }
  A = B; // copy array B to A
}

// 2.4. Insert 3 new random words into random positions
void Test_2_4(CArray<string>& A)
{
  int i, j;
  int len; // random length of random word
  string word; // random word
  char symbol; // random symbol
  int position; // random position (0, 1, ...)
  int t; // number of added words

  cout << "Test 2.4. Inserting 3 new random words to random positions" << endl;

  // snap to the timer when generating random words
  srand(time(NULL));

  // The loop of adding a random strings
  for (i = 0, t = 0; i < 3; i++, t++)
  {
    // get a random position
    position = rand() % (A.size() + t);

    // get a random word length of 1..6
    len = rand() % 6 + 1;

    // generate a random word
    word = "";

    for (j = 0; j < len; j++)
    {
      // form a random symbol
      symbol = 'a' + rand() % ((int)'z' - (int)'a' + 1);
      word += symbol; // add symbol to the word
    }

    // insert a random word into a random position
    A.insert(position, word);
  }
}

// A method that demonstrates how to work with std::string objects
void DemoString()
{
  CArray<string> A; // declare an array of words for testing

  Test_2_1(A);
  A.print();

  Test_2_2(A);
  A.print();

  Test_2_3(A);
  A.print();

  Test_2_4(A);
  A.print();
}

int main()
{
  DemoInt();
  DemoString();
}

 

2. The result of the program
Test 1.1. Form the array with 20 random numbers.
Array A:
40 55 83 90 71 0 38 98 15 49 44 69 74 82 84 90 88 14 93 10
Test 1.2. Sorting the array.
Array A:
0 10 14 15 38 40 44 49 55 69 71 74 82 83 84 88 90 90 93 98
Test 1.3. Deleting every secont item.
Array A:
10 15 40 49 69 74 83 88 90 98
Test 1.4. Inserting 10 random numbers to random positions
Array A:
10 15 38 93 84 40 83 71 49 69 40 88 74 74 83 15 88 90 44 98
Test 1.5. Clear the array.
Array A:
Array is empty.
Test 2.1. Adding 15 new words.
Array A:
txkhs crt paqkm shcni wgagcv ye jlww rayr qtjc usd bprcx s yvd pwxb jobx
Test 2.2. Sorting words.
Array A:
bprcx crt jlww jobx paqkm pwxb qtjc rayr s shcni txkhs usd wgagcv ye yvd
Test 2.3. Deleting words wit characters a..z
Array A:
jlww s txkhs
Test 2.4. Inserting 3 new random words to random positions
Array A:
jlww acrtk xk s qkm txkhs

 

3. Explanation of the program
3.1. Calling functions for testing class work

From the main() function, the DemoInt() and DemoString() functions are called. Each of the functions calls the corresponding test.

The DemoInt() function calls tests for the int type. Each test is designed as a separate function: Test_1_1(), Test_1_2(), Test_1_3(), Test_1_4(), Test_1_5(). Each of the tested functions receives as input parameter a reference to an array of type int.

CArray<int>& A

Since the passing of the array occurs by reference, that is, the ability to change the input array in the function.

The DemoString() function invokes the tests for the std::string class. The class string is implemented in the std namespace and is designed to work with char* character strings. The class contains a number of methods for working with character strings.



Each test for the string type is designed as a separate function: Test_2_1(), Test_2_2(), Test_2_3(), Test_2_4().As in the case of the int type, any of the functions receives as input parameter a reference to an array of the string type

CArray<string>& A

In each function tested, the input array is changed (passing the array by reference).

 

3.2. Why is the copy constructor and copy operator are implemented in the class?

The CArray class declares a dynamic array A of generalized type T. In fact, a pointer A to type T is declared

T* A; // data array

Depending on the situation, for array A, memory is allocated dynamically using the new operator. If memory is allocated dynamically in a class, then this class must have an implementation of the copy constructor and copy operator in order to avoid the disadvantages of bitwise copying. More details about the need for declarations in the class of the copy constructor and copy operator are described in the topic:

 

3.3. Features of memory allocation in the new operator

In a class, in different functions, memory is allocated using the new operator. There is a risk that memory may not be allocated. In this case, the system throws an exception. To ensure that the program does not stop, but displays the corresponding message, the call to the new operator is taken in a try…catch block as shown below

...
try {
  // attempt to allocate memory for array of type T
  A = new T[_A.count];
  count = _A.count;

  for (int i = 0; i < count; i++)
    A[i] = _A.A[i];
}
catch (bad_alloc e)
{
  count = 0;
  cout << "Error. Cannot allocate memory." << endl;
  cout << e.what() << endl;
}
...

As you can see from the code, an exception of type bad_alloc is caught in the catch block. bad_alloc is a class that describes an exception when memory is not allocated by the new operator. The bad_alloc class is inherited from the exception class and is part of the hierarchy of classes that describe exceptions in C ++.

 

3.4. Freeing memory in a class with delete[] operator

Since, memory for array A is allocated as for an array (and not a single element)

A = new T[count]

then you need to release it by the operator

delete[] A;

If memory was allocated as for a single element, then delete was to be used instead of delete[].

 

3.5. Additional internal functions of the class

In the class, in the private section, the following additional internal functions are implemented

  • CheckIndex() – checks if the index of array A is within acceptable boundaries;
  • Alloc() – returns a pointer to the allocated memory area of a given _count size, which is an input parameter.

These functions are called several times in different methods of the class.

 

3.6. Features of generating random numbers. Functions srand(), time(), rand()

In order to generate a random number in the program, the following functions are used:

  • srand() – sets the starting number for the rand() function. Based on this number, a sequence of random numbers will be generated by the rand() function;
  • rand() – returns an integer random number (generates a random number);
  • time() – a function from the time.h library.

Function time() with NULL parameter

time(NULL)

returns the number of milliseconds that have passed since January 1, 1970. This function is necessary in order to form a starting number for a random number generator. The number of milliseconds will always be different, since the launch of the program depends on the user and it is impossible to foresee the moment of launch.

The function time() is nested as a parameter in the srand() function

srand(time(NULL));

The srand() function sets the starting number, which will be used as the basis for generating a sequence of random numbers with the rand() function. This means that the union of the srand(), time(), rand() functions allows to generate different sequences of numbers with each call.

 


Related topics