Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Методичка по программированию.doc
Скачиваний:
11
Добавлен:
13.11.2019
Размер:
1.2 Mб
Скачать

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];

};