Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
пиппец 2.doc
Скачиваний:
106
Добавлен:
07.06.2015
Размер:
3.16 Mб
Скачать

62. Функция как средство структурирования программы.

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

Ф-я – это самостоятельная именованная часть программы, кот могут передаваться пар-ры и кот может возвращать какое-то зн-е.

Синтаксис [<тип возвращаемого зн-я>] <имя функции> (<список аргументов|void>){<объявления><операторы>[return [<выражение>]]}.

В Pascal: Function <имя>[(список формал пар-ров)]:<тип>; где <имя> - имя функции(идентификатор), (список формальных параметров)-список имен формальных переменных с указанием их типов. Имена переменных разделяются точкой с запятой. <тип> - тип возвращаемого функцией значения.

Формальные параметры - это переменные, которые принимают значения, переданные функции при ее вызове, в соответствии с порядком следования их имен в списке параметров. Формальные параметры делятся на:

-входные(такие, с помощью которых в подпрограмму передаются данные для ее работы),

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

-модифицируемые(например, обмен местами значений двух переменных).

Список формальных параметров может быть пуст (либо void),в этом случае говорят, что функция не имеет параметров. Каждый элемент списка формальных параметров имеет вид: <обозначение типа> <имя параметра>.В языке Си допустимы функции с переменным числом параметров (количество параметров которых при компиляции неизвестно).пр. int printf (const char* format, ...};

Фактические параметры – список выражений через запятую. Обязательно должно быть соответствие между списком формальных и фактических параметров:

-по количеству(кроме подпрограмм с переменным числом параметров);

-по типу(типы должны быть совместимы)

-по смыслу.

Тело функции представляет собой блок, т.е. часть программы, заключенную в фигурные скобки, включающую объявления и операторы. Оператор возврата функции – return. Предварительное объявление функции – прототип. Синт: [<тип возврата>]<имя функции>([<спецификации формальных параметров>]);Спецификации формальных параметров – тип параметра и имя параметра. Прототипы функции помещаются в отдельный заголовочный файл (*.h).В С вызов функции является выражением, тип которого определяется типом возврата этой функции.

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

Например, для функции с прототипом.double func{int, char*, double);допустимы следующие ее вызовы:func (3, " + п", 3 . 5), func[k, "String", d)

Передача параметров функции. Способы передачи параметров функции:по значению;по ссылке.

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

Передача параметров в функцию по значению предусматривает следующие шаги:

1) При подготовке к исполнению функции выделяются участки памяти для размещения ее формальных параметров.

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

3. Значения фактических параметров заносятся в программный стек.

4.Значения выражений - фактических параметров -считываются из стека и заносятся в участки памяти, выделенные для формальных параметров (см. шаг 1).

5.В теле функции выполняется обработка с использованием значений внутренних объектов-параметров, и результат передается в точку вызова функции как возвращаемое ею значение. Если для функции указан тип возврата void, то возвращаемое значение не определено.

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

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

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

А) пр. ф-я для обмена значениями двух вещественных переменных.

#include<stdio.h>

void swap(double* dl, double* d2)

{double temp,temp=*dl;*dl =*d2;*d2 = temp;}

void main ()

{double x = 6.8, у - -11.45; swap(&x, &y); printf("\nx = %5.3f, у = %5. 3f",x,y);//Увидим: x = -11.45, у = 6.8}

Б)пр.#include<stdio.h>

void swap(double dl, double d2)

{double temp;temp = dl; dl = d2; d2 = temp;}

void main()

{double x - 6.8, у = -11.45; swap(x, у);printf{"\nx=%5.3f,у =%5. 3f ",x, у] ;}//р-т

В Pascal: процедуры: Procedure <имя>[(список формал параметров)]; Процедура не возвращает зн-я, поэтому ее тип при описании не указывается. Вызов процедуры <имя процедуры>(<список фактич пар-ров>); ф-и <имя ф-и>(<список факт пар>); Заголовок подпрограммы содержит список формальных переменных, кот при вызове подпрограмм заменяются фактич перем. Существует два способа подстановки пар-ров: 1)подстановка зн-я (параметр-зн-я) 2) подстановка переменной (параметр-переменная). 1)передаются основной программой в подпрограмму виде их копий, поэтому фактич пар-р подпрограммой измениться не может. 2)указываются заданием зарезервированного слова var перед их идентификаторами в списке формальных пар-ров. При передаче пар-ров переменных в подпрограмму передаются их адреса в порядке, объявленном в заголовке подпрограммы.

Основным отличием ф-ии от процедуры: ф-ия имеет единственное возвращаемое значение, которое представляет собой результат; синтаксис описания функции отличается; в теле функции должен быть спец.оператор, обеспечивающий возврат из функции её значений(return в Си, rezult в Pascal); вызов функции отличается от вызова процедуры.

Типы параметров процедур и функций в Pascal(по способу передачи):

-параметры значения – описываются без дополнительных ключевых слов.

Procedure proc(x:byte, y:real, z:char); где x,y,z – параметры-значения

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

-параметры-переменные – описываются с использованием ключевого слова var:

Procedure swap(var x,y:real);

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

-параметры-константы – описываются с использованием ключевого слова const.

Procedure proc(const x:real; …)

63. Средства обработки текстовых данных в языках программирования.

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

Существует два способа хранения строк:

1)фиксированной длины – код длины строки содержится в самой строке,

2)нуль-терминальные строки - - символ нуль-терминала, символ окончания строки, т.о. длина строки ограничена только объемом памяти.

В первом случае строка не зависимо от количества хранимых символов имеет определенный размер. Такой способ хранения строк реализован в языке Turbo Pascal. В TP строка представляется как цепочка символов, при этом к каждому символу можно обратиться по его порядковому номеру. В памяти строки хранятся следующим образом: самый первый байт содержит текущую длину строки, т.к. для указания размера строки выделяется один байт, то ограничение по размеру строки составляет 255 символов, второй значащий байт содержит первый символ сроки и имеет индекс 1 т.о. нумерация символов в строке начинается с 1.

В языке Си в отличие от Pascal реализован второй способ хранения строковых данных. Строка в Си представляет собой массив символов, идентификатор сроки содержит указатель на начало этого массива. Для указания окончания строки используется специальный символ нуль-терминатор ‘\0’(такой массив называется строкой в формате ASCIIZ), т.о. при инициализации строки необходимо выделять памяти на единицу больше чем количество сохраняемых символов. Помимо обычных символов строковый массив может содержать специальные символы, специальными символами является совокупность символов начинающихся с символа ‘\’, примером таких символов являются : ‘\n’-символ перевода каретки, ‘\t’- символ табуляции и др., такие символы называются эскейп-последовательностями. Cледует подчеркнуть, что длина символьной константы 'n' равна 1 байту, а длина строки “n” равна 2 байтам.

Способы задания строк в Си:

1)строки, создаваемые статически, как одномерный статический массив:а) char str[10];

gets(str);

б)char str[80]=”Строка”;

2)размещение строк в динамической памяти

char *str; //объявили указатель на строку

int n;

puts(“введите длину строки”);

scanf(“%d”,&n);

str=(char*)malloc(n*sizeof(char)+1);

Существует специальная форма инициализации массива типа char-с помощью строки.

Например объявление

сhar str[] = “abc”;

Инициализирует массив str четырьмя символами ‘a’, ’b’, ’c’, ’\0’. Такое объявление эквивалентно следующему:

сhar str [] ={‘a’, ‘b’, ‘c’, ‘\0’, };

Двумерные массивы типа char инициализируются строковыми литералами:

Char strings [3][80] = {“ Первая строка ”, “Вторая строка”,”Третья строка”};

Так как имя строки (имя символьного массива без индекса), заданной как статический массив , есть указатель-константа, то его нельзя использовать в некоторых операциях, предполагающих изменение значения этого указателя (в качестве левого операнда операции присваивания):

сhar s[10];

s=”String”;//Ошибка

Следующий способ задания строки вполне корректен:

char s[10];

gets (s); //так как ф. gets не меняет значения своего аргумента указателя, а лишь помещает по этому адресу строку.

Строковый литерал можно связать с указателем на тип char:

  1. char* s;

s=”Strihg1”;

2)char* str=”String2”;

char s[10];

gets (s);

Функции для работы со троками вСи

Ф. strlen() поэволяет опред. длину строки. Возвращает длину в байтах, не учитывая завершающий нулевой символ.

Прототип unsigned (char* str);

Ф.strcpy() позволяет копировать строку по заданному адресу.

Прототип char* strcpy(char* sp, const char* si);

Ф.копирует строку, адрес которой задается значением параметра si, включая завершающий нуль-терминатор, по адресу, определяемому значением sp.

С помощью strcpy() можно удалить из строки подстроку

char* s=”Система”;

strcpy(s, s+3);

puts(s);//увидим тема

Ф. strncpy() позволяет копировать несколько первых символов строки по заданному адресу.

Прототип:

сhar * strncpy(char* sp, const char* si, int kol);

Ф. копирует kol сим. строки, адрес которой задается значением параметра si, по адресу, определяемому значение sp.

Копирование подстроки.

сhar s1[6], s2[]=”Программа”;

strncpy(s1,s2+3, 5);

puts(s1); // увидим грамм

Ф. strdup() позволяет создать копию строки

Прототип:

char* strdup(const char* str);

Ф. Выделяет необходимый участок памяти (через вызов функции malloc()) и копирует в него строку str. Ф. возвращает указатель на тот участок памяти,куда спопирована строка.

Ф. strcat() позволяет склеивать строки

Прототип:

Char* strcat(char* sp, const char* si);

Ф. приписывает строке sp строку si, добавляя в конец строки-результата(sp) нулевой символ(‘\0’).

Ф. strncat() позволяет склеивать строки

Прототип:

Char* strncat(char* sp, const char* si, int count);

Ф. приписывает count cимволов строки si в конец строки sp, добавляя в конец строки-результата(sp) нулевой символ(‘\0’).

Char s1[]=”str”, char s2[] =”ing1”, char s3[10];

S3=strcat(s1,s2,3);

Puts(s3); //увидим: String

Ф. char* strcmp(const char* str1, const char* str2);

Функция сравнивает строки str1 и str2 лексикографически и возвращает определяющее их значение: <0,=0,>0.

Ф. char* strchr(const char* str, int c);-функция ищет в строке str первое вхождение символа, имеющего код с.

Ф. char* strrchr(const char* str, int c);- функция ищет в строке str последнее вхождение символа, имеющего код с.

Ф.char* strrev(char* str);-переписывает заново строку str, меняя порядок следования символов в строке на противоположныйю.

Ф. char* itoa (int v, char* str, int baz);-преобразует целое число v в строку str, заверщающуюся нулевым символом. Ф. возвращает указатель на строку-результат.Значение аргумнта baz определяет систему счисления для представления результата(от2 до 36).

Ф.int atoi(char* str);-преобразует строку в целое число.

Объявлять строки можно следующим образом:

Pascal

S1:string[20];

S2:string[100];

S3:string;

Инициализация строк осуществляется с.о.:Pascal

S1:=’срока’;

Над строками можно производить следующие операции: сравнение, конкатенация строк. При сравнении строк происходит поэлементное сравнение используя ASCII коды.

При работе со строками можно использовать следующие ф-ции: Pascal

  1. Delete(str:string;index,count:integer) – удаление из строки str указанного числа символов count начтная с позиции index.

  2. insert(str1,str2:string;index:integer) – вставка строки str1 в строку str2 c позиции index.

  3. concat(s1,s2,..,s3:string) – сцепление строк .

  4. copy(str:string;index,count:integer) – копирование подстроки длиной count из строки str начиная с позиции index.

  5. length(str:string) – получение длины строки.

pos(str1,str2:string) – получение первого вхождения строки str1 в строку str2.

64 Процедурные средства ввода-вывода.

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

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

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

Во время работы с файлом с ним связывается указатель текущей позиции. Все операции считывания и записи данных из файла вып-ся с указателя, причем во время считывания УК-ль текущей позиции перемещается на величину считываемой либо записанной информации. Конец файла обозначается спец.именем EOF. Во всех ЯП существует ф-ия для управления положением указателя текущей позиции.

Средства языка Си для работы с внешними файлами представляют собой набор библиотечных ф-ий. Их прототипы нах-ся в stdio.h.

  1. ф-ии верхнего уровня(потоковые)

  2. ф-ии нижнего уровня(дескрипторные)

  3. консольного ввода-вывода.

Режимы открытия файла: текстовый, двоичный.

В текстовом режиме происходит трансформация: ‘\n’ CR #10(возврат каретки); LF #13(перевод строки). В двоичном режиме трансформации не происходит.

Открытие файла средствами языка СИ:

В момент открытия файла в программе образуется специальная структура, которая построена след.образом:

struct FILE

{<информация об открытом файле> };

FILE *fp; - имя файловой переменной, указатель на открытый поток.

Для открытия файла есть спец. ф-ия, возвращаемое значение которой FILE*:

FILE *fopen(const char *filename, const char*mode); //открывает файл с именем filename в режиме открытия mode, возвращает указатель на открытый поток.

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

FILE *f;

If((f=fopen(“data.dat”, “r”))==NULL)

{

perror(“error data.dat”);

exit(1);

}

Ф-ия закрытия файла:

int fclose(FILE *stream); //закрывает поток с именем stream. Если не удалось успешно закрыть, то возвращаемое значение EOF.

Пример: fclose(fp);

int fcloseall(); //закрыть все открытые потоки, кроме стандартных.

Режимы открытия файла: r(открыть только для чтения), w(открыть только для записи), a(открыть файл для добавления данных в его конец), r+(открыть файл для обновления, доступен для чтения и для записи), w+(открывается файл для чтения и записи), а+(открыть для обновления, а также для чтения и записи), b(открыть файл в двоичном режиме), t(открыть файл в текстовом режиме).

Поток – файл вместе с предоставленными ему средствами буферизации.

Стандартные потоки ввода-вывода:

1)stdin – станд.поток ввода

2)stdout – станд.поток вывода

3)stderr – станд.поток вывода сообщений об ошибках

4)stdaux – станд.доп.поток

5)stdprn – станд.поток вывода (связан с принтером)

Библиотечные функции для работы с содержимым файлов делятся на:

1)посимвольного в/в

2)построчного в/в

3)форматированного в/в

4)поблочного в/в

Язык Pascal

Файловый тип или переменную файлового типа можно задать одним из трех способов:

<имя>=FILE OF <тип>;

<имя>=TEXT;

<имя>=FILE;

В зависимости от способа объявления можно выделить 3 вида файлов:

-типизированные файлы (задаются предложением FILE OF…);

-текстовые файлы (определяются типом TEXT);

- нетипизированные файлы (определяются типом FILE).

Файловая переменная связывается с именем файла в результате обращения к стандартной процедуре ASSIGN:

ASSIGN (<ф.п.>,<имя файла или л.у.>);

Инициировать файл означает указать для этого файла направление передачи данных.

RESET(<ф.п.>) инициирует : чтение в файл.

REWRITE(<ф.п.>)-инициирует запись информации в файл или в логическое устройство.

APPEND(<ф.п.>)-инициирует запись в ранее существовавший текстовый файл для расширения.

Текстовые ф. связываются с ф.п., принадлежащими типу TEXT. Текстовые ф. предназначены для хранения текстовой информации. В Паскале текстовый файл трактуется как совокупность строк переменой длины. Доступ к каждой строке возможен лишь последовательно, начиная с первой. При создании текстового файла в конце каждой записи ставится специальный признак EOLN, а в конце всего файла – EOF.

Для доступа к записям применяются процедуры

READ([<ф.п.>],<с.п.ввода>)-обеспечивает ввод символов, строк и чисел.

READLN([<ф.п.>],<с.п.ввода>)-идентична предыдущей за исключением того, что после считывания последней переменной оставшаяся часть строки до маркера EOLN пропускается, поэтому след. Обращение к READLN или READ начинается с первого символа новой строки.

WRITE([<ф.п.>],<с.п.ввода>)-обеспечивает вывод информации в текстовый файл или передачу ее на логическое устройство,

WRITELN([<ф.п.>],<с.п.ввода>) - идентична предыдущей за исключением того, что выводимая строка символов завершается кодами CR , LF.

и функции SEEKEOLN,SEEKEOF,

EOLN([<ф.п.>])-возвращает TRUE, если во входном текстовом файле достигнут маркер конца строки.

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

Для доступа к записям применяются процедуры READ, WRITE, а так же функции

SEEK(<ф.п.>,<N-компонента>)-смещает указатель ф. к требуемому компоненту.

FILESIZE(<ф.п.>)-возвращает значение типа LONGINT, которое содержит количество компонентов файла.

FILEPOS(<ф.п.>)-возвращает значение типа LONGINT, содержащее порядковый номер компонента файла.

Нетипизированные ф. объявляются как файловые переменные типа FILE и отличаются тем, что для них не указан тип компонентов. Отсутствие типа делает эти ф. совместимыми с любыми др.файлами и позволяет организовать высокоскоростной обмен данными м/у диском и памятью.

При работе с этими ф.применяются все процедуры и функции, доступные тип-м ф.,за искл.READ, WRITE , кот. заменяются высоскоростными процедурами BLOKREAD(<ф.п.>,<буф.>,<[,NN]>) и BLOKWRITE(<ф.п.>,<буф.>,<[,NN]>)

CLOSE(<ф.п.>)-заравает файл

RENAME(<ф.п.>, <новое имя>)-переименовывает

RASE(<ф.п.>)-уничтожает

,FLUSH(<ф.п.>)-очищает внутренний буфер файла и , таким образом, гарантирует сохранность всех последних изменений файла не диске.

65 Объектно-ориентированные средства ввода-вывода.

Для организации ввода-вывода данных встроенных типов в программе на С++ используется заголовочный файл iostream.h, который содержит определение нескольких классов и объектов, обеспечивающих методы ввода – вывода. В частности, язык поддерживает четыре предопределенных объекта, выполняющих ввод-вывод:

Cin-ввод(обычно с клавиатуры)

Cout-вывод(обычно терминал)

Cerr-вывод ошибок (обычно клавиатура)

Clog-буферизованная версия Cerr

В файле iostream.h определены классы istream и ostream, которые используются для управления ввода и вывода соответственно. В этих классах перегружены операции ввода и вывода для встроенных типов данных. Например:

Class ostream {

Public:

Ostream & operator<<(char*);

Ostream & operator<<(int);

Ostream & operator<<(long);

Ostream & operator<<(double);

. . . .

};

Cout- объект класса Ostream. Оператор cout<<x; посылает значение переменой x в объект сout класса Ostream для вывода. Функция operator<< возвращает ссылку на объект Ostream, для к-ого она была вызвана. Поэтому к результату операции вывода << можно еще раз применить операцию вывода,т .е. неск.операций можно сцепить:

Cout<<”x = “<<x <<endl;

Такая запись проинтерпритируется как :

((cout.operator<<(“x=”)).operator<<(x)).operator<<(endl);

Аналогично опред. ввод cin.

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

#include<iostream.h>

#include<math.h>

Class Complex{

Double real;

Double image;

Public:

Complex(double r) { real=r; image=0;}

Complex(double r, double i) { real=r; double=i;}

~Complex () {}

Int isEqual (Complex &);

Int isGreater (Complex &);

Int isLess (Complex &);

}

Функция для вывода на экран компоексного числа может иметь вид:

Void complex :: printComplex ()

{

Cout<<real<<”+I *”<< image<<endl;

}

Объект можно напечатать, вызывая эту функцию.

Если перегрузить операцию << операцию для какого-либо класса, то с ее помощью можно будет напечатать объект этого класса точно так же как для встроенных типов данных:

Ostream & operator << (ostream & r, Complex c)

{

Return r <<c.real<<”+i*” <<c.image<<endl;

}

Cout <<c1;

Так как функция-операция возвращает ссылку на объект класса ostream, то можно сцепить нашу операцию в цепочку:

Cout << “Комплексное число:”<<c1;

Ф-ия для перегрузки операции << обязательно должна быть объявлена как дружественная классу Complex, а не как его член, потому что в операции вывода слева стоит объект другого класса. Ф-ия член класса первым аргументом всегда неявно получает указатель this, а операция вывода << должна получить ссылку на класс ostream.

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

istream & operator >> (istream & r, Complex &c)

{

Return r>>c.real>>c.image;

}

Схема иерархии потоковых классов

Ios-баз.потоковый класс

Istream-класс входных потоков

Ostream- класс выходных потоков

Iostream-- класс двунаправленных потоков ввода-вывода

Istrstream- - класс входных строковых потоков

Ostrstream- - класс выходных строковых потоков

Strstream класс двунаправленных строковых потоков ввода-вывода

Ifsream- класс входных файловых потоков

Ofstream- класс выходных строковых потоков

Fstream- класс двунаправленных файловых потоков ввода-вывода

Constream класс консольных потоков.

Вопрос № 66. Динамическое размещение данных в программировании.

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

1) статически (секция данных, стэк) – память распределяется в момент компиляции. (пример: int x[10];)

2)динамически – для дин.размещения памяти выделяется спец. Область памяти КУЧА. Объем динамически размещаемых данных опр-ся лишь в момент выполнения программы. Для работы с дин.памятью исп-ся спец.библиотечные ф-ии, расположенные в заголовочном файле #include <alloc.h>.

В Си существуют 2 способа доступа к переменной: ссылка на переменную по имени и использование механизма указателей.

Ук.-переменная-это переменная, предназначенная для хранения адреса в памяти.

Ук.-константа – это значение адреса оперативной памяти.

Синтаксис:

[<имя типа>] * <имя переменной>

*-операция косвенной адресации - операция разыменования указателя - Это операция обращения к содержимому памяти по адресу, к-ый хранится в переменной-указателе. Операнд – указатель. Результат – тот объект, который адресует указатель – операнд.

Значением выражения *р будет число, которое хранится по адресу р.

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

Int date = 1974; //Объявлена пер-я с инициализацией

Int *p = &date; // Объявлен указатель на тип int, к-ый инициализирован адресом пер-ой date

= - операция присваивания. Можно присваивать только значения указателей одинаковых типов.

Int i, *p1, *p2=&i;

Float s, *p3;

P1=p2; //Ук.р1 получил значение указателя р2, т.е.стал равен адресу переменной i

P3=p1; //нельзя

Операции адресной арифметики. К ук.применимы орерации суммирования, вычитания, инкремента и декремента. Имеется ограничение на использование операции суммирования: нельзя складывать две переменные-указатели. Можно прибавить к указателю целую величину.(р+1). При этом важной особенностью адресной арифметики является то, что вычисляемое значение зависит от типа указателя, т.е.от размера того объекта, на который указатель ссылается. Пример:

Long b, *p3, *p4=&b; p3=p4+2; //если р4=0А01, то р3=0А09, если длина типа – 4 байта.

Операцию вычитание можно применять не только к ук.и целой величине, но к двум указателям на объекты одного типа. Разность между ук.также зависит оттипа ук. Значение разности м/у ук.имеет тип int. Это значение вычисляется в единицах , кратных длине отдельного элемента данных того типа, к которому отнесен указатель. Пример:

Int x[5]; int *I, k, j; i= &x[o]; k= &x[5]; j=k-I; // j =4.

Разрешается сравнивать указатели одного типа.

Связь массивов с указателями.

Имя массива является ук.-констаной, равной адресу начала массива. Это позволяет осуществить другой способ доступа к элементам массива – с помощью механизма указателей. Используя операцию косвенной адресации (*) , можно получить доступ любому элементу массива.

Int a[5]; a[2]~*(a+2) a тождественно &a[0]

a[i]~a(a+i) a+i тождественно &a[i]

Указатель это пер-я, которая сама хранится в памяти, как и любая другая переменная, занимая либо 2, либо 4 байта.-> указатель на указатель: int **p;

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

Имя двумерного массива является указателем-констант на массив указателей-констант. Элементами такого массива являются указатели-константы на начало каждой иэ строк двумерного массива. Пример: int [3] [3] указателями-константами на нулевую, 1, 2 строки будут m[0], m[1],m[2], следующие выражения будут тождественными:

m[0] и &m[0] [0]

m[2] и &m[2] [0]

m и &m[0]

m+2 и &m[2]

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

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

Void* malloc (unsigned s); - выделяет память. Функция возвращает указатель на начало области дин-ой памяти длиной в s байт. В куче выделяется запрошенное число байт. Для универсальности тип возвращаемого функцией значения есть void*. Указатель такого типа перед использованием надо преобразовать к указателю другого типа спомощью операции явного приведения.

Int * x; x=(int*)malloc(1024);

Void* calloc (unsigned n, unsigned m);- выделяет память и инициализирует ее нулевыми значениями. Ф-я возвращает ук.на начало области обнуленной дин-ой памяти для размещения n элементов по m байт каждый.

Void* realloc (void* bl, unsigned new-bl); - распределяет память. Ф-я изменяет размер блока ранее выделенной дин-ой памяти до размера new-bl байт. Bl – адрес начала изменяемого блока.

Void free (void* bl); - освобождает ранее выделенный участок памяти. Bl – первого байта освобождаемого блока памяти. Ф-я не имеет возвращаемого значения.

67 Динамические информационные структуры.

Динамические информационные структуры используются для постоянно изменяющихся данных. Например: добавлять элементы или удалять.

Динамические информационные структуры: списки (разных видов: линейные, кольцевые, односвязные, двусвязные), стеки, очереди, деревья и др.

Список- структура данных, в которой каждый элемент сод-ит инф-ию о месте размещения другого (других) связанных элементов.

Линейный список- такой список, к-ый имеет начало и конец. Первый элемент – голова списка.

Кольцевой список- не имеет ни начала, ни конца, т.е. посл.элемент содержит информацию о первом.

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

Двусвязный список – такой список, когда каждый элемент содержит информацию о двух соседних элементах (предыдущем и последующем).

Схема линейного односвязного списка:

Head(внешний указатель)-------->информационные поля(next)-----информационные поля(next)--инф.поля(NULL).

Next – содержит информацию о том, где размещен след.элемент.

NULL – нулевой адрес

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

Если список не содержит ни одного звена, то такой список называется пустым. Для него head=NULL.

Реализация линейного односвязного списка средствами языка СИ:

struct Student

{

char name[50];

int number;

};

Struct Cell

{

Struct Student info;

Struct Cell*next; //адрес след.звена

};

//предположим, что список уже существует, требуется напечатать значения его элементов. Определим функцию:

Void print_list(struct Cell*head)

{

Struct Cell*current;

If(head==NULL)

{

Puts(“Список пуст”);

Return;

}

Current=head;

While(current!=NULL)

{

Printf(“%20s%d”, current->info.name, current->info.number);

Current=current->next;

}

}

Для работы с односвязным списком требуются следующие операции: добавление элемента в список; удаление элемента из списка; освобождение памяти, занятой элементом списка. Кроме этого, могут реализовываться и другие операции, например: вывод всех элементов списка; вывод заданного элемента списка; определение того, содержится ли в списке элемент. Число операций, определяемых для двусвязного списка, намного больше и может включать: Часто списки строят по иерархическому принципу. В этом случае элементы списка могут иметь внутреннюю структуру, организованную также по принципу списка (основной список оказывается состоящим из подсписков). Таким образом, сцепление элементов списка с помощью ссылок и наличие информационной части в каждом звене списка позволяют создать достаточно сложные структуры, пригодные для разных приложений. Реализация линейного односвязного списка средствами языка Си Линейный односвязный список представляет собой динамическую структуру, когда каждый элемент, кроме информационных полей, включает только указатель на следующий элемент списка. Доступ ко всему списку осуществляется через внешний указатель, который указывает на первый элемент списка. Под внешним указателем понимают тот, который не содержится внутри никакого элемента. Поле следующего адреса последнего элемента содержит нулевое значение , что означает конец списка.

Список, не содержащий элементов, называется пустым, или Поле следующего адреса последнего элемента содержит Поле следующего адреса последнего элемента содержит нулевое значение (Null ), что означает конец списка.

Элемент списка представляется в виде структурной переменной, которая в качестве одного из своих полей содержит указатель на следующий элемент списка (указатель на объект того же типа, что и сама структура), а также любое число информационных полей. Например:

struct <имя структурного типа> (

<информационные поля (данные)>

struct <имя структурного типа> ~ <указатель>;

Рассмотрим реализацию линейного односвязного списка в динамической памяти. Пусть объявлен шаблон:

struct Student {

char name{80); //ФИО студента

int number; //Номер зачетной книжки

Тогда элемент списка может иметь следующую структуру:

struct Cell (

struct Student~ next; //Указатель на следующий элемент

списка

struct Student info; //Поле данных )'

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

/» Функция формирует в динамической памяти структуру по шаблону Cell, запрашивая данные для информационных полей с клавиатуры, возвращает указатель на созданную структуру. »'/

struct Cell* cell new()

{

struct Cell new;

new=(struct Cell~)malloc(sizeof(struct Cell));

puts("Vvedite F.I.о studenta");

scanf("%s",& new->info.namе);

puts("Vvedite nomer zachetki");

'scanf("%d",а new->info пот)>ег)р

new->next=NULL;

return new;}

/* Функция добавляет новое звено (структуру new) к концу

списка head. Возвращает начало полученного списка.;I

struct Cell*add to end(struct Cell»head,struct Cell» new)

(

struct Cell»last head;

last head=last cell(head);

last head->next= new;

last head= new;

return head;

/ Функция возвращает количество звеньев в списке head.

int count cell(struct Cell*head)

(

int count=0;

struct Cell+current=NULL;

if(head =NULL)

return 0;

current=head;

while(current! NULL)

(

count++;

current=current->next;

)

return count;

// Функция удаляет первое звено списка head.

struct Cell*delete first cell(struct Cell head)

(

if(head==NULL )) head->next=-NULL)

return NULL;

struct Се11*весоп»);

second=head->next;

free(head);

return second;

//Функция возвращает указатель на последнее звено списка.

struct Cell*last cell(struct Cell'head)

(

struct Се11 сиггеп1;

if(head =NULL)

return NULL;

if(head->next~=NULL)

return head;

current/cad;

while(current->next!=NULL)

current=current->next;

return current;

)

Стек - дин-ая структура данных, в кот-ой добавление новых элементов осуществляется с конца. Для реализации стека необходимы 3 операции:

Pop()-извлечение элементов стека

Push()- помещение элементов в стек

Peek()- просмотреть содержимое.

struct Cell { struct Inf info; struct Cell * next;};

Функция push – добавляет элемент new_elem в стек с вершиной top и возвращает новую вершину стека.

Struct Cell* push (struct Cell* top, struct Cell* new_elem)

{//заполняем поле нового элемента new_elem, как ук. на след.

New_elem ->next=top;

//делаем новую вершину

Top=new_elem;

Return top; };

Pop – освобождает память, занятую этим звеном и возвращает указатель на новую вершину стека.

Struct Cell* pop (struct Cell* top)

{if(top==NULL)

Return top;

Else{

New_elem ->next=top;

//делаем новую вершину

Top= top ->next; Tmp=top; free(tmp);

Return top; } };

Очереди – это динамическая структура; представляющ-я собой набор данных, в которой добавление осущ-ся с 1-го конца, а извлечение с другого конца.

м68. Классы и объекты в ООП

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

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

Определение класса

Рассмотрим для примера концепцию "строка символов". Реализуем, например, вывод строки на экран в заданной позиции. Для этого создадим новый тип String. Набор данных, описывающих объект типа String, должен состоять, как минимум, из собственно символьной строки и двух переменных, определяющих позицию для размещения строки на экране. Определим основные операции: задание строки, установка координат строки, а также вывод строки в указанное место экрана. Простейшее определение класса String может выглядеть так:

class String

{

private:

char str[80];

int row, col;

public:

void setStr (char *);

void setCoord (int, int) ;

void printStr ( int = 0, int = 0) ;

};

Определение класса включает две части: заголовок класса, состоящий из ключевого слова class и имени класса; тело класса., заключенное в фигурные скобки и заканчивающееся точкой с запятой. Имя типа (у нас это String), которое следует за ключевым словом class, будет представлять новый тип, введенный пользователем. Это имя затем может быть использовано для объявления переменных (объектов) данного типа: String s1;

Тело класса содержит определение членов класса. Члены класса включают данные (в нашем примере саму строку и координаты) и функции - операции, которые могут быть выполнены над объектами типа String (задание строки, установка координат строки, печать строки).

Объявление данных-членов класса аналогично объявлению переменных, за исключением того, что не разрешается явная инициализация, Как и при объявлении переменных, данные-члены класса одного типа можно объявить одним оператором объявления: int max_len, top;

Рекомендуется делать объявления членов класса в порядке возрастания их размера. Это, как правило, дает оптимальное выравнивание. Функции, объявленные в теле класса, предназначены для выполнения действий над объектом класса. Функции-члены класса отличаются от обычных функций прежде всего тем, что они имеют полный привилегированный доступ ко всем данным-членам класса.

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

Доступ к членам класса

В примере описания класса String использованы ключевые слова private и public. Кроме этих слов, может быть также использовано ключевое слово protected. Эти ключевые слова называются метками, они управляют доступом к членам класса и делят тело класса части, различающиеся по уровню доступа, - "личную" (private или protected) и "общую" (public). Если члены класса объявлены после ключевого слова public, то они считаются общими, т.е. открытыми для доступа из любой точки программы в области видимости. Ключевые слова private и protected говорят о том, что следующие за ними члены класса доступны только для функций-членов класса (либо для привилегированных функций).

Обычно личная часть содержит данные, а общая — объявления (или определения) функций, но каких-либо ограничений на это не накладывается. Тем не менее, хорошим стилем программирования считается объявление данных-членов класса в части private, а функций-членов класса - в части public. Это обеспечивает следующие преимущества:

• при необходимости корректировки данных-членов класса в определении класса изменения надо проводить только в функциях, являющихся членами этого класса;

• обеспечена защита данных-членов класса от случайных изменений и несанкционированного доступа.

Если при определении класса ключевые слова private, public, protected опущены, то подразумевается доступ private (все поля класса считаются личными). Поэтому приведенное выше определение класса String эквивалентно следующему:

class String {

char str [80];

int row, col;

public:

void setStr (char *);

void setCoord (int, int);

void printStr ( int = 0, int = 0) ;

};

Определение функций-членов класса

Объявления функций-членов класса должны быть обязательно расположены в теле класса. А вот определение любой функции-члена класса может быть расположено как внутри, так и вне тела класса (встроенная либо внешняя функция).

При определении функции-члена класса вне описания класса (внешняя функция) имя этой функции в заголовке должно быть уточнено именем класса с помощью операции :: (она называется операцией разрешения области видимости) следующим образом: <имя класса> :: <имя функции>

Например, определение функции вне тела класса может выглядеть так:

void String :: setCoord (int x, int y)

{

row = x; col = y;

}

Если же определение функции-члена класса расположено в теле класса (встроенная функция), то внешне такое определение ничем не отличается от определения обычной функции:

class String {

char str[80];

int row, col;

public:

void setStr (char *);

void setCoord (int x, int y};

{

row = x; col = y;

}

void printStr ( int = 0, int = 0); };

Объекты класса

Определение класса не влечет за собой выделение какой-либо памяти. Память выделяется по мере объявления объектов класса. Так, объявление

String s1, s2;

вводит в программу переменные s1 и s2 класса String. Каждая переменная класса - это объект (или экземпляр класса). По этому объявлению распределится память для двух объектов: s1, s2. Под каждый объект выделится память, необходимая для хранения данных-членов класса String. Коды функций-членов класса хранятся отдельно от данных-членов класса, а именно, в сегменте кода программы. При этом хранится всего одна копия каждой функции-члена класса, хотя объектов в программе может быть несколько.

Надо различать понятия объекта и класса: класс - это тип, а объект - это переменная этого типа.

Важной особенностью класса является то, что функции-члены класса (за исключением т.н. статических функций класса) могут вызываться только после того, как в программе созданы объекты класса; более того, они вызываются только для данного конкретного объекта, и выполняемые ими действия никак не могут влиять на состояние других объектов этого типа:

setCoord (10, 20); //Такой вызов ошибочен, если //только в программе не определена //глобальная функция с таким же именем

String str;

str.setCoord (10, 20); //Установка координат для

//объекта str

Указатель this

Итак, функции-члены класса прикреплены к классу. Нельзя вызвать такую функцию (если она не является статической), не связывая ее с объектом этого класса.

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

Такое соответствие реализуется через использование т.н. неявного указателя this. Каждый раз при вызове нестатической функции-члена класса ей передаётся неявный аргумент, который имеет имя this и содержит адрес того объекта, для которого вызвана функция. Таким образом, указатель this всегда имеет тип <имя класса> *

Имя this является служебным словом языка, поэтому явно объявлять его не нужно (и невозможно).

Внутри тела функции-члена класса можно явно использовать этот указатель. Например, для класса String определение функции set Coord

void String :: setCoord (int x, int y) {

row = x;

col = y; } могло бы выглядеть так:

void String :: setCoord (int x, int y)

{

this -> row = x; this -> col = y; , } "

Однако, в этом примере нет никакой необходимости явного использования указателя this, так как данные конкретного объекта всегда доступны функции-члену класса с помощью имён данных класса (просто row или col). Явное использование указателя this необходимо в тех функциях, которые непосредственно работают с указателем на объект (например, при включении объекта класса в связный список).

Привилегированные функции

При написании программ могут встретиться ситуации, когда желательно иметь доступ к личным данным класса, минуя функции-члены этого класса. В частности, бывают случаи, когда необходимо выполнить некоторые действия, используя личные данные разных классов, то есть иметь одновременный доступ к личным данным нескольких классов. Наиболее распространенной является ситуация, когда функции-члены одного класса должны иметь доступ к личным членам другого класса. Язык предоставляет для некоторых функций (как обычных, так и являющихся членами какого-либо класса X) возможность получения доступа к личным членам класса Y. Такая функция называется привилегированной в классе Y. Для объявления привилегированной функции используется служебное слово friend.

Например:

class Y {//...

friend void func ();

// . . . };

Достаточно распространенной является ситуация, когда все функции-члены одного класса X являются привилегированными в другом классе Y. На этот случай предусмотрена упрощенная форма записи:

class Y {

friend X; };

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

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

class Cl ;

{

// f_func - не личный член класса, несмотря // на его объявление в личной части класса: friend void f_func (Cl*, int) ; int numb; //Личный член класса Cl public:

void m_func (int); };

//Обратите внимание на способ доступа к личному члену //класса numb из функции-члена класса m_func и из //привилегированной в этом классе функции f_func:

void f_func (Cl* cptr, int i) {

cptr -> numb = i;//Необходим явный указатель на

//объект }

void Cl::m_func (int i)

{ . . "'" ' ';,

numb = i;

>

void main (}

{

Cl obj;

//Сравните способы вызова и аргументы функций:

f_func(&obj, 10); obj.m_func (10); }

Указание на то, что функция является привилегированной, не транзитивно: если функции класса X являются привилегированными в классе Y, а функции класса Y - в классе Z, то это не означает, что функции класса X при-вилегированы в Z.

Использование спецификатора класса памяти static при объявлении членов класса (статические члены классов)

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

Статические данные-члены класса

Каждый объект класса имеет свою копию данных - членов класса. Иногда необходимо, чтобы все объекты одного класса имели доступ к какой-то общей переменной, а не к копии переменной в каждом объекте. Эта переменная может быть флажком условия, указателем на программу обработки ошибок для класса и т.д. Другими словами, в статических переменных удобно хранить общие характеристики класса.

Чтобы данное-член класса разделялось всеми объектами данного класса и хранилось в одном месте, его надо объявить со спецификатором класса памяти static.

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

Доступ к статическому члену класса выполняется, как и к любому другому члену класса; на статические члены класса распространяются правила видимости private, public и protected. Поэтому, если статический член объявлен, например, в личной части класса, получить доступ к нему можно лишь с помощью функций-членов этого класса (или привилегированных функций).

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

Статические функции-члены класса

Использование спецификатора класса памяти static применительно к именам функций имеет несколько другой смысл, чем его использование применительно к данным. В самом деле, только что рассмотренная интерпретация слова static не имеет смысла применительно к функциям-членам класса: для любой такой функции хранится только одна копия ее кода, которая используется при работе с различными объектами данного

класса.

Особенностью статических функций-членов класса является следующее:

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