Использование указателей на структуры
Можно объявить указатель на структуру:
// определение указателя на тип 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;