Разработка класса, который реализует массив строк типа char*
В языке C++ строку можно представить типом string или типом char*. Тип string есть классом и имеет мощный арсенал удобных средств для обработки строк. Работа со строками типа char* часто бывает сложна.
В данной теме продемонстрирован класс ArrayPChar, который представляет массив строк типа char*. Работа со строками типа char* не менее интересна чем со строками типа string. Тема будет полезна начинающим в изучении вопросов динамического выделения/освобождения памяти для массива, доступа по указателю в массиве указателей, работе с строками char*.
Содержание
- Условие задачи
- Решение
- 1. Общее построение класса. Добавление внутренних переменных
- 2. Разработка дополнительных методов класса
- 2.1. Метод CheckIndex(). Проверка корректности индекса массива
- 2.2. Метод CopyStr(char** dest, const char* source). Копирование строки с выделением памяти
- 2.3. Метод CopyArrayStr(char***, char**, int). Копирование массивов типа char* с выделением памяти
- 2.4. Метод Free(char**, int). Освобождение памяти в массиве
- 3. Разработка основных методов оперирования строками в соответствии с условием задачи
- 3.1. Конструктор без параметров ArrayPChar()
- 3.2. Конструктор с двумя параметрами ArrayPChar(char**, int)
- 3.3. Конструктор копирования ArrayPChar(const ArrayPChar&)
- 3.4. Метод GetAi(int). Чтение отдельной строки в массиве
- 3.5. Метод SetAi(const char* , int). Запись строки в массив по заданному индексу
- 3.6. Метод Add(). Добавить строку в конец массива
- 3.7. Метод Del(). Удаление элемента в заданной позиции
- 3.8. Операторный метод operator=(). Перегрузка оператора присваивания
- 3.9. Деструктор
- 3.10. Метод Print(). Вывод массива на экран
- 4. Метод main(). Тестирование класса
- 5. Текст всей программы. Сокращенный вариант
- Связанные темы
Поиск на других ресурсах:
Условие задачи
Разработать класс ArrayPChar, который оперирует строками типа char*. В классе должны быть реализованы:
- внутренние переменные, которые представляют массив строк;
- конструкторы, которые инициализируют массив различными способами;
- конструктор копирования;
- методы GetAi() и SetAi() для доступа к отдельной строке;
- операторная функция operator=(), которая переопределяет операцию присваивания. Функция должна реализовывать присваивание массивов строк;
- метод Add() добавления строки в конец массива;
- метод Del() удаления строки с заданной позиции;
- деструктор.
⇑
Решение
1. Общее построение класса. Добавление внутренних переменных
Прежде всего объявляется класс с именем ArrayPChar и в нем объявляются следующие внутренние переменные:
- A – массив строк типа char*. Тип массива char**;
- length – количество строк в массиве A.
#include <iostream> using namespace std; // Массив строк типа char* class ArrayPChar { private: // Внутренние данные char** A; // массив строк int length; // количество строк в массиве }
В будущем к этому классу будут добавлены разные методы в соответствии с условием задачи.
⇑
2. Разработка дополнительных методов класса
В раздел private класса нужно ввести ряд методов общего назначения. Эти методы (функции) будут вызываться из других методов. Методы общего назначения реализуют типичные операции со строками и массивом.
2.1. Метод CheckIndex(). Проверка корректности индекса массива
Метод CheckIndex() выполняет проверку, лежит ли индекс строки в массиве в допустимых пределах. Программный код метода следующий:
// Метод, который определяет, есть ли индекс index допустимым индексом массива. // Метод возвращает true, если значение индекса корректно. bool CheckIndex(int index) { return ((index >= 0) && (index < length)); }
Этот метод нужно скопировать в раздел private класса.
⇑
2.2. Метод CopyStr(char** dest, const char* source). Копирование строки с выделением памяти
Поскольку, копирование строк есть основной операцией в данном классе, то очень полезной будет реализация функции CopyStr(), которая копирует одну строку в другую. Особенность этой функции состоит в том, что внутри функции выделяется память для строки-назначения.
Функция получает 2 параметра:
- исходная строка source. Тип строки char*;
- указатель на строку-назначение dest (тип указателя char**). Обращение к строке-назначению *dest. Тип строки *dest – char*. Размер строки *dest определяется динамично на основе размера строки source.
Программный код функции следующий:
// Внутренняя функция - копирует строку source в dest, // память для dest выделяется внутри функции void CopyStr(char** dest, const char* source) { int i; // Выделить память для строки *dest try { *dest = new char[strlen(source) + 1]; } catch (bad_alloc e) { cout << e.what() << endl; return; } // Копирование с символом '\0' включительно for (i = 0; source[i]!='\0'; i++) (*dest)[i] = source[i]; (*dest)[i] = '\0'; }
Текст функции нужно поместить в раздел private класса ArrayPChar.
⇑
2.3. Метод CopyArrayStr(char***, char**, int). Копирование массивов типа char* с выделением памяти
На основе предыдущей функции копирования строки CopyStr() разработана функция копирования массивов CopyArrayStr(). Функция CopyArrayStr() получает 3 параметра:
- указатель на массив-назначение dest (тип указателя char***). Обращение к массиву *dest (тип массива char**). Обращение к i-й строке (*dest)[i]. Каждая строка (*dest)[i] имеет тип char*;
- массив-источник source (тип массива char**). Тип строки в массиве char*;
- количество строк length в массиве source.
Внутри функции CopyArrayStr() выделяется память для массива-назначения *dest. Размер выделенной памяти определяется на основе размера массива-источника source.
Текст функции следующий:
// Функция которая копирует массив строк source в массив dest. // Память для массива dest выделяется внутри функции void CopyArrayStr(char*** dest, char** source, int length) { int i, j; int n; // Выделить память для массива dest как массива указателей try { *dest = new char* [length]; } catch (bad_alloc e) { cout << e.what() << endl; return; } // Цикл копирования строк for (i = 0; i < length; i++) CopyStr(&((*dest)[i]), source[i]); }
Как видно из вышеприведенного кода, для копирования строк и выделения памяти для этих строк функция CopyArrayStr() вызывает функцию CopyStr() из п. 2.2. Чтобы передать указатель на строку (*dest)[i] в функцию CopyStr() используется следующее обращение
... CopyStr(&((*dest)[i]), source[i]); ...
Функцию CopyArrayStr() можно поместить в раздел private класса.
⇑
2.4. Метод Free(char**, int). Освобождение памяти в массиве
Частой операцией есть освобождение памяти для внутреннего массива A. Для реализации этой операции в классе разработана функция Free(), которая освобождает память для входного массива. Функция получает два параметра:
- массив строк;
- количество строк в массиве length.
Текст функции следующий:
// Внутренняя функция, которая освобождает память для входного массива void Free(char** A, int length) { if (length > 0) { // Освободить память, выделенную для каждой строки for (int i = 0; i < length; i++) delete[] A[i]; // Освободить память для массива указателей на строки delete[] A; } }
Проверка того, была ли выделена память, происходит на основе параметра length. Освобождение памяти происходит в два этапа. Сначала освобождается память, выделенная для каждой строки (тип элемента char*). Затем освобождается память, выделенная для массива указателей в целом (тип массива char**, тип указателя char*).
⇑
3. Разработка основных методов оперирования строками в соответствии с условием задачи
В разделе public класса объявляются методы, которые необходимы в соответствии с условием задачи.
3.1. Конструктор без параметров ArrayPChar()
Конструктор без параметров вызывается, если создается объект класса ArrayPChar в котором в массиве A нет ни одного элемента. Например,
// объявляется экземпляр (объект) с именем AC ArrayPChar AC; // вызовается конструктор без параметров
Текст конструктора без параметров следующий
... public: // Конструктор без параметров ArrayPChar() { A = nullptr; length = 0; } ...
⇑
3.2. Конструктор с двумя параметрами ArrayPChar(char**, int)
Конструктор с двумя параметрами предназначен для инициализации текущего массива элементами другого массива типа char**. Конструктор получает два параметра:
- массив строк _A. Тип массива char**. Тип строк в массиве char*;
- количество строк в массиве length.
... // Конструктор, который инициализирует внутренние данные значением другого массива ArrayPChar(char** _A, int _length) { // Объявить локальные переменные int i, j; // Скопировать A = _A CopyArrayStr(&A, _A, _length); // Установить количество строк length = _length; } ...
Как видно из вышеприведенного кода, всю работу по копированию массивов выполняет функция CopyArrayStr(), которая описывается в п. 2.3.
⇑
3.3. Конструктор копирования ArrayPChar(const ArrayPChar&)
В данном классе необходимо реализовывать конструктор копирования, так как в классе используются указатели для которых динамично выделяется память. Более подробно о необходимости использования конструктора копирования описывается в теме:
Текст конструктора копирования следующий
// Конструктор копирования ArrayPChar(const ArrayPChar& _A) { CopyArrayStr(&A, _A.A, _A.length); length = _A.length; }
⇑
3.4. Метод GetAi(int). Чтение отдельной строки в массиве
Обязательными методами являются методы, которые осуществляют чтение/запись конкретной строки, размещенного в позиции i. Метод GetAi() возвращает указатель на строку массива, который имеет позицию i.
// Чтение строки по индексу i char* GetAi(int i) { if (CheckIndex(i)) return A[i]; else return nullptr; }
⇑
3.5. Метод SetAi(const char* , int). Запись строки в массив по заданному индексу
Метод SetAi() реализует запись строки типа char* в массив в заданной позиции. Память, выделенная для предыдущей строки, перераспределяется под размер новой строки.
// Запись строки типа char* в строку A[i] по индексу i, // Функция возвращает true, если запись произошла успешно. bool SetAi(const char* str, int i) { // Проверка, находится ли индекс в допустимых пределах if (CheckIndex(i)) { // Освободить предварительно выделенную память для строки A[i] if (A[i] != nullptr) delete[] A[i]; // Скопировать строку: A[i] = str CopyStr(&A[i], str); return true; } return false; }
⇑
3.6. Метод Add(). Добавить строку в конец массива
В методе Add() осуществляется добавление строки в конец массива. В методе объявляется дополнительная переменная-массив A2 типа char**, которая служит копией основного массива с дополнительным последним элементом.
Общий алгоритм работы метода следующий:
- сформировать новый массив A2 с дополнительным последним элементом;
- скопировать данные из основного массива A в дополнительный массив A2;
- освободить память, предварительно выделенную для массива A;
- перенаправить указатель из массива A на область памяти, выделенную для массива A2.
Текст метода следующий
// Добавить строку в конец массива void Add(const char* str) { // 1. Объявить дополнительные внутренние переменные char** A2 = nullptr; // Дополнительный локальный массив // 2. Выделить память для массива A2 - на 1 элемент больше try { A2 = new char* [length + 1]; } catch (bad_alloc e) { cout << e.what() << endl; return; } // 3. Скопировать строки из A в A2 for (int i = 0; i < length; i++) CopyStr(&A2[i], A[i]); // 4. Освободить память, выделенную ранее для массива A Free(A, length); // 5. Записать последний элемент в A2, // память для A2[length] выделяется внутри функции CopyStr() CopyStr(&A2[length], str); // 6. Увеличить количество строк на 1 length++; // 7. Перенаправить указатель A на A2 A = A2; }
⇑
3.7. Метод Del(). Удаление элемента в заданной позиции
Полезной для массива есть операция удаления элемента из заданной позиции. Для реализации этой операции используется метод Del(). В методе используется дополнительная переменная-массив A2, которая служит копией основного массива A без строки, которую нужно удалить. Позиция строки определяется параметром index.
Общий алгоритм работы метода следующий:
- скопировать элементы из массива A в массив A2 в позиции index. Элемент в позиции index не включается в результирующий массив A2;
- скопировать элементы из массива A в массив A2 после позиции index. Таким образом, элемент в позиции index не включается в результирующий массив A2;
- перенаправить указатель из массива A на массив A2.
Текст метода следующий
// Удаление элемента из заданной позиции index = 0, 1, ..., length-1 void Del(int index) { // Дополнительный массив char** A2 = nullptr; // Проверка, корректно ли значение index if (CheckIndex(index)) { // Выделить память для массива A2 - на 1 строку меньше try { A2 = new char* [length - 1]; } catch (bad_alloc e) { cout << e.what() << endl; return; } // Цикл копирования данных из A в A2 // До позиции index for (int i = 0; i < index; i++) CopyStr(&A2[i], A[i]); // Копирование данных A2[i] = A[i] // После позиции index for (int i = index + 1; i < length; i++) CopyStr(&A2[i - 1], A[i]); // Освободить память, выделенную для A Free(A, length); // Уменьшить общее количество строк на 1 length--; // Перенаправить указатели A = A2; } }
⇑
3.8. Операторный метод operator=(). Перегрузка оператора присваивания
Операторный метод (функция) operator=() необходим, чтобы корректно выполнялось следующее присваивание
A1 = A2;
где A1, A2 – экземпляры (объекты) типа ArrayPChar. Если не выполнить перегрузки оператора присваивания (=), то будет выполняться побитовое копирование предоставляемое компилятором по умолчанию. Если в классе используются указатели (как в нашем классе ArrayPChar), побитовое копирование указателей приводит к тому, что указатели разных экземпляров (объектов) класса указывают на один и тот же участок памяти, а это недопустимо. После уничтожения объектов классов вызывется деструктор, который освобождает тот самый участок памяти два раза (для каждого объекта в отдельности). Первый раз (для первого объекта) уничтожение произойдет корректно, второй раз (для второго объекта) система сгенерирует исключение, поскольку память уже освобождена. В результате программа «вылетет». Более подробно о недостатках побитового копирования и необходимость перегрузки оператора присваивания и конструктора копирования описывается здесь.
Текст операторного метода operator=() следующий
// Перегруженный оператор присваивания ArrayPChar& operator=(ArrayPChar& _A) { // 1. Объявить дополнительные внутренние переменные char** A2; // 2. Освободить память, ранее выделенную для массива A Free(A, length); // 3. Проверка на корректность значений if (_A.length <= 0) { length = 0; A = nullptr; return *this; } // 4. Присваивание A = _A.A CopyArrayStr(&A, _A.A, _A.length); // 5. Установить новое количество строк length = _A.length; // 6. Вернуть текущий экземпляр return *this; }
⇑
3.9. Деструктор
Деструктор предназначен для освобождения динамически-выделенной памяти после того, как уничтожается объект класса ArrayPChar. В нашем случае память, выделенная динамично, освобождается во внутреннем методе Free(). Соответственно программный код деструктора следующий
// Деструктор ~ArrayPChar() { // Освободить память, выделенную для массива A Free(A, length); }
⇑
3.10. Метод Print(). Вывод массива на экран
Всегда при разработке класса целесообразно реализовывать метод, который выводит содержимое данных экземпляра класса. Это необходимо для проведения контроля, тестирования и т.п. В нашем случае реализован метод Print(), текст которого следующий
// Метод вывода массива строк на экран, // метод получает параметром комментарий, который также выводится. void Print(const char* text) { cout << "----------------" << endl; cout << text << endl; if (length <= 0) { cout << "Array is empty." << endl; return; } // Вывести строки for (int i = 0; i < length; i++) { cout << A[i] << endl; } }
⇑
4. Метод main(). Тестирование класса
В методе main() продемонстрировано использование класса ArrayPChar. По желанию можно изменить код метода для проведения собственных исследований.
void main() { ArrayPChar AC1; ArrayPChar AC2 = AC1; // Вызывается конструктор копирования AC1.Print("AC1"); AC2.Print("AC2"); AC1.Add("Hello!"); AC1.Print("AC1"); AC1.Add("World"); // Проверка конструктора копирования ArrayPChar AC3 = AC1; AC3.Print("AC3"); // Проверка переопределенного оператора присваивания AC2 = AC3; AC2.Print("AC2"); // Проверка функции GetAi() char* s1; s1 = AC2.GetAi(0); cout << "s1 = " << s1 << endl; // Проверка функции SetAi() AC2.SetAi("ABCD", 1); AC2.Print("AC2"); // Проверка функции Del() AC2.Add("JKLMN"); AC2.Add("OPRST"); AC2.Print("AC2"); AC2.Del(3); AC2.Print("AC2-[3]"); // Проверка конструктора, который получает массив типа char** const char* Array[3] = { "Programming", "C++", "Strings" }; ArrayPChar AC4((char**)Array, 3); AC4.Print("AC4"); AC4.Add("Java"); AC4.Print("AC4"); }
⇑
5. Текст всей программы. Сокращенный вариант
#include <iostream> using namespace std; // Массивы строк типа char* class ArrayPChar { private: // Внутренние данные char** A; // массив строк int length; // количество строк в массиве типа char* // Метод, определяющий, есть ли index допустимым индексом массива. // Метод возвращает true, если значение индекса корректно. bool CheckIndex(int index) { ... } // Внутренняя функция - копирует строку source в dest, // память для dest выделяется всередине функции void CopyStr(char** dest, const char* source) { ... } // Функція, копирующая массив строк source в массив dest. // Память для массива dest выделяется всередине функции void CopyArrayStr(char*** dest, char** source, int length) { ... } // Внутренняя функция, которая освобождает память для входного массива void Free(char** A, int length) { ... } public: // Конструкторы // Конструктор без параметров ArrayPChar() { ... } // Конструктор, инициализирующий внутренние данные значением другого массива ArrayPChar(char** _A, int _length) { ... } // Конструктор копирования ArrayPChar(const ArrayPChar& _A) { ... } // Методы доступа к отдельной строке GetAi(), SetAi() // Чтение строки по индексу i char* GetAi(int i) { ... } // Запись строки типа char* в строку A[i] по индексу i, // Функция возвращает true, если запись произошла успешно. bool SetAi(const char* str, int i) { ... } // --------------- Методы оперирования строками ---------------- // Добавить строку в конец массива void Add(const char* str) { ... } // Удаление элемента с заданой позиции index = 0, 1, ..., length-1 void Del(int index) { ... } // ---------------------------------------------------------- // Перегруженный оператор присваивания ArrayPChar& operator=(ArrayPChar& _A) { ... } // Деструктор ~ArrayPChar() { ... } // Метод вывода массива строк на экран, // метод получает параметром комментарий, который также выводится. void Print(const char* text) { ... } }; void main() { ... }
⇑
Связанные темы
- Класс string. Примеры использования
- Перегрузка оператора присваивания =. Примеры
- Конструктор копирования. Примеры использования. Передача объекта класса в функцию. Возврат объекта класса из функции
- Понятие побитового копирования. Пример. Необходимость использования конструктора копирования и оператора копирования для классов, содержащих динамическое выделение памяти
⇑