Указатель на указатель + динамическое выделение памяти (часть 2)

В первой части этой статьи был рассмотрен код, в котором мы определили функцию, выделяющую динамическую память для массива указателей и для строк, адреса которых хранят указатели этого массива. Так же она копировала новые строки в выделенную под них память. В этой статье доработаем нашу программу — добавим в нее еще две функции:

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() снова «попросим» показать все строки.

Вот что получится, когда скомпилируем код:

CppStudio.com

~~~~~Добавляем указатели на пять строк и заполняем строки данными~~~~~
11111111111111111
22222222222222222
33333333333333333
44444444444444444
55555555555555555

~~~~~Удаляем 3-ю строку и указатель на неё из массива указателей~~~~~
11111111111111111
22222222222222222
44444444444444444
55555555555555555

~~~~~Добавляем указатель в массив указателей и заполняем строку~~~~~
11111111111111111
22222222222222222
333
44444444444444444
55555555555555555

В обеих частях этой статьи вы увидели мало теории об указателе на указатель, но большой практический пример.  Хочется верить, что статья помогла вам разобраться или хотя бы приблизиться к пониманию этой темы. Если вопросы есть — хорошо. Оставляйте их в комментариях. Мы вам ответим.

Практика

К сожалению, для данной темы пока нет подходящих задач. Если у вас есть таковые на примете, отправте их по адресу: admin@cppstudio.com. Мы их опубликуем!

Автор: Marienko L.
Дата: 17.02.2014
Поделиться:

Комментарии

  1. Сергей Дейнеко

    А ведь можно переменную size тоже можно в функции передавать через указатель.

    И погда приращивать или уменьшать в самой функции, а не в main. Где можно забыть это сделать и будут ошибки.

  2. Alex Kart

    Почему не работает такая реализация AddPtr:

     

    pp = (char**) realloc(pp, sizeof(char)*(size+1));
    pp[size] = new char*[size+1];
    strcpy(pp[size], str);
  3. sqrt91

    Здавствуйте.

    Вопрос: можно создавать массив указателей на массивы разной длинны. Если нет, посоветуйте, каким образом создать таблицу со строками разной длины?

     

    Спасибо!

  4. asduj

    В функции **AddPtr мы удаляем pp так:

    delete [] pp;

    А в основной программе удаляем сначала каждую строку, а потом и весь массив

    for ( int i = 0; i < size; i++ )
    	{
    		delete [] pp[ i ];
    	}
    
    delete [] pp;

    Так как правильно удалять? Не будет ли утечки памяти, если мы удаляем сразу весь массив, без удаления сначала всех его строк?

    Как я понимаю вы в функции удаляем просто массив указателей в то время как объекты (то есть строки) будут оставаться в памяти занятыми. А мы, после того как удалили массив указателей, просто ссылаемся на другой участок памяти (тот который создался для copy)

    И еще вопрос: в этой же функции **AddPtr при добавлении строки в уже существующий массив мы копируем содержимое в промежуточный массив. А потом ссылаемся на него. Так а он не должен удалиться? Ведь он создан в пределах функции.

Оставить комментарий

Вы должны войти, чтобы оставить комментарий.