Double STEP(double x, int n)




{

int i; double s=1.0;

for(i=l; i<=n; i++) s*=x;

return s;

}

Здесь сначала описана главная функция, а затем функция STEP. Поскольку естественный порядок описания функ­ций нарушен, мы должны информировать функцию main о том, что где-то в программе описана функция STEP, которую следует использовать. Нужно объявить (декла­рировать) ее. Как видно из примера, делается это очень просто. Функция STEP имеет тип double, и ее объявля­ют в main вместе с описаниями переменных этого типа. Для того чтобы система могла отличить объявление пе­ременной от объявления функции, после имени функции в описании ставят круглые скобки.

Функция STEP от двух аргументов: х (вещественного типа) и n (целого типа), как легко убедиться, возвраща­ет n - ю степень числа х для неотрицательных целых n. Главное, на чем следует остановиться, это то, что в теле функции S TEP нам пришлось описать две дополни­тельные переменные - i и s, после чего изготовленная нами функция стала уже совсем похожей на уже совсем родную нам главную функцию main.

Мы упоминали о том, что переменные, описанные внутри функции, явля­ются локальными. Они "живут" только в этой функции и для других функций не существуют. В этой связи имена локальных переменных в разных функциях вполне мо­гут совпадать. Переменная х1 в функции М не имеет никакого отношения к переменной х1 в функции Р так же, как Миша, живущий в квартире 56, это вовсе не то же самое, что Миша, живущий в квартире 65. Совсем иное дело - глобальные переменные, описанные в про­грамме вне функций. Такие переменные (и константы) "живут" во всех функциях, описанных после описания глобальных переменных. Такие объекты видны из любой функции программы. Изменение значения глобальной пе­ременной, сделанное в одной из функций программы, не­медленно отобразится на ее значении во всех остальных функциях. Продолжая нашу аналогию, можно сказать, что Александр Сергеевич Пушкин - он и есть Пушкин и для квартиры 56 и для квартиры 65, и если президентом на выборах стал господин Смит, то он стал президен­том и для штата Техас и для штата Флорида.

Правда, и здесь возможны особенности: а что, если в некой се­мье с нестандартным воображением родившегося ребен­ка назвали А.С. Пушкин? Когда в этой семье говорят о Пушкине, то кого имеют в виду? В житейской ситуации, по-видимому, в этом случае требуются дополнительные пояснения. В С ничего такого не нужно. Существует следующее правило: если имя глобальной переменной со­впадает с именем локальной, для некоторой функции, переменной, то внутри этой функции операции с этим именем относятся к локальному объекту. Так, например, изменение локальной переменной никак не отразится на значении глобальной переменной с тем же именем. Наобо­рот, изменение глобальной переменной проявится во всех функциях, где такое имя не описано, а там, где описана ее локальная "тезка", с последней ничего не произойдет.

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

 

 

Один из способов решения такой задачи состоит в пе­редаче вычисленных значений в вызывающую програм­му в виде глобальных переменных:

 

#include<stdio.h>

#include<math.h>

#define Pi 3.1415927

double X,Y;

Void decart(double r, double f)

{

double pil80=Pi/180;

X=r*cos(f); Y=r*sin(f);

}

Void main()

{

double rr.ff;

scanf(“%lf %If'”,&rr.&ff);

decart(rr,ff);

printf(“%lf %lf",X,Y);

}

Прежде всего, поясним новый элемент программы, появившийся в третьей строке. Мы уже говорили, что конструкции, начинающиеся символом #, являются ин­струкциями препроцессору. Такие инструкции предпи­сывают системе еще до начала компиляции произвести над программой некоторые предварительные действия. Так, инструкция #include предписывает подключить к программе соответствующий заголовочный файл. По ин­струкции #define АAА ВВВ препроцессор просматривает весь текст программы и везде, где встречается фрагмент ААА, производится его замена на фрагмент ВВВ. Под­черкнем, что замена делается именно в тексте еще до компиляции программы. Необходимость в использовании этой препроцессорной инструкции возникает только в особых случаях, с которыми, однако, читателю, возмож­но, придется столкнуться, что и побудило нас включить эту конструкцию в программу.

Функция decart в данной программе не возвраща­ет никакого значения и поэтому имеет тип v oid. В те­ле функции описана локальная переменная pil80, кото­рая получает значение, используемое для преобразования углов из градусной меры в радианную. Использование здесь локальной переменной явно нецелесообразно. Автор сознательно допустил это для того, чтобы читатель сам попробовал сделать необходимые корректировки. Пере­дача в главную функцию вычисленных в decart значе­ний осуществляется с помощью глобальных переменных Х и Y.

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

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

 

const double pil80=3.1415927/180;

void DECART(double r,double f,double *x,double*y)

{

*x=r*sin(f*pil80);

*y=r*cos(f*pil80);

}

Void main()

{ double r,f,x,y;

scanf("%lf %lf”,&r,&f);

DECART(r,f,&x,&y);

printf("%lf %1f",x,y);

}

Для начала мы оптимизировали определение значе­ния pi180. Теперь оно стало вещественной константой, которая не может и не должна меняться нигде в про­грамме. Кроме того, сразу задано ее числовое значение через деление пифагорова числа на 180. Эта операция делается в программе один раз, что и является наиболее оптимальным.

Функция DECART имеет четыре аргумента. Два пер­вых - обычные переменные типа double, а третий и четвертый - указатели на double. Тело функции со­стоит из двух выражений присваивания. Первое из них следует понимать так: "По адресу, указанному третьим аргументом функции, записать значение, равное произведению первого аргумента на синус второго аргумента, умноженного на значение константы pi180 ". Аналогич­ную сентенцию можно сочинить и для второго выраже­ния присваивания.

В главной функции описаны четыре вещественные пе­ременные: r, f, х, у. Значения первых двух вводятся с консоли и далее используются как "входные" параметры в функции DECART. Значения х и у формируются в ре­зультате работы этой функции. Поскольку на месте двух последних аргументов должны стоять указатели, мы и написали в вызове функции DECART &x и .

Изложенные только что представления позволят нам окончательно разобраться с функцией scanf. Вспомним, что инициализируемую с консоли переменную мы снаб­жали символом & перед ее именем. Сейчас стало по­нятным, почему это необходимо. Функция scanf должна изменить значение вводимой переменной — единствен­ная возможность сделать это без привлечения глобаль­ных объектов заключается в использовании указателей. Именно по этой причине аргументом функции scanf является не сама переменная, а ее адрес.

Рассмотрим еще один пример на использование "са­модельной" функции без параметров. В этом примере мы познакомимся с еще одним генератором случайных чи­сел, описанным в заголовочном файле stdlib.h. Эта функция называется random, имеет аргумент типа int и возвращает целое случайное число из диапазона от 0 до N-1(включительно), где N — значение аргумента функции. С помощью этой функции изготовим генератор вещественных слу­чайных чисел в диапазоне от 0 до 1, В главной функции генератор будет использован для заполнения веществен­ного массива случайными числами:

 

#include<stdlib.h>

const double g=l./31999.;

Double RN()

{

return random(32000)*g;

}

Void rnain()

{

int i=0; double a[10];

while(i<10) printf("%lf\n",a[i++]=RN());

}

Легко видеть, что функция RN, которой не нужны аргу­менты, с использованием заготовленной глобальной кон­станты g реализует генерацию случайной величины в нужном диапазоне. Обратим внимание на то, что в главной программе производится печать очередного элемен­та массива, который прямо в функции печати и получает значение.

Следующая функция заполняет n первых элементов массива случайными целыми в диапазоне от 0 до 9:

 

void initarray(int *name, int n)

{

do *(name+- -n)=random(10); while(n);

}

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

 

Void main()

{

int mass[100],k;

printf("ЗАПОЛНИТЬ K="); scanf(“%d",&k);

initarray(mass,k);

printf("ЭЛЕМЕНТОВ МАССИВА\п");

while(k) printf(“%d\n",mass[- -k]);

}

Файлы

 

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

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

Сразу начнем со следующей программы:

 

Void main()

{ int i;

FILE *f;

f=fopen("one.dat","w");

for(i=l; i<15; i++) fprintf(f,"%d\n",i);

fclose(f); }

Здесь мы впервые столкнулись с еще одним типом данных. Название этого типа FILE (пишется именно заглавны­ми буквами, что для СИ нехарактерно). Переменная f имеет отношение к этому новому типу. Поскольку она фигурирует в описании со стоящей перед ней звездоч­кой, многоопытный читатель сделает вывод о том, что f является указателем. Так оно и есть, и указывает этот указатель на файл. Иными словами, f является адресом начала файла. Пока, однако, этот указатель не инициа­лизирован, не привязан ни к какому конкретному файлу. Инициализация указателя на файл (мы будем использо­вать также термин "файловая переменная") производит­ся одновременно с открытием файла, предоставлени­ем файла в распоряжение данной программе. Открытие файла производится функцией fopen и выглядит как вы­ражение присваивания:

 

ИмяФайловойПеременной=fореn("Строка1", "Строка2");

Эта процедура действительно является присваиванием, поскольку функция fopen, возвращающая адрес файла, который она открывает, имеет тип FILE*. Функция име­ет два аргумента. Строка1 задает имя открываемого файла. В DOS полное имя файла состоит, как правило, из собственно имени и расширения, разделенных точкой. В нашем примере открывается файл one.dat. Строка2 состоит из буквы, показывающей для каких операций от­крывается файл. Возможны следующие варианты:

 

"r" — файл открывается для чтения из него инфор­мации (read),

"w" — файл открывается для записи информации (write),

"a" — файл открывается для дозаписи информации в конец файла (append).

 

Буква w предполагает создание нового файла с ука­занным в Строке1 именем. Будьте внимательны: если на диске в рабочем каталоге уже существовал файл с таким именем, то его от­крытие для записи приведет к безвозвратной (и, вероят­но, безвременной) утрате старого файла.

Данная программа создает новый файл и записыва­ет в него 14 целых чисел от 1 до 14. Каждое число записывается в новую строку. Функция записи в файл fprintf весьма похожа на знакомую вам функцию печа­ти на экран printf. Разница состоит в том, что сразу за открывающей скобкой пишется файловая переменная, а далее после запятой все полностью совпадает с пра­вилами оформления печати на экран. Информация, за­писанная в файл, выглядит точно так, как и на экране. В этом можно убедиться, просмотрев созданный файл с помощью любого текстового редактора, в частности, и из системы TURBO С. Поработав с папкой, вы, навер­ное, ее закрываете. Это же следует сделать и с файлом. Функция закрытия файла fclose имеет в качестве аргу­мента имя файловой переменной. Можно отметить, что в случае успешного, как говорят военные - штатного, за­вершения программы все открытые файлы закрываются автоматически. Тем не менее, мы настоятельно рекомен­дуем вызывать функцию fclose сразу после того, как программа завершила работу с соответствующим фай­лом.

Теперь напишем программу, которая читает дан­ные из файла. Считаем, что на диске существует файл one.dat, созданный предыдущей программой:

 

Void main()

{ int i,j=10.k;

FILE *pp;

Pp=fopen(“one.dat","r");

for(i=0; i<j; i++)

{ fscanf(pp,”%d”,&k);

printf("%d\n",k); }

fclose(pp); }

В программе файл one .dat открывается для чтения. В цикле с помощью функции чтения из файла fscanf в пе­ременную k из этого файла читается очередное число, и его значение выводится на экран. После чтения десяти чисел файл закрывается, и программа завершает работу.

Функция fscanf также похожа на функцию ввода с клавиатуры scanf. В ней на первом месте стоит ука­затель на файл. С оформлением форматной строки дело обстоит несколько сложнее. Прежде всего, нужно знать, что если в форматной строке стоит некий символ, то вы должны быть уверены, что текущая позиция читаемого файла содержит именно этот символ. Например, если в форматной строке стоит \n, то и в файле в этот момент находится символ перехода на новую строку. В этой свя­зи рекомендуется, по крайней мере, на первых порах, во­обще не писать в форматной строке ничего, кроме фор­матных выражений. Избегайте даже пробелов. Учитывая эти тонкости, разработчики системы позаботились о том, чтобы функция fscanf в какой то степени сама разбиралась с размещением записей в файле. Как бы ни размещались числа: каждое в своей строке, группами в строках, все в одной строке — вы просто пишете подряд столько форматных выра­жений, сколько нужно прочитать чисел (или символов). Не обращайте внимание на то, как расположены данные в файле. Можете считать, что они все записаны подряд в строчку.

Нормально сформированный файл всегда имеет по­следним символом признак конца файла EOF (end of file). Существует специальная функция feof, имеющая аргу­ментом файловую переменную, которая показывает, до­стигнут ли конец файла. Для более ясного представления о работе функции feof представим файл в виде кинолен­ты, которая прокручивается так, что после очередного чтения данных следующий кадр устанавливается в " фай­ловое окно". В качестве кадра надо понимать очередной байт, записанный в файл. В самом последнем кадре фай­ла и находится признак конца файла EOF. Если после очередного чтения из файла в "файловом окне" оказал­ся признак конца, то при вызове функции feof она воз­вращает 1, иначе конец файла не достигнут, и функция возвращает 0.

Рассмотрим новую стандартную функцию int getc(FILE *f). Как видно из такого ее формального определения, функция getc возвращает целое число и имеет своим аргументом указатель на файл. Функция читает из файла очередной символ (байт) и возвращает его код (значение). Обратим внимание на то, что читает­ся один байт, а возвращаемое значение конвертируется в тип int, занимающий два байта.

Функция int putc(int с,FILE *f) записывает в очередную позицию файла символ, код которого задан первым аргументом. Первый аргумент имеет тип int, a при записи в файл значение конвертируется в тип char. Таким образом, на месте первого параметра может на­ходиться как код символа, так и сам символ. Функция возвращает код записанного символа, что может быть использовано в программе.

Теперь уместно, безотносительно к текущей теме - файлам, рассмотреть еще несколько полезных функций. Функция int getch(void), нам уже знакомая, читает символ, ска­жем так, из стандартного файла ввода/вывода. А стан­дартным файлом назовем консоль. Следовательно, функ­ция вводит символ с клавиатуры. До сих пор мы поль­зовались этой функцией для того, чтобы сохранить от­крытым рабочий экран до нажатия любой клавиши. Это действительно важное приложение функции, но ее основ­ным назначением является передача в программу симво­ла, считываемого с клавиатуры.

Функция int putch(int с), наоборот, передает сим­вол, заданный своим аргументом, в стандартный файл ввода/вывода. А чтобы перестать испытывать терпение читателя, то автор будет вынужден сказать по-простому: функция печатает на экран символ - свой аргумент.

В некоторых реализациях систем программирования, в частности в TURBO С, вы можете встретить мо­дификации этих функций getchar, putchar, getche и putche. Эти разновидности описанных выше функций отличаются режимом работы, в том числе буферизацией или не буферизацией ввода. Сказав эти загадочные слова, автор этим и ограничится, отослав любопытного читате­ля к справочной системе TURBO C++ и к литературе.

Наряду с чтением и записью одиночных символов су­ществуют функции чтения и записи строк, как для консо­ли, так и для файлов. Эти функции имеют имена gets, puts, fgets и fputs.

Функция char* gets(char* s) читает строку симво­лов с клавиатуры (до нажатия клавиши ввода) и поме­щает ее в память, начиная с адреса s. При этом в каче­стве аргумента функции целесообразно использовать имя описанного в программе символьного массива, в который будет помещена прочитанная с консоли строка.

Функция int puts(const char* s) может иметь сво­им аргументом указатель на символ - имя символьного массива или непосредственно строку символов в кавыч­ках. Строка или массив выводится на экран.

Из файла может быть прочитана или в файл может быть записана строка. Первую из этих задач выполняет функция char* fgets(char*, int, PILE*). Она чи­тает из файла, на который указывает последний аргу­мент, очередную строку, записывает ее в память по адре­су, на который указывает первый аргумент. При этом чи­тается количество символов, не превышающее значения второго аргумента, уменьшенного на единицу. В конец записанной в память строки автоматически добавляется признак конца строки \0. Сама функция, в соответствии с ее типом char*, возвращает адрес начала строки.

Следующая программа, текст которой будем считать находящимся в файле re.с, построчно читает сама себя и выводит на экран длины строк:

 

#include<stdio.h>

#include<string.h>

Void main()

{ FILE *f;

char *p;

int length;

f=fopen("re.c","r");

while(!feof(f))

{fgets(p,256,f);

length=strlen(p);

printf("%d\n",length); }}

Ограничитель длины читаемой строк здесь выбран равным 256, поскольку в редакторе TURBO С недопусти­мы строки длиннее 255 знаков. Можно обойтись без пере­менной length и использовать в цикле while единствен­ное выражение printf("%d\n",strlen(fgets(p,256,f))). Обратите внимание на то, что мы не пытались описать в программе символьный массив, в который записывается очередная строка. Вместо этого мы создали только ука­затель р на char - адрес начала строки. Так поступать вполне допустимо, хотя душа автора этому непроизвольно противится, поскольку при этом конкретная область памяти под строку не выделяется. В реальной программе автор описал бы массив р[256] и после этого спал бы по ночам спокойным сном, чего и читателю желает.

Функция int fputs (char*, FILE*) записывает в файл, указанный последним аргументом, строку, расположен­ную по адресу, указанному первым аргументом. Возвращается количество записанных символов.

Если выполняются строковые операции со стандартным файлом ввода/вывода, то есть с консолью, то используются функции char* gets (char*) и int puts(const char*). Наверное, уже нет нужды пояснять, что является аргументами этих функций и какие значе­ния эти функции возвращают.

Широкие возможности программисту предоставляет так называемый прямой доступ к файлу. Используя нашу аналогию файла с кинолентой, можно сказать, что пря­мой доступ к файлу позволяет устанавливать файловое окно на любой заданный кадр. Установка очередной позиции осуществляется функцией int fseek(FILE*,long, int). Первый аргумент указывает на файл, с которым нужно работать. Третий аргумент обязан иметь значе­ние системной константы SEEK_SET для текстовых файлов (а другие мы не рассматриваем и рассматри­вать не будем). Именно эта конструкция должна стоять на месте последнего параметра функции. Второй аргу­мент задает номер позиции, на которую должно быть установлено файловое окно, то есть номер байта, отсчи­танный от начала файла. Функция long ftell(FILE*) возвращает номер текущей позиции в файле. Функция void rewind(FILE*) устанавливает файловое окно в на­чало файла, то есть делает то же, что и функция fseek(p,0L, SEEK_SET), если р - указатель на файл. Числовая константа 0L есть "длинный ноль" - число 0 типа long.

ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ И ПРИЛОЖЕНИЯ С ГРАФИЧЕСКИМ ИНТЕРФЕЙСОМ

Динамическое выделение памяти

 

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

Для решения указанных и сходных проблем разработан аппарат так называемого динамического распределения памяти. Динамическое распределение было реализовано еще и в языке С. Для этого там использовались специальные функции, среди которых можно упомянуть alloc и malloc. Однако наиболее гибко это организовано именно в С++. Желаемое достигается использованием так называемого оператора new.

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

 

double *A;

 

Теперь непосредственно выделяем память. Выглядит соответствующая операция следующим образом:

 

A = new double;

 

Из синтаксиса последнего выражения следует, что оператор new, во-первых, распределяет память и, во-вторых, возвращает адрес начала выделенного блока. Далее работа с указателем А производится обычным образом. Например, следующая конструкция

 

*A=3.1415927;

 

означает, что по адресу А будет записано значение, приближенно равное числу Пифагора.

В отличие от статического выделения памяти, при использовании оператора new программисту следует самостоятельно позаботиться об очистке динамически выделенной памяти, когда она становится более не нужной. Очистка памяти выполняется оператором delete, и выглядит настолько просто, что комментарии не требуются:

 

delete A;

 

Заметим, что объявление указателя и непосредственное выделение памяти могут быть объединены в единое выражение. Так следующая строка выполняет распределение памяти под символьное значение.

 

char *s = new char;

 

При выделении памяти под отдельные (одиночные) значения использование операторов new и delete выглядит менее удобным в сравнении со статическим распределением, и с этим, пожалуй, можно согласиться. А вот при создании массивов данных динамическое выделение в некоторых задачах становится безальтернативно необходимым. Напомним, например, что такая потребность возникает когда заранее неизвестна длина необходимого массива, то есть длина определяется в процессе работы программы.

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

 

int *d;

 

d = new int [M];

 

Упрощенный формат будет иметь вид:

 

int *d = new int [M];

 

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

Поскольку, как мы знаем, имя массива как раз и является указателем на массив (адресом его начала), далее с массивом можно работать точно так, как и со статически заданным. Например, возможно следующее присваивание:

 

d[5]=7;

 

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

 

delete [] d;

 

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

Теперь рассмотрим динамическое выделение памяти под многомерные массивы, ограничившись двухмерным случаем. Прежде всего, напомним, что двухмерный массив можно рассматривать как массив, элементами которого являются, в свою очередь, массивы – массив массивов. Тогда имя двухмерного массива является указателем на массив указателей. Естественно предположить, что, например, для динамического создания символьного двухмерного массива следует начать с конструкции вида:

 

char **STRI = new char *[512];

 

STRI здесь является указателем на массив указателей. При этом «внешняя» размерность (размерность по первому индексу) равна 512. Каждый из 512 внешних элементов представляет собой снова массив, состоящий, допустим, из 24 элементов. Под каждый из них также необходимо динамически выделить память. Сделать это следует в цикле по всем внешним индексам:

 

for(i=0; i<512; i++) STRI[i] = new char[24];

 

Освобождение памяти, отведенной под двухмерный массив, приходится также выполнять в два этапа. Сначала в цикле выполняется «внутреннее» освобождение памяти:

 

for (i = 0; i<24; i++) delete [] STRI[i];

 

Затем высвобождается и удаляется указатель по внешнему индексу:

 

delete [] STRI;

 

Еще раз следует особо подчеркнуть необходимость освобождения памяти «вручную». Прежде всего, об этом следует помнить в тех случаях, когда динамическое выделение организуется внутри блока, который в программе выполняется неоднократно – в цикле, например. Если забыть применить оператор delete в этом блоке, то будет иметь место многократное распределение - так называемая утечка памяти в процессе выполнения программы. Зачастую при этом происходит исчерпание оперативной памяти компьютера и приложение, и вся система перестают работать.

 

Структуры

 

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

Формальное описание структуры, как типа данных, имеет следующий вид:

 

struct [Тег]

{

Объявления элементов структуры;

} [Переменные структуры];

 

Служебное слово struct означает, что объявлена структура. Необязательная (в связи с этим взятая в прямые скобки) конструкция Тег является именем типа структуры. При декларации в программе конкретной переменной структуры Тег будет выступать в роли имени типа (как int, long и прочее). В фигурных скобках помещаются описания членов структуры, называемых также элементами структуры и полями структуры. Описания полностью совпадают с описаниями переменных в программе, например, double s; int a[12]; char *sss; и так далее.

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

 

Struct NewStr

{

int a,b;

double c;

};

 

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

 

NewStr A,*B,C[4];

 

Здесь описана переменная структуры А, указатель на переменную структуры *B и массив из четырех переменных структуры С[4] описанного типа. Во-вторых, объявление переменных структуры можно сделать сразу при декларации типа структуры. В этом случае та же конструкция A,*B,C[4] помещается после закрывающей фигурной скобки. В последнем случае этом тег структуры задавать не обязательно.

Обращение к членам структуры в программе выполняется с помощью разделителя “.” (точка) или разделителя “->” (стрелка). Если обращение выполняется через имя переменной структуры, то используется первый вариант. Так в нашем примере для присвоения значения полю структуры b числового значения можно применить выражение присваивания:

 

A.b = 7;

 

Поскольку у нас также объявлен указатель на структуру , аналогичное выражение присваивания будет выглядеть так:

 

B->b=7;

 

Теперь рассмотрим небольшой пример, типичный для работы со структурами. Определим структуру, в которой будут содержаться данные на десятерых сотрудников отдела некоторой организации. Данные представляют собой порядковый номер сотрудника – целое значение num, фамилия, записанная в строке – символьный массив name, возраст – целая переменная age, пол – символьный массив из одного элемента sex, в который заносится буква f или m. Очевидно, все перечисленные элементы в свою очередь являются элементами массивов длиной 10 по числу сотрудников.

 

#include <stdio.h>

#include <conio.h>

#include <string.h>

Struct BASE

{

int num[10];

char name[10][24];

int age[10];

char sex[10][1];

};

 

Void main(void)

{

BASE bas1,bas2; int i;

FILE *f1;

bas1.num[0]=0;

strcpy(bas1.name[0],"Ivanov");

bas1.age[0]=44;

strcpy(bas1.sex[0],"m");

f1=fopen("C:\ivb\ppis\bas1f.txt","wb");

fwrite(&bas1,sizeof(bas1),1,f1);

fclose(f1);

f1=fopen("C:\ivb\ppis\bas1f.txt","rb");

fread(&bas2,sizeof(bas1),1,f1);

puts(bas1.name[0]);

fclose(f1);

getch();

}

 

В приведенном листинге объявлен тип структуры BASE, и в главной функции созданы два экземпляра структуры bas1 и bas2. В программе инициализируются поля для первого (точнее – нулевого) сотрудника с номером 0, фамилией Ivanov, возраст 44 года, пол мужской. Остальные девять «строк» в структуре остаются не инициализированными. По некоторому пути в файловой системе открывается для записи двоичный файл с именем bas1f.txt. Расширение txt не должно вводить читателя в заблуждение. Мода “wb” функции открытия файла fopen определяет то, что файл именно двоичный и открыт для записи. Нетрудно видеть, что в указанный дисковый файл целиком копируется структура bas1. Далее файл закрывается и вновь открывается уже для чтения. Содержимое файла копируется в экземпляр структуры bas2, и из этой структуры извлекается и выводится на экран фамилия пользователя. При правильной работе программы на экран должно быть напечатано, естественно, слово Ivanov. Наверное, нет нужды подробно пояснять, что здесь представлена «заготовка» работы со структурой в форме некоторой базы данных – ее создание, изменение и сохранение на диске в виде файла.

Классы

 

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

Переход от структуры к классу, как расширение структуры, в первую очередь связано с возможностью включения в класс кроме переменных-член



Поделиться:




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

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


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