Пример.
//файлfile1.cpp
extern int b; //описание внешней переменнойb
int func2(int,int); //прототип функцииfunc2
int a=5; //определение внешней переменнойа
int func1(int c) //определение функцииfunc1
{int f=func2(c,a); // f=30
return f*b; } // 30*10=300
//файлfile2.cpp
extern int k; //описание внешней переменнойk
int func2(int x, int y) //определение функцииfunc2
{return x*y+k;} // 2*5+20=30
//файл file3.cpp
#include<stdio.h> //подключение библиотечных функций
#include "file1.cpp" //подключение файлаfile1.cpp
#include "file2.cpp" //подключение файлаfile2.cpp
int b=10; //определение внешней переменнойb
int k=20; //определение внешней переменнойk
void main()
{int d=2,res; res=func1(d);//вызов фукцииfunc1 res=300
printf(“k=%d res=%d”,k,res);//вывод результатов на экран
}
В данном примере препроцессор подставил в файл filе3.cpp тексты файлов filе1.cpp и file2.cpp. Переменная a (определена как внешняя в файле filе1), функции func1(), func2()видны и в файле filе3, благодаря работе препроцессора. В файле filе1 для доступа к функции func2()(определена в файле filе2) прописан прототип этой функции. В файлах filе1, filе2для доступа к переменным b, k (определены как внешние в файле filе3) эти переменные описаны со словом extern.
Поведем некоторые итоги.
1) Функции по определению являются глобальными, и им по умолчанию присваивается класс памяти extern,поэтому функции доступны во всех файлах. Но перед первым использованием функций необходимо записать прототипы, которые необходимы для компилятора.
2) Другие объекты (переменные, массивы, структуры и т.д.) также могут использоваться в файлах, где они не определены. Но они должны быть глобальными, т.е. определены вне функций, и им необходимо присвоить класс памяти extern, если они используются выше своего определения при данном методе создания многофайловой программы.
Каждый объект может быть определен только один раз, но может быть неоднократно описан.
Если использовать в разных файлах одни и те же функции, переменные, то в начале всех файлов необходимо поместить прототипы нужных функций и объявление переменных. Можно поместить прототипы функций, объявления переменных в отдельный файл с расширением .h, и включать этот файл в начало других файлов с помощью оператора #include.
//файл common.h
exrtern int a;
exrtern int b;
exrtern int k;
int func1(int);//прототипфункцииfunc1
int func2(int, int);//прототип функцииfunc2
//файл filе1.cpp
#include "common.h"
int a=5; //определение внешней переменнойа
int func1(int c){int f=func2(c,a);return f*b;}
//файл file2.cpp
#include "common.h"
int func2(int x, int y) {return x*y+k;}
// файл file3.cpp
#include<stdio.h> //подключение библиотечных функций
#include "common.h" //подключение заголовочного файла
#include "file1.cpp" //подключение файлаfile1.cpp
#include "file2.cpp" //подключение файлаfile2.cpp
int b=10; //определение внешней переменнойb
void main()
{int d=2,res; res=func1(d+k);
printf(“k=%d res=%d ”,k,res);}
int k=20; //определение внешней переменнойk
Фактически оператор подставляет содержимое файла *.h в текущий файл перед тем, как начать его компиляцию. Файл с расширением .h называется файлом заголовков.
Для создания многофайловых программ существует удобный механизм многофайловой компиляции и линковки.
Для этого в ВС-31 необходимо выполнить следующие действия:
1) Разрабатываются и запоминаются отдельно несколько файлов, из которых только один файл с именем main()
2) В главном меню выбирается команда Project, в меню которого выбирается опцияOpen project, вводится имя проекта с расширением prj. В нижней части окна появится новое окно, а в строке появится опции Add (добавить), Delete (удалить) и др.
3) По команде Add появится список файлов текущего директория, из которого выбираются нужные файлы с расширением .срр. После занесения всех файлов в проект выбирается команда Done (закрыть). В окне проекта будут находиться все файлы, занесенные в проект. Для удаления файлов из проекта пользуются командой Delete (удалить).
4) Далее проект запускается на компиляцию и компоновку (Ctrl+F9), после чего образуется файл с расширением .ехе. Если надо перекомпилировать все файлы проекта (при использовании inline-функций и т.д.) в меню команды Compile выбирается команда Build all.
При разработке некоторой группы функций (для работы с матрицами, с комплексными числами и т.д.) бывает удобно объединить все эти функции в библиотеку функций. Библиотеки также создаются с помощью многофайловой компиляции и имеют расширение .lib.
Для создания библиотеки необходимо:
1) Выбрать команду главного меню Options->Make.В окне After Compiling установить Run librarian->OK.
2) Затем создать проект, включить в него необходимые файлы.
3) Откомпилировать проект, выполнив команду Compile->Makeили Compile->Build all. При этом создается файл с расширением .lib.
4) Затем надо создать новый проект. При этом выбирается команда главного Options->Make и в окне After Compiling устанавливается значение Run linker->OK. Задается новое имя проект, и в этот проект включают файл с функцией main(), файл с расширением .lib и другие необходимые файлы, которые не вошли в библиотеку. После компиляции образуется файл с расширением .ехе, который использует функции из библиотеки.
В библиотеку может также включаться и файл с главной функцией main(), но тогда в такую программу будет затруднительно вносить изменения. Поэтому обычно функция main()не входит в библиотеки. Пример.
//файл arith.h
float sum(float, float);
float sub(float, float);
float mul(float, float);
//файл sum.cpp
float sum(float a, float b){return a+b;}
//файл sub.cpp
float sub(float a, float b){return a-b;}
//файл mul.cpp
float mul(float a, float b){return a*b;}
Создадим библиотеку, присвоив проекту имя arith.prj и включив в него файлы sum.cpp, sub.cpp, mul.cpp. Откомпилируем этот проект, в результате получим библиотеку arith.lib.
Пример использования библиотеки arith.lib
//файл prog.cpp
#include<stdio.h>
#include "arith.lib"
int main()
{float c=15.0, d=3.0;
printf("sum=%f sub=%f mul=%f\n", sum(c,d), sub(c,d), mul(c,d));
return 0;}
sum=18.0 sub=12.0 mul=45.0
Если исходные файлы предварительно откомпилированы и хранятся в директории в виде объектных файлов (*.obj), то их можно объединить в библиотеку из командной строки, используя утилиту tlib.exe. Эта утилита хранится в директории BC31\bin. Для этого надо набрать в командной строке, например,
f:\users\prog>tlib.exe arith+sum.obj+sub.obj+mul.obj
В результате будет создан библиотечный файл arith.lib, который объединит файлы sum.obj, sub.obj, mul.cpp.
5 Объекты и их атрибуты
Объект в языке С++ – это некоторая поименованная область памяти. Переменная – это частный случай такой поименованной области памяти.
Каждый объект (переменная, массив, указатель, структура, объединение, функция, класс, файл) имеет некоторые атрибуты.
Для объекта задается тип, который: 1) определяет требуемое для объекта количество памяти при ее начальном определении; 2) задает совокупность операций, допустимых для данного объекта; 3) интерпретирует двоичные коды значений при последующих обращениях к объекту; 4) используется для контроля типов для обнаружения случаев недопустимого присваивания.
Кроме типов, для объекта явно или по умолчанию определяются:
§ класс памяти (задает размещение объекта);
§ продолжительность существования объектов и их имен
§ область действия связанного с объектом идентификатора (имени);
§ область видимости объекта;
§ тип компоновки.
Класс памяти определяет размещение объекта в памяти и продолжительность его существования. Класс памяти в общем случае зависит от места расположения объекта в программе. Для явного задания класса памяти при определении (описании) объекта используются или подразумеваются по умолчанию следующие идентификаторы:auto, register, static, extern.
auto –автоматически выделяемая, локальная память. Этот класс памяти всегда присваивается по умолчанию всем объектам, определенным в блоке или функции. Память под такие объекты запрашивается при каждом входе в блок или функцию, где они определены, и освобождается при выходе. При определении автоматических объектов они никак не инициализируются, т.е. их значение сразу не определено.
register –автоматически выделяемая, по возможности регистровая память.Спецификатор register аналогичен auto, но для размещения значений объектов используются по возможности регистры, а не участки основной памяти. В случае отсутствия регистровой памяти (регистры заняты другими данными) объекты класса registerобрабатываются как объекты класса auto.
static –статически выделяемая память. Этот класс памяти присваивается всем объектам, которые определены со спецификатором static.Память для таких объектов выделяется в начале выполнения программы и сохраняется до конца ее выполнения. Такие объекты по умолчанию инициализируются нулями. При явной инициализации значение заносится только первый раз.
extern –глобальная (внешняя) память.Этот класс памяти всегда присваивается по умолчанию всем объектам, определенным вне функций. Память для таких объектов выделяется в начале выполнения программы и сохраняется до конца ее выполнения. Такие объекты по умолчанию инициализируются нулями. По умолчания класс памяти extern имеют функции и все файлы.
Продолжительность существования объектов и их имен определяет период, в течение которого именам в программе соответствуют конкретные объекты в памяти. Три вида продолжительности: локальная (автоматическая), статистическая и динамическая.
Переменные с локальной (автоматической)продолжительностью существования – это объекты с автоматической и регистровой памятью. Они создаются при каждом входе в блок или функцию, где определены, и уничтожаются при выходе.
Объектам со статической продолжительностью существования память выделяется в начале выполнения программы и сохраняется до конца ее выполнения. Это объекты с классом памяти static и extern.По умолчанию статическую продолжительность существования имеют функции и все файлы.
Объекты с динамической продолжительностью существования создаются (получают память) и уничтожаются с помощью явных операций или функций в процессе выполнения программы. Созданный с помощью операции new и функции malloc()объект будет существовать до тех пор, пока память не будет явно освобождена с помощью операции delete или функции free().Динамическое распределение памяти используется тогда, когда не известно сколько объектов понадобится в программе.
Область действия объекта (имени) – это часть программы, в которой имя объекта может быть использовано для доступа к объекту. Бывает локальная и глобальная область действия. Зависит от того, где и как определены и описаны объекты: в блоке, в функции, в прототипе функции, в файле (модуль), в классе.
Файл является областью действия для всех глобальных объектов, т.е. для объектов, определенных вне любых функций и классов, от точки определения до конца программы. Для доступа к глобальным объектам выше определения их необходимо описать (для функции записать прототип). Если объект определен в блоке или функции, то область его действия – от точки определения до конца блока или функции. Область действия формальных параметров в определении функции – тело функции. Область действия идентификаторов в прототипе функции совпадает с концом прототипа функции.
Объекты с локальной областью действия могут иметь статическую продолжительность существования. Например, если в функции описать переменную static int K,то область действия – локальная (тело функции), а продолжительность существования – статическая.
Область видимости объекта – это часть программы, в которой имя объекта может быть использовано для просмотра значений объекта. Это понятие появилось с возможностью повторных определений идентификатора внутри вложенных блоков (или функций). Чаще всего область действия имени и видимость связанного с ним объекта совпадают. Область видимости может превышать область действия, но наоборот невозможно. Например:
void main()
{char oper; ---ù
int x=2,y=4,z; |
. . . |
{int k=5; –––ù |
int x=3; | |
x+=3 .. | |
} –––û |
…} –––û
В первом примере область видимости первого х весь фрагмент программы, второго хтолько блок. Область действия первого х весь фрагмент программы, за исключением блока, где определен второй х, в этом блоке область действиявторого х.
Тип компоновки, или тип связывания, определяет соответствие идентификатора конкретному объекту или функции в программе, исходный текст которой размещен в нескольких файлах (модулях). Все глобальные объекты имеют внешний вид компоновки. Файлы могут транслироваться отдельно. Такие файлы нуждаются во внешней компоновке. Для остальных объектов, которые находятся внутри файлов, используется внутренняя компоновка.
6 Ссылки
Ссылка – это еще одно имя существующего объекта.
Синтаксис определения ссылки следующий:
тип & имя_ссылки=инициализирующее_выражение;
гдеинициализирующее_выражение – это имя объекта, который уже имеет место в памяти, а тип– это тип этого объекта.
Например:
int x=20; //определена и проинициализирована переменнаях
int &pх=х; // определена ссылка, значением которой является
//адрес переменной х .
float g=5.5;//определена и проинициализирована переменнаяg.
float &pg=g; //определена ссылка, значением которой является
//адрес переменной g.
В определении ссылки символ & не является частью типа, т.е. xилиpх имеют типint. Имя_ссылки определяет место расположения инициализирующего объекта.
Ссылка – это адрес объекта, поэтому сходна с указателем. Но у ссылки имеются существенные особенности.
1) Обращение к ссылке аналогично обращению к переменной, для доступа к содержимому ссылки не нужно выполнять операцию разыменования. Например, допустимо:
double f=5.5;//определена и проинициализирована переменная f.
double &pf=f; //определена ссылка pf c адресом f
double *fp1=&f; //определен указатель fp1 c адресом f.
f=2.5; //переменной fприсвоено значение2.5
pf=1.5; //переменной fприсвоено значение1.5
*fp1=10.5; //переменной fприсвоено значение10.5
struct REC{int a; float b;}st1;
REC &ref_st=st1; // определена ссылка на структуру st1
REC *ptr_st=&st1; //определен указатель на структуруst1
ref_st.a=4; ref_st.b=0.5;
printf(“%d %f\n”, st1.a,st1.b); //4 0.5
(*ptr_st).a=41; ptr_st->b=12.5;
printf(“%d %f\n”, st1.a,st1.b);// 41 12.5
2) Размер ссылки – это размер переменной, связанной со ссылкой при инициализации, а размер указателя, в общем случае 4 байта.
int n1=sizeof(pf); // n1=8(типdouble)
int n2=sizeof(fp1);// n2=4(2 байта-сегмент, байта-смещение)
int n3=sizeof(ref_st); //n3=6
int n4=sizeof(ptr_st); //n4=4
3) При определении ссылки ее необходимо инициализировать.
int x=100;
int &хref; // ошибка!!
int &хref=х;// правильно!!
4) Нельзя переопределить ссылку, т.е. изменить адрес переменной, на которую она ссылается.
int x1=50;
хref=х1; //переменной х присвоится значение х1.
// ссылкахref будет по-прежнему ссылаться х .
Cсылки – это не настоящие объекты, поэтому существуют ограничения при определении и использовании ссылок:
1) Ссылка не может иметь тип void.
2) Ссылку нельзя создать с помощью операции new, т.е. для ссылки нельзя выделить новый участок памяти.
3) Нельзя определить ссылку на другую ссылку и указатель на ссылку.
4) Нельзя создать массив ссылок.
Например:
int a1=2,a2=4,a3=8;
int &ra;// нет инициализации ссылки
void ref_var=a1;// void недопустимо для ссылки
int ref_new=new(long &);// ссылке не выделяется память
int &&ref_ref=a2; // нет ссылок на ссылки
int ref_arr[3]={a1,a2,a3};// массивов ссылок не бывает
В определении ссылки можно использовать модификатор const,т.е. допустимо определить ссылку на константу. Например:
double d=2.728282; //определили переменную
const double &ref_d=d; //определили ссылку на константу
d=0.1;//допустимо
ref_f=0.2; // ошибка!!
При определении ссылок обязательна инициализация, но при описании ссылки инициализация необязательна в таких случаях:
1) при описании внешних ссылок.
float & ref1; // ошибка!!нет инициализации ссылки
extern float &ref2; //допустимо!Инициализируется в другом блоке
2) в спецификации формальных параметров и при описании типа функции.
3) при описании ссылок на компоненты класса.
При использовании ссылки в качестве формального параметра обеспечивается доступ из тела функции к соответствующему фактическому параметру, т.е. к участку памяти, выделенному для фактического параметра. Ссылка обеспечивает те же возможности, что и параметр-указатель, но при этом не нужно применять операцию разыменования. В этом случае фактический параметр является обычной переменной, а не адресом, как для параметра-указателя.