Создайте функцию main, в которой поочередно вызывайте эти модули и анализируйте значения адресов. Попробуйте убрать скобки при операциях delete и вновь проверьте результат. При отладке этой программы следует быть очень аккуратным, так как здесь в полной мере могут проявиться те особенности работы с указателями, которые отмечены в начале параграфа. Так, если в последнем цикле for по ошибке вместо i<3 ввести, например i<5, то возможно придется перезагружать всю систему с потерей незаписанного ввода. В Windows 95 это происходит именно так. Код программы весьма ненадежен, в особенности потому, что использованы числовые константы (4, 3, 2) в качестве спецификаторов размерностей массивов. Его цель — проиллюстрировать правила освобождения памяти и возможные ситуации при этом возникающие. Возможность свободной модификации критически важных параметров таких объектов, как многомерный массив, является одним из тех недостатков структурного подхода, которые привели к необходимости поисков новых подходов. Результатом таких поисков является объектно-ориентированный подход к разработке программ, к изучению которого мы скоро перейдем. После освобождения памяти указатели a, d и dd продолжают, тем не менее, указывать на те же адреса, что и до освобождения (однако, эта память уже не наша). В этом легко убедиться, если вставить до и после операций delete вывод:
printf ("\n a=%p d=%p dd=%p dd[1]=%p",a,d,dd,dd[1]);
В Windows NT 4.0 повторное обращение к освобожденной памяти приводит к полному краху. Чтобы в этом убедиться можно вставить после вызова FreeMem такую строку:
printf ("\n *a=%2.0f d[0]=%2.0f ",*a,d[0]);
Следовательно, наряду с отмеченными ранее типами ошибок при работе с указателями, имеет место еще один — работа с указателями, которые адресуют освобожденную память в heap, или обращение к памяти, ранее занимаемой объектом. Этот феномен рассматривается также в работах, посвященных проблемам защиты информации, там где рассматривается повторное использование объектов.
Проанализируйте программу и объясните что она выведет.
int a[3][3]={{1,2,3}, {4,5,6}, {7,8,9}};
int *pp[3]={a[0],*(a+1),a[2]};
int *p=*a;
void main()
{
for (int i=0; i<3; i++)
printf ("\na[%d][2-%d]=%d *a[%d]=%d *(*(a+%d)+%d)=%d",
i,i,a[i][2-i],i,*a[i],i,i,*(*(a+i)+i));
for (puts("\n"), i=0; i<3; i++)
printf ("\npp[%d]=%p *pp[%d]=%d p[%d]=%d",
i,pp[i],i,*pp[i],i,p[i]);
}
Указатели делятся на две основные категории: указатели на переменные и указатели на функции. Обе категории содержат адреса памяти, но они имеют существенно различные свойства и назначение. Для них также справедливы и различные правила использования. Указатели на функцию используются с целью вызова различных функций, удовлетворяющих определенному прототипу. Выполнение каких-то арифметических операций над указателем на функцию лишено смысла и не допускается в языке C++. Указатели на переменные, обычно на массивы, допускают произведение арифметических действий над собой с целью, например, пробега по массиву. Хотя указатели и содержат числа со свойствами целых без знака (unsigned int), тем не менее для них существуют свои собственные правила и ограничения при выполнении операций присвоения, преобразования и адресной арифметики. Следующий пример демонстрирует использование указателя на функции. Объявление: double (*pFunc)(double); определяет переменную pFunc, имеющую тип указателя на функции. Эта переменная способна указывать на любую функцию, которая возвращает вещественное число и требует одно вещественное число в качестве параметра. Обрамляющие скобки обязательны, так как без них выражение задает прототип функции, которая возвращает указатель на double и требует параметр типа double.
void main()
{ // Использование указателя на функции
double (*pFunc)(double); // Указатель
unsigned c=1;
while (c)
{ // Цикл проверки
printf ("\n\tSelect a function (0 - to quit)\n"
"\n 0. Quit"
"\n 1. Sqrt"
"\n 2. Sin"
"\n 3. Tan\n\n\t");
c = getch()-'0'; // Реакция пользователя
switch(c)
{
case 1: pFunc = sqrt; break;
case 2: pFunc = sin; break;
case 3: pFunc = tan; break;
case 0: break;
default: c =0 ; break;
}
if (c)
{
double y = pFunc(1.); // Вызов с помощью указателя
printf ("\n y(1.) = %f\n",y);
}
}
}
Обратите внимание на выражение getch()-'0', использованное для анализа символа, введенного пользователем. Вычитая код символа '0' (равный 48), мы преобразуем код введенного символа в целое число, равное расстоянию символа в таблице кодов от символа '0'. Таким образом, если была введена цифра, то выражение дает число, равное этой цифре. Если введена не цифра, то переменная unsigned c содержит некоторое недопустимое число, которое игнорируется и цикл запроса ввода повторяется.
¨ Разработайте программу, которая создает квадратную матрицу (размер задается пользователем на этапе выполнения).
¨ Заполните ее произвольными числами, например, с помощью функции rand().
¨ Динамически создайте вектор той же размерности, что и матрица.
¨ Заполните его такими значениями, чтобы каждый терм произведения первой строки матрицы с этим вектором был равен единице.
¨ Напишите функцию умножения матрицы на вектор и с ее помощью проверьте результат.
¨ Создайте динамический массив указателей на строки текста (размер задается пользователем на этапе выполнения).
¨ Введите эти строки.
¨ Отсортируйте их с помощью функции AnySort или с помощью Вашей (вновь созданной) функции.
Уважаемый посетитель!
Чтобы распечатать файл, скачайте его (в формате Word).
Ссылка на скачивание - внизу страницы.