В первой части этой статьи был рассмотрен код, в котором мы определили функцию, выделяющую динамическую память для массива указателей и для строк, адреса которых хранят указатели этого массива. Так же она копировала новые строки в выделенную под них память. В этой статье доработаем нашу программу — добавим в нее еще две функции:
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 так:
А в основной программе удаляем сначала каждую строку, а потом и весь массив
for ( int i = 0; i < size; i++ ) { delete [] pp[ i ]; } delete [] pp;Так как правильно удалять? Не будет ли утечки памяти, если мы удаляем сразу весь массив, без удаления сначала всех его строк?
Как я понимаю вы в функции удаляем просто массив указателей в то время как объекты (то есть строки) будут оставаться в памяти занятыми. А мы, после того как удалили массив указателей, просто ссылаемся на другой участок памяти (тот который создался для copy)
И еще вопрос: в этой же функции **AddPtr при добавлении строки в уже существующий массив мы копируем содержимое в промежуточный массив. А потом ссылаемся на него. Так а он не должен удалиться? Ведь он создан в пределах функции.