Использование указателей на структуры

Можно объявить указатель на структуру:

// определение указателя на тип Worker

Worker *pw;

а затем присвоить ему адрес существующей переменной указанного типа:

pw = &worker; // worker – переменная типа Worker

Объявление и инициализацию указателя, как обычно, можно совместить:

Worker *pw = &worker;

Для доступа к элементам (полям) структуры через указатель используется операция -> (“стрелка”, селектор):

pw->salary = 150000;

1.2.2.3. Об операциях . и –>

Операция –> является кратким способом записи доступа к значению структуры по ее адресу. Другой, более подробный способ записи предыдущего оператора:

(*pw).salary = 150000;

Операции . и –>, наряду с операцией индексирования [], имеют наивысший приоритет среди всех операций (выше, чем унарные).

Поэтому, например, код

Worker *pw = staff;

++pw->code;

увеличит значение переменной code начальной (с нулевым индексом) структуры массива структур staff, а не значение указателя pw.

Динамические структуры и массивы структур

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

Worker *pw = new Worker; // выделение памяти для структуры

pw->age=28;

pw->code=3983;

Worker *pwd = new Worker[k]; // выделение памяти для массива из k структур

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

delete pw;

delete [] pwd;

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

pwd[2].age=28;

(pwd+2)->code=3983;

(*(pwd+2)).salary=35000;

(скобки необходимы, так как приоритет операций . и –> выше, чем приоритет операции *).

Легко догадаться, что способы доступа к полям элементов статического массива структур те же, поскольку массив в C++ реализован как указатель на его начало:

Worker staff[100];

...

int nAge = staff[1].age;

int iCode = (staff+2)->code;

int iAge = (*(staff+2)).age;

Динамический массив структур можно реализовать как массив указателей.

Комбинируя структуры и массивы можно строить достаточно сложные, универсальные и гибкие структуры данных.

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

Классы

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

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

Введение в ООП

Применение классов лежит в основе объектно-ориентированного программирования (ООП) – программирования с использованием объектов.

Три кита ООП – инкапсуляция, наследование, полиморфизм.

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

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

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

2. Язык C++: ввод-вывод

Известно, что операции ввода-вывода имеют сильную аппаратно-операционную зависимость. По этой причине в C/C++ средства ввода-вывода отделены от языка и вынесены в отдельные библиотеки. В языке C, предшественнике C++, операции ввода-вывода были реали­зованы с помощью набора стандартных функций библиотеки stdio. В C++ для ввода или вывода данных используется набор стандартных классов, кото­рые объединены в объектно-ориентированной библиотеке iostream.

Поскольку C++ в основном совместим с C, в нем доступны стандартные функции ввода-вывода языка C.

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

Потоки

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

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

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

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

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

Для поддержки потоков библиотека C++ содержит иерархию классов, построенную на основе двух базовых классов – ios и streambuf. Класс ios содержит общие для ввода и вывода поля и методы, класс streambuf обеспечивает буферизацию потоков и их взаимодействие с физическими устройствами. От этих классов наследуется класс istream для входных потоков и ostream – для выходных. Два последних класса являются базовыми для класса iostream, реализующего двунаправленные потоки. Ниже в иерархии классов располагаются классы файловых (ifstream, ofstream, fstream) и строковых потоков.

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

Класс istream реализует поток ввода, класс ostream – поток вывода. Эти классы определены в файле заголовков iostream.h. Библиотека потоков ввода-вывода определяет четыре глобальных системных объекта: cin, cout, cerr и clog. При этом cin представляет стандартный входной поток, cout, cerr и clog – стандартный выходной поток. Объекты cout, cerr и clog принадлежат к классу ostream, объект cin – к классу istream. По умолчанию стандартный входной поток связан с клавиатурой, стандартный выходной поток – с экраном. Наличие нескольких объектов для вывода информации обеспечивает возможность разделять обычный вывод и, скажем, сообщения об ошибках (cerr и clog предназначены именно для этого).

Разница между cout и cerr существенна в операционных системах типа Unix – они используют разные дескрипторы для вывода. В других системах они существуют в основном для совместимости.

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

2.3. Операции >> и <<

Операция >> для класса istream и операция << для класса ostream определены для всех встроенных типов языка C++ и для указателей на стро­ку символов (char*). Эти операции – операции побитового сдвига, переопределенные ("перегруженные") для входных и выходных потоков. Перегрузка операций, реализованная в C++, позволяет придать операциям дополнительный смысл и расширить область их действия. Если мы хотим использовать такую же запись для ввода и вывода других типов данных, определенных в программе, для них нужно определить ("перегрузить") эти операции.

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

Если в качестве второго операнда операции >> используется переменная числового типа (int, long, short, double, float), введенная символьная последовательность преобразуется к требуемому числовому типу (естественно, для корректного выполнения данной операции вводимая последовательность символов должна представлять собой запись числа соответствующего типа). При вводе чисел вещественных типов (double, float) для разделения целой и дробной части следует использовать точку, для ввода чисел с нулевым значением дробной части можно использовать соответствующие целые числа. Правым ограничителем при вводе числового значения является первый разделитель или первый недопустимый символ.

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

Операция << возвращает в качестве результата ссылку на поток вывода. Это позволяет упрощать запись выражений, соединяющих несколько операций вывода, например:

cout << x << ' ' << y;

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

((cout << x) << ' ') << y;

Аналогично реализована операция ввода <<, позволяющая соединять несколько операций ввода:

cin >> x >> y;

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

Файловые потоки

В библиотеке C++ для ввода-вывода файлов существуют классы ifstream (для файловых входных потоков), ofstream (для файловых выходных потоков) и fstream (для файловых двунаправленных потоков). Эти классы – наследники, соответственно, классов istream, ostream и iostream, которые, в свою очередь, наследуют свойства класса ios. Это означает, что классы файлового ввода-вывода наследуют соответствующие операции и методы ввода-вывода, поля и способы фор­матирования и т.д. Классы файлового ввода-вывода определены в файле заголовков fstream.h.

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

- создание файлового потока;

- открытие потока и связывание его с файлом;

- передача данных (ввод/вывод);

- закрытие файла;

- уничтожение потока.

В программе файловый поток объявляется как объект одного из классов файлового ввода-вывода (ifstream, ofstream или fstream):

ifstream f1; // создание потока f1 для файлового ввода

ofstream f2; // создание потока f2 для файлового вывода

Как и любые другие объекты, потоки могут создаваться статически (как f1 и f2 в предыдущем примере) или динамически (с помощью операции new):

ofstream *f3 = new ofstream; // динамическое создание потока f3

// для файлового вывода

Классы файлового ввода-вывода содержат специальные операции и методы работы с файлами – открытия, проверки состояния, работы с текущей позицией, закрытия файлов, а также ввода-вывода и форматирования информации. Большинство из них унаследовано от классов-предков (ios, istream, ostream). Связывание потока с конкретным файлом осуществляется одним из двух возможных способов – либо при создании потока:

ofstream f4("d:\\test4.txt"); // создание потока f4 для файлового вывода,

// открытие и связывание его с файлом

либо при открытии уже созданного потока с помощью метода open:

f1.open("d:\\test5.txt"); // открытие ранее созданного потока f1

// и связывание его с файлом

И в том и другом случае указывается имя файла и, если необходимо, режимы его открытия, описанные в разделе 11.4.3.

Передача данных выполняется операциями >> и << или методами ввода-вывода, описанными в разделе 11.4.5. Закрытие файла происходит либо посредством выполнения метода close:

f4.close();

f3->close();

либо автоматически при уничтожении потока (при завершении программы или при удалении динамически созданного потока с помощью delete):

delete f3;