Перегрузка оператора присваивания =
Назначение конструктора копирования и оператора присваивания почти эквивалентны – оба копируют один объект в другой. Однако конструктор копирования инициализирует новые объекты, тогда как оператор присваивания заменяет содержимое существующих объектов.
Разница между конструктором копирования и оператором присваивания:
- если новый объект должен быть создан до того, как может произойти копирование, используется конструктор копирования (это включает в себя передачу или возврат объектов по значению);
- если новый объект не нужно создавать до того, как может произойти копирование, используется оператор присваивания.
Пример:
Создан объект s1 и для него выделена динамическая память под массив. Затем так же отдельно создается объект s2. Но после выполнения инструкции s2 = s1; (т.к. копирование выполняется побитово) объекты s1 и s2 будут хранить адрес одного и того же массива по одному адресу. Потом включается деструктор, очищает память после объекта s2, пытается очистить память после объекта s1, а часть этой памяти (выделенная под массив) оказывается уже очищенной.
Можно использовать следующую конструкцию:
Здесь объект s2 еще не создан и для его конструирования сразу запускается конструктор копирования.
Использование перегрузки с помощью функции operator =:
В строке 25: если указатель не null, т.е. если вдруг указатель хранит адрес выделенной для массива памяти, то очистить ту память (чтоб не было утечки), здесь if вернет true.
В 49 строке не сработает конструктор копирования, потому что была описана перегрузка оператора присваивания – она и сработает.
Всё корректно работает:
Обычный оператор присваивания можно использовать и таким образом:
- int x = 1;
- int y = 0;
- int z = 2;
- z = y = x; // у всех будет значение 1
Но применительно к объектам такой синтаксис просто так не сработает:
Но если сделать возврат значения:
Тогда всё скомпилируется:
Перегрузка операторов сравнения
Сравнение объектов – это сравнение содержимого объектов.
Просто так сравнить объекты не получится:
Использование перегрузки с помощью функций operator == и operator !=
Пример:
В функции сравнивается содержимое объектов – значения всех свойств.
this – это cat1, а в oldCat передается ссылка на объект cat2. Функция перегрузки вызывается в 32 и 33 строках в момент использования == и !=.
По такому же принципу выполняется перегрузка операторов
- >
- <
- >=
- <=
Если у объектов есть массивы, то эти массивы должны сначала сравнить, одинаковое ли у них количество элементов, затем выполнить поэлементное сравнение.
Перегрузка арифметических операторов
Суммирование объектов – это суммирование содержимого объектов.
Просто так суммировать объекты не получится:
Перегрузка арифметических операторов работает по тому же принципу, что и перегрузка операторов сравнения:
По такому же принципу выполняется перегрузка операторов
- –
- *
- /
- %
Если для содержимого объектов память выделяется динамически, то необходимо при перегрузке предусмотреть очистку памяти.
Если в объектах есть массивы, то работа с ними должна выполняться поэлементно.
Перегрузка операторов инкремента и декремента (префиксной и постфиксной формы)
Просто так выполнить операцию инкремента (или декремента) для объекта не получится:
Префиксная форма:
Постфиксная форма:
Отличие от префиксной только в том, что по стандарту языка нужно добавить (бессмысленный, костыльный) параметр, чтобы компилятор мог отличить: префиксная это форма или постфиксная.
Перегрузка оператора индексирования []
Обычный способ обращения к свойству:
Без перегрузки через объект обратиться к элементу массива не получится:
Если в разделе public описана перегрузка оператора [], тогда можно через объект обратиться напрямую к элементу массива, который (массив) является свойством класса:
Из строки 19 индекс [1] передается в качестве параметра в функцию перегрузки и записывается в параметр index. Далее строка 9 возвращает значение по ссылке на этот элемент в строку 19. Если значение возвращать по ссылке, то с помощью перегрузки можно также и записывать новое значение.
Если убрать & из 7 строки, то будет возвращаться не ссылка на хранящееся в массиве значение (на число 20), а копия значения (копия числа 20):
Следовательно, изменения в массиве происходить не могут, и компилятор протестует.
Тем не менее просто вывести эту копию с помощью cout всё еще возможно.
Такой манёвр не удастся из-за строк 12 – 15:
Перегрузка оператора (). Функтор
Функтор (или функциональный объект) – это объект, использование которого возможно подобно вызову функции. В следующем примере использование объекта s уподоблено механизму вызова функции, т.е. s – это функтор:
Исключение – это такая ситуация, в результате которой генерируется ошибка, и выполнение программы прерывается.
Пример:
Обработка исключительных ситуаций – это механизм языка программирования, предназначенный для описания реакции программы на ошибки времени выполнения (например, такие ошибки, как попытка деления на ноль, выход за границы массива, истощение свободной памяти).
Механизм обработки исключений базируется на ключевых словах try (попытаться), throw (бросить) и catch (поймать). Функция пытается выполнить фрагмент кода; если в коде содержится ошибка, функция бросает (генерирует) сообщение об ошибке, которое должна поймать (перехватить) вызывающая функция.
Область применения исключений намного шире, чем обработка ошибок. Исключения предназначены для работы с исключительными ситуациями, которые вовсе не обязаны быть ошибочными. Они лишь должны быть исключительными по сравнению с обычным ходом выполнения программы. Настолько исключительными, чтобы не имело смысла каждый раз делать упреждающую проверку. Особенно удобно использовать исключения для передачи управления (а, возможно, и данных) сразу через несколько уровней в иерархии вызовов или вложенных друг в друга блоков кода. Как и в случае с оператором goto, исключения следует использовать только в случае крайней необходимости, потому что исключения усложняют задачу по предугадыванию путей выполнения программы, усложняют задачу по сопровождению кода, снижают быстродействие работы программы.
1. Выполнить по образцу.
Напишите следующий программный код и протестируйте работу программы:
Если num2 равен нулю, то бросить “Деление на ноль недопустимо” в параметр oshibka в 26 строке. В 28 строке вывести содержимое oshibka.
2. Выполнить по образцу.
Напишите следующий программный код и протестируйте работу программы:
Здесь тоже с помощью catch перехватывается ошибка и передается способ ее обработки. Оператор принимает параметр типа int, чтобы использовать его значение для вывода информации о том, в чем заключается ошибка.
Исключения могут быть выброшены в любом месте кода: для этого нужно прописать throw и указать, что именно нужно “бросить” в catch.
3. Выполнить по образцу.
Напишите следующий программный код и протестируйте работу программы:
Обратите внимание: для передачи информации об ошибке правильнее использовать универсальный класс exception.
При возникновении исключения (throw) C++ копирует сгенерированный объект в некоторое нейтральное место. После этого просматривается конец текущего блока try. Если блок try в данной функции не найден, управление передается вызывающей функции, где и осуществляется поиск обработчика. Если и здесь не найден блок try, поиск повторяется далее, вверх по стеку вызывающих функций. Этот процесс и называется разворачиванием стека. На каждом этапе разворачивания стека все объекты, которые выходя из области видимости, уничтожаются так же, как если бы функция выполнила команду return. Это оберегает программу от потери ресурсов и “праздно шатающихся” неуничтоженных объектов.
Когда необходимый блок try найден, программа ищет первый блок catch, который должен находиться сразу после закрывающей скобки блока try. Если тип сгенерированного объекта совпадает с типом аргумента, указанным в блоке catch, управление передается этому блоку; если же нет, проверяется следующий блок catch. Если в результате подходящий блок не найден, программа продолжает поиск уровнем выше, пока не будет обнаружен необходимый блок catch. Если искомый блок не обнаружен, программа аварийно завершается.
4. Выполнить по образцу.
Напишите следующий программный код и протестируйте работу программы:
В одной программе может быть несколько блоков try, catch, throw. При выполнении блока throw будет производиться поиск подходящего (по параметрам в скобках) блока catch (если такой блок не будет найден, то программа аварийно завершит свою работу). В примере сработает блок catch из 26 строки.
5. Выполнить по образцу.
Написать следующий программный код:
Протестируйте работу программы:
Потоки для работы с файлами создаются как объекты следующих классов:
- ofstream – класс для вывода (записи) данных в файл
- ifstream – класс для ввода (чтения) данных из файла
- fstream – класс для чтения и для записи данных (двунаправленный обмен)
Чтобы использовать эти классы, в текст программы необходимо включить дополнительный заголовочный файл .
1. Выполнить по образцу.
Метод open()
Написать следующий программный код:
Чтобы сохранить данные в файл, этот файл нужно сначала создать или открыть файл, чтобы дописать данные (т.е. если файл есть, то записать данные; если файла нет, то автоматически создать его и записать данные). Для всего этого используется метод open(), который является перегруженным и поэтому может выполнять различные действия в зависимости от переданных в него параметров.
Например, в качестве параметра можно передать имя файла или путь к файлу. Нажать F5 для запуска отладки.
Можно создать и использовать строковую переменную, в которую записать путь или имя файла:
Можно получить значение этой переменной от пользователя (через консоль):
2. Выполнить по образцу.
Метод is_open()
Написать следующий программный код:
Стандартный метод is_open() возвращает true, если удалось открыть файл, или возвращает false, если файл открыть не удалось.
При новом очередном запуске программы данные будут перезаписываться. Сначала было:
Потом программу переписали, снова запустили. Прежние данные не сохранились, а были перезаписаны:
Если в одной программе несколько записей (10 и 11 строки), то данные будут дописываться в файл:
Или можно так с тем же результатом:
3. Выполнить по образцу.
Метод close()
Написать следующий программный код:
После того как файл был создан/открыт и данные были записаны, необходимо закрыть файл (например, чтобы он был доступен для редактирование другими пользователями).
Параметр ofstream::app (где app – это статическая переменная, означающая append (англ.) – добавить) необходим для того, чтобы указать на необходимость дописывания новых данных к уже существующим данным без их предварительного удаления. В файле file5.txt уже хранилось “12345”.
Дописать следующий код:
Дописать следующий код (для пользовательского ввода):
4. Получить от пользователя массив данных типа double, записать их в файл.
5. Выполнить по образцу.
Чтение данных из файла
Написать следующий программный код:
Чтение данных из файла может осуществляться побайтово (посимвольно) или построчно.
Условие в цикле: пока в файле есть символы, метод get() возвращает true, считывая и выводя из файла в консоль очередной символ: сначала ‘1’, потом ‘2’, потом ‘3’, потом ‘\n’; потом ‘2’ и т.д.
Так же посимвольно возможно считывание данных любого типа:
6. Выполнить по образцу.
Метод eof()
Написать следующий программный код:
Метод eof() (end of file) возвращает значение false до тех пор, пока не будет достигнут конец файла. Оператор >> в строке 16 будет считывать данные до первого пробельного символа.
Попробовать в 17 строке не использовать endl.
Получится:
Внезапный баг при использовании eof():
Если в самом текстовом файле в конце последнего символа нажать Enter, то последним элементом в файле будет “\n”. После того как 17 строка кода выведет “лишней.”, метод eof() обнаружит, что там еще не конец файла – там еще что-то есть. Значит начнется новая итерация цикла. В 16 строке в stroka пробельные символы не записываются, значит в stroka всё ещё будет храниться “лишней.”, которая и выведется с помощью той же 17 строки кода.
Попробовать способ решения:
Чтобы организовать построчное считывание, нужно использовать метод getline() из библиотеки (метод getline() считывает строку до первого Enter’а):
7. Создать на рабочем столе файл формата .txt, написать в нем небольшой текст, переместить файл в simba\\:обмен файлами\001. Затем создать проект, скопировать из simba файл другого студента в папку со своим проектом. Вывести в консоль содержимое файла.
8. Выполнить по образцу.
Запись объекта в файл
Написать следующий программный код:
Теперь если просто открыть файл, можно увидеть, что данные хранятся там в бинарном виде. Читать их следует через программу.
9. Выполнить по образцу.
Чтение объекта из файла
Написать следующий программный код:
Нажать F5, чтобы запустить отладку.
10. Описать класс по заданной предметной области (3-5 свойств). Создать массив из нескольких объектов. Записать значения для свойств объектов (или получить данные от пользователя). Выполнить запись данных об объектах в один файл. Выполнить вывод данных об объектах в консоль.
11. Выполнить по образцу.
Класс fstream
Написать следующий программный код:
fstream (сокращение от «FileStream») — заголовочный файл из стандартной библиотеки C++, включающий набор классов, методов и функций, которые предоставляют интерфейс для чтения/записи данных из/в файл. Для манипуляции с данными файлов используются объекты, называемые потоками (“stream”).
Протестировать работу программы:
Открыть файл, чтобы посмотреть результат:
12. Выполнить по образцу.
Перегрузка оператора <<
Написать следующий программный код:
Открыть файл, чтобы посмотреть результат.
13. Выполнить по образцу.
Перегрузка оператора >>
Написать следующий программный код:
В папке с проектом уже был файл myFile.txt с данными. Нужно эти данные из файла вывести в консоль.
Открыть файл, чтобы посмотреть результат.
Шаблон класса
Шаблоны позволяют определить конструкции (функции, классы), которые используют определенные типы. При этом на момент написания кода точно не известно, что это будут за типы. Иными словами, шаблоны позволяют определить универсальные конструкции, которые не зависят от определенного типа.
Шаблон класса (class template) позволяет задать тип для объектов, используемых в классе. Принцип работы такой же, как и у шаблонных функций:
template
или
template
Пример:
В шаблонном классе Saving могут содержаться как свойства типа T, так и обычные свойства, например, double balance. При создании объекта необходимо в <> передавать тип данных для T, например, .Метод SizeOfType просто выводит размер переменной accountNum переданного ранее типа.
Кроме того, можно так же как и в шаблонных функциях, назначить T типом возвращаемого значения. Можно передавать в T такой тип данных как имя класса. Можно создать несколько типов: T1, T2, T3 и т.д. и передавать в параметрах несколько разнотипных значений:
template
…
Saving saving1 (25, “q1w2”);
Saving saving2 (25, 30);
Наследование шаблонных классов
Чтобы получить название типа данных, нужно обратиться через оператор typeid (указав в его скобках тип данных) к стандартной функции name() (эта функция из стандартной библиотеки std::type_info).
typeid(double) – это объект. Через него идет обращение к функции:
Если функция не определяется, то нужно подключить библиотеку #include
Пример:
Базовый шаблонный класс Saving, шаблонный подкласс PersonalSaving. В 17 строке при указании базового класса нужно указать и имя типа для шаблона (). При конструировании объекта сначала вызывается 17 строка. Значение из (acNum) копируется в (T value), и тип T становится int. В этой же 17 строке указано, что есть наследование, и что следует обратиться к конструктору базового класса Saving(value). Т.е. сначала должна конструироваться часть из базового класса. В базовом классе в 8 строку конструктор принимает из 20 строки значение 25 из (value). Выполняет 10 строку. Затем конструируется часть из подкласса (там пусто). Далее 37 строка вызывает метод ShowTypeName(), в нем через оператор typeid() идет обращение к стандартному методу name(), который возвращает тип данных аргумента – Saving ::accountNum.
Так тоже сработает:
Шаблоны класса можно частично специализировать, при этом получившийся класс по-прежнему будет шаблоном. Частичная специализация позволяет частично настроить код шаблона для определенных типов:
В этой программе описан шаблонный класс Saving (строки 4-12), который может принимать для своего метода Display() любой тип данных в параметр value.
Также описана специализация для шаблона класса (строки 14-22): если объект создавался с типом, например, string, то для этого объекта сработает метод Display() из специализированного шаблона.
Умные указатели
Если в программном коде есть new, значит для него должен быть соответствующий delete. Но ручные манипуляции с памятью могут быть чреваты.
Например, следующими ошибками:
- утечки памяти;
- разыменовывание нулевого указателя, либо обращение к неициализированной области памяти;
- удаление уже удаленного объекта.
Умные указатели – это классы-обертки для обычных указателей, которые позволяют не заботиться о ручном освобождении памяти с помощью оператора delete и таким образом избежать или уменьшить вероятность появления ошибок при работе с памятью.
Пример:
Указатель auto_ptr
Пример:
Если создать второй объект (38 строка) и выполнить операцию “=” из объекта sp1, то на самом деле в результате этого второй объект будет указывать на ту же область памяти, что и первый объект.
После того, как первый объект выйдет из зоны видимости, деструктор очистит тот участок памяти, который он занимал. Но поскольку второй объект указывал на тот же участок памяти, то деструктор при уничтожении второго объекта снова попытается очистить уже очищенный участок, что приведет к ошибке:
Чтобы этого не произошло, используется указатель auto_ptr.
Вместо этого:
нужно написать так:
и подключить библиотеку:
В этом случае при выходе первого объекта из области видимости память не будет очищена, а просто первый указатель (именно в момент выхода первого объекта из области видимости) перестанет указывать на этот участок памяти. Второй указатель будет иметь доступ к этому участку памяти, пока тоже не выйдет из области видимости.
Указатель unique_ptr
С unique_ptr такой маневр не удастся:
Чтобы сменить владельца данных для unique_ptr, нужно использовать функцию move из пространства имен std: нужно присвоить результат работы этой функции в новую переменную:
В этом случае при выполнении строки 40 указатель up1 сразу же обнулится, т.е. то же самое, что и при работе с auro_ptr, но обнуление первого указателя произойдет раньше. Т.о. в этом случае вообще не могут два этих умных указателя указывать на один и тот же участок памяти.
Вместо move можно использовать так же работающую функцию из стандартной библиотеки swap():
Указатель shared_ptr
shared_ptr в отличие от unique_ptr может одновременно хранить в двух указателях ссылки на один и тот же участок памяти.
Таких shared_ptr может быть в программе сколько угодно. При выходе объектов из области видимости память очистится только тогда, когда последний shared_ptr начнет уничтожаться.
Такая возможность имеется потому что в shared_ptr есть счетчик ссылок – статическая переменная, увеличивающая на 1 свое значение при появлении очередного указателя на ту же область в памяти. При выходе очередного такого указателя из области видимости в деструкторе проверяется, есть ли еще указатели на эту же область памяти. Если есть, то освобождения памяти не происходит, а просто уменьшается на 1 значение той статической переменной-счетчика.
Умный указатель shared_ptr считается самым удобным и поэтому наиболее используемым из умных указателей.
Использование умных указателей при работе с динамическими массивами
Пример:
Благодаря умному указателю shared_ptr после выхода массива из области видимости память из-под него очистится автоматически.
Вектор – шаблон из стандартной библиотеки C++, реализующий динамический массив. Фактически это замена стандартному динамическому массиву (память для которого выделяется вручную с помощью оператора new). Использование векторов позволяет избежать утечек памяти и облегчает работу программисту.
Пример:
Для вывода или изменения значения элемента можно обратиться к нему по индексу (так же как и при работе с обычным массивом). Обращение к нескольким элементам вектора осуществляется так же как и обращение к нескольким элементам массива – с использованием цикла for:
Возможна инициализация вектора так же как и массива – через {}.
Если выводить вектор по индексам с помощью [], то не будет проверяться, был ли выход за границы вектора: программа может крашнуться во время выполнения, может не крашнуться:
Функция at()
Можно выводить элементы вектора с помощью стандартной функции at():
Если для вывода вектора использовать at(), то всегда будет выполняться проверка, нет ли выхода за границы вектора. Если есть, то точно будет ошибка времени компиляции:
Вывод через at() будет работать медленнее, чем вывод через [], как раз из-за проверки на выход за границы вектора.
Функция push_back()
Стандартная функция push_back() применяется для добавления элементов в вектор.
Пример 1:
Пример 2:
Функция pop_back()
Стандартная функция pop_back() удаляет последний элемент вектора:
Функция clear()
Стандартная функция clear() удаляет все элементы вектора:
Функции size() и empty()
С помощью функции size() можно узнать размер вектора, а с помощью функции empty() можно проверить, пустой ли вектор:
Функцию size() удобно использовать во втором параметре цикла for:
Проверка на пустоту: если вектор пуст, то empty() вернет true (1), если не пуст, то вернет false (0).
Пример 1:
Пример 2:
Функция capacity()
Пример:
Внутри вектора используется простой динамический массив. При этом вектор позволяет почти неограниченно добавлять данные в него: запрашивает у ОС блок памяти большего размера, копирует туда все текущие данные, добавляет туда новые данные и освобождает старый блок памяти, т.е. так же как и динамический массив. Но есть одна особенность: если память в текущем блоке закончилась и нужно еще добавить данные, он запрашивает новый блок памяти куда большего размера, чем необходимо (размер памяти “про запас” рассчитывается по коэффициенту исходя из размера имеющегося массива). Зачем: допустим, надо добавлять в конец вектора много элементов в цикле. Оно будет работать очень медленно, если на каждой итерации придется заново перевыделять память на N+1 элементов, потом это все копировать и т.д. Но если запросить сразу много памяти, то перевыделять память придется реже.
В 9 строке значение (5) будет указывать кол-во реальных элементов и для size(), и для capacity(). При этом все элементы будут инициализированы нулем. Если нужно инициализировать все элементы другим числом, то нужно указать это число в качестве второго параметра:
Функция reserve()
С помощью стандартной функции reserve() можно принудительно указать, сколько нужно перевыделить памяти для этого вектора (динамического массива). Если заранее известно, что будет добавляться много данных, то так можно сделать вместимость вектора побольше:
Функция shrink_to_fit()
Если известно, что в какой-то момент вектор больше дополняться не будет, то можно сэкономить память, использовав стандартную функцию shrink_to_fit(), которая ужмёт вектор до реального размера: в этой программе была выделена новая память для массива из 8 элементов, туда были скопированы эти элементы, а память из-под старого массива была возвращена в кучу:
Функция resize()
Изменение размера вектора с помощью стандартной функции resize():
Ключевое слово typedef
Если наименование типа состоит из нескольких слов и выглядит громоздко, то можно с помощью ключевого слова typedef дать этому типу псевдоним:
Односвязный список
Линейный однонаправленный список — это структура данных, состоящая из элементов одного типа, связанных между собой последовательно посредством указателей. Каждый элемент списка имеет указатель на следующий элемент. Последний элемент списка указывает на NULL. Элемент, на который нет указателя, является первым (головным) элементом списка. Здесь ссылка в каждом узле указывает на следующий узел в списке. В односвязном списке можно передвигаться только в сторону конца списка. Узнать адрес предыдущего элемента, опираясь на содержимое текущего узла, невозможно. Получить напрямую элемент (по индексу как в массиве) нельзя. Список располагается в памяти не по порядку, как массив, поэтому для добавления/удаления элемента не нужно создавать новый список и копировать туда элементы, как это делается с массивом.
Пример:
Результат:
Или можно рандомно заполнить список, получив от пользователя размер списка:
Далее нужно организовать освобождение памяти при выходе списка из области видимости.
Немного подкорректированная main():
Результат (удалён первый хэд):
В конечном итоге нужно будет удалить весь список. Для этого надо добавить метод clear():
main():
Результат:
Логичнее разместить вызов метода clear() в деструкторе: когда будет выход из main(), удалится список:
main():
Результат:
Метод push_front() для добавления элементов в начало списка:
Метод insert() для добавления элемента по нужному индексу:
main():
Метод removeAt() для удаления элемента по нужному индексу:
main():
Метод pop_back() для удаления последнего элемента:
main():
Цикл for each
Цикл for each – это цикл на основе диапазона. Он предназначен для тех случаев, когда требуется перебрать все элементы в массиве, векторе, списке и т.п.
В строке 12 в скобках: из массива myArray будут по одному браться значения элементов массива (с начала и до конца массива), копируется (по значению) в переменную value и для каждого значения будет выполняться тело цикла.
Можно передавать значения по ссылке:
Аналогичный пример со списком:
Итератор
Итератор – это такая структура данных, которая используется для обращения к определенному элементу в контейнерах STL. С помощью итераторов удобно перебирать элементы. Итератор описывается типом iterator. Но для каждого контейнера конкретный тип итератора будет отличаться. Обычно итераторы используются с контейнерами set, list, а у вектора для этого применяются индексы.
Для получения итераторов контейнеры в C++ обладают такими функциями, как begin() и end(). Функция begin() возвращает итератор, который указывает на первый элемент контейнера (при наличии в контейнере элементов). Функция end() возвращает итератор, который указывает на следующую позицию после последнего элемента, то есть по сути на конец контейнера. Если контейнер пуст, то итераторы, возвращаемые обоими методами begin() и end(), совпадают. Если итератор begin не равен итератору end, то между ними есть как минимум один элемент.
Пример:
С помощью арифметики указателей через итератор можно получить доступ к последующим элементам вектора:
Стандартная функция end() “указывает” в конец последнего элемента.
Если сделать итератор константным, то через вектор всё равно можно изменить значение элемента:
Если сделать итератор константным, то через итератор (константный итератор myIterator) нельзя изменить значение элемента:
Можно использовать reverse_iterator, чтобы получить элементы в обратном порядке. Тогда функция rbegin() будет “указывать” в конец и брать элементы, начиная с конца вектора, а rend(), наоборот, будет “указывать” в начало:
Функция advance() позволяет сдвинуть итератор на указанное число позиций (та же самая операция, что и в арифметике указателей):
У векторов есть свои возможности для итерации по элементам, но, например, для односвязных списков эти функции являются актуальными.
Функция insert() позволяет добавить элемент в начало:
Сместив итератор, можно вставить нужное значение в любую позицию вектора:
или так:
Функция erase() позволяет удалить первый элемент:
Сместив итератор, можно удалить нужное значение из любой позиции вектора:
У метода erase() есть перегрузка, которая позволяет удалить диапазон элементов, например, удалить элементы начиная с нулевого четыре элемента:
Можно объявить переменную, не указав явно ее тип (но обязательно инициализировать) с помощью ключевого слова auto. Тогда тип данных переменной будет автоматически определяться в зависимости от того, что в нее записано:
Для обычных переменных это нежелательно использовать, т.к. ухудшается читабельность кода. Но в случае, например, с итераторами такая запись с auto будет выглядеть более аккуратной:
Перечисления в языке C++ прямо наследуют поведение перечислений языка C, за исключением того, что перечисляемый тип в C++ — настоящий тип, и ключевое слово enum используется только при объявлении такого типа. Если при обработке параметра являющегося перечислением, какое-либо значение из перечисления не обрабатывается (например один из элементов перечисления забыли обработать в конструкции switch), то компилятор может выдать предупреждение о забытом значении.
1. Выполнить по образцу.
Написать следующий программный код:
В 4 строке помощью ключевого слова enum создано перечисление. В {} для первого элемента можно указать любое число, с которого нужно начинать перечисление, иначе по умолчанию перечисление начнется с нуля. Теперь в kasha хранится 1, в sup – 2, в salat – 3. В 10 строке в переменную для пользовательского ввода numDinner записывается 1. Далее в конструкции switch реализуется выбор. С помощью enum т.о. можно избежать использования “магических чисел” в программе и повысить самодокументируемость кода. На самом деле в 21, 25 и 29 строках сравниваются числа – 1, 2 и 3, соответствующие элементам перечисления.
Нажать F5, чтобы запустить отладку:
2. В папке 001 найти и скопировать файл enum Planet.exe в папку D:\bin\Student, запустить. Написать код этой программы, используя перечисление. Текстовый вывод информации можно сократить до названия планеты.
3. Выполнить по образцу.
Написать следующий программный код:
Как использовать перечисления в программе с классами: создано перечисление в 4 строке; описан класс Светофор с геттером и сеттером. В классе есть закрытое свойство color. Тип возвращаемого значения геттера – перечисление, т.е. вернется что-то из списка – красный, желтый или зеленый. Сеттер принимает параметр типа перечисление Color (т.е. принимает цвет) и записывает его в свойство color для текущего объекта. В main() создается объект s класса Светофор. В 29 строке через этот объект вызывается сеттер и ему передается в качестве параметра элемент red из перечисления Color. Далее можно дописать все ветки условий. В ветке if вызывается через объект s геттер и будет возвращен тот цвет, который там сейчас хранится – red. Тут же он будет сравниваться == с элементом red перечисления Color. Если получится true, то сработает 33 строка.
В этой программе логично будет расположить перечисление Color внутри класса Svetofor. При обращении к Color нужно будет теперь указывать имя класса Svetofor::Color.
Переписать код следующим образом:
Если элементы перечисления должны хранить какие-то содержательные значения, а не просто 0, 1, 2 и т.д., то можно необходимые значения им присвоить:
Теперь это перечисление Color можно считать организованным набором констант. Например, в этой задаче они будут хранить количество секунд, в течение которого горит каждый сигнал светофора. Вывести это значение:
Пространство имён (namespace) — некоторое множество, под которым подразумевается модель, абстрактное хранилище или окружение, созданное для логической группировки уникальных идентификаторов.
Идентификатор, определённый в пространстве имён, ассоциируется с этим пространством. Один и тот же идентификатор может быть независимо определён в нескольких пространствах. Таким образом, значение, связанное с идентификатором, определённым в одном пространстве имён, может иметь (или не иметь) такое же значение, как и такой же идентификатор, определённый в другом пространстве. В пространстве имен std содержатся все компоненты стандартной библиотеки C++, например: cout, iostream.
1. Выполнить по образцу.
Над одним проектом могут работать несколько разработчиков. Они могут именовать одинаково какие-либо элементы кода. Кроме того, может появиться необходимость подключения какой-либо стандартной библиотеки, содержащей имя элемента, которое уже используется разработчиком. Такой код при сборке приведет к ошибке. Чтобы избежать таких ошибок, можно заключать элементы в собственноручно созданные пространства имен. Пространство имен может содержать классы, функции, переменные и т.д.
Написать следующий программный код:
Нажать F5 для запуска отладки.
2. Выполнить по образцу.
Ситуация, когда два разработчика описывают в разных частях проекта одну область, например, Car. Они могут использовать одно и то же пространство имен, но имена функций внутри этого одного пространства имен должны быть разными.
Написать следующий программный код:
Нажать F5 для запуска отладки.
Если один и тот же разработчик описывает обе части Car, то он расположит их внутри одного блока namespace Car (переписать код):
Нажать F5 для запуска отладки.
Если подключить (3 строка) описанное разработчиком пространство имен, то в программе можно будет не указывать каждый раз Car:: (переписать код):
Но при наличии одноименных функций в разных пространствах имен все равно придется явно указывать с помощью оператора ::, какую именно функцию требуется вызвать.
Два разработчика описывают Car. Один описывает датчик скорости, другой – датчик оборотов двигателя. Оба используют пространство имен Car, обоим нужно описать функцию sensor(). В таком случае во избежание ошибки из-за одинаковых имен один из разработчиков может заключить свою функцию в еще одно пространство имен.
Переписать код:
Нажать F5 для запуска отладки.