Out.close();//закрытие потока 6 глава




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\.

 

 

Исходные файлы и объявление переменных

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

При такой структуре исходной программы функции, находящиеся в разных исходных файлах могут использовать глобальные внешние переменные. Все функции в языке программирования С++ по определению внешние и всегда доступны из любых файлов. Например, если программа состоит из двух исходных файлов, как показано на рис.2, то функция main() может вызывать любую из трех функций fun1, fun2, fun3, а каждая из этих функций может вызывать любую другую.

main () {... } fun1() {... } . fun2() {... } fun3() {... }
file1.c file2.c
Рис.2. Пример программы из двух файлов

 

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

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

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

Все функции в языке программирования С++ имеют глобальное время жизни и существуют в течение всего времени выполнения программы.

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

Если объект объявлен внутри блока, то он видим в этом блоке, и во всех внутренних блоках. Если объект объявлен на внешнем уровне, то он видим от точки его объявления до конца данного исходного файла.

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

Спецификатор класса памяти в объявлении переменной может быть auto, register, static или extern. Если класс памяти не указан, то он определяется по умолчанию из контекста объявления.

Объекты классов auto и register имеют локальное время жизни. Спецификаторы static и extern определяют объекты с глобальным временем жизни.

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

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

Переменная с классом памяти auto автоматически не инициализируется. Она должна быть проинициализирована явно при объявлении путем присвоения ей начального значения. Значение неинициализированной переменной с классом памяти auto считается неопределенным.

Спецификатор класса памяти register предписывает компилятору распределить память для переменной в регистре, если это представляется возможным. Использование регистровой памяти обычно приводит к сокращению времени доступа к переменной. Переменная, объявленная с классом памяти register, имеет ту же область видимости, что и переменная auto. Число регистров, которые можно использовать для значений переменных, ограничено возможностями компьютера, и в том случае, если компилятор не имеет в распоряжении свободных регистров, то переменной выделяется память как для класса auto. Класс памяти register может быть указан только для переменных с типом int или указателей с размером, равным размеру int.

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

Пример:

/* объявления переменной i на внутреннем уровне с классом памяти static. */

/* исходный файл file1.c */

main() {... }

fun1() { static int i=0;... }

/* исходный файл file2.c */

fun2() { static int i=0;... }

fun3() { static int i=0;... }

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

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

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

Пример:

/* объявления переменной i, являющейся именем внешнего массива длинных целых чисел, на локальном уровне */

/* исходный файл file1.c */

main() {... }

fun1() { extern long i[];... }

/* исходный файл file2.c */

long i[MAX]={0}; f

un2() {... }

fun3() {... }

Объявление переменной i[] как extern в приведенном примере делает ее видимой внутри функции fun1. Определение этой переменной находится в файле file2.c на глобальном уровне и должно быть только одно, в то время как объявлений с классом памяти extern может быть несколько.

Объявление с классом памяти extern требуется при необходимости использовать переменную, описанную в текущем исходном файле, но ниже по тексту программы, т.е. до выполнения ее глобального определения. Следующий пример иллюстрирует такое использование переменной с именем st.

Пример:

main() { extern int st[];... }

static int st[MAX]={0};

fun1() {... }

Объявление переменной со спецификатором extern информирует компилятор о том, что память для переменной выделять не требуется, так как это выполнено где-то в другом месте программы.

При объявлении переменных на глобальном уровне может быть использован спецификатор класса памяти static или extern, а так же можно объявлять переменные без указания класса памяти. Классы памяти auto и register для глобального объявления недопустимы.

Объявление переменных на глобальном уровне - это или определение переменных, или ссылки на определения, сделанные в другом месте программы. Объявление глобальной переменной, которое инициализирует эту переменную (явно или неявно), является определением переменной. Определение на глобальном уровне может задаваться в следующих формах:

1. Переменная объявлена с классом памяти static. Такая переменная может быть инициализирована явно константным выражением, или по умолчанию нулевым значением. То есть обявления

static int i=0;

и

static int i;

эквивалентны, и в обоих случаях переменной i будет присвоено значение 0.

2. Переменная объявлена без указания класса памяти, но с явной инициализацией. Такой переменной по умолчанию присваивается класс памяти static. То есть объявления

int i=1;

и

static int i=1;

будут эквивалентны.

Переменная объявленная глобально видима в пределах остатка исходного файла, в котором она определена. Выше своего описания и в других исходных файлах эта переменная невидима (если только она не объявлена с классом extern).

Глобальная переменная может быть определена только один раз в пределах своей области видимости. В другом исходном файле может быть объявлена другая глобальная переменная с таким же именем и с классом памяти static, конфликта при этом не возникает, так как каждая из этих переменных будет видимой только в своем исходном файле.

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

В объявлениях с классом памяти extern не допускается инициализация, так как эти объявления ссылаются на уже существующие и определенные ранее переменные.

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

 

 

Связанные списки

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

Связанным списком называется совокупность однородных объектов в каждом из которых есть поле с указателем на следующий объект. Такие объекты называются узлами (node) связанного списка. Связанный список напоминает массив объектов, элементы которого разбросаны по разным участкам памяти, но связаны между собой указателями.

 

 

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

 

struct sotrudnik{

char name[20];

int year_b;

sotrudnik *link;

};

 

Создадим новый тип – указатель на структуру sotrudnik.

 

typedef sotrudnik *PtrNode; /* создание новой переменной типа указатель на узел*/

Попробуем теперь создать первый (головной) узел связанного списка.

PtrNode head; // присвоили имя head – т.е. головной узел

head=new sotrudnik; /* в памяти выделяется место под структуру сотрудник */

 

Теперь нужно присвоить значения переменным узла

 

(*head).name=”Иванов Иван Иванович”; /* содержимому находящемуся по адресу содержащемуся в указателе head, т.е. структуре, а точнее ее полю, присваивается значение*/

(*head).year_b=1980;

 

Здесь в левой части записаны сразу два оператора: разыменовывание *, и оператор точка для обращения к члену структуры или класса. Эти операторы можно объединить одним оператором стрелка -> и записать эти строчки так:

 

head->name=”Иванов Иван Иванович”;

head->year_b=1980;

 

В конечном итоге должна быть следующая программа

 

#include<iostream.h>

#include<string.h>

#include<windows.h>

//описание узла

struct sotrudnik{

char name[30];

int year_b;

sotrudnik *link;

};

//объявление указателя на узел

typedef sotrudnik *PtrNode;

main(){

SetConsoleOutputCP(1251);

PtrNode head;

head=new sotrudnik;

strcpy(head->name,"Иванов Иван Иванович");

head->year_b=1980;

head->link=NULL;

/*NULL-это нулевое значение указателя, это не адрес, а символ, говорящий о том, что этот указатель не содержит адреса*/

cout<< head->name;

cout<<endl<<head->year_b;

cout<<endl<<head->link;

char z;

cin>>z;

}

 

Программа создает список с одним узлом. Теперь, чтобы создать связанный список нужно добавить новый узел. Новый узел можно вставить как в начало, так и в конец связанного списка. Поместить новый узел в конец, значит в последнем узле старого списка изменить значение указателя с NULL на адрес нового узла. Поместить новый узел в начало значительно проще, т.к. ничего не нужно переделывать в старом списке. Именно так мы поступим.

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



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2016-02-16 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: