Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
lek_1_slaydy.doc
Скачиваний:
1
Добавлен:
17.11.2019
Размер:
417.79 Кб
Скачать

Void AnyFunction(void)

{

cout « AnyValue « '\n';

AnyValue++;

. . .

}

Все это, конечно, предположительно, но один факт не вызывает сомнений: вставка тела AnyFunction() не­посредственно в цикл for ускорит выполнение программы:

for (int i = 0; i <MAX; i++)

{cout << AnyValue << '\n'; AnyValue++;

. . .

}

Альтернативно можно объявить функции inline (встраиваемыми), вставляя тем самым их тела в поток кода. Как это сделать, показано в листинге 1.9.

Листинг 1.9. INLINE.CPP (демонстрация встраиваемых функций)

1:

#include <

iostream.h>

2:

3:

inline int

max(int a, int b)

4:

{

5:

if (a >=

b)

6:

return a;

7:

else

8:

return b;

9:

}

10:

11:

main()

12:

{

13:

int x, y,

z;

14:

15:

cout << "

X? ";

16:

cin >> x;

17:

cout << "

Y? ";

18:

cin >> y;

19:

z = max(x

, y);

20:

cout << "

max(a, b) == " << z << '\n';

21:

return 0;

22:

}

Для того чтобы объявить функцию встраиваемой, напишите перед ней ключевое слово inline, как показано в строке 3. Пишется функция как обычно (строки 4-9). Ничего необычного в ее содержимом нет — все, что выполняется в любой функции, выполнится и для объявленной встраиваемой. Обычно встраиваемые функ-ции.подобные той, что определена в строках 3-9, объявляются в заголовочных файлах и включаются в каж­дый модуль, где необходимо их использование. Встраиваемую функцию необходимо полностью определить до того, как можно будет ее использовать, и включение ее текста в заголовочный файл — самый простой способ удовлетворения этого требования.

Используйте встраиваемые функции в точности так же, как вы используете обычные. Например, в строке 19 вызывается max() для определения большего из двух целых чисел с присвоением этого значения переменной z. Конечно, в скомпилированном коде Borland C++ не вызывает функцию max() в строке 19. Вместо этого компи­лятор вставляет тело функции непосредственно в программу, компилируя строку 19, как если бы она была напи­сана следующим образом:

if (x >= у)

z = x;

else z = у;

Время, сэкономленное в этом примере, минимально. Однако, если max() вызывается тысячи раз в цикле, быстродействие программы может существенно улучшится.

Встраиваемые функции сродни регистровым переменным. Когда вы объявляете встраиваемую функцию, вы лишь даете указание компилятору, и, если возможно, тело функции будет вставлено прямо туда, откуда эта функция вызывается. Разумеется, компилятор — не золотая рыбка, и нет гарантии, что так будет с каждой вашей командой. Если встраиваемый код, к примеру, слишком велик, то компилятор может отказаться вставить функцию в поток кода, и вместо этого сгенерирует обычный вызов. Также при компиляции программ для Turbo Debugger все встраиваемые функции будут преобразованы в обычные вызываемые функции, которые можно будет отлаживать. Хорошие программы на С++ работают корректно вне зависимости от того, как скомпилированы в них встраиваемые функции.

Управление памятью с помощью new и delete

Функ­ции malloc(), farmalloc(), farcalloc(), free(), farfree() и другие одинаково доступны для всех программ на С и С++.

Однако С++ предлагает альтернативные операторы управления памятью new и delete, которые могут делать то же самое, что и стандартные функции, и более того.

Используйте new для выделения памяти динамическим переменным. Используйте free, чтобы освободить память, выделенную new, для ее использования в последующих операторах new.

new и deleteунарные операторы, а не функции. Использование этих операторов вместо стандартных функций управления памятью дает возможность при необходимости са­мостоятельно обрабатывать ситуации управления памятью.

Чтобы выделить память для переменной, имеющей тип double, и присвоить ее адрес указателю, вы можете написать

double *dp = new double;

Вы можете часто видеть схожее объявление, делающее вызов new похожим на вызов функции:

double *dp = new(double);

Это не так, и лишние круглые скобки просто игнорируются. Используется dp так же, как и любой другой ука­затель на память, выделенную malloc() или подобной функцией.

Для присваивания значения содержимому памяти, адрес которой хранится в dp,

*dp = 3.14159;

Закончив использование динамической переменной, удалите ее следующим образом:

delete dp;

Удаление указателя означает освобождение выделенной ранее памяти, адрес которой хранится в dp, для последующего использования.

Для освобождения памяти, выделенной с помощью new, необходимо использо­вать только delete. Динамические переменные, созданныефункцией malloc(), следует уничтожать с помощью функции free() (или, возможно, farfree()). Вы можете использовать malloc() и new в одной и той же про­грамме, но вы не можете смешивать способы управления памятью С и С++.

Обнаружение ошибок, связанных с нехваткой памяти

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

  • new возбуждает исключительную ситуацию;

  • new возвращает нуль.

В старых версиях С++ оператор new в любом случае возвращал нуль в случае неудачной попытки выделе­ния памяти, обычно вследствие нехватки свободного пространства. В новейших версиях C++ специфика­ции языка позволяют new возбуждать исключительную ситуацию. Пока вы не узнаете больше о С++, преждевременно говорить об исключительных ситуациях, и лучше использовать new как и ранее, перепрограммировав его так, чтобы в случае ошибки выделения памяти возвращался нуль.

Для этого включите заголовочный файл NEW.H в вашу программу и добавьте следующий оператор в main() или в любое другое место до первого использования new:

set_new_handler(0);

Функция set_new_handler() устанавливает адрес обработчика (подпрограммы времени исполнения), исполь­зуемой new. Обработчик по умолчанию заставляет new возбуждать исключительную ситуацию для ошибок.

При переносе имеющихся программ на С++ в среду Borland C++ 4.5, возможно, будет необходимо вызвать set__new_handler(0) вначале программы для обеспечения ее правильной работы, Это изменение следует внести в программу в том случае, если она непредвиденно завершится с сао6щением"аbnоrma1 termination" (аварийное завершение), которое может быть вызвано возбуждением new исключительной ситуации в случае ошибки выделения памяти.

Листинг 1.10 поясняет, как использовать new для выделения памяти для структур, а также демонстриру­ет, что происходит, когда программе не хватает свободной памяти. Вы можете скомпилировать и запустить программу как EasyWin-приложение из интегрированной оболочки или же из командной строки DOS. B обо­их случаях компилятор выдаст предупреждение "Unreachable code" (Недостижимый код). (В следующем раз­деле разъяснено, в каких случаях выдается это предупреждение.)

Листинг 1.10. MEMERR.CPP (демонстрация new и ошибок, связанных с нехваткой памяти)

1 : #include <iostream.h>

2: #include <new.ti>

3: #include <stdlib.h>

4:

5: struct q {

6: int ia[1024]; // 2048-байтовая структура

7: };

8:

9: main()

10:{

11: struct q *qp; // Указатель на структуру q

12:

13: set_new_handler(0); // Указание new вернуть 0 в случае неудачи

14: for (;;) { // Повторять следующий оператор "без конца"

15: qp = new q; // Выделить новую структуру типа q

16: cout « qp« '\n'; // Вывести адрес структуры

17: if (qp == 0) { // В случае возврата new 0 следует:

18: cout « "Out of memory\n"; // Вывести сообщение об ошибке

19: exit(1); // Прервать программу с кодом завершения 1

20: }

21: }

22: return 0;// В этой строке выдается предупреждение "unreachable code"

23: }

В качестве программных данных в строках 5-7 объявляется структура с массивом 1,024 целых значений. При обработке этой структуры new вьаделяет 2,048-байтовый блок (учитывая тот факт, что целое значение за­нимает два байта). Повторяя эту операцию до бесконечности, программа исчерпывает запасы свободной памя­ти, демонстрируя, что случается, когда new не может выделить ее достаточно.

Чтобы new возвращал нуль в случае ошибки, программа передает 0 функции set_new_handler(). После этого "бесконечный" цикл for выполняет несколько операторов. Первый из них вызывает new для выделения 2048-байтовой структуры и присваивает ее адрес указателю qp. Далее оператор вывода отображает адрес, хра­нящийся в qp. Если qp — нуль, то это означает, что new не сумел выделить достаточно памяти, программа выводит сообщение об ошибке и затем аварийно завершает выполнение с помощью вызова функции exit(). Вот что отобразилось на дисплее при запуске этой программы (адреса могут отличаться от тех, что будут вы­ведены на вашем экране):

0x1366 0x1b8a

0x0000

Out of memory

Если программа не вызывает set_new_handler(), new возбуждает исключительную ситуацию вместо того, чтобы вернуть нуль. Для демонстрации этого отличия, закомментируйте или удалите оператор set_new_handler(), скомпилируйте программу заново и запустите ее. Теперь программа выведет:

0x1366 0x1b8a

0x0000

Abnormal program termination

В предыдущем тесте программа самостоятельно обнаружила ошибку, связанную с нехваткой памяти, и ава­рийно завершилась с помощью вызова функции exit(). В новом тесте new возбудил исключительную ситуа­цию, которая явилась причиной аварийного завершения.

Использование простых динамических переменных

Хотя MEMERR.CPP демонстрирует, что происходит при нехватке памяти, в обычных обстоятельствах эта программа — не лучший пример использования new. Например, использование "бесконечного" цикла for — плохой стиль программирования, хотя в данном случае необходимо было вызвать ошибку выделения памяти независимо от того, сколько оперативной памяти у вашего компьютера. Из-за этого цикла компилятор выдает предупреждение о том, что оператор return в строке 22 "недостижим". Это предупреждение означает, что ком­пилятор обнаружил отсутствие пути, ведущего к выполнению одного или нескольких операторов в программе, что указывает на возможно неправильный оператор в каком-либо месте программы.

В обычных условиях следует использовать new для выделения памяти переменным, структурам или масси­вам, как показано ниже. Например, вы можете объявить структуру как в MEMERR:

struct q { int ia[1024]; // 2048-байтовая структура

Затем использовать new для выделения памяти для переменной типа q:

struct q *qp; // Объявление указателя на переменную типа структуры q

qp = new q; //Выделение памяти для переменной типа структуры q ,

Если new завершился успешно, то qp содержит адрес переменной типа структуры q. Вы можете использо­вать переменную точно так же, как и обычную такого же типа. Когда вы завершили использование динамичес­кой переменной, следует освободить занимаемую ею память с помощью оператора delete. Освобожденная па­мять в дальнейшем будет доступна для будущих использований new:

delete qp; // Удаление и освобождение памяти, адресованной qp

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

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

Между прочим, всегда неплохо удалять (delete) указатель, даже если он равен нулю. Конечно, после уда­ления указателя память, адресовавшаяся им прежде, более не будет зарезервирована для программного ис­пользования. Удаление того же самого указателя повторно или использование адресованной им памяти может вызвать серьезные ошибки. Никогда не делайте так, как показано ниже:

double *dp; // Объявление указателя на вещественное значение

dp = new double; // Выделение памяти и присвоение ее адреса dp

*dp = 3.14159; // Присваивание значения выделенной памяти

delete dp; // Удаление выделенной памяти. Пока все хорошо

*dp = 9.9999; // ??? Серьезная ошибка! Не делайте этого!

delete dp; // ??? Серьезная ошибка! Разрушает кучу

Приведенный фрагмент корректен, исключая два последних оператора. После удаления dp нельзя повторно использовать указатель, поскольку удаленная память может быть зарезервирована для другой переменной. По­вторное удаление указателя, как это произошло в последнем операторе, — также серьезная ошибка, так как это может повредить кучу, дважды добавив один и тот же блок памяти в список свободного пространства в этой об­ласти. Обязательное правило — удалив указатель, никогда не используйте его повторно ни для каких целей. Конечно, вы можете повторно использовать указатель для адресации памяти заново выделенной new:

dp = new double; // Выделение памяти для динамической переменной

*dp = 3.14159; // Присваивание значения динамической переменной

delete dp; // Удаление памяти. Не используйте dp после этого!

dp = new double; // Конечно, вы можете выделить память для еще одной

// переменной

*dp = 9.9999; // Присваивание значения заново выделенной переменной

delete dp; // Удаление памяти. Опять же, не используйте затем dp!

Использование динамических строк

Вы можете использовать new для выделения динамической символьной строки практически любого разме­ра. Листинг 1.11 демонстрирует, как это делается.

Листинг 1.11. NEWSTR.CPP (объявление строк с помощью new)

1 : #include <iostream.h>

2:

3: #define SIZE 80

4:

5: main()

6: {

7: char *sp;

8:

9: sp = new char[SIZE];

10: cout « "String?";

11: cin.getline(sp, SIZE);

12:' cout « "You entered:" « sp « '\n';

13: delete[] sp; 14: return 0;

15: }

Строка 9 вьщеляет символьный массив размером SIZE в байтах и присваивает адрес первого байта этого мас­сива sp. Строки 10-12 демонстрируют использование динамических строк, запрашивая и выводя строку текста.

Строка 13 удаляет динамический символьный массив. Borland С++ разрешает написать этот оператор без скобок следующим образом:

delete sp; // ???, но безопасно в Borland C++

Следуя более строгому протоколу С++, необходимо использовать пустые скобки в выражении delete[], указывающие компилятору, что удаляется массив, а не простая переменная. (В Borland C++ скобки необходи­мы только в том случае, если удаляется массив классовых объектов, но об этом позже.)

В ранних версиях С++ при удалении массива нужно было указывать количество элементов. В С++ вер­сии 2.0, например, строка 15 имела бы вид delete[SIZE] sp;. Borland С++ игнорирует SIZE в таком случае и выдает предупреждение о попытке использовать устаревшую форму delete.

Использование динамических массивов

Конечно, строки — это те же символьные массивы, и тот факт, что new и delete также могут использовать­ся для создания и освобождения динамических массивов любого типа, не будет для вас сюрпризом. Листинг 1.12 демонстрирует это.

Листинг 1.12. NEWARRAY.CPP (объявление массивов с помощью new) 1:

  1. #include <iostream.h>

  2. #define COUNT 100 // Количество целых значений

  3. main()

  4. {

  5. int *array; // Массив целых значений

  6. int i; // Индекс массива

  7. array = new int[COUNT];

  8. // Заполнение массива

  9. for (i = 0; i < COUNT; i++)

  10. array[i] = i;

  11. // Вывод содержимого массива

  12. for (i = 0; i < COUNT; i++) {

  13. cout.width(8);

  14. cout << array[i];

  15. }

  16. delete[ ] аггау;

  17. return 0;

  18. }

В этом примере в строке 7 объявляется указатель на массив целых значений. В строке 10 используется new для выделения места для 100 целых значений. Затем массив заполняется значениями от 0 до 99 и выводится в 8-символьных столбцах с помощью цикла for в строках 17-20. Строка 21 удаляет массив, используя пустые скобки в выражении delete[ ] для того, чтобы, как и в предыдущем листинге, указать компилятору, что удаля­ется массив, а не одно целое значение.

Многомерные динамические массивы

Для объявления указателя на двух- или трехмерный массив (или большей размерности) надо указать количест­во элементов во второй и последующей позициях. Например, для массива 10x20 вещественных значений двойной точности:

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]