Структуры данных языка Си++

КАК создать функцию, не возвращающую значение?
КАК написать функцию, возвращающую значение?
КАК построить двумерный массив?
КАК создать свою структуру данных?

КАК и для чего используются модификаторы?
КАК сэкономить память, используя спецификторы?



Данный раздел очень важный, так как именно он научит нас использовать всё многообразие средств языка С++ для работы с данными. Практически любая программа так или иначе обрабатывает данные - это основная задача программирования. И от того, как вы правильно структурируете данные зависит очень многое.

КАК написать функцию не возвращающую значение?

В предыдущих примерах мы с вами уже создавали функции сами, например GetCurrentDir(). На практике вы будете на 90% использовать функции, которые создали сами и лишь на 10% стандартные функции СИ.
Функции бывают двух типов. Первый тип в языке Pascal называетая процедурой. Это функция, которая не возвращает значения. Такие функции предназначены для того, чтобы сэкономить размер исходного кода программы и не писать один и тот же повторяющийся код несколько раз. Например если вы хотите написать в нескольких местах экрана фразу: "Я люблю С++", вам придётся повторить практически один и тот же код несколько раз:

textcolor(1);
gotoxy(1, 1);
cprintf("Я люблю Си++");

textcolor(2);
gotoxy(2, 2):
cprintf("Я люблю Си++");


А если вы хотите заполнить экран такими сообщениями? Страшно и вообразить! Чтобы не повторять этот код, мы объединим его в функцию, которая в качестве аргументов будет принимать строку с текстом, которую мы хотим вывести, координаты экрана, в которых эта строка должна будет появиться, и цвет. Вот как будет выглядеть такая функция:

void OutTextXY(char *text, int x, int y, int color)
{
gotoxy(x,y);
textcolor(color);
cprintf(text);
}

Тип void ("пустой") означает, что эта функция не имеет типа, так как не возвращает никакого значения. Она предназначена только для выполнения каких-то действий.
В скобках перечислены аргументы функции, которые мы будем подставлять в виде чисел. Для аргументов необходимо указать тип.

Дальше мы начинаем использовать функции библиотеки conio.h ("console input/output" - "консольный ввод-вывод"), которая творит настоящие чудеса в текстовом режиме, позволяя менять цвет символов, место их положения - одним словом работает с экраном в симольном режиме.

Функция gotoxy(int x, int y) переводит каретку в координаты x,y - таким образом мы можем вывести текст в любом месте экрана. В текстовом режиме стандарный экран представляет собой поле для отображения 80 символов по горизонатли и 25 по вертикали. Это всё, на что мы можем расчитывать. Поэтому расчитывая место появления текста, необходимо исходить из реальных возможностей.

Функция textcolor(int color) задаёт цвет текста. В системе Windows это практически никак не реализовано. Если мы и работаем с консолью, мы обычно видим чёрный экран с серым текстом, однако в системе Linux очень часто цвет текста бывает разноцветным. Это очень удобно. Например в случае критической ошибки, пользователь получает сообщение красным цветом, а в случае успешной работы программы - зелёным.

Функция clrscr() очищает экран. Есть смысл начинать с неё каждую новую программу, чтобы экран очищался от всяких служебных сообщений, которые были на нём раньше и содержал только результаты работы нашей программы.

Функция getch() в простейшем своём применении ожидаёт нажатия клавиши. Если мы разместим её в конце программы, она не завершится, пока мы не нажмём какую-нибудь клавишу, и мы сможем спокойно просмотреть результаты работы на экране.

#include<stdio.h>
#include<conio.h>

//определение нашей функции
void OutTextXY(char *text, int x, int y, int color)
textcolor(color);
gotoxy(x,y);
cprintf(text);
}

void main()
{
char *str="Я люблю Си++!";
int textcolor=1;
int x=1;
int y=1;

clrscr();

do {
OutTextXY(str, x, y, textcolor);
x++;
y++;
textcolor++;
}while(x!=80);

getch();

}

Свою функцию мы определили до тела main(), значит мы можем ссылаться на неё из любого места программы. Кстати, main() тоже имеет тип void, значит это функция, которая не возвращает значения. Всё в языке С подчиняется единым правилам. Если бы мы объявили функцию main, как int main или просто main() в конце её тела необходимо было бы возвращать значение.

void main() {
...
}

int main() {

return 0;
}

main() {

return 0;
}


Возвращаемое значение 0 - признак успешного завершения программы. Оно возвращается оператором return. В следующем разделе мы научимся создавать функции, которые возвращают значения.

КАК написать функцию возвращающую значение?

Очень часто, особенно в вычислительных программах, функция должна возвращать какое-то значение. Простой пример функции, которая что-то возвращает - функция sqrt библиотеки math.h

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

double answer = sqrt(4);

Для написания такой функции тип void нас уже не устроит. Необходимо обязательно указать тип.

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

#include<stdio.h>
#include<math.h>
#include<conio.h>

//функция, типа double

double Pifagorus(double x, double y)
{
double c, d;

c =x*x+y*y;
d=sqrt(c);

return d; //функция возвращает значение переменной d типа double
}

void main()
{
double xl = 100; //cопротивление индуктивности
double xc = 1000; //cопротивление ёмкости

clrscr();

double z = Pifagorus(xl, xc); //полное сопротивление контура
printf("Полное сопротивление колебательного контура равно: % lf Ом", z);

getch();
}

От функции типа void наша функция типа double отличается тем, что мы должны обязательно указать этот тип, а в конце функции возвратить значение того же типа, что и сама функция.

КАК применить перечисления?

Часто в программах используются числовые значения для задания нужного параметра. Например цвет в функции textcolor принимает значения от 0 до 15. Если мы пишем textcolor(4), у нас будет красный цвет, а если textcolor(0) черный, но это очень не очевидно. И глядя в свой же код, мы будем долго вспоминать какой textcolor к чему относится. Однако можно каждому цвету дать идентификатор с помощью перечисления enum.

enum {

BLACK,
BLUE,
GREEN,
LIGHT_BLUE,
RED,
PINK,
YELLOW,
WHITE
};


Такая запись будет означать, что BLACK=0, BLUE = 1, ... WHITE = 7. После такого объявления, запись

textcolor(RED);

будет эквивалентна записи:

textcolor(4);

Enumeration (перечисление) - самая простая структура для хранения данных. Тем не менее она позволяет уйти от безликих числовых параметров, заменив их гораздо более читаемыми строковыми.

Если вы хотите упростить запись, но у вас используются не перечисляемые типы: например 9600, 14400, 19200, можно использовать директивы #define, которые тоже часто помогают скрыть числа от глаз программиста:

#define LOW_SPEED 9600
#define NORMAL_SPEED 14400
#define HIGH_SPEED 19200


...

Тогда запись

SetPortSpeed(HIGH_SPEED);

будет равноценна записи:

SetPortSpeed(19200);

КАК построить многомерный массив?

До сих пор мы обходились одномерными массивами. В таком массиве можно хранить например года рождения:

int masyears[4]={1989, 1991, 1996, 1987};

или фамилии:

char *surnames[3]={"Иванов", "Петров", "Савельев"};

В математике такие массивы называются строками или векторами. Но допустим, нам нужно сохранить состояние игрового поля игры "крестики-нолики", "морской-бой" или шахматной доски. Мы можем записать состояние всех клеток в одномерный массив, но гораздо информативнее будет создать массив-таблицу, в котором будут столбцы и строки, как и на игровом поле.

Так, для игры "крестики-нолики", у нас будет массив 3Х3

int pole[3][3] = { 1, 1, 0,
                       
  0, 1, 0,
                       
  1, 0, 1 };

Для игры в шахматы 8Х8

int doska[8][8];

Для "морского боя" - 10Х10

int battle_pole[10][10];

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

int mas[2][2];


Когда мы присваиваем значение одному из элементов такого массива, мы должны обязательно использовать полную индексацию.

Так мы заполняем первую строчку:
mas[0][0]=6; mas[1][0]=1989;

а так вторую:

mas[0][1]=2; mas[1][1]=2000;


Если мы хотим построить трёхмерный массив, это тоже несложно:

int 3dMas[20][10][8];

индексировать его мы будем так:

3dMas[0][0][0] = 10;
3dMas[0][0][1] = 11;
...
3dMas[19][9][7] = 1890;

Массивы всем хороши, но они подходят для хранения данных только одного типа, например int или float. Если же мы хотим структурировать данные разного типа, нам больше подойдут структуры.

КАК создать свою структуру?

Мы уже дважды использовали структуры в своей работе. В первый раз это была структура атрибутов файла ffblk, во-второй структура даты и времени time_t. Мы создавали экземпляры этих структур и заполняли их содержимым, возможно не до конца понимая, что мы делаем. Итак, сейчас мы с ними разберёмся.

Допустим мы хотим хранить данные о компакт-дисках, которые имеются в нашей коллекции. Чем характеризуется диск? Прежде всего названием. Это конечно же строковый тип. Потом год выпуска. Возможно утилиты для работы с Windows 96-го года нам уже не подойдут, поэтому год нам также немаловажен. Следующий параметр - фирма изготовитель. Каждый диск записан какой-то фирмой, и упорядочив свои диски по фирме, мы сразу узнаем, какая фирма производит больше всего дисков, а какая фирма производит самые лучшие диски, то есть произведём некоторую статистику. Для аудиодисков немаловажно знать - сколько на нём песен. Ещё возможно мы хотим запомнить цену (тип float, так цена редко бывает не дробной) и краткую информацию - описание диска (например, какие на диске есть программы или какие представлены исполнители).

Конечно же описать с помощью массива столь разнотипные данные невозможно. Однако при использовании структур, все данные будут сведены вот в такую простенькую структуру:

struct CD {
char name[25]; //название диска
int year; //год выпуска
char firm[15]; //фирма
int kol; //сколько песен
float price; //цена
char info[256]; //информация о диске
} cd;


Для того, чтобы в этой структуре что-то хранилось, надо сначала занести в неё эти данные. Напишем пример ввода структуры и вывода её на экран.

#include <iostream.h>
#include <conio.h>

//объявляем структуру
struct CD {
char name[25];
int year;
char firm[15];
int kol;
float price;
char info[256];
} cd;



main()
{
CD cd; //создаём экземпляр структуры

cout<<"Введите название диска: ";
cin>>cd.name;

cout<<"Введите год выпуска диска: ";
cin>>cd.year;

cout<<"Введите название диска: ";
cin>>cd.firm;

cout<<"Введите количество песен на диске: ";
cin>>cd.kol;

cout<<"Введите цену диска: ";
cin>>cd.price;

cout<<"Введите краткую информацию о диске: ";
cin>>cd.info;
cout<<"Нажмите любую клавишу...";

getch();

cout<<"Диск: "<<cd.name<<", "<<cd.year<<" года выпуска. Фирма изготовитель: "<<cd.firm<<endl;
cout<<"Цена: "<<cd.price<<endl<<". Краткая информация: "<<cd.info;
getch();
}


Обратите внимание, что мы вводим и выводим не price, name, info, а cd.price, cd.name, cd.info. Мы создали экземпляр структуры CD, то есть переменную типа "структура CD". Эта переменная состоит из нескольких переменных, к которым мы сможем обратиться только с помощью тега cd.

cin>>cd.info;

cout<<cd.name;


Это хорошо, скажете вы! Но в моей коллекции сотни дисков! Для того, чтобы хранить информацию о нескольких дисках, надо создать массив структур. Создадим массива типа СD.

#include<iostream.h>
#include<conio.h>

struct CD {
char name[25];
int year;
char firm[15];
int kol;
float price;
char info[256];
} cd;

main()
{
CD cd[2]; //массив CD

clrscr();
textcolor(2);

int i=0;

do {

cout<<"\nName: ";
cin>>cd[i].name;
cout<<"Year: ";
cin>>cd[i].year;
cout<<"Firm: ";
cin>>cd[i].firm;
cout<<"Kol: ";
cin>>cd[i].kol;
cout<<"Price: ";
cin>>cd[i].price;
cout<<"Info: ";
cin>>cd[i].info;
cout<<"Нажмите любую клавишу...";
getch();
i++;
} while(i<2);

i=0;
clrscr();

do {
cout<<"\nName: "<<cd[i].name<<", "<<cd[i].year<<endl<<" year. Firm: "<<cd[i].firm<<endl;
cout<<"Price: "<<cd[i].price<<endl<<". Infoя: "<<cd[i].info;
i++;
} while(i<2);
getch();

return 0;

}

Конечно, вряд ли у вас в коллекции всего 2 диска, но где 2, там и 200 - число элементов можно увеличивать до бесконечности.


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

//создание файла-записи
#include<iostream.h>
#include<conio.h>
#include<stdio.h>

//Стpуктуpа данных компакт-диска
struct CD {
char name[20];
char sur[20];
int kol;
int year;
float price;
};


CD mas[4]; //массив типа CD

//Пpоцедуpа ввода в базу
CD inbase(CD cd)
{
cout<<"Введите название диска: "<< endl;
cin.getline(cd.name,20); //считать стpоку длиной 20 символов
cin.getline(cd.sur, 20);
cout<<"Введите количество песен: "<< endl;
cin>>cd.kol;
cout<<"Введите год издания: "<< endl;
cin>>cd.year;
cout<<"Введите цену"<< endl;
cin>>cd.price;
return cd; //функция возвpащает заполненную стpуктуpу
}

//Пpоцедуpа ввода в базу
void finbase(FILE *f, CD cd)
{

fprintf(f, "%s ", cd.name);
fprintf(f, "%s ", cd.sur);
fprintf(f, "%d ", cd.kol);
fprintf(f, "%d ", cd.year);
fprintf(f, "%f ", cd.price);

}


//Вывод массива из файла
void foutmas(FILE *f, CD cd)
{
//Считываем значение из файла
fscanf(f, "%s", cd.name);
fscanf(f, "%s", cd.sur);
//Выводим его на экpан
cout<<"Hазвание: "<<cd.name<<cd.sur<< endl;
fscanf(f, "%d", cd.kol);
cout<<"Количество песен: "<<cd.kol<< endl;
fscanf(f, "%d", cd.year);
cout<<"Год издания: "<<cd.year<< endl;
fscanf(f, "%f", cd.price);
cout<<"Цена: "<<cd.price<< endl;
}


int main()
{
CD c; //Экземпляp стpуктуpы CD
int i=0;

clrscr();
FILE *f=fopen("data.dbf", "w+"); //Создаем файл

do {
mas[i]=inbase(c); //Заполняем элемент массив
finbase(f, mas[i]); //Записываем элемент в файл
i++;
fseek(f, 1, SEEK_CUR); //Пеpеходим на в файле на один символ
}while(i<3);


i=0; //Обнуляем пеpеменную цикла
clrscr();
printf("Hажмите клавишу для вывода базы на экpан...\n");

do {
foutmas(f, mas[i]); //Считываем массив из файла и выводим на экpан
i++;
}while(i<3);

fclose(f); //Закpываем файл
return 0; //Пpизнак удачного завеpшения пpогpаммы
}

Так примерно выглядели базы данных начала 80-х годов. Полицейские участки и библиотеки, справочные службы и кадровые службы хранили всю свою информацию в базах данных, основанных на структурах. Такие базы хороши, если вам не нужно ничего менять. Однако если мы захотим немного переделать нашу структуру данных - добавить ещё одно поле или что-то удалить, нам придётся всё переделывать сначала. Кроме того, файл, в котором хранятся данные совершенно не читаем для всех других программ, он не имеет никакого формата или стандарта, поэтому в дальнейшем о таких базах данных нам придётся забыть.

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

Дальнейшее расширение структур - классы, которые могут хранить не только данные разных типов, но и функции. Но об это чуть позже.

Как и для чего используются модификаторы?

Пришло время поговорить о модификаторах. Это не совсем очевидная часть языка, так как можно писать даже очень большие программы, фактически ничего о них не зная. Но представление о них всё же надо иметь.
Модификаторы бывают следующие: cdecl, pascal, interrupt, эти модификаторы записываются перед именем переменной, несколько меняя её свойства. Кроме этого, существуют ещё: const, volatile, near, far, huge, которые могут воздействовать не только на идентификатор, но и на звёздочку, которая следует за модифкатором.

Когда мы объявляем стоку:

char *str="машина";

Мы по сути говорим, что str - указатель на область памяти, в которую записано слово "машина". В str будет храниться не сама строка, а адрес оперативной памяти, начиная с которого записана строка.

Когда мы задаём строку так:

char str[6]="машина";

Тем самым мы говорим, что начиная с этого адреса, записаны 6 символов. В первом случае размер явно не указывается. Это указатель. Во втором случае - указывается. Это массив.

Модификатор const

Как можно догадаться из названия, этот модификатор не даёт нам ничего сделать с переменной. Есть два типа const.

1-й случай: не разрешается менять значение указателя, тогда как значение строки менять можно.

char *const str="машина";


Теперь если мы захотим написать

str="корвет"

то у нас ничего не выйдет, но если мы применим функцию:

strcopy(str, "корвет")

тем самым мы меняем не указатель, а строку, и значение строки будет изменено. Функция осуществит побитное копирование в область памяти, на которую указывает str, и значение указателя изменено не будет.

2-й случай:. не разрешается менять даже значение строки

char const *str="машина";

Иногда, когда программа активно использует память, случаются нежелательные изменения значений переменной в процессе работы. Применение const позволяет выявить такие нежелательные изменения и сделать программу гораздо более надёжной. Если вы не хотите, чтобы какое-то значение менялось без вашего ведома, поставьте модификатор const. Также если вы собираетесь хранить данные в ROM или NVRAM, обязательно убедитесь, что они имеют тип const *, чтобы ваше устройство не перепрограммировало само себя.

Модификатор volatile

А этот модификатор противоположен по смыслу. В Windows часть используется многопоточность и несколько потоков сразу могут обращаться к одной и той же переменной. Модификатор volatile показывает, что значение переменной может быть изменено и хранить её значение в регистре не нужно.

Пример:

int count=0;

while(count<100) {
count++;
//здесь выполняются какие-то действия
}


Цикл будет выполняться, пока count не достигнет 100. Можно попытаться другим потоком изменить значение count присвоив ей 100 (в Windows это в порядке вещей). Казалось бы, цикл должен остановиться, но не тут-то было. Переменная count загруженная в регистр и мы не сможем её изменить в течении всего цикла. И если до вмешательства процесса count была 40, то после его вмешательства, когда мы казалось бы присвоили ей 100, она будет прочитана из регистра, увеличена на 1 и станет 41.
volatile - выход из положения. Он позволяет организовать не записывать переменную в регистр, зная, что её значение может быть изменено.

volatile int count=0; //теперь переменная стала разделяемой

while(count<100) {
count++;
//здесь выполняются какие-то действия
}

Модификаторы near, far и huge

В DOS существует понятие "дальней" (far) и ближней ("near") памяти. Указатели с модификатором near - 16 битовые. Доступная память такого указателя ограничена размером сегмента данных - 64 Кб. Естественно их применение ещё имеет место в ассемблерном коде и только.

Указатель типа far - 32 битный, то есть 32 разрядный. Кроме адреса сегмента, он содержит ещё и смещение от этого адреса. При таком указателе доступно обращение к памяти в пределах 1 Мб, однако значение такого указателя всё равно может изменяться только в пределах 64 Кб.

char far list[SIZE]; //показывает, что для доступа к массиву должны использоваться 32 разрядные адреса

char far *next, far *prev, far *cur;


Элементы массива list могут храниться тоже только в переменных, объявленных, как char far. Обратите внимание, что модфикатор far при объявлении ставится перед каждой переменной.

Указатель huge - 32 битный, и значение его может изменяться в пределах 1 Мб.

int huge data[65000]; //размер массива превышает 64 Кб, поэтому он типа huge

Также говорят, что для near используются команды ближнего вызова, а для far, huge - дальнего. Операции отношения и сравнения допустимы только над указателями huge.

Модификаторы cdecl, pascal, interrupt

Первые две опции используются для совместимости с языком Pascal. При компилляции файла, его преобразуют так называемый двоичный код или объектный файл с расширением **.obj. Такой же двоичный файл получается при компиляции программы на Pascal. Для компоновщика не будет разницы из чего делать exe файл, так как формат объектного файла единый.
Модификаторы cdecl и pascal были сделаны с тем расчётом, что в организации могут вестись проекты на Си и Паскале одновременно - кто из программистов что лучше знает. Потом готовые модули собираются в один большой проект. Для совместимости применяются модификаторы cdecl и pascal.

cdecl - опция компиляции, которая присваивает всем указателям и функциям тип pascal, который приводит к тому, что идентификатор преобразуется к верхнему регистру, к нему не добавляется символ подчёркивания. Посылка аргументов функций в стек в таком случае происходит не в обратном направлении, как в Си, а в прямом.

Модификатор interrupt служит для процедур и функций, работающий с регистрами микропроцессора. До функции с таким модификатором окружение будет сохранено, а после неё - восстановлено. Таким образом, функция может менять содержимое регистров, а после её выполнения все регистры примут состояние, которые было до начала выполнения этой функции.

Спецификаторы: auto, register, static и extern

Все переменные характеризуются временем жизни - периодом, в течении которого переменная существует в памяти. Представьте, что вы объявили в начале программы переменную char *str. На неё могут ссылаться все функции в программе, но даже когда они не ссылаются, эта переменная будет сохраняться в памяти, пока программа не завершится:

char str[MAXPATH];

void Func()
{
str=GetCyrrentDir();
.....
SetCurrentDir(str);
}

void Func1()
{
str=GetFileName();
OpenFile(str);
}


Мы объявили переменную str глобально, следовательно её область видимости допускает, что функции Func() и Func1() могут её использовать. Но даже когда они не запускаются, переменная str всё также остаётся в памяти, занимая 256 байт.

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

void Func()
{
char *str=GetCurrentDir();
...
SetCurrentDir(str):
}

void Func1()
{
char *str=GetFileName();
OpenFile(str);
}


Конечно, 256 байт это не много, но представьте, что такая переменная не одна, а их беузмно много? Счёт пойдёт на мегабайты. А если запустить сразу несколько экземпляров такой программы? В один прекрасный момент Windows выдаст сообщение: "Запуск программы невозможен. Закройте одну или несколько программ и повторите попытку".
В Си есть так называемые классы памяти для удобства управления областью видимости переменной (будет она видна только в этом модуле или во всех файлах проекта), и её временем жизни (в течение работы функции или пока не завершится программа).
Классы памяти бывают автоматические (локальное время жизни) и статические (время жизни глобально). Следующее объявление:

auto float x,y;

показывает, что переменные x и у типа float создаются локально и будут действовать только в теле той функции, в которой их создали. При входе в блок эти переменные создаются и при выходе из него - уничтожаются. Для интенсивно используемых локальных переменных и параметров функций применяется тип register. Тогда переменная будет храниться не в оперативной памяти, а в одном из высокоскоростных аппаратных регистров.

register int x=1;

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

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

static int x=10;

Если объявить её локально, она будет существовать не только до завершения работы функции, но и после прекращения её работы. И при следующем запуске функции, эта переменная будет хранить своё значение с прошлого раза.

void Count()
{
static int x;
x++;
}


К следующему запуску функции переменная будет хранить значение на 1 больше.

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

//головной файл main.cpp
#include "mylib1.h"
#include "mylib2.h"

extern double a0;
extern double b;
extern double alfa;

main()
{
int i;
...
if (i==0) {
double answer = retAnswer();
}

else {

double answer = myAnswer();
}
}


//1-й библиотечный файл mylib1.h

int retAnswer() {
double b = a0*b*exp(alfa);
return b;
}

//2-й библиотечный файл mylib2.h

int myAnswer()
{
double c=a0/(b*alfa*alfa);
return c;
}

В данном примере у нас программа разнесена на три файла: main.cpp, mylib1.h и mylib2.h. Но во всех трёх файлах "видны" переменные a0, b, alfa, потому что они объявлены, как extern. Таким образом, эти переменные не передаются, как аргументы функции, но подключаются из главного файла. Естественно, создавать такие переменные наименнее экономично, и если нет необходимости, лучше этого не делать.

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

Назад Содержание Вперёд