Пример. extern int b; //описание внешней переменнойb

// файл 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 или имеют тип 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) при описании ссылок на компоненты класса.

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


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: