Студопедия.Орг Главная | Случайная страница | Контакты | Мы поможем в написании вашей работы!  
 

Структуры и объединения



В языках Си/Си++ понятие структуры аналогично понятию записи (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 | Нарушение авторского права страницы | Мы поможем в написании вашей работы!



studopedia.org - Студопедия.Орг - 2014-2024 год. Студопедия не является автором материалов, которые размещены. Но предоставляет возможность бесплатного использования (0.029 с)...