Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Язык программирования Сpp 25.09.11 (2).doc
Скачиваний:
16
Добавлен:
19.08.2019
Размер:
10.09 Mб
Скачать

Упражнения

  1. Введите, откомпилируйте и запустите предыдущие примеры программ. Затем поэкспериментируйте с ними, меняя фрагменты и исследуя результаты.

  2. Что неправильно в данном фрагменте?

int main(){

throw 12.23;

. . .

3. Что неправильно в данном фрагменте?

try {

// ...

throw 'а';

catch(char *){

4. Что может произойти при возбуждении исключительной ситуации, для которой не задано соответствующей инструкции catch?

Обработка исключительных ситуаций, возбуждаемых оператором new

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

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

В соответствии со стандартом Standard C++, когда требование на выделение памяти не может быть выполнено, оператор new возбуждает исключительную ситуацию bad_alloc. При невозможности перехватить эту исключительную ситуацию программа завершается. Хотя для коротких программ такой алгоритм кажется вполне очевидным и понятным, в реальных приложениях должны не только перехватить, но и каким-то разумным образом обратать эту исключительную ситуацию. Для доступа к указанной исключительной ситуации в программу необходимо включить заголовок <new>.

В представленном ниже примере с оператором new использование блока try/catch дает возможность проконтролировать неудачную попытку выделения памяти.

#include <iostream>

#include <windows>

#include <new>

using namespace std;

int main() {

SetConsoleOutputCP(1251);

int *p;

try {

p = new int; // выделение памяти для целого

} catch (bad_alloc xa) {

cout << "Ошибка выделения памяти\n";

return 1;

}

for(*p=0;*p<10;(*p)++)

cout<<*p<<" ";

delete p; // освобождение памяти

return 0;

}

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

#include <iostream>

#include <windows>

#include <new>

using namespace std;

main(){

SetConsoleOutputCP(1251);

double *p;

// цикл будет продолжаться вплоть до исчерпания ресурса памяти

do {

try {

p = new double[100000]; } catch (bad_alloc xa) {

cout <<"Ошибка выделения памяти\n";

return 1;

}

cout << "Выделение памяти идет нормальнс\n";

} while(p);

return 0;

В качестве примера формирования исключений рассмотрим еще одну программу, которая находит наибольший общий делитель двух чисел x и y . Этот пример взят из [4]. Алгоритм программы прост. На каждом шаге выполняются сравнения:

если x==y, то ответ найден ;

если x<y, то y заменяется значением y-x;

если x>y, то x заменяется значением x-y.

//программа нахождения НОД

#include <iostream.h>

int GCM(int x, int y)

{try {if(x==0||y==0) throw "\nZERO!";

if(x<0) throw "\nNegativ parameter 1.";//

if(y<0) throw "\nNegativ parameter 2.";

while (x!=y)

{if(x>y) x=x-y;

else

y=y-x;

}

return x;

}

catch (const char *report)

{cerr<<report<<"x="<<x<<", y="<<y;

return 0;

}

}

void main()

{

cout<<"\nGCM(66,44)="<<GCM(66,44);

cout<<"\nGCM(0,7)="<< GCM(0,7);

cout<<"\nGCM(-12,8)="<< GCM(-12,8);

}

В заключение следует сказать, что программы, содержащие исключения следует запускать при помощи программ типа «проводника». Запуск из среды программирования может повлечь появление дополнительных сообщений отладчика (дебаггера) типа

.

Модули

Когда программист создает программу, то часто она выполняется в виде отдельных законченных кусков, которые связаны друг с другом. Например, в одном файле могут храниться классы созданные программистом. В другом функции объявленные в этих классах. В третьем вспомогательный код, обеспечивающий выход из программы в случае ее аварийного завершения. Наконец, есть головной файл в котором находится функция main. Если в программировании принимают участие несколько человек, то такое разделение является просто необходимым. Короче говоря, программа С++ почти всегда состоит из нескольких законченных, с точки зрения программирования, кусков или "модулей".

Модуль часто называют исходным файлом, иногда - единицей трансляции. Он состоит из описаний типов, переменных, констант и функций. Естественно, что между модулями должна существовать связь. В интегрированных средах программирования, каковыми явяляются Borland C++ Builder и Microsoft Visual C++ это делается достаточно просто. Все модули включаются в один проект. Компиляция проекта автоматически связывает модули, так, что обращение к элементам, находящимя в всоседем модуле выполняется так, как будто весь текст программы находится в одном файле.

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

Рассмотрим как это выполнить в виде 3-х файлов в среде Builder. В среде MS Visual C++ это делается аналогичным образом.

Сначала используя средства Windows создадим специальныю папку в которой будем сохранять проект. Затем создадим консольное приложение и запишем в нем головной файл, который назовем main. Такое имя вовсе не обязательно. Просто оно позволит лучше ориентироваться в файлах.

//головной файл main.cpp

#include”head.h”

int main(){

float x,y;

SetConsoleOutputCP(1251);

cout<<"Для вычисления среднего введите 2 числа"<<endl;

cout<<"x="; cin>>x;

cout<<"y="; cin>>y;

middl m(x,y);

cout<<"Среднее арифметическое="<<m.ma<<endl

<<"Сренее геометрическое="<<m.mg;

char z;

cin>>z;

return 0;

}

Сохраним его в созданной папке, как файл с именем main.cpp.

Теперь с помощью меню File|New|Other в открывшейся витрине файлов выберем CppFile. Запишем в него класс с объявлением, но без определения функций и сохраним в той же папке с именем class.cpp.

//файл с именем class.cpp

class middl{

private:

//Прототипы функций

float mar(float x, float y);// вычисление среденго арифметического

float mgeo(float x, float y);// для среднего геометрического

public:

float ma, mg;

middl(float x,float y){ //конструктор

ma=mar(x,y);

mg=mgeo(x,y);

}

~middl(){} //деструктор

};

Теперь вновь с помощью меню File|New|Other выберем HeaderFile.

//Файл head.h

#include <iostream>

#include<math>

#include<windows>

using namespace std;

#include “class.cpp”

float middl::mar(float x, float y){

return (x+y)/2;}

float middl::mgeo(float x, float y){

return sqrt(x*y);}

Сохраним его в той же папке с именеи head.h.

Теперь сохраним проект MyProjecr3Module в той самой папке с помощью пункта меню File|Save Project As , и запустим, наконец, его на компиляцию.

После успешной компиляции появится файл MyProjecr3Module с расширением .exe. Это и есть тот файл, котрый готов к выполнению. Размер его сравнительно мал, менее 15,5 кБ. К слову говоря, объем памяти занимаемый одной страницей данного текста составляет более 25кБ.

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

Рассмотренный способ объединения файлов в проект вовсе не предусмотрен языком С++, а является следствием работы интегрированной системы, точнее следствием труда программистов которые ее создали. В языке С++ для создания многомодульных приложений применяется иной механизм. Для того, чтобы связаться из одного исходного файла с другим используется ключевое слово extern . Например, если необходима функция объявленная в соседнем файле, то в данном файле нужно объявить ее прототип.

extern {

int function1(arg1);

void function2(arg2,arg3);

}

В некоторых компиляторах разработанных в последнее время слово extern используется со строкой "С", например, код

extern "C"{

int function1(arg1);

void function2(arg2,arg3);

}

Данный пример говорит о том, что далее потребуются две функции которые находятся в каком-то другом месте. Это другое место может находится в том же модуле, но ниже, или в другом модуле.

В качестве примера сделаем следующее. Запишем в разные файлы две программы код которых приведен ниже. В первом файле с помощью функции Factorial вычисляется величина 7!. Во втором описывается сама функция Factorial. Первый файл содержит функцию main() из которой вызывается функция Factorial второго файла. В соответствии с правилами языка С++, перед вызовом функции должен быть описан хотя бы ее прототип, что сделано с помощью оператора extern. Обратите внимание, что используемая в программе переменная также описана в другом модуле.

//файл с именем extern1.cpp

#include <iostream>

using namespace std;

extern unsigned long factorial; /*объявление переменной factorial

*осуществлено в другом модуле*/

extern unsigned long Factorial (unsigned long); /*описание

*функции Factorial выполнено в другом модуле*/

void main()

{

factorial=Factorial(7);

cout<<"7!(factorial):"<<factorial<<endl;

// cin>> factorial;

}

//файл с именем extern2.cpp

unsigned long factorial; //объявление переменной factorial

unsigned long Factorial(unsigned long num)

{

return num>2?num*Factorial(num-1):num; /*Это оператор if записанный в такой форме. Если условие выполняется, то выполняется то, что записано после вопросительного знака (в данном случае рекурсивная функция) в противном случае выполняется то, что записано после двоеточия*/

}

Если вы работаете только с компилятором, то нужно откомпилировать сразу оба файла в командной строке. Для этого нужно указать имя файла содержащего компилятор и имена компилируемых файлов. В качестве такого компилятора возьмем компилятор “Borland C++ Builder”, от расположен в Borland/Cbuilder6/Bin/Bcc32.exe. Тогда это выгдядит так: bcc32.exe extern1.cpp extern2.cpp. После нажатия на клавиатуре кнопки Enter в директории с компилированными файлами сформируются еще 4 файла

extern1.exe

extern1.obj

extern1.tds

extern2.obj

Запустим extern1.exe. В результате получим:

7!(factorial):5040

Приведем небольшой законченный пример, в котором строка определяется в одном файле, а печатается в другом. Идея этого примера заимствована из книги Бьерна Страуструпа «Язык программирования С++». В примере созданы три файла header.h, main.cpp и f.cpp . В файле header.h определяются нужные типы, он состоит только из объявлений:

// header.h – это название файла

extern char * prog_name; //ссылка на указатель

extern void f (); //объявление прототипа функции f()

//Как ни странно на этом файл закончился

Еще раз напомним, что созданный файл состоит только из объявлений.

Файл main.cpp является основной программой:

// main.cpp – это название второго файла

#include "header.h" /*подключение файла с именем header.h

* хранящемся в той же директории */

char * prog_name = "примитивный, но законченный пример";

int main ()

{

f (); /* вызов функции f(). Компилятор поймет,

* что искать ее нужно в заголовочном файле*/

}

а строка печатается функцией из файла f.cpp:

// f.cpp – это имя третьего файла

#include <iostream.h>

#include "header.h"

#include <windows.h>

void f ()

{SetConsoleOutputCP(1251);

cout << prog_name << '\n';

}

После того как будут совместно откомпилированы все файлы исполняемая программа даст следующий результат:

Использование слова extern в такой, можно сказать традиционной манере, несколько устарело. Самый распространенный способ обеспечить согласованность описаний внешних во всех исходных файлах - поместить такие описания в заголовочные файлы. Заголовочные файлы можно включать во все исходные файлы, в которых требуются описания внешних файлов. Например, описание функции sqrt хранится в заголовочном файле стандартных математических функций с именем math, поэтому, если нужно извлечь квадратный корень из 4, можно написать:

#include <math>

using namespace std

//...

x = sqrt(4);

В команде включения заключенное в угловые скобки имя файла (в нашем примере - <math>) ссылается на файл, находящийся в стандартном пространстве имен. Файлы, находящиеся в других пространствах имен, вызываются также, но при этом нужно указать какому пространству имен они принадлежат.

Отвлечемся на некоторое время от пространств имен. Вспомним о том, что не все компиляторы поддерживают директиву using. Как же быть если нам попался именно такой компилятор? Ничего страшного в этом нет. Все действия по созданию заголовочных файлов довольно схожи, только в этом случае нужно указывать библиотечные файлы вместе с их расширением. Например:

#include <math.h>

//...

x = sqrt(4);

В команде включения заключенное в угловые скобки имя файла (в нашем примере - <math.h>) ссылается на файл, находящийся в стандартном каталоге включаемых файлов. В среде «Borland Builder 6» это каталог Borland\CBuilder6\Include. Файлы, находящиеся в других каталогах, обозначаются своими путевыми именами, взятыми в кавычки. Поэтому в следующих командах:

#include "math1.h"

#include "D:\ Borland\CBuilder6\Projects\math2.h"

включаются файл math1.h из текущего каталога пользователя и файл math2.h из каталога D:\ Borland\CBuilder6\Projects\.