В первой части этой статьи был рассмотрен код, в котором мы определили функцию, выделяющую динамическую память для массива указателей и для строк, адреса которых хранят указатели этого массива. Так же она копировала новые строки в выделенную под них память. В этой статье доработаем нашу программу — добавим в нее еще две функции:
1) char **DelPtr (char **pp, int size, int ncell)
будет удалять указанный нами элемент (ncell
) из массива указателей и освобождать память занимаемую строкой и указателем на неё. Таким образом наш массив указателей как бы «схлопнется» после удаления строки и указателя.
2) char **InsPtr (char **pp, int size, int ncell, char *str)
будет добавлять в указанный нами элемент массива указателей (ncell
) новый указатель на новую строку (*str
). В результате этого массив указателей «раздвинется» на один элемент.
Чтобы лучше понять происходящее, не просто читайте, а набирайте код. Практикуйтесь:
#include <iostream> #include <string.h> using namespace std; //прототипы функций char **AddPtr (char **pp, int size, char *str); char **DelPtr (char **pp, int size, int ncell); // удаление строки и указателя char **InsPtr (char **pp, int size, int ncell, char *str); //вставка строки и указателя int main() { setlocale(LC_ALL, "rus"); int size = 0; char **pp = 0; cout << "~~~~~Добавляем указатели на пять строк и заполняем строки данными~~~~~" << endl; pp = AddPtr(pp, size, "11111111111111111"); size++; //1 pp = AddPtr(pp, size, "22222222222222222"); size++;//2 pp = AddPtr(pp, size, "33333333333333333"); size++;//3 pp = AddPtr(pp, size, "44444444444444444"); size++;//4 pp = AddPtr(pp, size, "55555555555555555"); size++;//5 for(int i = 0; i < size; i++) //показываем все строки на экран cout << pp[i] << endl; cout << endl; cout << "~~~~~Удаляем 3-ю строку и указатель на неё из массива указателей~~~~~" << endl; pp = DelPtr(pp, size, 2); //вызов функции удаления указателя size--; //4 for(int i = 0; i < size; i++) //показываем оставшиеся строки cout << pp[i] << endl; cout << endl; cout << "~~~~~Добавляем указатель в массив указателей и заполняем строку~~~~~" << endl; pp = InsPtr(pp, size, 2, "333"); //вызов функции вставки указателя size++; //5 for(int i = 0; i < size; i++) //показываем все строки на экран cout << pp[i] << endl; cout << endl; //освобождение памяти for(int i = 0; i < size; i++) { delete [] pp[i]; } delete [] pp; return 0; } char **AddPtr (char **pp, int size, char *str) { if(size == 0){ pp = new char *[size+1]; } else{ char **copy = new char* [size+1]; for(int i = 0; i < size; i++) { copy[i] = pp[i]; } delete [] pp; pp = copy; } pp[size] = new char [strlen(str) + 1]; strcpy(pp[size], str); return pp; } char **DelPtr (char **pp, int size, int ncell) { char **copy = new char* [size-1]; //временная копия //копируем адреса указателей, кроме ячейки с номером ncell for(int i = 0; i < ncell; i++) { copy[i] = pp[i]; } for(int i = ncell; i < size-1; i++) { copy[i] = pp[i+1]; } delete [] pp[ncell]; // освобождаем память занимаемую строкой delete [] pp; //освобождаем память занимаемую массивом указателей pp = copy; //показываем, какой адрес хранить return pp; } char **InsPtr (char **pp, int size, int ncell, char *str) { char **copy = new char* [size+1]; //временная копия for(int i = 0; i < ncell; i++) { copy[i] = pp[i]; } copy[ncell] = new char[strlen(str) + 1]; //выделяем память для новой строки strcpy(copy[ncell], str); for(int i = ncell+1; i < size+1; i++) { copy[i] = pp[i-1]; } delete [] pp; pp = copy; return pp; }
Вначале объявляем все прототипы необходимых функций — строки 6 — 8. В строках 14 — 35, то что мы рассмотрели в предыдущей части статьи — наращиваем массив указателей, вызывая функцию char **AddPtr()
, записываем в него адреса новых строк и показываем строки на экран. В строке 38 вызываем новую функцию вот таким образом — pp = DelPtr(pp, size, 2);
. Передаем в нее указатель на указатель, размер массива указателей и номер ячейки массива указателей, данные которой надо удалить. Номер ячейки 2
, значит удалится 3-я строка и указатель на нее. Значение, которое возвращает эта функция, присвоим указателю на указатель pp
. Посмотрим, как работает эта функция. В строке 85 объявляем указатель на указатель copy
. Он станет временной копией нашего массива указателей. Выделяем для него память размером на один элемент меньше того, который был передан в функцию. В строках 87 — 94, используя два цикла for
, копируем во временную копию массива указатели на все строки, кроме того, индекс которого (ncell
) ввели при вызове функции. Затем освобождаем память, которую занимает удаляемая строка и память которую занимает массив указателей. В строке 98 присваиваем нашему указателю на указатель адрес временного. Теперь он указывает на блок памяти с 4-мя строками. Функция отработала. А чтобы проверить, как хорошо она справилась с поставленной задачей, в main()
пропишем вывод на экран всех строк, на которые указывают указатели из массива — строки 41 — 42.
В строке 46 вызываем функцию вставки нового указателя в массив указателей — pp = InsPtr(pp, size, 2, "333");
. Функция принимает, в виде параметров, указатель на указатель, текущий размер массива указателей, номер ячейки, в который надо добавить новый указатель, и новую строку, адрес которой будет хранить этот указатель. Мы передали число 2, значит новый указатель займет место указателя на 3-ю строку. Перейдем к определению функции — к строкам 103 — 122. В нем мы, так как и в функции DelPtr()
, создаем временную копию массива указателей. Только памяти выделим на одну ячейку больше, чем было передано в функцию, для того чтобы вставить туда новый указатель. Далее производится копирование указателей массива до переданной в функцию ячейки, выделение памяти для новой строки и ее запись, и копирование оставшихся указателей — строки 106 — 117. И для проверки, в main()
снова «попросим» показать все строки.
Вот что получится, когда скомпилируем код:
~~~~~Добавляем указатели на пять строк и заполняем строки данными~~~~~
11111111111111111
22222222222222222
33333333333333333
44444444444444444
55555555555555555
~~~~~Удаляем 3-ю строку и указатель на неё из массива указателей~~~~~
11111111111111111
22222222222222222
44444444444444444
55555555555555555
~~~~~Добавляем указатель в массив указателей и заполняем строку~~~~~
11111111111111111
22222222222222222
333
44444444444444444
55555555555555555
В обеих частях этой статьи вы увидели мало теории об указателе на указатель, но большой практический пример. Хочется верить, что статья помогла вам разобраться или хотя бы приблизиться к пониманию этой темы. Если вопросы есть — хорошо. Оставляйте их в комментариях. Мы вам ответим.
Комментарии
Сергей Дейнеко
А ведь можно переменную size тоже можно в функции передавать через указатель.
И погда приращивать или уменьшать в самой функции, а не в main. Где можно забыть это сделать и будут ошибки.
Alex Kart
Почему не работает такая реализация AddPtr:
sqrt91
Здавствуйте.
Вопрос: можно создавать массив указателей на массивы разной длинны. Если нет, посоветуйте, каким образом создать таблицу со строками разной длины?
Спасибо!
asduj
В функции **AddPtr мы удаляем pp так:
А в основной программе удаляем сначала каждую строку, а потом и весь массив
Так как правильно удалять? Не будет ли утечки памяти, если мы удаляем сразу весь массив, без удаления сначала всех его строк?
Как я понимаю вы в функции удаляем просто массив указателей в то время как объекты (то есть строки) будут оставаться в памяти занятыми. А мы, после того как удалили массив указателей, просто ссылаемся на другой участок памяти (тот который создался для copy)
И еще вопрос: в этой же функции **AddPtr при добавлении строки в уже существующий массив мы копируем содержимое в промежуточный массив. А потом ссылаемся на него. Так а он не должен удалиться? Ведь он создан в пределах функции.