1. Базовые элементы 1.1. Комментарии 1.2. Функция «main» 1.3

advertisement
1. БАЗОВЫЕ ЭЛЕМЕНТЫ ...................................................................................... 6
1.1. Комментарии ....................................................................................................... 6
1.2. Функция «main» ................................................................................................. 7
1.3. Идентификаторы и пространства имен......................................................... 7
1.4. Типы ...................................................................................................................... 8
1.5. Переменные ......................................................................................................... 9
1.6. Преобразование типов ..................................................................................... 10
1.7. Константы .......................................................................................................... 11
1.8. Потоковый ввод – вывод................................................................................. 12
1.9. Операции ............................................................................................................ 15
1.9.1. Арифметические операции ........................................................................ 15
1.9.2. Поразрядные операции .............................................................................. 17
1.9.3. Условный оператор...................................................................................... 19
1.9.4. Операции отношения .................................................................................. 20
1.9.5. Логические операции .................................................................................. 20
1.10. Стандартные математические функции.................................................... 21
1.11. Упражнения ..................................................................................................... 23
2. БАЗОВЫЕ КОНСТРУКЦИИ ............................................................................ 25
2.1 Ветвление ............................................................................................................ 25
2.1.1. Конструкция «if – else» ............................................................................... 25
2.1.2. Конструкция «switch» ................................................................................. 27
2.2. Циклы ................................................................................................................. 28
2.2.1. Цикл «for» .................................................................................................... 28
2.2.2. Цикл «while» ................................................................................................ 30
2.2.3. Цикл «do – while» ........................................................................................ 30
2.2.4. Операторы циклов ....................................................................................... 31
2.2.5. Вложенность ................................................................................................ 32
2.3. Упражнения ....................................................................................................... 33
2.3.1. Ветвления ..................................................................................................... 33
2.3.2. Циклы ........................................................................................................... 35
2.3.3. Вложенность ................................................................................................ 35
3. УКАЗАТЕЛИ. МАССИВЫ. СТРУКТУРЫ .................................................... 37
3.1. Указатели ........................................................................................................... 37
3.2. Массивы ............................................................................................................. 39
3.2.1. Статические массивы .................................................................................. 39
3.2.2. Динамические массивы .............................................................................. 40
3.3. C – Строки ......................................................................................................... 42
3.4. Ссылки ............................................................................................................... 44
3.5. Структуры ......................................................................................................... 45
3.6. Объединение ...................................................................................................... 47
3.7. Переопределение типов ................................................................................... 48
3.8. Упражнения ....................................................................................................... 49
3.8.1. Статические и динамические массивы ..................................................... 49
3.8.2. Многомерные массивы ............................................................................... 50
3.8.3. Строки .......................................................................................................... 51
4. ФУНКЦИИ............................................................................................................ 53
4.1. Простые функции ............................................................................................. 53
4.2. Рекурсия ............................................................................................................. 56
4.3. Указатели и ссылки ......................................................................................... 57
4.3.1. Передаваемые параметры ........................................................................... 57
4.3.2. Возвращаемые значения ............................................................................. 58
4.3.3. Ссылки и структуры в функциях ............................................................... 59
4.3.4. Указатели и массивы в функциях .............................................................. 60
4.3.5. Указатели и ссылки на функцию ............................................................... 61
4.4. Шаблоны функций ........................................................................................... 63
4.5. Упражнения ....................................................................................................... 65
4.5.1. Простые функции ........................................................................................ 65
4.5.2. Параметры – ссылки и параметры — указатели ...................................... 66
5. СТАНДАРТНЫЕ КЛАССЫ .............................................................................. 67
5.1. Класс «pair»........................................................................................................ 67
5.2. Класс «complex» ................................................................................................ 68
5.3. Класс «string» ..................................................................................................... 68
5.4. Контейнеры. Алгоритмы. Итераторы .......................................................... 70
5.5. Класс «vector» .................................................................................................... 73
5.6. Упражнения ....................................................................................................... 77
5.6.1. Класс «string»............................................................................................... 77
5.6.2. Класс «vector» .............................................................................................. 78
6. ФАЙЛОВЫЙ ВВОД – ВЫВОД ........................................................................ 79
6.1. Операции ............................................................................................................ 79
6.2. Методы................................................................................................................ 80
6.3. Флаги .................................................................................................................. 81
6.4. Двоичный режим .............................................................................................. 82
6.5. Упражнения ....................................................................................................... 83
ЛЛИТЕРАТУРА ........................................................................................................ 85
Данное учебно-методическое пособие предназначено для студентов,
изучающих программирование на языке высокого уровня. Целью пособия
является формирование как общих, так и специализированных знаний и
приобритение навыков программирования на объектно-ориентированном языке
С++.
Каждый раздел данного пособия содержит теоретический материал о
рассматриваемом предмете, иллюстрирующие примеры и практические задачи
для усвоения материала и получения соответствующих навыков
программирования. В первом разделе рассматриваются базовые элементы
языка, знание которых является основопологающим при программировании.
Второй раздел содержит описание базовых конструкций, составляющих
неотъемлимую часть практически любой программы. Такие формы
представления данных, как массивы и структуры, а также приемы работы с
ними через указатели рассматривается в третьем разделе. Четвертый раздел
описывает создание и применение функций. В пятом разделе рассматриваются
стандартные классы, посредством которых осуществляются обработка и
хранение различных типов данных. Шестой раздел содержит описание
стандартных потоковых классов и методов их приминения.
Введение
В теории программирования существует несколько подходов к
представлению всего того, чем оперирует программист при написании кода —
парадигм. Из них можно выделить два наиболее распространенных —
процедурное программирование (пример – языки С, Pascal) и объектно
ориентированное (С++, Java, C#).
Программа, написанная на процедурном языке, представляет собой
набор действий – инструкций, последовательное выполнение которых приводит
к требуемому результату. При написании больших программ, отдельные
инструкции объединяют в функции так, чтобы облегчить восприятие
программы – повысить ее читабельность, а функции, аналогично инструкциям,
объединяют в модули. Однако процедурный подход к написанию кода обладает
некоторыми недостатками, среди которых неограниченность доступа функций
к данным, а также разделение данных и операций над ними.
Основой объектно ориентированного подхода является объединение
данных и операций, выполняемых над этими данными, в единое целое —
объект. Тип объекта называется классом, а сам объект называется экземпляром
данного класса. Функции объекта называются методами, они и реализуют
операции над данными, когда как к самим данным прямого доступа нет. Это
свойство называется сокрытием – инкапсуляцией.
В связи с тем, что язык С++ произошел из языка С, с одной стороны,
любая конструкция языка С корректна и для С++, а с другой, сама структура
программы на языке С++ основана на процедурных средствах, таких как
операции над переменными, циклы, ветвления, работа с функциями.
1. Базовые элементы
1.1. Комментарии
В качестве первого примера приведена простейшая программа. Ее код
не демонстрирует особенных возможностей языка, однако является важным в
плане понимания структуры кода программы на языке С++.
Листинг 1.1. Helloword.
/* Данная программа выводит на экран строку «Hello, World!» ,
* после чего ожидает нажатия любого символа на клавиатуре
* для своего завершения.
*/
#include <iostream>
using std::cout;
int main (void)
{
cout << "Hello, World!";
system("pause");
return 0;
}
Первая строка кода программы является комментарием – тем текстом,
который предназначен только для программиста, и который не будет
рассматриваться компилятором как исполняемый код. Комментарий может
оформляться двумя способами. В первом случае комментарием считается
любой текст, следующий после пары символов «//» и до символа конца строки.
Во втором случае, унаследованном от языка С, комментарий представляет
собой текст, расположенный между символами «/*» и «*/». Он может быть
много строчным.
Использование комментариев является более чем просто полезным, так
как на них возлагают функции описания тех или иных частей программы. Это
важно потому, что со временем функциональность отдельных участков кода
забывается, а если код был написан кем-то еще, то его разбор может оказаться
весьма трудоемким процессом. Также, комментарии могут передавать
некоторую мета информацию, как например имя автора, версию или дату
создания. Таким образом, сопровождение кода, содержащего комментарии,
осуществляется гораздо легче, а его читабельность заметно повышается.
Однако, пространные комментарии малоприятны и утомительны. Если есть
ощущение того, что необходимо закомментировать каждую строчку кода,
возможно стоить переписать сам код более удобочитаемым образом, ведь не
стоит забывать, что компилятору безразлична семантика, которая может быть
направлена на повышение читабельности кода.
1.2. Функция «main»
Обязательным для каждой программы является наличие функции
«main», потому что именно с ее вызова начинается выполнение программы.
Функция с именем «main» должна быть в точности одна, иначе неизвестно с
какой именно функции должно начаться исполнение, что вызовет ошибку при
компиляции.
Имя любой функции стоит перед скобками «()», в которых могут
определяться параметры функции, а слово перед именем функции, в примере
это «int», определяет тип возвращаемого значения. Следом за круглыми
скобками должны находиться фигурные «{}», они определяют те границы, в
пределах которых расположены выполняемые функцией действия. Этот блок
операций называется телом функции. Так выглядят объявления функций. Для
выполнения производимых функцией действий необходимо вызвать эту
функцию, где ей должны в к круглых скобках передаться значения параметров,
если таковые есть. Так в примере вызывается функция «system()» с параметром
«"pause"». Данная функция выполняет указанную в качестве параметра
команду оболочки, после чего выполнение кода программы продолжится.
Подробно функции и их параметры будут рассмотрены позже.
1.3. Идентификаторы и пространства имен
Идентификатором называют имя класса, переменной, функции и других
программных сущностей. Он может состоять из букв латинского алфавита в
обоих регистрах, цифр и символа «_» и не должен начинаться с цифры. Язык
С++ – регистрозависимый, т.е. идентификаторы «variable» и «VARIABLE»
различны. Идентификатор должен быть уникальным, т.е. не должен совпадать с
другими идентификаторами или зарезервированным словом. Имена
выбираются, отталкиваясь от назначения переменной или функции. Типичным
примером является префикс «tmp», говорящий о том, что данная переменная
является временной.
Таблица 1.1. Зарезервированные слова.
and
and_eq
asm
auto
bitand
bitor
bool
break
case
catch
char
dynamic_cast
goto
not_eq
short
try
class
else
if
operator
signed
typedef
compl
enum
inline
or
sizeof
typeid
const
explicit
int
or_eq
static
typename
const_cast
export
long
private
static_cast union
continue
extern
main
protected
struct
unsigned
default
false
mutable
public
switch
using
delete
float
namespace
register
template
virtual
do
for
new
reinterpret_cast
this
void
double
friend
not
return
true
volatile
wchar_t
while
xor
xor_eq
Длинные идентификаторы очень неудобны, но в случае повторного
использования одного имени, возникнет ошибка – конфликт имен. Подобные
проблемы решает контекст идентификатора –
пространство имен –
«namespace». Все имена группируются в соответствии со своим назначением, и
каждой группе присваивается свой идентификатор. Несколько групп могут
объединяться в одну. Отдельная группа и есть пространство имен, т.о.
уникальность должна соблюдаться лишь в рамках одного (данного)
пространства имен.
Обращение к классу или вызов функции происходит посредством
оператора разрешения контекста «::». Если, к примеру, в пространстве имен
«Math» существует еще одно – «Complex», где находиться искомая функция
«Pow()», то ее вызов выглядит так: «Math::Complex::Pow()». Такие обращения
утомительно и писать, и читать. Избавиться от этого помогает директива
«using». Она позволяет сделать доступным пространство имен без явного к
нему обращения: «using namespace Math::Complex». Возможно и включение
отдельных идентификаторов «using Math::Complex::Pow». В обоих случаях
обращаться к функции можно так, как если бы она являлась членном данного
пространства имен. Различие состоит в том, что при включении всего
пространства имен «Complex» будут доступны и другие неиспользуемые
функции, которые могут вызвать конфликты имен.
В примере строка «using namespace std;» делает доступными все
содержимое пространства имен «std». Оно определено в библиотечном файле
«iostream», для включения которого необходимо использовать директиву
препроцессора «#include» Для использования своих собственных файлов надо
вместо символов «<>» использовать «""».
1.4. Типы
Большинство
распространенных
языков
программирования
поддерживают различные типы данных, но их реализация зависит от
конкретной аппаратной архитектуры. В языке С++ существует поддержка
следующих встроенных типов, размеры которых приведены для 64-разрядной
машины семейства x86.
В языке С++ существует оператор «sizeof», который поваляет
определить размер типа на конкретной аппаратной архитектуре. Так размер,
например, типа «float» может быть определен посредством оператора
«sizeof(float)».
Каждый из приведенных типов может быть знаковым, что задается по
умолчанию или при помощи спецификатора «signed», или беззнаковым, если
используется спецификатор «unsigned». В случае, если переменная задана, как
беззнаковая, ее старший бит, отвечающий за знак, освободится для значения.
Так диапазон значений «short» лежит в пределах от -32768 до 32767, а
«unsigned short» – от 0 до 65535.
Таблица 1.2. Встроенные типы.
Тип
long double
double
float
long long
long
int
short
wchar_t
char
bool
Размер
12
8
4
8
4
4
2
2
1
1
Значение
Дробное
Дробное
Дробное
Целое
Целое
Целое
Целое
Символьное
Символьное
Логическое
Приоритет
Старший
Младший
Типы «short», «int», «long», а также тип «long» увеличенной разрядности
представляют числовые типы, типы «float», «double» и «double» увеличенной
разрядности представляют дробные числа, тип «char» и «whar_t» –
символьные, интерпретируемые как код некоторого символа, а «bool» –
логический, принимающей одно из двух значений «true» или «false». Тип
«wchar_t» был унаследован из языка С, где не являлся базовым.
В языке С++ существует еще один тип – «void». Он является базовым,
однако объектов этого типа не существует, в связи с чем используется он
только для определения функции, как не возвращающей какого-либо значения
или непринимающей каких-либо параметров, или для задания типа указателя на
объекты неизвестного типа.
1.5. Переменные
Переменные являются фундаментальной частью любого языка. Каждая
из них имеет свои имя и значение. Имя переменной адресует закрепленный за
ней участок памяти, и когда переменной присваивается новое значение, оно
записывается в этот участок. Связывание идентификатора с выделяемой
областью памяти называется объявлением, а если при этом в память заносится
первоначальное значение – определением или инициализацией, при этом, если
попытаться получить значение переменной до того, как ей будет впервые
присвоено значение, результатом будет то, что находилось в закрепленным за
переменной участке памяти до ее объявления. Если выделение памяти для
переменной происходит во время выполнения программы, то оно называется
динамическим, если же выделение происходит на этапе компиляции, –
статическим.
Переменная может быть объявлена в каком-либо классе, в функции или
пространстве имен. Если переменная объявлена в пространстве имен, но вне
класса, то она является глобальной для всех классов и функций, объявленных в
томже пространстве имен. Если же переменная объявлена, например, внутри
функции, то она называется локальной, и внешнее к ней обращение
невозможно. Таким образом, область видимости переменной может быть
глобальной или локальной.
Важным параметром переменной является ее время жизни. Оно
начинается с момента объявления переменной и длиться до конца блока
операций, заключенных в фигурные скобки, в которых объявлена и сама
переменная, или до конца выполнения программы, в зависимости от
спецификатора, выделяющего память одного из четырех классов.
Спецификатор «auto» используется по умолчанию, без необходимости его
явного указания, и задает время жизни до конца блока операций. Спецификатор
«register» позваляет сохранять переменную не в оперативной памяти, а в
свободном регистре. В случае, если свободного регистра нет, переменная
станет «auto». Спецификатор «extern» сообщает компилятору о том, что
переменная будет объявлена (уже без спецификатора «extern») в другом файле.
Спецификатор «static» увеличивает время жизни до конца выполнения
программы. Так для переменной, объявленной как «static» в функции, память
выделиться только один раз – при первом вызове функции, а сама переменная
будет сохранять свое значение между вызовами. Еще одним эффектом
применения данного спецификатора является инициализация переменной
нулевым значением и ограничение ее видимости текущим файлом.
1.6. Преобразование типов
При работе с переменными часто возникают ситуации, когда
переменной одного типа необходимо присвоить значение переменной другого
типа. В большинстве случаев компилятор корректно выполняет преобразование
типов, и нет необходимости во вмешательстве программиста. Такие случаи
называются неявным преобразованием и выполняются в соответствии с
приоритетами типов.
Особым случаем при неявном преобразовании можно считать
преобразование с участием типа «bool». Переменные этого типа, имеющие
значение «true», преобразуются к «1», а «false» – к значению «0», при этом
любое значение, отличное от «0», будет интерпретироваться как «true», а
нулевое как «false». Еще одним особым случаем является преобразование с
участием типа «char». При преобразовании его к любому числовому типу,
переменная получит в качестве результата – ASCII – код символа, хранящегося
в переменной типа «char», а при обратном преобразовании число будет
интерпретироваться как ASCII – код.
При использовании макроопределений проверка соответствия типов не
выполняется вовсе, по этому и рекомендуется пользоваться «const» –
переменными.
Явное преобразование типов используется, в основном, при работе с
пользовательскими типами данных, когда компилятор не в состоянии
выполнить преобразование автоматически. При таком преобразовании
используются операторы «const_cast», «static_cast», «reinterpret_cast» и
«dynamic_cast». Конструкция преобразования типов языка С, где тот тип, к
которому требуется преобразовать, указывался в круглых скобках перед
выражением, также допустима, но не рекомендуется к применению в С++.
Оператор «const_cast» позволяет отменить признак постоянства. Оператор
«static_cast» используется, если преобразование поддерживается языковыми
средствами напрямую. Так в случае деления целочисленных переменных
результат также является целочисленным, чего и помогает избежать
«static_cast». Оператор «reinterpret_cast» является более мощным по сравнению
со «static_cast», но и менее безопасным, и применяется в случаях, когда
формально преобразование неразрешено, но имеет определенный смысл, как
при преобразовании типов указателей и ссылок. Оператор «dynamic_cast»
обеспечивает динамический контроль типов при работе с классами, но
применим и к обычным ссылкам и указателям.
Листинг 1.2. Использование static_cast.
#include <iostream>
using std::cout;
int main (void)
{
float z;
z = (10 - 2) / 3;
cout << z << "\n";
z = static_cast<float>(10 - 2) / 3;
cout << z << "\n";
return 0;
}
1.7. Константы
При инициализации переменных и в ряде других случаев могут
использоваться константы. Целочисленные константы могут задаваться в
различных системах счисления. Десятичные числа записываются цифрами
обычным образом. Перед шестнадцатеричными ставится символы «0х», а
восьмеричные — нулем. Дробные константы состоят из целой части,
разделяющей точки, дробной части, символа «е» или «Е» и экспоненты в виде
целой константы. Так запись «3.5e2» равносильна записи «350.0». Строковые
константы записываются в двойных кавычках «""», а символьные — в
одинарных «''». Символьными константами называются также и управляющие
последовательности, специальные символы, регулирующие вывод информации
на экран. В примере – это символ перевода строки «\n». Так как символы «"»,
«'» и «\» используются в служебных целях, то их непосредственный вывод
осуществляется при помощи экранирования символом «\». Также, с помощью
«\» можно выводить символы, используя их ASCII – код: «\xHH», где «HH» –
это шестнадцатеричный код требуемого символа.
Таблица 1.3. Специальные символы.
Символ
\a
\b
\n
\r
\t
Описание
Сигнал
Возврат на шаг
Перевод строки
Возврат в начало строки
Табуляция
Символ
\''
\'
\\
\xHH
Описание
Двойные кавычки
Одинарные кавычки
Обратная косая черта
Символ с ASCII – кодом HH
При задании констант можно явным образом определять их тип,
указывая после значения определенный символ. Для литерала «long» – это «L»
или «l», для «unsigned» – это – «U» или «u», для типа «float» – «F» или «f».
В случае, если в программе не должно происходить изменения значения
какой-либо переменной, ее объявляют как «const». В таком случае попытка
изменить ее вызовет ошибку компиляции. В языке С для этого использовались
макроопределения директивой «#define», что сохранилось и в С++, однако
такой подход несет в себе потенциальные проблемы, связанные с
преобразованиями типов, и затрудняет последующее сопровождение кода.
Унаследованным из языка С является особый тип констант «enum» –
перечисление. Он представляет из себя список целочисленных констант. Если
значения констант не заданы, то первая приравнивается к нулю, следующая – к
единице и т.д.
Листинг 1.3. Перечисления.
#include <iostream>
using namespace std;
enum Esc {BEL='\a', BSP='\b', NEW='\n', RET='\r', TAB='\t' }; //спецсимволы;
enum Errors { OK, ER_FILE, ER_VALUE };
//ошибки;
int main (void)
{
cout << static_cast<char>(NEW) << endl;
return OK;
}
Важным является тот факт, что в пространство имен добавляется не
только идентификатор перечисления, но и имена всех его элементов. Обычно,
идентификаторы элементов перечисления состоят из символов в верхнем
регистре, чтобы отличать их от других идентификаторов.
1.8. Потоковый ввод – вывод
В С++ операции ввода и вывода реализуются с помощью объектов «cin»
и «cout», доступных при подключении стандартной библиотеки <iostream>.
Они предназначены для работы со стандартными потоками. Потоком же
называют некоторый абстрактный канал связи между источником данных и их
приемником. Для объекта «cin» источником является консоль, а для объекта
«cout», консоль является приемником, поэтому «cin» используется для ввода
данных, а «cout» – для их вывода.
Для того, чтобы вывести на экран какие-либо данные, необходимо
воспользоваться оператором вывода «<<», а для ввода данных используется
оператор ввода «>>». Наравне со стандартными потоками вывода возможно и
использование унаследованных из языка С функций «printf()» или «scanf()»,
однако их использование представляется более утомительным, т.к. эти функции
требуют явного указания типов данных. Более того, операторы ввода и вывода
могут быть перегружены, что делает работу со своими классами аналогичной
работе со стандартными.
Потоковые операции ввода и вывода являются каскадируемыми, т.е. нет
необходимости при каждой операции ссылаться на объект потока. Это связано
с тем, что возвращаемым результатом данных операторов является сам поток,
который и может быть снова использован.
Листинг 1.4. Каскадирование.
#include <iostream>
using namespace std;
int main (void)
{
char c;
int i;
cout << "enter char x and int y" << endl;
cin >> c >> i;
cout << "c = " << c << endl << "i = " << i << endl;
return 0;
}
Важным свойством потоков является их способность различать
манипуляторы, оказывающие действие на данные, следующие за ними.
Наиболее часто употребляется манипулятор «endl», функциональность
которого для консоли аналогична выводу символа «\n» – переводу строки. В
стандартной библиотеке, в файле <iomanip> определены различные
манипуляторы, позволяющие форматировать поток.
Листинг 1.5. Использование манипуляторов.
#include <iostream>
#include <iomanip>
using namespace std;
int main (void)
{
const bool x = true;
cout << boolalpha << x << " or " << noboolalpha << x << endl;
const unsigned int y = 15;
cout << "hex: " << hex << y << endl;
cout << "oct: " << oct << y << endl;
cout << "dec: " << dec << y << endl;
double z = 1234567889e-9;
cout << z << endl;
cout << left << setw(10) << setfill('0') << z << endl;
cout << right << setw(10) << setfill('0') << z << endl;
cout << setprecision(9) << z << endl;
cout << noshowpoint << 1.0 << endl;
cout << showpoint << 1.0 << endl;
return 0;
}
Таблица 1.4. Некоторые стандартные манипуляторы.
Манипулятор
boolalpha /
noboolalpha
hex / dec / oct
setprecision()
setw()
setfill()
internal
left / right
showpoint /
noshowpoint
showpos
skipws / noskipws
ws
uppercase
scientific / fixed
Описание
Логические переменные в виде «true», «false» или «1», «0»
Числа в шестнадцатеричном, десятичном, восьмеричном виде
Задание числа выводимых дробных разрядов
Задание ширины поля вывода чисел
Задание символа – заполнителя свободного пространства
Заполнение между знаком или основанием и самим числом
Задание положения поля данных
Дробные чисела без дробной части с точкой или без нее
Выводить знак «+» перед положительными числами
Пропуск пробельных символов или их считывание, как лексем
Пропуск пробельной последовательности
Вывод в верхнем регистре шестнадцатеричных символов, «E»
и «X»
Экспоненциальный вывод числа или вывод с фиксированной
точкой
Наряду с манипуляторами применяются различные методы по работе с
потоками ввода – вывода. Метод «ignore()» удаляет из потока ввода часть
символов. Метод «clear()» сбрасывает флаги состояния потока, например, флаг
«goodbit» – отсутствия ошибок. Доступ к флагам осуществляется
соответствующими методами; для «goodbit» – это метод «good()».
Листинг 1.6. Методы.
#include <iostream>
#include <iomanip>
#include <fstream>
using namespace std;
int main (void)
{
int iter;
cin >> iter;
cout << cin.good() << endl;
cin.clear(ios::goodbit);
cin.ignore(1, '\n');
cin >> iter;
cout << endl;
//ввод целого числа;
//при неверном вводе;
//сброс флага good;
//удаление из потока введеного числа;
//очередной ввод;
return 0;
}
1.9. Операции
Работа с переменными осуществляется в выражениях, которые содержат
знаки операции и операнды. Операции могут производиться над одним, двумя
или тремя операндами, соответственно различают унарные, бинарные и
тернарные операции.
Любое использование операндов и знаков операций называется
выражением. Выражение может состоять из нескольких знаков операций и
операндов. В таком случае, операции выполняются в соответствии со своими
приоритетами. Порядком выполнения можно управлять, используя скобки «()».
Обычно, результат операции сохраняется в некоторой переменной. Это
сохранение производится посредством операции присваивания «=». Все
операции можно также разделить на группы в соответствии с их назначением:
арифметические, поразрядные, логические, отношения и условный.
1.9.1. Арифметические операции
К бинарным арифметическим операциям относятся следующие: «+», «–
», «*», «/», «%». К унарным арифметическим операциям относятся операции:
«–», «++» и «––».
Листинг 1.7. Арифметические операции.
#include <iostream>
using namespace std;
int main (void)
{
int x;
cout << "Enter x: ";
cin >> x;
int y;
cout << "Enter y: ";
cin >> y;
cout << "x/y\t= " << x / y << " and " << x % y << endl;
cout << "x/y\t= " << 1.0 * x / y << endl;
cout << "-x+2y\t= " << -x + 2 * y << endl;
cout << "(2-x)y\t= " << (-x + 2) * y << endl;
return 0;
}
Таблица 1.5. Арифметические операции.
Знак
Синтаксис
Описание
–
–a
+
a+b
Сумма a и b
–
a– b
Разность a и b
*
a*b
Произведение a и b
/
a/b
Деление a на b
%
a%b
Остаток от деления a на b
++
++a или a++
Префиксный или постфиксный инкремент a
––
– –a или a– –
Префиксный или постфиксный декремент a
Взятие отрицательного значения a
Операция инкремента (увеличение значения на 1), как и декремента
(уменьшение значения на 1), может быть записана в постфиксной (когда знак
записан после операнда) или префиксной (когда знак записан до операнда)
формах. Различие форм заключается в приоритетах данных операций.
Операция, записанная в префиксной форме выполнится первой среди
нескольких операций, а в постфиксной – самой последней, в том числе после
операции присваивания или записи в поток вывода. Наглядно различия
демонстрирует следующий код.
Листинг 1.8. Префиксная и постфиксная записи операций инкремента и
декремента.
#include <iostream>
using namespace std;
int main (void)
{
int x = 8;
cout << x << endl;
cout << ++x << endl;
cout << x++ << endl;
cout << x << endl;
return 0;
}
Стоит заметить, что язык С++ позволяет записывать некоторые
операции с последующим присваиванием сокращенным образом, что позволяет
уменьшить код и сделать его более наглядным. Для арифметических операций:
«+=», «–=», «*=», «/=», «%=».
1.9.2. Поразрядные операции
К поразрядным операциям относятся бинарные «&», «|», «^», «>>»,
«<<» и унарная «~». Следует отметить, что операция сдвига влево является, по
сути, операцией умножения на число, равное двум в степени количества
смещаемых бит, а сдвиг вправо – деление на аналогичное число. Данные
операции выполняются быстрее арифметических, поэтому в приложениях, где
важна скорость выполнения, могут их заменять, однако, не рекомендуется
везде, где есть деление или умножение на число, кратное двум, использовать
операции сдвига, т.к. это может усложнить восприятие кода.
Таблица 1.6. Поразрядные операции.
Знак
>>
<<
~
&
|
^
Синтаксис
a >> b
a << b
~a
a&b
a|b
a^b
Описание
Поразрядный сдвиг a на b бит вправо
Поразрядный сдвиг a на b бит влево
Поразрядное отрицание a
Поразрядная конъюнкция над a и b
Поразрядная дизъюнкция над a и b
Поразрядное XOR над a и b
Листинг 1.9. Поразрядные операции.
#include <iostream>
using namespace std;
int main (void)
{
const unsigned int x = 5;
const unsigned int y = 3;
cout << "x = " << x << endl;
cout << "y = " << y << endl;
cout << "x&y = " << (x&y) << endl;
cout << "x|y = " << (x|y) << endl;
cout << "x^y = " << (x^y) << endl;
cout << "x&y ^ x|y = " << ((x&y) ^ (x|y)) << " = x^y" << endl;
cout << "x<<1 = " << (x << 1) << " = 2x" << endl;
cout << "y<<2 & x = " << ((y << 2) & x) << endl;
cout << "x+y >> 1 = " << (x+y>>1) << " = x+y / 2" << endl;
return 0;
}
В приведенном примере все поразрядные операции расположены внутри
скобок. Это связано с тем, что иначе операторы сдвига будут
интерпретироваться компилятором, как операторы вывода.
Следующий код демонстрирует различия между знаковым и
беззнаковым типами на примере типа «short», а также саму интерпретацию
знакового бита.
Листинг 1.10. Манипуляции со старшим битом знаковых и беззнаковых
типов.
#include <iostream>
using namespace std;
int main (void)
{
short z;
z = 0x7FFF;
//максимальное число для short = 0х7FFF;
cout << "z :\t" << hex << z << " : " << dec << z << endl;
//вывод следующего за числом в z через сумму с 1;
cout << "z+1 :\t" << hex << z+1 << " : " << dec << z+1 << endl;
//вывод следующего за числом в z через его инкремент;
cout << "++z :\t" << hex << ++z << " :" << dec << z << endl;
//вывод следующего за числом в z с беззнаковым интерпретированием;
cout << "uz :\t" << hex << static_cast<unsigned short>(z) <<
" : " << dec << static_cast<unsigned short>(z) << endl << endl;
//перевод числа из положительного в отрицательное;
z = -105;
cout << "z :\t" << hex << z << " : " << dec << z << endl;
z = ~z;
z++;
cout << "z :\t" << hex << z << " : " << dec << z << endl << endl;
//перевод числа из отрицательного в положительное;
z = 501;
cout << "z :\t" << hex << z << " : " << dec << z << endl;
z--;
z = ~z;
cout << "z :\t" << hex << z << " : " << dec << z << endl << endl;
return 0;
}
Сокращенная запись существует и для поразрядных операций: «>>=»,
«<<=», «|=», «&=» , «^=».
1.9.3. Условный оператор
В С++ существует условный оператор, являющийся тернарным: « a ? b :
c ». Этот оператор, в зависимости от значения первого операнда «a», стоящего
перед символом «?», выполняет одну из двух операций. Первый операнд
интерпретируется, как имеющий логический тип, и, если он имеет значение
«true», выполняется операция перед символом двоеточия «:» – «b», иначе –
операция после данного символа – «c». В случае, если выполняемых операций
несколько, они должны следовать через запятую, а сами операнды можно
записывать в скобках при явном задании следования выполнения операций в
составных операторах или просто для большей наглядности.
Листинг 1.11. Условный оператор.
#include <iostream>
using namespace std;
int main (void)
{
bool x;
cout << "Enter bool x (0 or 1): ";
cin >> x;
cout << (x ? "true" : "false") << endl;
int y;
cout << "Enter int y: ";
cin >> y;
y ? (cout << "not", cout <<" 0" << endl) : (cout << "0" << endl);
return 0;
}
1.9.4. Операции отношения
Операциями отношения являются исключительно бинарные: «>», «<»,
«>=», «<=», «==» и «!=», а их результат имеет тип «bool». Данные операции
могут, к примеру, использоваться в первом операнде тернарного оператора.
Листинг 1.12. Операции отношения.
#include <iostream>
using namespace std;
int main (void)
{
double x;
cout << "Enter x: ";
cin >> x;
double y;
cout << "Enter y: ";
cin >> y;
cout << ((x == y) ? "x == y" : "x != y") << endl;
cout << "max(x,y): ";
cout << ((x >= y) ? (cout << "x = ", x) : (cout << "y = ", y)) << endl;
cout << "x " << ((x < 0) ? "< 0" : ">= 0") << endl;
cout << "y " << ((y >= 0) ? ">= 0" : "< 0") << endl;
return 0;
}
Таблица 1.7. Операции отношения.
Знак
<
>
<=
>=
==
!=
Синтаксис
a<b
a>b
a <= b
a >= b
a == b
a != b
Описание
Сравнение a меньше b
Сравнение a больше b
Сравнение a меньше или равно b
Сравнение a больше или равно b
Сравнение a равно b
Сравнение a не равно b
1.9.5. Логические операции
Существует и еще одна группа операторов, называемых логическими,
среди них как бинарные: «&&» и «||», так и унарный «!». Их операнды имеют
булевый тип. Логическая операция «&&» соответствует союзу «и» при
построении логического вывода, операция «//» – союзу «или», а «!» – частице
«не».
Таблица 1.8. Логические операции.
Знак
&&
||
!
Синтаксис
Описание
a && b
Логическая конъюнкция а и b
a || b
Логическая дизъюнкция а и b
!a
Логическое отрицание a
Листинг 1.13. Логические операции.
#include <iostream>
using namespace std;
int main (void)
{
bool x = true;
bool y = false;
cout << ((x && y) ? "true" : "false") << endl;
cout << ((x || y) ? "true" : "false") << endl;
cout << ((!x || y) ? "true" : "false") << endl;
y = true;
cout << ((x && y) ? "true" : "false") << endl;
x = y = false;
cout << ((x || y) ? "true" : "false") << endl;
cout << (!(x || y) ? "true" : "false") << endl;
return 0;
}
1.10. Стандартные математические функции
Зачастую требуется выполнение таких операций, как возведение в
степень или вычисление тригонометрической функции. Эти и некоторые
другие функции реализованы в стандартной библиотеке и доступны при
подключении файла «cmath», являющегося С++ версией заголовочного файла
«math.h» стандартной библиотеки языка С.
Таблица 1.9. Некоторые стандартные математические функции.
Функция
Выполняемое действие
abs(x)
Модуль x
asin(x)
Арксинус угла x
acos(x)
atan(x)
sin(x)
cos(x)
tan(x)
Арккосинус угла x
Арктангенс угла x
Синус угла x в радианах
Косинус угла x в радианах
Тангенс угла x в радианах
Функция
floor(x)
ceil(x)
log(x)
log10(x)
pow(x, y)
exp(x)
sqrt(x)
Выполняемое действие
Округление в меньшую
сторону
Округление в большую
сторону
Натуральный логарифм x
Десятичный логарифм x
Возведение x в степень y
Экспонента в степени x
Корень квадратный x
5
Нижеследующий код решает задачу:
x 3  cos( 2 x  9)
, где


1
log 10 ( 5 )
sin ( x) 

2
x – наибольшее из решений уравнения y  5 y  6  0 .
Листинг 1.14. Использование стандартных математических функций.
#include <iostream>
#include <cmath>
using namespace std;
int main (void)
{
double a, b, c;
cout << "ax^2 + bx + c = 0" << endl;
cout << "Enter a: ";
cin >> a;
cout << "Enter b: ";
cin >> b;
cout << "Enter c: ";
cin >> c;
//нахождение корней через дискриминант;
double D = b * b - 4 * a * c;
double x1 = (-b + sqrt(D)) / 2;
double x2 = (-b - sqrt(D)) / 2;
double x = (x1 > x2) ? (x1) : (x2);
cout << "x = " << x << endl;
double chisl = pow(x, 3.0/5) + abs( cos( 2*x - 9));
double znam = ceil( log10( 1.0/pow( sin(x), 5)));
cout << chisl / znam << endl;
return 0;
}
1.11. Упражнения
Вычислить значения выражений.
1. 3 sin 2 ( x)  5 cos( x 2 )
18. 4 tan( x ) sin( x 2 )
2. ln( x 2 )  ln( 3x)
3. 6 tan 3 ( x)  x
19.
ln( 3 x )
log 10 ( x 3 )
4. sin 2 ( log 10 (10 x) )
20. cos(6 x) cot( x 3 )
5. 7 cos(sin( x))
21. ln( x  3 ) ln( 4 x  x 3 )
6.
sin 4 x  cos 4 x
cos 2 2 x
7. 3 sin( 4 x 2 )
8. 5e 2 x
9. 9 tan( ln( x))
10.
7 arcsin x
arccos 2 3 x
11. cos x x sin x
22.
23. 2 cos 2 x  9 tan 8 x
25. 
1 
5
 cos x 
26. cot( 3 x 2 )
27. 5log 10 e x 
28.
2
14.
cos x
sin x 2
2
3
24. ln x 2 log 10 x 3
12. ln( 10x) log 10 ( x )
13. arccos x  arcsin x
2e cos 2 x
sin x 2
log 10 (5 x)
log 10 (3x)
29. sin 2 x  cos 2 x
30. 10e log
10
x
15. ln( x 2  5x)  3x
31. tan x ln x
 arctan x 3 
16. 

 7x 
17. ln 10 x
 cos( 2 x) 

32. 
 sin( 2 x) 
3
33. x sin 2 x cos 2 x
34. cos 5 e x
2

37. 
cos( 2 x) tan( 4 x) 

2

38. arctan ln x

ln x 
3 
 x 
35.  tan 
39. sin 5 x cos 2 x 5
36. arccos 2 sin 3 x
40. arcsin x log

10
x
2. Базовые конструкции
Все вычисления и операции той или иной программы, должны
выполняться в определенном порядке, зачастую в зависимости от множества
условий. Этот порядок и сами условия задаются инструкциями управления –
управляющими конструкциями.
Любое выражение языка С++ становится инструкцией, если оно
завершается точкой с запятой, при этом если перед символом «;» нет
выражения, то такая инструкция называется пустой и в ряде случаев
используется для предотвращения синтаксических ошибок. Объединение
нескольких инструкций в одну, называемую составной или блоком,
выполняется при помощи фигурных скобок «{}». Блок представляется, с точки
зрения синтаксиса, одной инструкцией.
Все управляющие конструкции языка С++ делятся на три типа:
ветвление, цикл и безусловный переход. Последний тип – тип одной
конструкции. Она состоит из метки – идентификатора, оканчивающегося
символом двоеточия, и оператора перехода к этой метке «goto», что реализует
переход выполнения программы к новому участку кода , что называется «без
видимых на то причин». Безусловный переход используется, в основном, при
низкоуровневом программировании на языках типа assembler и не
рекомендуется к применению не только при объектно – ориентированном
программировании на С++, но даже и при модульном программировании на
языке С.
2.1 Ветвление
Ветвление представляет из себя принятие решения о том, в каком
направлении продолжать выполнение программы и реализуется двумя
конструкциями: «if – else» и «switch».
2.1.1. Конструкция «if – else»
При использовании конструкции «if – else» решение о выборе
последующих действий производится на основании выражения, имеющего
булевый тип или приводимый к таковому. Данная конструкция схожа с
тернарным условным оператором (вернее, сам оператор был введен в язык, как
более удобная в использовании запись часто употребимой условной
конструкции). Если выражение в скобках верно, то будет выполнена
инструкция, расположенная следом за этими скобками, иначе та, что после
идентификатора «else». Данная конструкция может выполнять не только
отдельные инструкции, но и группы, благодаря наличию в языке блоков
инструкций, синтаксически представляемых одной инструкцией. В
приведенном ниже листинге условная конструкция выполняет теже действия,
что и условный оператор.
Листинг 2.1. Конструкция if-else.
#include <iostream>
using namespace std;
int main (void)
{
bool x;
cin >> x;
cout << (x ? "true" : "false") << endl;
if (x)
cout << "true" << endl;
else
cout << "false" << endl;
return 0;
}
Важным аспектом при использовании данной конструкции является то,
что она может быть вложена одна в другую. Также при использовании
условной конструкции допустимо опускать определение действия в том случае,
если его и не требуется. Однако, в таком случае, необходимо следить за тем,
чтобы каждая «else» – часть соответствовала своей «if» – части. Так, если в
нижеследующем листинге не использовать «{}» для первого «if», то любое
число вне заданного интервала, в том числе и большее десяти, будет
определяться, как отрицательное, т.е. «else» – часть будет отнесена ко второму
«if».
Листинг 2.2. Вложенность if-else.
#include <iostream>
using namespace std;
int main (void)
{
int y;
cin >> y;
if (y >= 0) {
if (y < 10)
cout << "in range" << endl;
} else {
cout << "negative" << endl;
}
return 0;
}
2.1.2. Конструкция «switch»
В некоторых случаях сравнение ветвления выполняется несколько раз
подряд, при этом результат ветвления зависит от значения единственной
переменной. Такую сложную для восприятия последовательность «if – else»
позволяет заменить конструкция «switch». В листинге данная конструкция
идентична каскаду «if – else».
Листинг 2.3. Конструкция switch.
#include <iostream>
using namespace std;
int main (void)
{
int x;
cin >> x;
cout << "if-else: ";
if (x == 0) {
cout << "0" << endl;
} else if (x == 1) {
cout << "1" << endl;
} else if (x == 2) {
cout << "2" << endl;
} else {
cout << "not 0, 1, 2" << endl;
}
cout << "switch: ";
switch (x)
{
case 0:
cout << "0" << endl;
break;
case 1:
cout << "1" << endl;
break;
case 2:
cout << "2" << endl;
break;
default:
cout << "not 0, 1, 2" << endl;
break;
}
return 0;
}
Данная конструкция выполняет инструкции начиная с той, для которой
значение константы в строке «case» совпадает со значением используемой
переменной, вплоть до первого оператора «break» или, если его нет, до конца
конструкции. Т.е. оператор «break» осуществляет выход из самой конструкции.
Подобное поведение объясняется тем, что данная конструкция реализуется
переходами по меткам, как при использовании инструкции «goto».
Инструкция, следующая за «default», будет выполнена, если не
произошло ни одного совпадения. Другими словами, она определяет действие
по умолчанию. Любая инструкция может быть заменена заключенной в «{}»
группой – блоком.
2.2. Циклы
Циклы используются в тех случаях, когда необходимо выполнить одну и
туже или похожую последовательность действий несколько раз. В языке С++
для этих целей предусмотрены три инструкции: «for», «while» и «do – while».
Последовательность действий, заданная в теле цикла, будет повторяться
до тех пор, пока условное выражение цикла верно, т.е. значение данного
булевого выражения будет иметь истинное значение. Всвязи с этим циклы
можно разделить на два типа: с заранее известным и неизвестным количеством
повторений – итераций.
Вне зависимости от используемого типа цикла, выход из него может
быть произведен с помощью оператора «break», а завершение текущей
итерации и переход к следующей – с помощью оператора «continue».
Один и тотже цикл может быть реализован различными инструкциями, с
применением операторов «break» и «continue» или без них. Выбор конкретной
реализации зависит только от программиста и его целей. Все листинги данного
раздела не призваны быть наиболее эффективными или эталонными для
решения
поставленных
задач,
но
демонстрируют
возможности
предоставляемые используемыми в каждом примере циклами.
2.2.1. Цикл «for»
Цикл с заранее известным числом итераций удобнее реализуется с
помощью инструкции «for». Типичный цикл «for» выглядит следующим
образом.
Листинг 2.4. Квадраты чисел от 0 до 9.
#include <iostream>
using namespace std;
int main (void)
{
for (int i = 0; i < 10; i++)
cout << i*i << " ";
return 0;
}
В примере, в строке, инициализирующей цикл, задано три выражения,
определяющих поведение цикла. Первое выражение инициализирует некоторые
данные, являющиеся для цикла некоторой точкой отсчета. При использовании
цикла «for» удобнее всего этим выражением инициализировать счетчик
количества итераций. Второе выражение задает условие выполнения итераций.
Чаще, это сравнение или логическое выражение, но может использоваться все,
что приводимо к типу «bool». Последнее выражение определяет действие,
выполняемое по окончании каждой итерации. Обычно это инкремент или
декремент счетчика, значение которого в итоге перестает удовлетворять
сравнению во втором выражении. Следующий пример запрашивает два числа и
возводит первое в степень второго.
Листинг 2.5. Возведение в степень.
#include <iostream>
using namespace std;
int main (void)
{
unsigned int x;
int y, z;
cin >> y >> x;
for (z = 1; x > 0; x--)
z *= y;
cout << z << endl;
return 0;
}
При инициализации цикла «for» отдельные выражения могут быть
опущены, при этом символ двоеточия быть должен.
Листинг 2.6. Подсчет факториала вводимого числа.
#include <iostream>
using namespace std;
int main (void)
{
unsigned int x;
cin >> x;
unsigned int y = 1;
for ( ; x > 0 ; )
y *= x--;
cout << y << endl;
return 0;
}
2.2.2. Цикл «while»
Цикл «while» инициализируется единственным выражением – условным,
при этом проверка условия выполняется перед очередной итерацией. Такой
цикл называется циклом с предусловием.
Листинг 2.7. Возведение в степень.
#include <iostream>
using namespace std;
int main (void)
{
unsigned int x;
int y, z = 1;
cin >> y >> x;
while (x--)
z *= y;
cout << z << endl;
return 0;
}
2.2.3. Цикл «do – while»
Цикл «do – while» аналогичен циклу «while», за исключением того, что
проверка условия выполняется после тела цикла, в связи с чем «do – while»
называется циклом с постусловием, т.е. первая итерация выполняется вне
зависимости от истинности условия.
Листинг 2.8. Поиск делителей вводимого числа.
#include <iostream>
using namespace std;
int main (void)
{
unsigned int x;
cin >> x;
unsigned int i = x;
do
cout << i << ( x % i ? " no" : " yes" ) << endl;
while (--i);
return 0;
}
2.2.4. Операторы циклов
В ряде случаев, в особенности при критичности времени вычислений,
выполнение отдельных итераций не является необходимым, более того,
увеличивает общее время выполнеия. Избежать этих итераций помогает
оператор «continue». Другой оператор – «break» – вовсе прекращает
выполнение текущего цикла, что также позволяет избежать неоправданного
увеличения времени исполнения.
Операторы «continue» и «break» могут использоваться в любых циклах,
будь то «while», «for» или «do – while». Так следующая программа вычисляет
квадраты всех четных чисел в интервале введенных значений, пропуская
вычисление квадратов нечетных чисел.
Листинг 2.9. Использование инструкций continue и break.
#include <iostream>
using namespace std;
int main (void)
{
unsigned int x, y;
cin >> x >> y;
while (1) {
x++;
if (x % 2)
continue;
if (x >= y)
break;
cout << x << " : " << x*x << endl;
}
return 0;
}
2.2.5. Вложенность
Любой из приведенных циклов может быть вложен в любой другой, т.е.
тело одного одного цикла может содержать другой, при этом все переменные,
объявленные в теле цикла верхнего уровня доступны и во вложенных циклах,
что справедливо и для инициализирующих эти циклы выражений.
n
m
Следующий пример вычисляет  cos i j .
i 1 j 1
Листинг 2.10. Применение вложенности циклов при решении задач
нахождения сумм и произведений.
#include <iostream>
#include <cmath>
using namespace std;
int main (void)
{
unsigned int n, m;
cin >> n >> m;
double P = 1;
for (int i = 1; i <= n; i++)
double E = 0;
for (int j = 1; j <= m; j++)
E += pow(cos(j), i);
P *= E;
}
cout << P << endl;
{
//для каждого сомножителя;
//для каждого слагаемого;
return 0;
}
Особое внимание стоит уделить расстановке операторных скобок «{}» с
тем, чтобы не возникло путаницы вложенности. Операторы «continue» и
«break» выполняются для текущего уровня вложенности.
n
m
 j  1, ( j mod 3)  0
.
1, ( j mod 3)  0
Следующий пример вычисляет  y (i, j ), y  
i 1 j 1
Листинг 2.11. Использование инструкций continue и break.
#include <iostream>
#include <cmath>
using namespace std;
int main (void)
{
int n, m;
cin >> n >> m;
double E = 0;
for (int i = 1; i <= n; i++) {
double P = 1;
for (int j = 1; j <= m; j++) {
if (j % 3)
continue;
//пропуск итерации при ( j mod 3)  0 ;
P *= j - i;
}
E += P;
}
cout << E << endl;
return 0;
}
2.3. Упражнения
2.3.1. Ветвления
Найти решения заданных функций, используя ветвления «if – else»,
«switch» или условный оператор. Число «x» вводится пользователем с
клавиатуры.
 x, x  0,
 x, x  0;
cos 2 (3 x), x  0,5,
11. 
1. 
sin x 3 , x  0,5;
 x 3  2 x, x 2  1,
2.  2
2
 x  3 x, x  1;
cos x, sin x  0,
3.  3
2
cos x, sin x  0;
 3
10 x
 5,
 x  8,
3
4. 
2
 x 8  3x, 10 x  5;

3
2
10 cos 3 x,23 x 2  6,

5. 7 sin 4 x,23 x 2  6,

2
2
3
12 tan x,2 x  6;
log 10 x, ( x mod 10)  0,
ln x, ( x mod 10)  0;
6. 
x , x  0,
x , x  0;
7. 
e, x  10,
 2
e , x  100,
12.  3
e , x  1000,
0, x  10,100,1000;

x
10 , x  10,

13. e x ,0  x  10,
 x 2 , x  0;


 3
 x 5  6 , log 10 x  3,

6
14.  5 , log 10 x  3,
x  3
 x  9, log 10 x  3;


arctan x, x  0,
arctan(  x), x  0;
15. 
arcsin x, ( x mod 2)  0,
arccos x, ( x mod 2)  0;
16. 
 x2
3
 , x  10,
3

8. 3x 2 , x 3  10,

 x 2 , x 3  10;

ln( x 2 ), x  0,
9. 
ln(  x), x  0;
sin x, x  1,
cos x, x  2,
10. 
tan x, x  3,
 x, x  1,2,3;
 ln x
 x 2 , x  0,
 x
e
17.  2 , x  0,
x
0, x  0;


log 10 x 2 , x  20,
18. 
ln x, x  20;
1, x  0,

19. e,0  x  1,
ln x, x  1;

 sin 3x
, x  0,
20.  3 x 5
0, x  0;

2.3.2. Циклы
Найти решения заданных функций, используя циклы «for», «while», «do
– while». Число «n» вводится пользователем с клавиатуры.
n
1.
 (sin 2 i  cos i 2 )
i 0
n
2.
 ln( 3i
2
n
i 1
n

5
i 0
5 x 3 , (i mod 2)  0,
4.  y (i), y  
i 0
3 x 5 , (i mod 2)  0,
14.  y(i), y  
i 10
 (10 log
15. 
7
i )
10
i 1
i 1
ln i, (i mod 10)  0,
log 10 i, (i mod 10)  0;
n
n
n
i4
8i 2
n
16.  e cos 3 (6i)
n
 tan i 
2
i 0
i 0
7 sin 3i, i  0,

17.  y(i), y  2 cos 5i, i  0,
i  n
1, i  0;

n
7.
12i 2
7i 2  8
n
n
6.
cos i 2
2i
13.  5 log 10 i
7  i3
i 0
5.
i 1
12. 
)
i 1
3.
n
11. 
 e 
n
i
i 0
i3
8. 
i 1 5i  7
n
n
18.  tan i
i 0

cos i, i  0,
n
9.
 y(i), y  sin

i  n
2
2
i, i  0;
19.  3 i 2 
n
i 1
n
10. 
i 1
cos 7 i 8
5i 3
n
20.  6ln i 
i 1
2.3.3. Вложенность
Найти решения заданных функций, используя циклы «for», «while», «do
– while». Числа «n» и «m» вводятся пользователем с клавиатуры.
n
1.
n
m
 (i  j )
11 .
i 1 j 1
n
2.
3.
i 1
j 1
n
n
10
3 j
 (cos i  sin j)
cos 2 j
4 . 
i  0 j  0 3  sin i
m
n
5.
 tan( ij )
n
13 .
7.
)
17 .
cos 2 i

2
i 1 j 1 sin j
19 .
m
10 .
 e
i 1 j 1
i
10 j
n
 (ln j  log
10
i)
m
 (ie  ln j)
i 1 j 1
18 .
m
j
 i
n
2
 (cos 2 3i  sin 3 2 j)
n
m
i 1 j 1
j 1
n
n
m
 sin( 2 cos(i  j))
n
16 .
i 0 j 0
9.
ij
i 1 j 1
m
n
10
i 0 j 0
i 0 j 0
8.
3
n
 log
n
14 .
m
 (2i  j
3
i 1 j 1
15 .
6 .  log 10  (i  j )
n
m
i 1 j 1
i 0 j 0
i 1
 tan i cot j 
n
m
n
i
n
12 .
i 0 j 0
n
 ln j 
i 1 j 1
m
 i log
n
log 10 (i 2 )

i 1 j 1 log 10 (10 j )
n
m
n
m
 sin( 7i) cos( j
i 0 j 0
n
20 .
n
 e  (2 j
i
i 0
j 0
1
3
 1)
3
)
3. Указатели. Массивы. Структуры
Память любого С++ – приложения делится на две части: область «heap»
и область «stack». Наглядной абстракцией для представления стека может
служить колода карт. Так в стеке верхняя карта – текущая область видимости
программы, например, область видимости выполняемой в данный момент
функции. В тот момент, когда текущая функция вызывает очередную, то уже
новая текущая карта располагается поверх данной, а при завершении ее работы
– удаляется. Такие карты часто называют фреймами. Стековые фреймы
эффективны для реализации изолированной работы функций. Другая область –
«heap» – не зависит от текущего стекового фрейма. В эту область можно
поместить и переменные, объявленные в теле функции, с тем, чтобы они
существовали после ее завершения («static»). Область «heap» не является
структурированной, по этому представима как некоторый набор ячеек памяти.
3.1. Указатели
Язык С++ предусматривает два способа выделения памяти для объектов:
статическое выделение во время компиляции, и динамическое – во время
выполнения программы. Статическое выделение памяти является более
эффективным, однако размер того или иного объекта не всегда известен до
компиляции, что делает динамическое выделение памяти более гибким.
Реализация указателей происходит посредством встроенного типа,
объекты которого хранят адреса других объектов. Указатель объявляется, как
указатель на определенный тип, и не может указывать на объекты другого типа.
Исключением является «void» – указатель, способный указывать на объекты
любого типа. Однако, такие указатели не могут быть разыменованы.
Для работы с указателями необходимы две специальные операции:
взятие адреса «&» и разыменование «*», называемое также косвенной
адресацией. Результат операции «&» – адрес объекта, а использование «*»
позволяет обратиться к значению в памяти, на которую указывает
разыменовываемый объект.
К числу необходимых для динамического
выделения памяти операторов относятся операторы «new» и «delete»,
аналогичные С – функциям «malloc()» и «free()». Следующий листинг дает
первоначальное представление об указателях.
Листинг 3.1. Применение указателей.
#include <iostream>
using namespace std;
int main (void)
{
int i = 1;
int *pint = &i;
//указатель на int – переменную i;
*pint = 2;
//косвенное присвоение i числа 2;
cout << i << endl;
int j = 3;
pint = &j;
(*pint)++;
cout << j << endl;
//указатель на int – переменную j;
//косвенный инкремент j;
//выделение участка памяти размером int, занесение туда числа 255 и
//создание указателя на данный участок;
int *ptr = new int(255);
//вывод адреса участка памяти и хранящегося в нем значения;
cout << ptr << " : [ " << *ptr << " ] " << endl;
//освобождение связанной с указателем памяти;
delete ptr;
return 0;
}
Особое внимание следует уделить использованию скобок в строке с
инкрементом. При их отсутствии увеличилось бы не то значение, на которое
указывает «pint», а адрес, хранимый указателем. При этом, адрес был бы
увеличен на число, равное размеру типа указателя.
Листинг 3.1. Инкремент указателя и приоритет разыменования.
#include <iostream>
using namespace std;
int main (void)
{
double *pint = new double(0);
cout << pint << " + " << sizeof(*pint) << " = ";
*pint++;
cout << pint << endl;
return 0;
}
Указатель, как и другие объекты, может быть объявлен как константа.
Существенное влияние на интерпретацию спецификатора «const» оказывает его
положение. В случае если «const» поставлен перед символом «*», он защищает
от изменения то значение, которое адресует указатель. В случае, если «const»
поставлен после указанного символа, он защищает сам адрес, хранимый
указателем.
3.2. Массивы
Массивы предназначены для хранения ряда однотипных значений так,
чтобы доступ к отдельным значениям был возможен по их позиции в этом
массиве. Элементы массива располагаются в памяти последовательно, занимая
идущие друг за другом ячейки памяти. Так как память для массива выделяется
в момент компиляции, его размерность должна быть известна заранее, другими
словами, она должна являться константой.
3.2.1. Статические массивы
Массив индексируется с нуля, поэтому и номер его последнего элемента
на единицу меньше его размера. Определение массива происходит с
использованием фигурных скобок, в которых помещаются требуемые значения.
При определении значений элементов массива без указания его размера, он
вычисляется путем подсчета числа элементов в «{}» – списке инициализации. В
случае, если список неполный, оставшиеся элементы инициализируются
нулевыми значениями, что справедливо и для вовсе пустого списка. Список
инициализации, превышающий размер массива, вызовет ошибку. Доступ к
элементам массива осуществляется с помощью операции «[]» с указанием
индекса элемента.
Листинг 3.2. Массивы.
#include <iostream>
using namespace std;
int main (void)
{
int array1[10];
for (int i = 0; i < 10; i++)
array1[i] = i;
int array2[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int array3[2*5] = { 0, 1, 2, 3, 4 };
for (int i = 0; i < 10; i++)
cout << array1[i] << " " << array2[i] << " " << array3[i] << endl;
return 0;
}
Массивы в С++ могут быть многомерными. Такие массивы
располагаются в памяти, также как и одномерные. Обращение к элементам
массива происходит через обращение к подмассивам. Инициализацию
многомерных массивов продемонстрирует следующий листинг.
Листинг 3.3. Многомерные массивы.
#include <iostream>
using namespace std;
int main (void)
{
const int dasize = 3;
double darray2d[dasize][dasize] = { { 0, 1, 2 }, { 3, 4, 5 }, { 6, 7, 8 } };
for (int i = 0; i < dasize; i++)
{
for (int j = 0; j < dasize; j++)
cout << darray2d[i][j] << " ";
cout << endl;
}
const int fasize = 10;
float farray4d[fasize][fasize][fasize] = {};
return 0;
}
3.2.2. Динамические массивы
В том случае, если количество элементов массива неизвестно заранее,
можно воспользоваться одним из двух способов. Первый заключается в том,
чтобы заранее выделить в стековом фрейме достаточное количество памяти,
указав такую размерность, которая, как ожидается, не будет превышена.
Однако мало того, что такой подход крайне неэффективен в аспекте
занимаемой программой памяти, так еще все же может быть превышена
указанная размерность. При втором подходе, память для элементов массива
выделяется динамически, а доступ к ней производится через указатель.
Важным аспектом здесь является тот факт, что доступ к обычному массиву
происходит по указателю на его первый элемент, а адрес некоторого элемента
массива представляет собой число, равное сумме адреса начального элемента и
произведения индекса требуемого элемента на тип элементов массива. Так
третий элемент массива «int» – значений (для «int», размер которого составляет
четыре байта), расположенного начиная с адреса, к примеру, 0x22FF00, имеет
адрес 0x22FF0С.
Благодаря этому, после выделения памяти работа с идентификатором
указателя на динамически выделенную память происходит также, как и с
идентификатором обычного массива.
Листинг 3.4. Динамические массивы.
#include <iostream>
using namespace std;
int main (void)
{
unsigned int x;
int *pinta;
cin >> x;
//выделение памяти под x значений типа int;
pinta = new int[x];
for (int i = 0; i < x; i++)
pinta[i] = i;
//операция освобождения памяти для массивов имеет «[]»;
delete[] pinta;
return 0;
}
При использовании многомерных динамических массивов следует
учитывать тот факт, что динамические подмассивы не располагаются в
смежных ячейках. В связи с этим, для создания, к примеру, двухмерного
массива необходимо создать указатель на одномерный массив, каждый элемент
которого, в свою очередь, будет также являться указателем на массив. Создание
каждого отдельного подмассива, как и его удаление, должно быть явным.
Листинг 3.5. Многомерные динамические массивы.
#include <iostream>
using namespace std;
int main (void)
{
unsigned int x, y;
cin >> x >> y;
//указатель на массив указателей на миссив
// из x элементов типа int каждый;
int **pinta = new int*[x];
//явное создание подмассивов;
for (int i = 0; i < x; i++)
pinta[i] = new int[y];
//явное удаление подмассивов;
for (int i = 0; i < x; i++)
delete[] pinta[i];
delete[] pinta;
return 0;
}
3.3. C – Строки
В языке С++ существует два вида строк. Первый тип – строки в стиле
языка С, представляющие из себя «char» – массивы с нулевым завершающим
элементом. Работа с такими строками не отличается от работы с
обыкновенными «char» – массивами. В стандартном библиотечном файле
«cstring» объявлены функции по работе с С – строками.
Вторым типом строк являются объекты стандартного класса «string».
Данный класс будет рассмотрен позднее.
Таблица 3.1. Основные функции по работе с С – строками.
Определение
char *strcat(str1, str2),
char *str1, *str2;
char *strncat(str1, str2, n);
char *str1, *str2;
int n;
int strcmp(str1, str2);
char *str1, *str2;
int strncmp(str1, str2, n);
char *str1, *str2;
int n;
char *strcpy(str1, str2);
char *str1, *str2;
char *strncpy(str1, str2, n);
char *str1, *str2;
int n;
int strlen(str);
char *str;
char *strchr(str, n);
char *str;
int n;
char *strrchr(str, n);
char *str;
int n;
char *strpbrk(str1, str2);
Описание
Сцепление двух строк
Сцепление двух строк, где из второй берется не более n
символов
Сравнение двух строк в лексикографическом порядке
Сравнение первых n символов двух строк в
лексикографическом порядке
Копирование строки str1 в строку str2
Копирование не более первых n символов строки str1 в
строку str2
Определение длинны строки (без нулевого символа)
Нахождение первого вхождения символа n
Нахождение последнего вхождения символа n
Нахождение в строке str1 любой символ из строки str2
char *str1, *str2;
int strspn(str1, str2);
char *str1, *str2;
int strcspn(str1, str2);
char *str1, *str2;
char *strtok(str1, str2);
char *str1, *str2;
Определение длинны части строки str1, содержащей
символы из строки str2
Определение длинны части строки str1, не содержащей
символы из строки str2
Выделение из строки str1 отрезков, разделенных
символами из строки str2
Листинг 3.6. Подсчет длинны С – строки.
#include <iostream>
#include <cstring>
using namespace std;
int main (void)
{
char *cstr = "Hellow, World!";
char *pstr = cstr;
//указатель на начало строки;
int i = 0;
do
{
//подсчет количества итераций;
i++;
//инкремент указателя, пока он не
//станет ссылаться на конец строки;
} while (*++pstr);
//вывод строки;
cout << cstr << endl;
//длинна через счетчик;
cout << i << endl;
//длинна через указатели;
cout << pstr - cstr << endl;
//длинна через стандартную функцию;
cout << strlen(cstr) << endl;
return 0;
}
Стандартный класс «string» предоставляет больше функциональности и
удобства, чем строки в стиле языка С. Однако, в ряде задач данный класс не
может быть использован, в таких случаях применяются старый формат строк.
Примером может служить разбор параметров командной строки.
Функции «main()» оболочкой передается количество входных
параметров и сами параметры, с которыми программа была вызвана. Однако,
эти параметры могут быть проигнорированы, если определить функцию
«main()» как непринимающую значений. Для количества параметров обычно
используется «int» – переменная с именем «argc», а для самих параметров –
указатель на С-строки «argv», с которым можно работать как с двухмерным
массивом символов. Первым параметром всегда является имя самой
программы. Любой параметр может быть задан в двойных кавычках «""», тогда
строка – параметр будет завершаться не символом пробела, а завершающей
кавычкой, что позваляет задавать параметры с внутренними разделительными
символами.
Листинг 3.7. Удаление из строки всех вхождений символа.
/*Заданы должны быть три параметра: имя программы (по умолчанию),
* строка для удаления символов и сам удаляемый символ,
* представляемый в программе массивом из одного элемента.
*/
#include <iostream>
#include <cstring>
using namespace std;
int main (int argc, char *argv[])
{
if (argc == 3 && !argv[2][1]) {
//если параметры верны;
for (int i = 0; i < strlen(argv[1]); i++) //для всех символов строки;
if (argv[1][i] == argv[2][0])
//если найден искомый;
for (int j = i; j < strlen(argv[1]); j++) //начиная с него;
argv[1][j] = argv[1][j+1]; //смещение на один символ;
cout << argv[1] << endl;
} else {
cout << "use two params - word and char" << endl;
}
return 0;
}
Смещение всех символов на одну позицию влево «затирает» найденный
символ, уменьшая размер строки. Изменение размера строки происходит
посредством смещения завершающего нулевого символа. Однако, следует
помнить, что функция «strlen()» определяет длину строки без учета этого
символа.
3.4. Ссылки
Ссылка является альтернативным именем объекта и обязательно должна
быть инициализирована. Над ссылками не могут выполняться какие-либо
действия, поэтому она всегда ссылается на тот объект, которым была
инициализирована.
Листинг 3.8. Применеие ссылок.
#include <iostream>
using namespace std;
int main (void)
{
int x = 1;
int &y = x;
//ссылка на переменную x;
cout << y << endl << x << endl << endl;
y++;
cout << x << endl;
x++;
cout << y << endl;
return 0;
}
По
сути,
ссылка
является
константным
указателем,
разыменовывающимся при каждом обращении, и над которым не может быть
выполнено никаких операций. При использовании ссылок отсутствует
необходимость проверять ее, как указатель, на неравенство нулю, так как она
всегда адресует какой-либо объект. В связи с чем, к примеру, если параметр
функции должен ссылаться на разные объекты во время исполнения или
принимать нулевое значение, следует использовать параметр – указатель.
3.5. Структуры
Структура представляет собой переменные или функции, объединенные
для удобства работы с ними, так как они могут трактоваться и в качестве
самостоятельных элементов, и как единое целое. Простейшим примером
структуры может быть точка в пространстве – тройка значений координат.
Важным аспектом при работе со структурами является их возможность
выступать и в роли параметров при вызове функции, и в роли возвращаемого
функцией значения, при этом передачу больших структур удобнее
осуществлять не посредством передачи значения, а с использованием
механизма указателей или ссылок. В случае с указателем, вместо записи,
обусловленной приоритетами операций, «(*структура).элемент» удобнее
использовать «структура->элемент».
Листинг 3.9. Примеры структур.
#include <iostream>
using namespace std;
//точка в пространстве – набор из трех координпт;
struct Point
{
double x;
double y;
double z;
};
int main (void)
{
Point p;
p.x = 1;
p.y = 2;
p.z = 3;
p.z++;
cout << p.x << " " << p.y << " " << p.z << endl;
Point *ptrp = &p;
ptrp->y++;
cout << p.x << " " << p.y << " " << p.z << endl;
return 0;
}
Объявление переменных типа структуры может происходить
непосредственно за определением самой структуры. Это могут быть, в том
числе, и массивы.
Листинг 3.10. Массивы и структуры.
#include <iostream>
using namespace std;
//задание массива из четырех точек;
struct Point
{
double x;
double y;
double z;
} curve[4] = { { 1.2, 1.7, 1.4 },
{ 2.1, 2.8, 2.3 },
{ 2.2, 2.8, 4.3 },
{ 3.3, 3.1, 4.1 } };
int main (void)
{
for (int i = 0; i < 4; i++)
cout << curve[i].x << " " << curve[i].y << " " << curve[i].z << endl;
return 0;
}
Структуры применяются также для создания так называемых. битовых
полей. В такой структуре размер каждого элемента указывается в битах после
символа двоеточия и все они имеют одинаковый тип. Размер такой структуры
определяется целым числом количества байт, необходимого для хранения
одного элемента указанного типа, и вмещающего все длинны элементов. Так
например, битовое поле из семи «char» – элементов имеет размер одни байт –
размер типа «char», а поле из девяти элементов тогоже типа – два байта.
Листинг 3.11. Битовые поля.
#include <iostream>
using namespace std;
//битовое поле;
struct Flags
{
char x : 1;
char y : 1;
char z : 6;
} f;
int main (void)
{
int x;
cin >> x;
f.x = x;
cout << sizeof (f) << endl;
return 0;
}
3.6. Объединение
Унаследованным из языка C является и тип «union» – объединение.
Объединение может хранить данные любого из объявленных в ней типов, при
этом в нем в каждый момент времени хранится значение только одного типа.
Листинг 3.12. Объединения.
#include <iostream>
using namespace std;
union varvalue
{
int ival;
float fval;
double dval;
} varval;
int main (void)
{
cin >> varval.ival;
//теперь в объединении – целочисленное значение;
cout << varval.ival << " " << varval.fval << " " << varval.dval << endl;
cin >> varval.fval;
//теперь в объединении – дробное значение;
cout << varval.ival << " " << varval.fval << " " << varval.dval << endl;
cin >> varval.dval;
//теперь в объединении дробное значение другого типа;
cout << varval.ival << " " << varval.fval << " " << varval.dval << endl;
return 0;
}
3.7. Переопределение типов
Средство «typedef» предоставляет возможность давать типам данных
новые имена. Данное средство применимо не только к стандартным
встроенным типам, но и к более сложным объявлениям.
Листинг 3.13. Переопределение типов.
#include <iostream>
using namespace std;
//создание псевдонима для указателя на символ;
typedef char* String;
//структура – запсь из двух строк;
struct Person
{
String name;
String surname;
};
//создание псевдонима для целогочисленногг значения;
typedef int Number;
//структура – запись из двух строк и номер;
struct Record
{
Number number;
Person person;
};
int main (void)
{
Record rec;
rec.person.name = "Ivan";
rec.person.surname = "Ivanov";
rec.number = 1;
cout << rec.number << ". " << rec.person.name
<< " " << rec.person.surname << endl;
return 0;
}
3.8. Упражнения
Выполнить задания из разделов «3.8.1. Статические и динамические
массивы» и «3.8.2. Многомерные массивы» в двух вариантах.
1. Для произвольного массива, элементы которого имеют любой из
встроенных числовых типов, с заданным числом элементов
(статический).
2. Для массива, элементы которого имеют любой из встроенных числовых
типов, размерность которого и значения элементов задаваемых
пользователем с клавиатуры (динамический).
3.8.1. Статические и динамические массивы
1. Найти и вывести на экран наименьший элемент.
2. Найти и вывести на экран разность модулей максимального и
минимального элементов.
3. Найти и вывести на экран, все пары соседних элементов, где первый
меньше второго.
4. Найти и вывести на экран все положительные элементы.
5. Найти и вывести на экран сумму всех положительных элементов.
6. Найти и заменить все максимальные элементы их отрицательными
значениями.
7. Найти и вывести на экран индексы всех нечетных элементов.
8. Найти и вывести на экран все элементы, индексы которых кратны пяти.
9. Найти и вывести на экран два элемента, стоящие рядом и отличающиеся
на единицу.
10.Найти и вывести на экран наибольший элемент.
11.Найти и вывести на экран индекс элемента, модуль которого является
максимальным.
12.Найти и вывести на экран все элементы, кратные пяти.
13.Найти и вывести на экран все элементы, индексы которых кратны трем.
14.Найти и вывести на экран индексы всех четных элементов.
15.Найти и вывести на экран, все пары соседних элементов, где первый
больше второго.
16.Найти и поменять местами максимальный и минимальный элементы.
17.Найти и вывести на экран все отрицательные элементы.
18.Найти и вывести на экран количество отрицательных элементов.
19.Найти и заменить все минимальные элементы их отрицательными
значениями.
20.Сдвинуть массив циклически на два элемента влево.
3.8.2. Многомерные массивы
1. Поменять местами первую строку со строкой, в которой расположен
первый минимальный элемент.
2. Заменить все расположенные ниже главной диагонали отрицательные
числа нулями.
3. Вычислить сумму всех чисел всех строк, индексы которых являются
четными числами.
4. Заменить все расположенные выше дополнительной диагонали
отрицательные числа значениями модуля этих чисел.
5. Поменять местами последний столбец со столбцом, в котором
расположен первый максимальный элемент.
6. Поменять местами попарно все столбцы, неучитывая последний в случае
нечетного их количества.
7. Вычислить среднее арифметическое элементов первого столбца, в
котором расположено наибольшее количество отрицательных чисел.
8. Отобразить зеркально относительно середины все элементы столбца, в
котором расположен элемент, модуль которого является минимальным.
9. Зеркально отобразить массив относительно дополнительной диагонали.
10.Заменить все расположенные выше главной диагонали положительные
числа их отрицательными значениями.
11.Выполнить операцию, аналогичную нахождению определителя матрицы.
12.Поменять местами первый столбец со столбцом, в котором расположен
первый минимальный элемент.
13.Вычислить произведение всех чисел всех столбцов, индексы которых
являются нечетными числами.
14.Вычислить среднее арифметическое элементов последней строки, в
которой расположено наименьшее количество положительных чисел.
15.Отобразить зеркально относительно середины все элементы строки, в
которых расположен элемент, модуль которого является максимальным.
16.Поменять местами попарно все строки, неучитывая последнюю в случае
нечетного их количества.
17.Зеркально отобразить массив относительно главной диагонали.
18.Поменять местами последнюю строку со строкой, в которой расположен
первый максимальный элемент.
19.Заменить все расположенные ниже дополнительной диагонали
положительные числа их увеличенными вдвое значениями.
20.Выполнить операцию, аналогичную транспонированию матрицы.
3.8.3. Строки
Выполнить для строки («strarg») и двух символов («charg1», «charg2»),
полученных в качестве параметров, передаваемых при вызове.
1. Подсчитать количество вхождений символа «charg1» в строку «strarg».
2. Вставить символ «charg1» после каждого вхождения символа «charg2».
3. В зависимости от значения «charg2» вывести символ перед «charg1» или
после него.
4. Вставить символ «charg1» перед каждым вхождением символа «charg2».
5. Подсчитать количество символов между символами «charg1» и «charg2».
6. Удалить все символы из строки «strarg» после последнего вхождения
символа «charg1».
7. Подсчитать количество символов в строке «strarg» после последнего
вхождения символа «charg1».
8. В зависимости от значения «charg2» удалить из строки «strarg» символ
после «charg1» или вставить «charg2» после него.
9. Удалить из строки «strarg» символ, следующий за символом «charg1».
10.Вставить символ «charg1» в конец строки «strarg».
11.Подсчитать длину строки «strarg» без учета вхождений символа
«charg1».
12.Определить, равны ли части строки «strarg» до символа «charg1» и после
«charg2».
13.В зависимости от значения «charg2» вывести часть строки «strarg» до
символа «charg1» или после него.
14.Определить, равны ли части строки «strarg» до символа «charg1» и после
него.
15.Удалить все символы из строки «strarg» перед первым вхождением
символа «charg1».
16.Подсчитать количество символов в строке «strarg» до первого вхождения
символа «charg1».
17.Удалить из строки «strarg» символ, предшествующий символу «charg1».
18.Вставить символ «charg1» в начало строки «strarg».
19.Разбить строку «strarg» на подстроки, разделенные символом «charg1».
20.Определить, равны ли части строки «strarg», первая из которых
заключена между символами «charg1» и «charg2», а вторая –
объединение частей строки до символа «charg1» и после «charg2».
4. Функции
4.1. Простые функции
Функции применяются для разделения больших задач на подзадачи и
используются для замены часто повторяющихся участков кода. Такой подход
скрывает детали реализации тех или иных действий, что делает код более
понятным. Применение функций
также способствует более легкому
сопровождению кода и его уменьшению.
Часто в процессе программирования прибегают к такому методу, как
нисходящее проектирование. При таком подходе, задача с самого начала
реализации разбивается на подзадачи, каждая из которых, в свою очередь,
также разбивается на подзадачи и т.д. В итоге, конкретные действия
реализуются на последних этапах написания кода.
Каждая функция имеет свое собственное имя. Параметры, передаваемые
функции, должны содержаться в скобках и следовать через запятую.
Результатом функции является значение определенного типа, называемое
возвращаемым, за исключением того случая, когда функция объявлена, как
вовсе не возвращающая значения при помощи «void». Все выполняемые
функцией действия – тело функции – заключаются в фигурные скобки.
Совокупность имени функции, типа возвращаемого значения, списка
параметров и выполняемых ей действий называется определением. Функция
может быть определена только один раз, за исключением «inline» – функций
(встраиваемых). Помимо определения, функция может иметь объявление –
прототип – совокупность типа возвращаемого значения, имени функции и
списка параметров, которое может быть не единственным.
Вызов функции для исполнения происходит посредством оператора
вызова «()», при этом внутри скобок должны быть перечислены фактические
параметры функции – передаваемые ей значения.
Листинг 4.1. Функция вывода элементов массива.
#include <iostream>
using namespace std;
//Определение функции.
void array_out (int iar[], const int N)
{
for (int i = 0; i < N; i++)
cout << iar[i] << endl;
}
int main (void)
{
const int N = 10;
int x[N] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
//вызов функции, невозвращающей значения и принимающей
//значения указателя на первый элемент массива и его размер;
array_out(x, N);
return 0;
}
Вызов функции может быть выполнен двумя способами. Если
объявление содержит спецификатор «inline», то тело функции подставляется в
место ее вызова, иначе – происходит обычный вызов с передачей управления,
при этом, «inline» – функции всегда должны иметь определение. Однако, этот
спецификатор является только «рекомендацией» компилятору, который может
и не встроить данную функцию в место ее вызова. Функция должна быть
объявлена до своего вызова, однако может быть определена после него.
Оператор «return» выполняет возврат значения из функции, если функция не
имеет в качестве возвращаемого значения тип «void», а также осуществляет
выход из функции – возврат управления. С случае с «void» оператор «return» не
обязателен.
Листинг 4.2. Встраиваемые функции.
#include <iostream>
using namespace std;
//Объявление функции – прототип.
int eq (int a, int b);
//Определение встраиваемых функций.
inline int abs (int a)
{ return a > 0 ? a : -a; }
inline int max (int a, int b) { return a > b ? a : b; }
inline int min (int a, int b) { return a < b ? a : b; }
int main (void)
{
int x = 10;
int y = -10;
cout << abs(y) << endl;
cout << max(x, y) << endl;
cout << min(x, y) << endl;
cout << eq(x, y) << endl;
cout << eq(abs(x), abs(y)) << endl;
return 0;
}
//Определение объявленной ранее функции.
int eq (int a, int b)
{
return max(a, b) == min(a, b) ? 1 : 0;
}
В качестве параметров могут передаваться как переменные, так и
константы. Проверка соответствия и преобразования типов выполняются
также, как и при инициализации переменных или вычислении значений какихлибо выражений. Переменные, хранящие входные параметры, существуют до
конца выполнения функции, после чего уничтожаются. Аналогичным
поведением обладают любые переменные, объявленные внутри функции.
Исключением являются только те, что объявлены со спецификатором «static».
В связи с тем, что память для таких переменных выделяется в области «heap»,
время их жизни увеличивается до конца выполнения программы, при этом
очередной вызов функции будет иметь дело со значением этой переменной,
заданным при предыдущем вызове.
Зачастую возникают ситуации, когда необходимо совершить
аналогичные действия над данными различных типов. В таких ситуациях
используют перегрузку – использование одного имени для различных
операций, так, операция «+» используется для различных встроенных типов.
Подобный подход удобен для восприятия и легко может быть распространен на
функции – перегруженные функции отличаются только списком параметров, и
среди одноименных будет выбрана та, чьи формальные параметры наиболее
точно соответствуют фактическим.
Язык С++ предоставляет также возможность задавать значения
параметров по умолчанию. Эти значения будут использованы в качестве
параметров в том случае, если фактические параметры не указаны.
Листинг 4.3. Перегрузка функций.
#include <iostream>
using namespace std;
//Функция вывода целых значений значений.
void output (int val, int base = 10)
{
if (base == 10)
cout << dec << val << endl;
if (base == 16)
cout << hex << val << endl;
}
//Функция вывода дробных значений значений.
void output (double val, …)
{
cout << fixed << val << endl;
}
int main (void)
{
int base, ival;
cin >> ival >> base;
//вызов целочисленной версии с параметром по умолчанию;
output( ival );
//вызов целочисленной версии с заданным параметром;
output( ival, base );
double dval;
cin >> dval;
//вызов дробной версии;
output( dval );
return 0;
}
4.2. Рекурсия
Рекурсивной называется функция, которая обращается к себе самой
прямо или косвенно. В такой функции обязательно должно быть условие
завершения, иначе рекурсия будет бесконечна. После выполнения этого
условия, значение функции будет «всплывать», пока управление не будет
возвращено в место вызова.
Листинг 4.4. Рекурсия.
#include <iostream>
using namespace std;
//Рекурсивное вычисление факториала.
int factorial (int val)
{
return !val ? 1 : val * factorial( val-1 );
}
//Рекурсивное возведение в степень.
double power (double val, int deg)
{
return !deg ? 1 : val * power( val, deg-1 );
}
int main (void)
{
int ival;
cin >> ival;
cout << factorial(ival) << endl;
double dval;
cin >> dval;
cout << power(dval, ival) << endl;
return 0;
}
Рекурсивные функции могут определять бесконечное вычисление без
явных повторений, однако использовать такие функций следует при явном
наличии рекурсии в алгоритме или представлении данных.
4.3. Указатели и ссылки
4.3.1. Передаваемые параметры
Параметрами функции могут быть не только копии каких-либо
объектов, но и сами эти объекты. Подобный механизм реализуется посредством
параметров – указателей и параметров – ссылок. Типичным примером является
передача функции массива в качестве параметра. Для больших массивов
копирование значений всех элементов весьма трудоемко и неэффективно.
Вместо этого, массив передается через указатель на его первый элемент и
значение количества его элементов или указатель на последний.
Листинг 4.5. Указатели, как передаваемые параметры.
#include <iostream>
#include <cstring>
using namespace std;
//функция удаления последнего элемента из строки
//как из стека с возвращением удаляемого символа;
char pop (char *str)
{
int length = strlen(str);
char poped = *(str + length — 1);
//сохранение последнего элемента;
*(str + length - 1) = *(str + length); //удаление последнего элемента;
return poped;
}
int main (void)
{
char *str = new char[20];
cin >> str;
cout << pop(str) << endl;
cout << str << endl;
//резервирование памяти под строку;
//удаление последнего символа;
return 0;
}
Другим примером использования параметров – указателей является
передача структур.
4.3.2. Возвращаемые значения
Указатели и ссылки могут использоваться не только для передачи
значений в функцию, но и для их возвращения.
Листинг 4.6. Указатели, как возвращаемые значения.
#include <iostream>
#include <cstring>
using namespace std;
//функция удаления последнего элемента из строки
//как из стека с возвращением полученной строки;
char* pop (char *str)
{
int length = strlen(str);
*(str + length - 1) = *(str + length); //удаление последнего элемента;
return str;
}
int main (void)
{
char *str = new char[20];
cin >> str;
cout << pop(str) << endl;
//резервирование памяти под строку;
//удаление последнего символа;
return 0;
}
Еще один примечательный способ использования ссылок или указателей
демонстрирует следующий листинг, приведенный для варианта с ссылками.
Здесь возвращаемое функцией значение является ссылкой, в связи с чем над
самим вызовом могут выполняться различные операции.
Листинг 4.7. Возвращение из функции адреса.
#include <iostream>
using namespace std;
int x = 10;
int & x_seter(void)
{
return x;
}
int main (void)
{
x_seter()++;
cout << x << endl;
return 0;
}
4.3.3. Ссылки и структуры в функциях
Следующий листинг демонстрирует применение механизма ссылок при
работе со структурами.
Листинг 4.8. Указатели и структуры в функциях.
#include <iostream>
#include <string>
using namespace std;
//запись;
struct Record
{
int number;
string name;
string surname;
};
//функция создания записи;
void MakeRecord (Record& newrec)
{
cin >> newrec.number;
cin >> newrec.name;
cin >> newrec.surname;
}
//функция вывода записи на экран;
void PrintRecord (Record & outrec)
{
cout << outrec.number << ". " << outrec.name << " " << outrec.surname;
}
int main (void)
{
Record rec;
MakeRecord(rec);
PrintRecord(rec);
cout << endl;
return 0;
}
4.3.4. Указатели и массивы в функциях
Следующий листинг демонстрирует применение механизма указателей
при работе с динамическими массивами.
Листинг 4.9. Указатели и массивы в функциях.
#include <iostream>
using namespace std;
/* создание массива;
* входные параметры – размеры и значение элемента;
* возвращается указатель на массив указателей;
*/
int ** new_array (int nsize, int msize, int init_val = 0)
{
int **pinta = new int*[nsize];
for (int i = 0; i < nsize; i++) {
pinta[i] = new int[msize];
for (int j = 0; j < msize; j++)
pinta[i][j] = init_val;
}
return pinta;
}
/* вывод массива;
* входные параметры – массив и размеры;
*/
void out_array (int **pinta, int nsize, int msize)
{
for (int i = 0; i < nsize; i++) {
for (int j = 0; j < msize; j++)
cout << pinta[i][j] << " ";
cout << endl;
}
}
/* освобождение памяти;
* параметры – массив и количество подмассивов;
*/
void del_array (int **pinta, int nsize)
{
for (int i = 0; i < nsize; i++)
delete[] pinta[i];
delete[] pinta;
}
int main(void)
{
int n, m;
cin >> n >> m;
int **array = new_array(n, m);
out_array(array, n, m);
del_array(array, n);
//создание массива;
//вывод массива;
//удаление массива;
return 0;
}
4.3.5. Указатели и ссылки на функцию
Указатели и ссылки могут адресовать не только переменные, но и
функции. Такой прием чаще применяется совместно с использованием массива,
элементы которого и адресуют различные функции. В следующем примере
циклически будет сортироваться один и тотже массив в соответствии с
функциями сравнения из массива, элементы которого их и адресуют.
Листинг 4.10. Указатели и ссылки на функции.
#include <iostream>
using namespace std;
typedef int (*cmp) (int &, int &);
//функции сравнения «<», «<=», «>», «>=»;
inline int cmp_small
(int & cmpv1, int & cmpv2) { return cmpv1 <
cmpv2?1:0;}
inline int cmp_small_eq (int & cmpv1, int & cmpv2) { return cmpv1 <=
cmpv2?1:0;}
inline int cmp_great
(int & cmpv1, int & cmpv2) { return cmpv1 >
cmpv2?1:0;}
inline int cmp_great_eq (int & cmpv1, int & cmpv2) { return cmpv1 >=
cmpv2?1:0;}
/* функция сортировки;
* входные параметры – сортируемый массив, его размер и функция
сравнения;
*/
void sorts (int ar[], const int size, cmp compare)
{
int tmpv;
for (int j = 0; j < size; j++)
for (int i = 0; i < size-1; i++) {
//сравнение и обмен элементов;
if (compare( ar[i], ar[i+1] )) {
tmpv = ar[i];
ar[i] = ar[i+1];
ar[i+1] = tmpv;
}
}
}
int main (void)
{
//сортируемый массив;
const int iar_size = 15;
int iar[iar_size] = { 44, 12, 35, 13, 44, 95, 17, 58, 76, 60, 29, 41, 48, 76, 67 };
//массив функций сравнения;
const int fiar_size = 4;
cmp fiar[fiar_size] = { cmp_small, cmp_great, cmp_small_eq, cmp_great_eq
};
for (int i = 0; i < iar_size; i++)
cout << iar[i] << " ";
cout << endl << endl;
//сортировка и вывод результата сортировки;
for (int j = 0; j < fiar_size; j++)
{
sorts( iar, iar_size, fiar[j] );
for (int i = 0; i < iar_size; i++)
cout << iar[i] << " ";
cout << endl;
}
return 0;
}
4.4. Шаблоны функций
Одной из наиболее частых проблем при реализации тех или иных
функций является их зависимость от типа входных параметров и возвращаемых
значений. Так, например, функция нахождения большего из двух чисел должна
быть представлена в нескольких вариантах – для каждого из встроенных типов,
что сильно загромождает код. Более того, в ряде случаев, когда, например,
подключается пользовательская библиотека набора одних и техже функций для
разных типов данных, некоторые из них в программе могут никогда и не
вызываться.
Разрешение подобных ситуаций возможно посредством механизма
шаблонов. Шаблонизированные функции, по сути, являются функциями, в
которых тип данных представлен в качестве параметра. Из одного шаблона
может быть сгенерирован код множества функций, где параметрами шаблона
могут быть не только встроенные типы, но и стандартные или разработанные
пользователем классы.
Важным в механизме шаблонов является работа компилятора. В тот
момент, как он встречает в коде шаблон функции, генерации кода –
конкретизации шаблона – не происходит. Это связано с тем, что компилятор в
этот момент не знает какой конкретный вариант функции будет вызван. В связи
с этим генерация кода происходит в момент вызова функции, когда известны
все параметры, на основании которых и происходит вывод аргументов
шаблона.
У шаблона может быть несколько параметров, которые должны
следовать через запятую.
Листинг 4.11. Шаблоны функций.
#include <iostream>
using namespace std;
//шаблон с идентификатором типа элементов массива arelem;
//и размера массива arsize;
template <class arelem, class arsize>
//тип размера не является шаблонным параметром;
arsize find (arelem ar[], arsize size, arelem & element)
{
//поиск позиции первого вхождения заданного элемента;
arsize ret_pos = -1;
for (long i = 0; i < size; i++)
if (ar[i] == element) {
ret_pos = i;
break;
}
return ret_pos;
}
int main(void)
{
long size;
cin >> size;
double *pinta = new double[size];
for (long i = 0; i < size; i++)
pinta[i] = i;
double value;
cin >> value;
cout << find(pinta, size, value) << endl;
delete[] pinta;
char* str = "string like array";
char ch;
cin >> ch;
cout << find(str, 17, ch) << endl;
return 0;
}
Шаблон предоставляет возможность функции иметь тип параметров или
тип возвращаемого значения, не являющийся шаблонным параметром. Другой
возможностью является явное задание типов, которое используется при
невозможности автоматического вывода аргументов шаблона. Эти типы
задаются в угловых скобках через запятую, при этом хвостовые параметры
могут опускаться.
Листинг 4.12. Явное задание аргументов шаблона.
#include <iostream>
using namespace std;
template <class retype, class atype, class btype>
retype max (atype aval, btype bval)
{
return aval > bval ? aval : bval;
}
int main(void)
{
short x = 11;
float y = 11.3;
//первый аргумент – возвращаемый тип — double;
//оставшиеся выведутся автоматически;
cout << max<double>(x, y) << endl;
return 0;
}
4.5. Упражнения
Все функции, выполняющие нижеследующие задания должны быть
выполнимы для различных встроенных числовых типов.
4.5.1. Простые функции
1. Разработать функцию, вычисляющую площадь прямоугольника
2. Разработать функцию, вычисляющую корни квадратного уравнения.
3. Разработать функцию, вычисляющую количество размещений без
повторений.
4. Разработать функцию, определяющую, является ли число – степенью
числа два.
5. Разработать функцию, вычисляющую площадь треугольника.
6. Разработать функцию, вычисляющую количество перестановок.
7. Разработать функцию, определяющую все делители числа.
8. Разработать функцию, вычисляющую площадь трапеции.
9. Разработать функцию, вычисляющую длину окружности.
10.Разработать функцию, вычисляющую количество сочетаний с
повторениями.
11.Разработать функцию, определяющую наибольший общий делитель двух
чисел.
12.Разработать функцию, определяющую является ли число простым.
13.Разработать функцию, вычисляющую количество сочетаний без
повторений.
14.Разработать функцию, вычисляющую длину отрезка по координатам его
точек.
15.Разработать функцию, выполняющую преобразование числа к числу в
системе счисления с задаваемым значением базиса.
16.Разработать функцию, вычисляющую площадь круга.
17.Разработать функцию, определяющую координатные четверти, в которых
лежит заданная коэффициентами гипербола.
18.Разработать функцию, определяющую наименьшее общее кратное двух
чисел.
19.Разработать функцию, определяющую координатные четверти, в которых
лежит заданная коэффициентами парабола.
20.Разработать функцию,
повторениями.
вычисляющую
количество
размещений
с
4.5.2. Параметры – ссылки и параметры — указатели
1. Разработать функции, выполняющие
Статические и динамические массивы».
2. Разработать функции, выполняющие
Многомерные массивы».
задания
из
раздела
«3.8.1.
задания
из
раздела
«3.8.2.
5. Стандартные классы
5.1. Класс «pair»
Данный класс представляет из себя пару значений. Он определен в
заголовочном файле «utility».
Доступ к значениям осуществляется через «first» и «second». Оба
элемента пары могут иметь любой тип данных. При создании пары возможно
сразу указать первоначальные значения. Создание пары возможно также
посредством функции «make_pair()», которой в качестве параметров должны
быть переданы значения элементов.
Над парами определены операции сравнения, выполняющиеся
поэлементно. Результат данных операций в большей степени зависит от первых
элементов сравниваемых пар, чем от вторых. Такое поведение связано с
реализацией сравнения при помощи логических операций «&&» и «||». При их
использовании, в случае значения выражения, стоящего до знака операции и
равного «false» или «true» соответственно, возвращается соответствующее
значение, а вычисление выражения после знака операции не происходит. То
есть в ряде случаев вторые элементы пар не сравниваются вовсе.
Листинг 5.1. Класс pair.
#include <iostream>
#include <utility>
#include <string>
using namespace std;
int main (void)
{
//создание двух пар с заданными значениями и без задания;
pair<int, string> showPair1(1, "Ivanov"), showPair2;
//создане пары функцией make_pair();
showPair2 = make_pair(2, "Petrov");
if (showPair1 != showPair2) {
//создание пары копирующим конструктором;
pair<int, string> showPair3(showPair1);
cout << showPair3.first << ". " << showPair3.second << endl;
}
cout << (showPair1 > showPair2 ? showPair1 : showPair2).second << endl;
return 0;
}
5.2. Класс «complex»
Класс «complex» – класс комплексных чисел. Для его использования
необходимо подключить файл «<complex>» .
Комплексное число состоит из действительной и мнимой частей: «real»
и «imag». Обе части числа могут иметь любой из встроенных арифметических
типов. Доступ к ним осуществляется через «real» и «imag» соответственно.
Над комплексными числами определены арифметические операции
сложения, разности, умножения и деления, а также проверки на равенство и
неравенство.
Комплексные числа могут вводиться и выводиться при помощи потоков.
Вывод выглядит следующим образом: «(real, imag)». Ввод может
осуществляться тремя способами: «real», «(real)», «(real,imag)». Допустимо
также непосредственное присвоение комплексному числу значения одного из
арифметических типов.
Над
комплексными
числами
могут,
помимо
стандартных
математических, выполняться операции в полярной системе координат: «abs()»,
«arg()», «polar()», «norm()».
Листинг 5.2. Комплексные числа.
#include <iostream>
#include <complex>
using namespace std;
int main(void)
{
complex<double> x;
x = 3;
cout << polar(abs(x), arg(x)) << endl;
complex<int> y;
cin >> y;
cout << pow(abs(x), 2) << " = " << norm(x) << endl;
return 0;
}
5.3. Класс «string»
Данный класс предоставляет больше возможностей, по сравнению с С –
строками, однако в некоторых случаях всеже приходится пользоваться
последними, как например, при разборе параметров командной строки.
Стандартный строковый класс описан в файле «string». После его
включения использование
встроенных типов.
строк
выглядят
также,
как
использование
Таблица 5.1. Операции со строкой.
Название
[]
+
==
at
compare
copy
data
empty
erase
find
find_first_not_of
find_first_of
find_last_not_of
find_last_of
getline(istr, str, ch)
insert
append
length
replace
size
substr
Описание
Доступа к символу по индексу без проверки на существование
Конкатенация
Сравнения
Доступ к символу по индексу с проверкой на существование
Сравнение строк или их чистей
Копирование части строки
Приведение к С – строке
Проверка на пустоту
Удаление части строки
Поиск вхождения
Первое вхождение символа, отсутствующего в указанной
строке
Первое вхождение символа, присутствующего в указанной
строке
Последнее вхождение символа, отсутствующего в указанной
строке
Последнее вхождение символа, присутствующего в указанной
строке
Считывание строки с пробельными символами и символами
конца строки внутри из потока istr в строку str до символа –
ограничителя ch
Вставка части строки
Добавление символов в конец строки
Длинна строки
Замена части строки
Определение размера строки
Получение части строки
Следующий
листинг
стандартного класса «string».
Листинг 5.3. Класс string.
#include <iostream>
#include <string>
using namespace std;
int main (void)
демонстрирует
некоторые
возможности
{
//создание строки;
string stext1;
//ввод значения созданной строки;
getline( cin, stext1, '.' );
//создание строки;
string stext2;
//ввод значения созданной строки;
cin >> stext2;
//замена первого вхождения введенного слова многоточием;
if (stext1.find(stext2))
stext1.replace(stext1.find(stext2), stext2.size(), "...");
//затирание второго вхождения;
if (stext1.find(stext2))
stext1.erase(stext1.find(stext2), stext2.length());
//преобразование к С – строке;
char *cstr = const_cast<char*>(stext1.data());
cout << cstr << endl;
return 0;
}
5.4. Контейнеры. Алгоритмы. Итераторы
В стандартной библиотеке помимо «pair» и «string» присутствуют и
другие классы, называемые контейнерами. Они предназначены для хранения
определенных структур данных, при этом тип самих данных может быть
любым. Наличие таких классов обусловлено неэффективностью применения
встроенных средств.
Контейнеры делятся на последовательные, адаптивные и ассоциативные.
В последовательных контейнерах элементы расположены в ряд и связаны меж
собой позицией. К таким контейнерам относятся:
 Вектор – «vector» (динамически изменяемый массив).
 Список – «list» (связанный список).
 Очередь с двусторонним доступом – «deque» (вставка и выборка как с
конца очереди, так и с начала).
Адаптивные контейнеры построены на базе последовательных, предоставляя к
ним ограниченный интерфейс.
 Очередь – «queue» (работает по принципу FIFO: первый вошел – первый
вышел).
 Стек – «stack» (работает по принципу LIFO: последний вошел – первый
вышел).
 Приоритетная очередь – «priority_queue» (элементы достигают выхода в
соответствии со своими приоритетами).
В
ассоциативные контейнерах элементы не
располагаются
последовательно, а хранятся в виде дерева. Доступ к ним осуществляется
посредством ключей, модифицируемых самими контейнерами автоматически.
К таким контейнерам относятся:
 Отображение – «map» (состоит из пар ключа и ассоциированного с ним
значения).
 Мультиотображение – «multimap» (отображение с поддержкой
одинаковых элементов).
 Множество – «set» (вырожденное отображение, в котором ключам не
соответствуют никакие значения).
 Мультимножество – «multiset» (множество с поддержкой одинаковых
элементов).
Каждый контейнер имеет свои встроенные методы.
Таблица 5.2. Указатели на элементы.
Название
begin()
end()
rbegin()
rend()
Описание
Указывает на первый элемент
Указывает на последний элемент
Указывает на первый элемент в обратном направлении
Указывает на последний элемент в обратном направлении
Таблица 5.3. Доступ к элементам.
Название
front()
back()
at()
[]
Описание
Первый элемент
Последний элемент
Элемент с заданным индексом, с проверкой (не для списка)
Элемент с заданным индексом, без проверки (не для списка)
Таблица 5.4. Операции со стеком и очередями.
Название
push_back()
pop_back()
push_front()
pop_front()
Описание
Добавление в конец
Извлечение из конца
Добавление в начало (только для списков и двусторонних очередей)
Извлечение из начала (только для списков и двусторонних очередей)
Таблица 5.5. Операции со списками.
Название
insert(a, c)
insert(a, b, c)
erase(a)
clear()
Описание
Вставка элемента a перед элементом в позиции c
Вставка b элементов a перед элементом в позиции c
Удаление элемента в позиции a
Очистка вектора
Таблица 5.6. Ассоциативные операции.
Название
[]
Описание
Элемент с заданным ключом (для контейнеров с уникальным
ключом)
find()
Элемент с заданным ключом
lower_bound() Поиск первого элемента с указанным ключом
upper_bound() Поиск первого элемента с ключом, больше указанного
equal_range() Поиск lower_bound() и upper_bound()
key_comp()
Копирование элемента с указанным ключом
value_comp() Копирование элемента с указанным значением
Таблица 5.7. Другие операции.
Название
size()
empty()
max_size()
resize()
Описание
Количество элементов
Проверка на пустоту
Максимальный размер контейнера
Установка новой размерности
capacity()
Размер выделенной для вектора памяти
reserve()
Выделение для вектора памяти
swap()
!=, ==, >, <, >=, <=, =
=
assign(a, b)
Обмен векторов элементами
Операторы сравнения и оператор присвоения
Копирующее присваивание
Присвоение вектору a элементов b
Эффективная работа с разнообразными структурами данных происходит
не только посредством простых встроенных в соответствующие классы
методов, но и посредством обобщенных «algorithm» и численных «numeric»
алгоритмов. Алгоритмы представляют из себя функции, выполняющие
определенные действия над элементами контейнера. Они являются
независимыми в том смысле, что могут применяться не только к определенным
стандартным контейнерам, но и к разработанным пользователем.
Связь между контейнерами и алгоритмами осуществляется посредством
итераторов, представляющих из себя указатели на элементы контейнера.
Применение к итератору операции разыменования «*» возвратит значение
адресуемого элемента. Итераторы могут быть инкрементированы, проходя по
всем элементам контейнера.
Следует заметить, что итераторы механизм связывания со стандартными
алгоритмами посредством итераторов применимо не только к контейнерам, но
и к таким классам как «string».
5.5. Класс «vector»
Класс «vector» реализует массив с расширенными возможностями и
представляет из себя полноценный контейнер. Такой массив доступен после
включения заголовочного файла «vector». Он позволяет не только изменять
элементы, но и добавлять новые и удалять существующие элементы, изменяя
размер самого массива. Помимо операций удаления и вставки элементов
данный класс поддерживает операции сравнения, получения количества
элементов, операции по управлению выделением памяти и др.
Таблица 5.8. Конструкторы класса вектор.
Метод
vector()
vector(a)
vector(a, b)
vector(c)
Описание
Создание пустого вектора
Создание вектора из a элементов со значением по умолчанию
Создание вектора из a элементов со значением b
Создание копии вектора c
Движение по элементам вектора может осуществляться в обоих
направлениях, в зависимости от типа итератора, при этом используются разные
начальные значения итераторов: «begin» и «end» для прямого итератора
«iterator», «rbegin» и «rend» для обратного «reverse_iterator».
Листинг 5.4. Класс vector.
#include <iostream>
#include <vector>
using namespace std;
//функция отображения элементов вектора;
void showvec(vector<int> & v)
{
cout << ": ";
for (int i = 0; i < v.size(); i++)
cout << v.at(i) << " ";
cout << endl;
}
int main (void)
{
int size;
cin >> size;
//создание вектора и задание значений его элементов;
vector<int> vec1;
for (int i = 0; i < size; i++)
vec1.push_back(i);
//создание вектора и инициализация значений;
//его элементов копирующим конструктором;
vector<int> vec2 = vector<int>(vec1);
showvec(vec1);
showvec(vec2);
cout << endl;
//вывод элементов вектора;
//вывод элементов вектора;
vec2.erase( vec2.end()-1 );
//удаление последнего элемента;
vec2.insert( vec2.begin(), vec1[0] ); //вставка элемента в первую
позицию;
swap(vec1, vec2);
//обмен векторов элементами;
showvec(vec1);
showvec(vec2);
//вывод элементов вектора;
//вывод элементов вектора;
cout << "vec1 " << (vec1 >= vec2 ? ">=" : "<") << " vec2" << endl;
//создание итераторов векторов и их инициализация первыми
элементами;
vector<int>::iterator iter1 = vec1.begin();
vector<int>::iterator iter2 = vec2.begin();
cout << ": ";
//сравнение элементов вектора;
while ((iter1 != vec1.end()) && (iter2 != vec2.end()))
cout << (*iter1++ == *iter2++) << " ";
cout << endl << endl;
//изменение размера вектра;
vec1.resize(2);
//переопределение размера вектора и значений его элементов;
vec2.assign(2, 0);
showvec(vec1);
//вывод элементов вектора;
showvec(vec2);
//вывод элементов вектора;
cout << "vec1 == vec2 is " << boolalpha << ( vec1 == vec2
)<<endl<<endl;
vec1.clear();
vec2 = vec1;
//очистка вектора;
//копирование векторов;
showvec(vec1);
//вывод элементов вектора;
showvec(vec2);
//вывод элементов вектора;
cout << boolalpha << ( !vec1.empty() && vec2.size() ) << endl;
return 0;
}
Для работы с векторами может быть использовано несколько
обобщенных алгоритмов, описанных в файлах «algorithm» и «numeric»
соответственно.
Таблица5.9. Некоторые стандартные обобщенные алгоритмы.
Название
accumulate
Описание
Сумма инициирующего значения и значений элементов из диапазона
(вместо суммы может быть использована указанная бинарная
операция)
copy
Копирование последовательности элементов
count
Подсчет количества указанных элементов
fill
Заполняет диапазон элементов указанным значением
find
Поиск указанного значения и возврат указывающего на него
итератора
for_each
Применяет указанную функцию к каждому элементу диапазона
(функция не должна менять значения элементов)
generate
Заполняет диапазон элементов вызовом указанной функции
max
Определение большего из двух элементов
max_element Определение наибольшего элемента
merge
Объединяет две отсортированные последовательности в одну
min
Определение меньшего из двух элементов
min_element Определение наименьшего элемента
random_shuff Перестановка элементов из указанного диапазона в случайном
le
порядке
remove
Удаление из диапазона элементов с указанным значением
replace
Замена элементов с указанным значением на элементы с новым
reverse
Изменение порядка следования элементов на противоположный
rotate
Перемещение всех элементов перед указанным в конец
search
Поиск подпоследовательности
sort
Переупорядочивание элементов по возрастанию
swap
Обмен указанных значений
Листинг 5.5. Стандартные алгоритмы.
#include <iostream>
#include <vector>
#include <numeric>
#include <algorithm>
using namespace std;
//функция отображения элементов вектора в обратном порядке;
void rshow (vector<int> & v)
{
vector<int>::reverse_iterator riter;
//реверсивный
итератор;
for (riter = v.rbegin(); riter < v.rend(); riter++)
cout << *riter << " ";
cout << endl;
}
int main (void)
{
const int size = 9;
vector<int> vect1;
vector<int> vect2(size);
for (int i = 1; i <= size; i++)
vect1.push_back(i);
rshow(vect1);
//подсчет суммы значений элементов;
cout << accumulate(vect1.begin(), vect1.end(), 0) << endl;
//сортировка по возрастанию;
sort(vect1.begin(), vect1.end(), greater<int>());
rshow(vect1);
//заполнение второго вектора элементами из первого;
copy(vect1.begin(), vect1.end(), vect2.begin());
rshow(vect2);
//поэлементное сравнение векторов;
cout << boolalpha << equal(vect1.begin(), vect1.end(), vect2.begin()) <<
endl;
const int elem = 5;
//удаление из вектора элемента со значением elem;
remove(vect1.begin(), vect1.end(), elem);
rshow(vect1);
//определение наличия элемента со значением elem;
if (find(vect1.begin(), vect1.end(), elem ) == vect1.end())
cout << "there is no \'" << elem << "\' element" << endl;
return 0;
}
Следует заметить, что вектор может быть и многомерным. Для этой
цели в качестве типа вектора указывается не встроенный тип, а очередной
вектор.
Листинг 5.6. Многомерный вектор.
#include <iostream>
#include <vector>
using namespace std;
int main(void)
{
const int size = 5;
//пять подвекторов;
vector<vector<int> > ivec(size);
//пять элементов в каждом подвекторе;
for (int i = 0; i < size; i++)
ivec[i].resize(size);
ivec[2][2] = 1;
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
cout << ivec[i][j] << " ";
}
cout << endl;
}
return 0;
}
5.6. Упражнения
Выполнить без использования обобщенных алгоритмов, лишь методами
класса «string» или «vector».
5.6.1. Класс «string»
В введенном с клавиатуры тексте, состоящем из нескольких слов и
оканчивающимся точкой, как символом – ограничителем.
1. Найти все повторяющиеся слова.
2. Удалить из текста все символы, встречающиеся в первом слове.
3. Составить строку из символов, повторяющихся в каждом отдельном
слове.
4. Найти все слова, длинна которых меньше четырех символов.
5. Отобразить зеркально все слова, состоящие из четного количества
символов.
6. Найти все слова, которые являются частью другого слова.
7. Поменять попарно местами символы во всех словах.
8. Удалить из последнего слова все символы, встречающиеся в других
словах.
9. Отобразить текст зеркально.
10.Найти все неповторяющиеся слова.
11.Удалить все повторяющиеся в слове символы, оставив первое вхождение.
12.Поменять попарно местами все слова текста.
13.Найти все слова, длинна которых составляет пять символов.
14.Удалить из последнего слова все символы, невстречающиеся в других
словах.
15.Составить строку из слов, несодержащих повторяющихся символов.
16.Найти все слова, часть которых является другим словом.
17.Составить строку из символов, неповторяющихся в каждом отдельном
слове.
18.Удалить из текста все символы, невстречающиеся в первом слове.
19.Упорядочить слова текста в лексикографическом порядке.
20.Посчитать частоту встречаемости каждого символа из текста.
5.6.2. Класс «vector»
1. Выполнить с применением векторов задания из раздела «3.8.1.
Статические и динамические массивы».
2. Выполнить с применением векторов задания из раздела «3.8.2.
Многомерные массивы».
6. Файловый ввод – вывод
6.1. Операции
При работе с файлами используется библиотека «fstream», в которой
описаны классы «ofstream» и «ifstream». Экземпляры этих классов
предоставляют потоковый способ работы с файлами. Он осуществляется
посредством операторов «>>» и «<<» и различных методов, описанных далее.
Листинг 6.1. Файловый вывод.
#include <iostream>
#include <iomanip>
#include <fstream>
#include <string>
using namespace std;
int main (void)
{
//поток вывода в файл file.txt;
ofstream fout("file.txt");
int ival;
cout << "enter integer value : ";
cin >> ival;
fout << setw(10) << setfill('0') << showpos << internal << ival << endl;
double dval;
cout << "enter double value : ";
cin >> dval;
fout << fixed << dval << endl;
string str;
cout << "enter string : ";
cin >> str;
fout << str << endl;
return 0;
}
Листинг 6.2. Файловый ввод.
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main (void)
{
//поток ввода из файла file.txt;
ifstream fin("file.txt");
int ival;
double dval;
string str;
fin >> ival >> dval >> str;
cout << ival << endl << dval << endl << str << endl;
return 0;
}
6.2. Методы
Среди методов файлового ввода – вывода есть методы работающие не
только с отдельными символами или массивами, но и оперирующие строками.
Таблица 6.1. Методы «ifstream».
Название
gcount
get
ignore
peek
putback
read
seekg
tellg
eof
open
close
Описание
Подсчет символов, прочитанных за последнее обращение к потоку
Посимвольное чтение
Чтение и удаление строки, ограниченной разделителем или длинной
Чтение одного символа из потока без его удаления
Возврат последнего прочитанного символа обратно в поток
Чтение символов из файла
Установка позиции в файле
Определение позиции в файле
Определение достижения конца файла
Открытие файла, ассоциированного с потоком
Закрытие файла, ассоциированного с потоком
Таблица 6.2. Методы «ofstream».
Название
flush
put
seekp
tellp
write
open
close
Описание
Очистка буфера и запись разделителя строк
Посимвольная запись
Установка позиции в файле
Определение позиции в файле
Запись в файл
Открытие файла, ассоциированного с потоком
Закрытие файла, ассоциированного с потоком
Листинг 6.3. Методы файлового ввода – вывода.
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main (void)
{
string buf;
//поток вывода в файл file.txt;
ofstream fout( "file.txt" );
//считывание строки текста;
getline( cin, buf );
//запись считанного текста в файл file.txt;
fout << buf;
//закрытие файла, ассоциированного с потоком;
fout.close();
//поток ввода из файла file.txt;
ifstream fin( "file.txt" );
//чтение из файла file.txt и вывод на экран его содержимого;
while (!fin.eof())
cout << static_cast<char>(fin.get());
cout << endl;
//закрытие файла, ассоциированного с потоком;
fin.close();
return 0;
}
6.3. Флаги
Зачастую, при операциях ввода – вывода возникают те или иные
ошибки. Для их обработки используются специальные флаги, доступ к которым
осуществляется соответствующими методами.
Таблица 6.3. Флаги ошибок.
Название
badbit
eofbit
failbit
goodbit
hardfail
Описание
Недопустимая операция
Конец файла
Сбой операции
Отсутствие ошибок
Критическая ошибка
Таблица 6.4. Флаговые методы.
Название
eof
fail
bad
good
clear
Описание
Статус флага eof
Статус флага badbit или failbit, или hardfail
Статус флага badbit или hardfail
Статус флага goodbit
Снимает все флаги или устанавливает указанный
6.4. Двоичный режим
Главным образом, форматированный файловый ввод – вывод
применяется в тех случаях, когда содержимое самого файла должно быть
читаемым, а размер данных не велик. В противном случае, целесообразнее
применять двоичный ввод – вывод. Этот режим, как и любой другой, требует
задания.
Таблица 6.5. Биты режимов.
Название
in
out
ate
app
trunc
nocreate
noreplace
binary
Описание
Чтение (предустановлен для «ifsteream»)
Запись (предустановлен для «ofsteream»)
Чтения с конца файла
Записи с конца файла
Открытие с обрезанием файла до нулевой длинны, если он существует
Не создавать файл, если его не существует
Не открывать существующий файл без установки «ate» или «app»
Бинарный режим
Листинг 6.4. Двоичный режим.
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
//структура данных - запись;
struct Record
{
int number;
string name;
string surname;
};
int main (void)
{
Record rec1;
rec1.name = "Ivan";
rec1.surname = "Ivanov";
rec1.number = 1;
//двоичная запись в файл;
ofstream fout( "db.txt", ios::binary );
//запись с помощью reinterpret_cast;
fout.write( reinterpret_cast<char*>(&rec1), sizeof(Record) );
fout.close();
Record rec2;
//двоичное чтение из файла;
ifstream fin( "db.txt", ios::binary );
//чтение с помощью reinterpret_cast;
fin.read( reinterpret_cast<char*>(&rec2), sizeof(Record) );
fin.close();
cout << rec2.number << ". " << rec2.name << " " << rec2.surname <<
endl;
return 0;
}
Закрытие файлов может происходить автоматически, но если, например,
два потока связаны с одним файлом, необходимо закрыть первый, перед тем,
как будет открыт второй.
6.5. Упражнения
Выполнить следующие упражнения для файлов, имена которых
переданы через аргументы командной строки.
1. Прочитать из первого файла последовательность целых чисел и записать
сумму ее элементов во второй.
2. Создать структуру данных, содержащую поля имени, фамилии, отчества
и даты рождения. Осуществить двоичный файловый ввод – вывод данной
структуры.
3. В двух файлах заданы последовательности целых чисел. Четные числа из
обоих последовательностей записать в первый файл, а нечетные во
второй.
4. Дан файл, содержащий последовательность действительных чисел.
Отсортировать по возрастанию положительные числа из данной
последовательности и результат записать в данный файл.
5. Из файла, содержащего текст, удалить все повторяющиеся слова.
6. Обменять содержимое двух файлов местами.
7. Дан файл, содержащий последовательность целых чисел. Отсортировать
по убыванию четные числа из данной последовательности и результат
записать в новый файл.
8. В файле, содержащем последовательность целых чисел, записать все
числа в обратном порядке в шестнадцатеричном представлении.
9. Прочитать из первого файла последовательность целых чисел и записать
произведение ее элементов во второй.
10.Из первого файла, содержащего текст, удалить все слова,
несодержащиеся во втором файле.
11.Из файла, содержащего текст, удалить самое длинное слово.
12.Дан файл, содержащий последовательность целых чисел. Отсортировать
по возрастанию нечетные числа из данной последовательности и
результат записать в данный файл.
13.В файле, содержащем текст, изменить на обратный порядок символов в
каждом слове, при этом порядок слов оставить неизмененным.
14.Дан файл, содержащий последовательность действительных чисел.
Отсортировать по убыванию отрицательные числа из данной
последовательности и результат записать в новый файл.
15.В двух файлах заданы последовательности действительных чисел.
Положительные числа из обоих последовательностей записать в первый
файл, а отрицательные во второй.
16.Создать структуру данных, содержащую поля наименования продукта,
его категории и цены. Осуществить двоичный файловый ввод – вывод
данной структуры.
17.Из файла, содержащего текст, удалить самое короткое слово.
18.В файле, содержащем текст, состоящем из букв латинского алфавита,
заменить символы «i» и «j» на «1», «to» на «2», «fo» на «4» и «at» на «8».
19.Из первого файла, содержащего текст, удалить все слова, содержащиеся
во втором файле.
20.Дан файл, содержащий текст. Выстроить лексикографически все слова,
содержащиеся в файле, и результат записать во второй файл.
Ллитература
1.
Б. Керниган, Д. Ритчи, «Язык программирования С»,
ISBN 5-7940-0045-7.
2.
Б. Страуструп, «Дизайн и эволюция С++. Классика CS»,
ISBN 5-469-01217-4.
3.
Б. Страуструп, «Язык программирования С++. Специальное издание»,
ISBN 5-7989-0226-2.
4.
C. Липпман, Ж. Лажойе, «Язык программирования C++. Вводный курс»,
ISBN 5-7940-0070-8.
5.
Николас А. Солтер, Скотт Дж. Клепер, «С++ для профессионалов»,
ISBN 5-8459-1065-X.
6.
Лафоре, «Объектно ориентированное программирование в С++»,
ISBN 978-5-94723-302-5.
Download