![]() |
Главная Случайная страница Контакты | Мы поможем в написании вашей работы! | |
|
В языках Си/Си++ понятие структуры аналогично понятию записи (record) в Паскале. Это структурированный тип данных, представляющий собой поименованную совокупность разнотипных элементов. Тип структура обычно используется при разработке информационных систем, баз данных.
Правила использования структур обсудим на примере.Сведения о выплате студентам стипендии требуется организовать в виде, показанном на рисунке.
Рис – 3
Элементы такой структуры (фамилия, курс, группа, стипендия) называются полями. Каждому полю должно быть поставлено в соответствие имя и тип.
Формат описания структурного типа следующий:
struct имя_типа
{определения_элементов};
В конце обязательно ставится точка с запятой (это оператор). Для рассмотренного примера определение соответствующего структурного типа может быть следующим:
struct student {char fam[30];
int kurs;
char grup [ 3];
float stip;
};
После этого student становится именем структурного типа, который может быть назначен некоторым переменным. В соответствие со стандартом Си это нужно делать так:
struct student stud1, stud2;
Правила Си++ разрешают в этом случае служебное слово struct опускать и писать
Здесь stud1 и stud2 — переменные структурного типа. Допускаются и другие варианты описания структурных переменных. Можно вообще не задавать имя типа, а описывать сразу
Переменные:
struct {char fam[30];
int kurs;
сhar grup[ 3 ];
float stip;
} stud1, stud2, *pst;
В этом примере кроме двух переменных структурного типа объявлен указатель pst на такую структуру. В данном описании можно было сохранить имя структурного типа student.
Обращение к элементам (полям) структурной величины производится с помощью уточненного имени следующего формата:
имя структуры.имя элемента
Снова все похоже на Паскаль. Примеры уточненных имен для описанных выше переменных:
stud1. fam; stud1.stip
Значения элементов структуры могут определяться вводом, присваиванием, инициализацией. Пример инициализации в описании:
student stud1={ "Кротов", 3, "Ф32", 350};
Пусть в программе определен указатель на структуру
student *pst, stud1;
Тогда после выполнения оператора присваивания
рst=&stud1;
к каждому элементу структурной переменной stud1 можно обращаться тремя способами. Например, для поля fam
stud1.fam или (*pst).fam или pst ->fam
В последнем варианте используется знак операции доступа к элементу структуры: —>. Аналогично можно обращаться и к другим элементам этой переменной:
рst->FIO, рst->grup, pst->stip.
Поля структуры могут сами иметь структурный тип. Такие величины представляют многоуровневые деревья.
Допускается использование массивов структур. Например, сведения о 100 студентах могут храниться в массиве, описанном следующим образом:
student stud [100];
Тогда сведения об отдельных студентах будут обозначаться, например, так: stud[1].fam, stud[5].kurs и т.п. Если нужно взять первую букву фамилии 25-го студента, то следует писать:
stud[25].fam[0].
Элемент структуры типа поля битов. Использование структуры в программе на Си позволяет работать с отдельными битами, т. е. с разрядами двоичного кода. Для этого используются элементы структуры типа поля битов. Формат структуры, содержащий поля битов, следующий:
struct имя_структуры
{ тип имя_поля_1: длина_в_битах;
тип имя_поля_2: длина_в_битах;
тип имя поля N: длина_в_битах;
};
В качестве типа полей могут использоваться спецификаторы int, unsigned, signed. Минимальной величиной такого типа может быть структура, состоящая всего из одного битового поля.
Объединение. Объединение — это еще один структурированный тип данных. Объединение похоже на структуру и в своем описании отличается от структуры тем, что вместо ключевого слова struct используется слово union.
union имя_типа
{определения_элементов};
Объединение отличается от структуры способом организации во внутренней памяти. Все элементы объединения в памяти начинаются с одного байта.
Пусть в программе описана структура:
struct s
{ int i;
char ch;
long int L;
};
Расположение ее элементов в памяти будет следующим:
байт | байт | байт | байт | байт | байт | байт |
i | сh | L |
Элементы структуры занимают последовательные ячейки памяти с размером, соответствующим типу. Общий размер структуры равен сумме длин полей.
А теперь рассмотрим объединение со следующим описанием:
union s
{ int i;
char ch;
long int L;
};
Величина с таким типом в памяти будет расположена следующим образом:
байт | байт | байт | байт | |
ch | ||||
i | ||||
L | ||||
Поля объединения накладываются друг на друга. Общий объем занимаемой памяти равен размеру самого большого поля.
Изменение значения любого поля объединения меняет значения других полей.
13 ПОТОКОВЫЙ ВВОД-ВЫВОД В СТАНДАРТЕ СИ ++
Под вводом-выводом в программировании понимается процесс обмена информацией между оперативной памятью и внешними устройствами: клавиатурой, дисплеем, магнитными накопителями и т. п. Ввод — это занесение информации с внешних устройств в оперативную память, а вывод — вынос информации из оперативной памяти на внешние устройства. Такие устройства, как дисплей и принтер, предназначены только для вывода; клавиатура — устройство ввода. Магнитные накопители (диски, ленты) используются как для ввода, так и для вывода.
Основным понятием, связанным с информацией на внешних устройствах ЭВМ, является понятие файла. Всякая операция ввода-вывода трактуется как операция обмена с файлами: ввод — это чтение из файла в оперативную память; вывод — запись информации из оперативной памяти в файл. Поэтому вопрос об организации в языке программирования ввода-вывода сводится к вопросу об организации работы с файлами.
Вспомним, что в Паскале мы использовали представления о внутреннем и внешнем файле. Внутренний файл — это переменная
файлового типа, являющаяся структурированной величиной. Элементы файловой переменной могут иметь разный тип и, соответственно, разную длину и форму внутреннего представления. Внутренний файл связывается с внешним (физическим) файлом с помощью стандартной процедуры Assign. Один элемент файловой переменной становится отдельной записью во внешнем файле и может быть прочитан или записан с помощью одной команды. Попытка записать в файл или прочитать из него величину, не совпадающую по типу с типом элементов файла, приводит к
ошибке.
Аналогом понятия внутреннего файла в языках Си/Си++ является понятие потока. Отличие от файловой переменной Паскаля состоит в том, что потоку в Си не ставится в соответствие тип. Поток — это байтовая последовательность, передаваемая в процессе ввода-вывода.
Поток должен быть связан с каким-либо внешним устройством или файлом на диске. В терминологии Си это звучит так: поток должен быть направлен на какое-то устройство или файл.
Основные отличия файлов в Си состоят в следующем: здесь отсутствует понятие типа файла и, следовательно, фиксированной структуры записи файла. Любой файл рассматривается как байтовая последовательность:
Байт0 | Байт1 | Байт2 | EOF |
Стрелочкой обозначен указатель файла, определяющий текущий байт файла. EOF является стандартной константой — признаком конца файла.
Существуют стандартные потоки и потоки, объявляемые в программе. Последние обычно связываются с файлами на диске, создаваемыми программистом. Стандартные потоки назначаются и открываются системой автоматически. С началом работы любой программы открываются 5 стандартных потоков, из которых основными являются следующие:
• stdin — поток стандартного ввода (обычно связан с клавиатурой);
• stdout — поток стандартного вывода (обычно связан с дисплеем);
• stderr — вывод сообщений об ошибках (связан с дисплеем).
Кроме этого, открывается поток для стандартной печати и дополнительный поток для последовательного порта.
Работая ранее с программами на Си, используя функции ввода с клавиатуры и вывода на экран, мы уже неявно имели дело с первыми двумя потоками. А сообщения об ошибках, которые си-стема выводила на экран, относились к третьему стандартному потоку. Поток для работы с дисковым файлом должен быть открыт в программе.
Работа с файлами на диске. Работа с дисковым файлом начинается с объявления указателя на поток. Формат такого объявления:
FILE *имя_указателя;
Например:
FILE *fр;
Слово FILE является стандартным именем структурного типа, объявленного в заголовочном файле stdio.h. В структуре FILE содержится информация, с помощью которой ведется работа с потоком, в частности: указатель на буфер, указатель (индикатор) текущей позиции в потоке и т.д.
Следующий шаг — открытие потока, которое производится с помощью стандартной функции fopen (). Эта функция возвращает конкретное значение для указателя на поток и поэтому ее значение присваивается объявленному ранее указателю. Соответствующий оператор имеет формат:
имя_указателя=:fореn (имя_файла, режим_открытия);
Параметры функции fореn () являются строками, которые могут быть как константами, так и указателями на символьные массивы. Например: fр=fореn("test.dat","r");
Здесь test.dat — это имя физического файла в текущем каталоге диска, с которым теперь будет связан поток с указателем fр. Параметр режима r означает, что файл открыт для чтения. Что касается терминологии, то допустимо употреблять как выражение «открытие потока», так и выражение «открытие файла».
Существуют следующие режимы открытия потока и соответствующие им параметры:
Параметр Режим
r открыть для чтения
w создать для записи
а открыть для добавления
r+ открыть для чтения и записи
w+ создать для чтения и записи
а+ открыть для добавления или создать для чтения
и записи
Как уже отмечалось при изучении Паскаля, надо хорошо понимать, что открытие уже существующего файла для записи ведет к потере прежней информации в нем. Если такой файл еще не существовал, то он создастся. Открывать для чтения можно только существующий файл.
Поток может быть открыт либо для текстового, либо для двоичного (бинарного) режима обмена.
Смысл понятия остается прежним: это последовательность символов, которая делится на строки специальными кодами — возврат каретки (код 13) и перевод строки (код 10). Если файл открыт в текстовом режиме, то при чтении из такого файла комбинация символов «возврат каретки — перевод строки» преобразуется в один символ \n — переход к новой строке. При записи в файл осуществляется обратное преобразование.
При работе с двоичным файлом никаких преобразований символов не происходит, т. е. информация переносится без всяких изменений.
Указанные выше параметры режимов открывают текстовые файлы. Если требуется указать на двоичный файл, то к параметру добавляется буква b. Например: rb, или wb, или r+b. В некоторых компиляторах текстовый режим обмена обозначается буквой t, т.е. записывается а+t: или rt.
Если при открытии потока по какой-либо причине возникла ошибка, то функция fореn () возвращает значение константы NULL. Эта константа также определена в файле stdio.h. Ошибка может возникнуть из-за отсутствия открываемого файла на диске, нехватки места в динамической памяти и т. п. Поэтому желательно контролировать правильность прохождения процедуры открытия файла. Рекомендуется следующий способ открытия:
FILE *fр;
if (fр=fореn("test.dat", "r")==NULL
{puts("Не могу открыть файл\n");
return;
}
В случае ошибки программа завершит выполнение с закрытием всех ранее открытых файлов.
Закрытие потока (файла) осуществляет функция fclose(), прототип которой имеет вид:
Int fclose(FILE *fptr);
Здесь fptr обозначает формальное имя указателя на закрываемый поток. Функция возвращает ноль, если операция закрытия прошла успешно. Другая величина означает ошибку.
Запись символов в поток производится функцией putc() с прототипом
int put(int ch, FILE *fptr);
Если операция прошла успешно, то возвращается записанный символ. В случае ошибки возвращается константа EOF.
Считывание символа из потока, открытого для чтения, производится функцией gets() с прототипом
int gets(FILE *fptr);
Функция возвращает значение считываемого из файла символа. Если достигнут конец файла, то возвращается значение EOF. Заметим, что это происходит лишь в результате чтения кода EOF. Исторически сложилось так, что gets () возвращает значение типа int. То же можно сказать и про аргумент сh в описании функции puts (). Используется же в обоих случаях только младший байт. Поэтому обмен при обращении может происходить и с переменными типа char.
Запись и чтение целых чисел. Запись целых чисел в поток без преобразования их в символьную форму производится функцией putw () с прототипом
In putw(int, FILE *fptr);
Если операция прошла успешно, то возвращается записанное число. В случае ошибки возвращается константа EOF.
Считывание целого числа из потока, открытого для чтения, производится функцией getw() с прототипом
int getw(FILE *fptr);
Функция возвращает значение считываемого из файла числа. Если прочитан конец файла, то возвращается значение EOF.
Запись и чтение блоков данных. Специальные функции обмена с файлами имеются только для символьного и целого типов данных.
В общем случае используются функции чтения и записи блоков данных. С их помощью можно записывать в файл и читать из файла вещественные числа, массивы, строки, структуры. При этом, как и для ранее рассмотренных функций, сохраняется форма внутреннего представления данных.
Функция записи блока данных имеет прототип
Int fread (void *buf, int bytes, int n, FILE*fptr);
Здесь
buf — указатель на адрес данных, записываемых в файл;
bytes — длина в байтах одной единицы записи (блока данных);
n — число блоков, передаваемых в файл;
fptr — указатель на поток.
Если запись выполнилась благополучно, то функция возвращает число записанных блоков (значение п).
Функция чтения блока данных из файла имеет прототип
Int fwrite (void*buf, int bytes, int n, FILE*fptr);
Форматный обмен с файлами. С помощью функции форматного вывода можно формировать на диске текстовый файл с результатами вычислений, представленными в символьном виде. В дальнейшем этот файл может быть просмотрен на экране, распечатан на принтере, отредактирован с помощью текстового редактора. Использовавшаяся нами ранее функция printf() для организации вывода на экран является частным вариантом функции fprintf().Не будет ошибкой, если в программе вместо printf () написать fprintf (stdin,...).
14 ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ В СИ++
Основным отличием языка Си++ от Си является наличие в нем средств объектно-ориентированного программирования (ООП). Часто в литературе язык Си++ определяют именно как язык объектно-ориентированного программирования. Ранее в разд. 3.23 мы уже обсуждали основные понятия и приемы ООП на примере Турбо Паскаля. Для Си++ базовые понятия ООП, естественно, остаются теми же: это инкапсуляция, наследование и полиморфизм. Реализация ООП на Си++ несколько более гибкая, чем в Турбо Паскале. Существуют определенные терминологические отличия. Первое такое отличие заключается в следующем: вместо понятия «объектный тип данных», применяемого в Турбо Паскале, в Си++ используется понятие «класс».
Класс — это структурированный тип, включающий в себя в качестве элементов типизированные данные и функции, применяемые по отношению к этим данным. Таким образом, инкапсуляция (объединение параметров и методов) заложена в составе элементов класса: типизированные данные — это параметры, а методы реализованы через функции.
Тип «класс» устанавливается для объектов. Принято говорить:
однотипные объекты принадлежат одному классу.
Синтаксис объявления класса подобен синтаксису объявления структуры. Объявление начинается с ключевого слова сlаss, за которым следует имя класса. Основное отличие класса от структур состоит в том, что все члены класса по умолчанию считаются закрытыми и доступ к ним могут получить только функции — члены этого же класса. Однако режим доступа к элементам класса может
быть изменен путем его явного указания. Для этого перед элементами класса записывается соответствующий спецификатор доступа. Существуют три таких спецификатора:
• private (частный);
• public (общедоступный);
• protected (защищенный).
Режим доступа private обозначает, что соответствующий элемент может использоваться только функциями данного класса. Этот режим доступа устанавливается по умолчанию. Элементы с режимом доступа public доступны в других частях программы. О режиме protected будет сказано немного позже. Чаще всего режим доступа к данным (переменным) бывает private, а к функциям — public. Это отражено в приведенном выше формате объявления класса.
Наследование — второе фундаментальное понятие ООП. Механизм наследования позволяет формировать иерархии классов. Класс-наследник получает свойства класса-предка. Как и в Турбо Паскале, в классе-наследнике могут быть объявлены новые дополнительные элементы. Элементы-данные должны иметь имена, отличные от имен предка. Элементы-функции могут быть новыми относительно предка, но могут и повторять имена функций своих предков. Как и в Турбо Паскале, здесь действует принцип «снизу вверх» при обращении к функции: функция потомка перекрывает одноименную функцию своего предка.
Для того чтобы элементы-данные класса-предка были доступны функциям класса-потомка, этим элементам должен быть поставлен в соответствие режим доступа protected (защищенный).
Конструкторы и деструкторы. Смысл этих понятий аналогичен их смыслу в Турбо Паскале. Основное назначение конструктора —
инициализация элементов-данных объекта и выделение динамической памяти под данные. Конструктор срабатывает при выполнении оператора определения типа «класс для объекта». Деструктор освобождает выделенную конструктором память при удалении объекта.
Области памяти, занятые данными базовых типов, таких, как int float, double и т.п., выделяются и освобождаются системой автоматически и не нуждаются в помощи конструктора и деструктора. Именно поэтому в программах, рассмотренных в примерах 1 и 2, конструкторы и деструкторы не объявлялись (система все равно создает их автоматически).
Конструктор и деструктор объявляются как члены-функции класса. Имя конструктора совпадает с именем класса. Имя деструктора начинается с символа ~ (тильда), за которым следует имя класса.
Полиморфизм допускает использование функций с одним и тем же именем (а также операций) применительно к разным наборам аргументов и операндов, а также к разным их типам, в зависимости от контекста программы. В Си++ полиморфизм реализован через механизм перегрузки.
Внутри класса допускается существование нескольких функций с одинаковым именем, но различающимися типами результатов и наборами формальных параметров. При обслуживании обращения к такой функции компилятор выбирает подходящий вариант в зависимости от количества и типов аргументов.
Перегрузка операций. Полиморфизм в Си++ реализуется не только через механизм перегрузки функций, но и через перегрузку операций.
15 ФОРМАТИРОВАННЫЙ ВВОД-ВЫВОД В СИ++
Для организации ввода-вывода в Си++ можно использовать средства языка Си (conio.h). Однако в Си++ существует стандартная библиотека классов, ориентированная на организацию потокового ввода-вывода. Классы ввода-вывода образуют иерархию по принципу наследования. Базовым в этой иерархии является класс ios (исключение составляют лишь классы буферизированных потоков). В классе ios объединены базовые данные и методы для ввода-вывода. Прямыми потомками класса ios являются классы istream и ostream. Класс istream— это класс входных потоков; ostream — класс выходных потоков. Потомком этих двух классов является iostream — класс двунаправленных потоков ввода-вывода. С этим классом мы уже много раз имели дело, подключая его к программам с помощью головного файла iostream.h.
Объект cout принадлежит к классу ostream и представляет собой поток вывода, связанный с дисплеем. Объект cin принадлежит классу istream и является потоком ввода, связанным с клавиатурой. Оба эти объекта наследуются классом iostream.
Знак «обозначает перегруженную операцию вставки символов в поток вывода cout-, а» — знак операции извлечения из потока ввода cin.
Для организации форматированного потокового ввода-вывода в Си++ существуют два средства:
• применение функций-членов класса ios для управления флагами форматирования;
• применение функций-манипуляторов.
Управление флагами форматирования. Флаги форматирования — двоичные коды, управляющие форматом выводимых значений. В
заголовочном файле iostream.h определено следующее перечисление, задающее флаги форматирования:
enum{
scipws = 0х0001 отбрасывание пробелов
left = 0х0002 выравнивание по левому краю поля
right = 0х0004 выравнивание по правому краю поля
internal = 0х0008 заполнение пустых позиций
dec = 0х0010 выдача в десятичном виде
oct = 0х0020 выдача в восьмеричном виде
hex = 0х0040 выдача в шестнадцатеричном виде
showbase = 0х0080 выдача основания сист. счисления
showpoint = 0х0100 выдача позиции точки
uppercase = 0х0200 выдача в формате хх.хххх Ехх
showpos = 0х0400 выдача знака у положит. числа
scientific = 0х0800 выдача в форме с плавающ. точкой
fixed = 0х1000 выдача в форме с фиксир. точкой
unibuf = 0х2000 улучшенная выдача
stdio = 0х4000 освобождение потока
};
Фактически в этом списке содержатся имена констант, определяющие флаги соответствующих назначений. Коду формата соответствует целый тип long.
Изменить состояние флагов формата можно с помощью функции-члена класса ios, имеющей прототип
long setf (long flags)
Например, чтобы установить флаг showbase в активный режим (включить) применительно к стандартному потоку вывода cout, используется оператор
cout.setf(ios::showbase);
Для установки флагов можно использовать побитовые операции. Например: cout. setf (ios:: left | ios::hex);
В результате включатся одновременно флаги, управляющие выравниванием по левому краю и выводом целых значений в ше-стнадцатеричной системе.
Для выключения используется функция
unsetf(long flags);
Например, для отмены вывода основания системы счисления используется оператор:
cout.unsetf (ios:: showbase);
Вот еще некоторые функции-члены класса ios:
long flags (void) - возвращает текущее состояние флагов;
int width(int len) — возвращает текущую ширину поля вывода и устанавливает значение ширины, равное len;
char fill (char ch) —возвращает текущий символ заполнения и устанавливает новый символ заполнения ch;
int precision(int num) —возвращает текущее число деся-тичных знаков после точки и устанавливает значение этого параметра равным num.
Пример17: Следующая программа иллюстрирует применение рассмотренного способа управления форматным выводом.
#include <iostream.h>
void main (void)
{
long fl;
fl=cout. flags ();
cout«"Исходное состояние флагов: "«fl«"\n";
//Выведется целое число — код флагов, //установленных по умолчанию
cout.set(ios::shoupos);
cout.set: (ios::scientific);
cout«123«" "«1. 234567 8«"\n";
//Выведется:
//+123 +1.23456е+00
cout.set(ios::hex::showbase);
cout.unsetf (ios:: showpos);
cout.width (15);
cout.precision (10);
cout«123«" "«123.456«" "1. 2345678«"\n";
//Выведется:
//0х7В 1.23456е+02 1.2345б78е+00
cout<<"Новое состояние флагов: "<<cout. flags ()
<<"\n";
//Выведется:
//Новое состояние флагов:0х28С1
cout. flags (fl); //Возврат к исходному состоянию
cout:<<"После восстановления исходных флагов:\n";
cout:<<123<<" "<<123. 456<<1. 2345678<<"\n";
//Выведется:
//После восстановления исходных флагов:
//123 123.456 1.234567
}
Дата публикования: 2014-11-02; Прочитано: 921 | Нарушение авторского права страницы | Мы поможем в написании вашей работы!