Язык программирования C/C++
Выбери формат для чтения
Загружаем конспект в формате docx
Это займет всего пару минут! А пока ты можешь прочитать работу в формате Word 👇
Лекция 1 Язык программирования C/C++
Язык C – это язык программирования системных и прикладных программ для профессиональных программистов. Основные достоинства языка – это лаконичность, большая мощность, возможность использования машинно-ориентированных средств. Язык включает все управляющие конструкции структурного программирования, что позволяет создавать программы в хорошем стиле.
Язык C появился при создании операционной системы UNIX в 1972 году. Один из авторов – Денис Ритчи. По классификации – язык универсальный, не очень высокого уровня. Основное назначение – написание операционных систем и проблемных программ.
Язык C++ - это компилируемый, статически типизированный язык программирования общего назначения. Он поддерживает такие парадигмы программирования как процедурное программирование, объектно-ориентированное программирование, обобщённое программирование. Язык имеет богатую стандартную библиотеку. В сравнении с его предшественником - языком C - наибольшее внимание уделено поддержке объектно-ориентированного и обобщённого программирования.
Язык C++ возник в начале 1980-х годов. Его автор - Бьёрн Страуструп. Одним из принципов разработки языка было сохранение совместимости с языком C. Однако C++ не является в строгом смысле надмножеством языка C. Существует множество программ, которые могут одинаково успешно транслироваться и компиляторами C, и компиляторами C++, но это множество включает не все возможные программы на C.
Новый язык, неожиданно для автора, приобрёл большую популярность среди коллег, и вскоре Страуструп уже не мог лично поддерживать его. Первый коммерческий выпуск языка C++ состоялся в октябре 1985 года. Никто не обладает правами на язык C++, он является свободным. Однако сам документ стандарта языка не доступен бесплатно.
Язык C++ является одним из самых популярных языков программирования. Синтаксис C++ унаследован от языка C.
Элементы языка
В тексте любого языка можно выделить следующие уровни:
- алфавит, т.е. неделимые знаки языка;
- слово (или лексема) – минимальная единица языка, имеющая определённый смысл;
- словосочетание (выражение);
- предложение (оператор) – законченное описание некоторого действия.
Алфавит языка C/C++ включает прописные и строчные латинские буквы (26+26), арабские цифры, специальные и пробельные символы.
Специальные символы: +-*/%.,;:\^()[]{}|”?’~<=>$_!
К словам (лексемам) относятся:
- идентификаторы,
- ключевые (зарезервированные, служебные) слова,
- константы,
- знаки операций,
- разделители.
Идентификатор (имя) – последовательность латинских букв, арабских цифр и символов подчёркивания, которая начинается не с цифры. Максимальная длина не оговаривается, однако некоторые компиляторы накладывают на неё ограничения (но не менее 8 символов). Принято, что идентификаторы, составленные программистом, начинаются с буквы. В системных функциях имена обычно начинаются с подчёркивания.
Ключевые слова зарезервированы. Они имеют специальное значение для компилятора.
Константа – объект, значение которого не может меняться. Компилятор определяет константу по форме её написания.
Таблица 1.1 – Типы и форматы констант
Константы
Формат
Пример
Целые
десятичный: последовательность десятичных цифр, начинающаяся не с 0
восьмеричный: нуль, за которым следует последовательность восьмеричных цифр
шестнадцатеричный – 0x или 0X, за которым следуют шестнадцатеричные цифры
8, 19125
015, 020, 071
0xA9, 0X325
Вещественные
десятичный: [цифры].[цифры] (может быть опущена целая либо дробная часть, но не обе сразу)
экспоненциальный:
[цифры][.][цифры]E|e[+|-]цифры (может быть опущена целая, либо дробная часть, но не обе сразу)
5.7, .82, 54.
.2E6, 11e-3,
1.2e+2
Символьные
один или два символа в апострофах
‘A’, ‘\0’, ‘\n’
Строковые
последовательность символов в кавычках
“ab12dF4\n"
Символьные константы, состоящие из одного символа, занимают в памяти 1 байт и относятся к типу char, двухсимвольные – 2 байта и относятся к типу int, при этом первый символ размещается в байте с меньшим адресом.
Символ обратной косой черты \ используется для представления:
- символов, не имеющих графического изображения (например, ‘\a’ – звуковой сигнал, ‘\t’ – горизонтальная табуляция, ‘\n’ – перевод курсора);
- символов апострофа (‘), обратной косой черты (\), знака вопроса (?), кавычек (“);
- любого символа с помощью указания его восьмеричного (1-3 цифры) или шестнадцатеричного (1-2 цифры) кода (‘\103’, ‘\xF5’).
Последовательность символов, начинающаяся с обратной косой черты, называется управляющей или escape-последовательностью. Она интерпретируется как одиночный символ и может использоваться в строковых константах, например, \“ - для отличия символа кавычки в строке от кавычки, закрывающей строку (“Кто сказал \”Мяу\”\?”).
В заголовочном файле определены именованные константы, достаточно часто используемые при написании программ, содержащих математические расчёты. Часть этих констант приведена в таблице 1.2.
Таблица 1.2 – Константы, которые определены в файле
Имя
Значение
Математическое обозначение
M_E
2.71828182845904523536
e
M_LOG2E
1.44269504088896340736
log2e
M_LOG10E
0.434294481903251827651
log10e
M_LN2
0.693147180559945309417
ln2
M_LN10
2.30258509299404568402
ln10
M_PI
3.14159265358979323846
π
M_PI_2
1.57079632679489661923
π/2
M_PI_4
0.785398163397448309616
π/4
M_1_PI
0.318309886183790671538
1/π
M_2_PI
0.636619772367581343076
2/π
M_1_SQRTPI
0.564189583547756286948
1/
M_2_SQRTPI
1.12837916709551257390
2/
M_SQRT2
1.41421356237309504880
M_SQRT_2
0.707106781186547524401
1/
Точность определения этих значений составляет 21 значащую десятичную цифру.
Разделители – к ним относятся все специальные и пробельные символы (число пробелов не имеет значения), кроме символа подчёркивания.
Знаки операций – один или несколько символов, определяющих действия над операндами. Внутри знака операции пробелы не допускаются.
Лекция 2 Типы данных языка
Тип данных определяет размер памяти, отводимой для хранения величин этого типа, способ интерпретации хранимого значения и набор операций и стандартных функций, которые могут быть применены к величинам этого типа. При выборе типа каждой величины программист должен руководствоваться здравым смыслом и той информацией, которая у него есть из постановки задачи.
Все типы можно разделить на основные (простые) и составные. К составным типам относятся массивы, структуры, перечисления, объединения, указатели, ссылки, функции и классы.
Основные (стандартные) типы часто называют арифметическими, поскольку данные этих типов можно использовать в арифметических операциях. Спецификации основных типов:
- int – целый;
- char – символьный;
- bool – логический;
- float – вещественный;
- double – двойной точности.
Для уточнения внутреннего представления используются спецификаторы типа (модификаторы, квалификаторы):
- short –короткий;
- long – длинный;
- signed – знаковый;
- unsigned – беззнаковый.
Типы арифметические целочисленные
int – выделяется слово памяти, т.е. в зависимости от типа компьютера и версии компилятора 2 или 4 байта.
short – модификатор, говорящий, что надо выделить именно 2 байта.
short int≡short. Диапазон возможных значений от -32768 до 32767.
long – модификатор, говорящий, что надо выделить именно 4 байта.
long int≡long. Диапазон значений от -2147483648 до 2147483647.
unsigned – модификатор, говорящий о том, что во внутреннем представлении только неотрицательные числа.
unsigned short int≡unsigned short. Диапазон значений от 0 до 65535.
unsigned long int≡unsigned long. Диапазон значений от 0 до 4294967295.
signed – модификатор. По умолчанию все целочисленные типы считаются знаковыми, поэтому этот модификатор можно опускать.
char – выделяется 1 байт памяти. Используется для хранения одиночных символов из таблицы кодировки и целых чисел. Может быть со знаком и без знака.
char – диапазон значений от -128 до 127.
unsigned char – диапазон значений от 0 до 255.
В стандарте языка C99 дополнительно определены модификаторы long long и unsigned long long к типу int для хранения больших целых чисел со знаком и без знака.
long long – модификатор, говорящий, что надо выделить 8 байт. Диапазон значений от -9223372036854775808 до 9223372036854775807.
unsigned long long – выделяется 8 байт. Диапазон значений от 0 до 18446744073709551615.
Типы арифметические с плавающей точкой
float – выделяется 4 байта памяти (1 разряд – знак мантиссы, 8 разрядов – значение порядка, 23 разряда – значение мантиссы). Диапазон значений ±(10-37…1038).
double – выделяется 8 байт памяти (1+11+52). Диапазон возможных значений ±(10-307…10308). Отмечу, что long float≡double.
long double – выделяется 10 байт памяти (1+16+63). Диапазон возможных значений ±(10-4932…104932).
Константы с плавающей точкой по умолчанию имеют тип double. Их тип можно явно указать с помощью суффиксов F, f (float) и L, l (long).
Тип void
Множество значений пусто. Используется для определения функций, которые не возвращают значения, или для указания пустого списка аргументов функций. Кроме того, этот тип используется для создания универсальных указателей.
Тип bool
Логический тип. Возможные значения true и false. Тип реализован в поздних версиях языка C++. Ранее специально не предусматривался. Во внутреннем представлении 0 – false, остальное – true.
В заголовочных файлах и определены константы, представляющие предельные значения для различных типов данных. Некоторые именованные константы, определённые в файле , приведены в таблице 1.3.
Таблица 1.3 – Константы, которые определены в файле
Константа
Значение
Пояснение
CHAR_MAX
127
максимальное значение типа char
CHAR_MIN
-128
минимальное значение типа char
UCHAR_MAX
255
максимальное значение типа unsigned char
SHRT_MAX
32767
максимальное значение типа short
SHRT_MIN
-32768
минимальное значение типа short
USHRT_MAX
65535
максимальное значение типа unsigned short
INT_MAX
32767
максимальное значение типа int
INT_MIN
-32768
минимальное значение типа int
UINT_MAX
65535
максимальное значение типа unsigned int
LONG_MAX
2147483647
максимальное значение типа long
LONG_MIN
-2147483648
минимальное значение типа long
ULONG_MAX
4294967295
максимальное значение типа unsigned long
Лекция 3 Объекты
Объекты – это именованная область памяти, доступная для обработки. Объекты подразделяются:
- на скалярные и составные;
- на переменные и константы;
- по типам.
Перед использованием все объекты должны быть описаны. Вид оператора описания:
[класс памяти] [const] тип имя [инициализатор];
Область действия идентификатора – часть текста программы, в которой его можно использовать для доступа к связанной с ним памяти. Объекты могут быть локальными и глобальными.
Локальные описаны внутри блока {}; область действия – с момента описания до конца блока, включая вложенные блоки.
Если описание вне любого блока – то это глобальный объект, и область его действия – файл (модуль), в котором он описан.
Время жизни объекта может быть постоянным (т.е. в течение времени выполнения программы) и временным (в течение исполнения блока).
Область видимости идентификатора – часть текста программы, из которой допустим обычный доступ к связанной с ним памяти. Чаще всего область действия и область видимости совпадают. Исключение: во вложенном блоке описан объект с таким же именем. В этом случае внешний объект во вложенном блоке невидим, хотя он входит в область его действия. Если это глобальная переменная, то к ней можно обратиться, используя операцию доступа к области видимости ::.
Спецификации классов памяти:
- auto – автоматический. Память выделяется в стеке. Освобождение памяти происходит при выходе из блока, в котором помещено описание. Время жизни – с момента описания до конца исполнения блока. Для локальных объектов этот класс памяти используется по умолчанию;
- extern – внешний. Объект определён в другом месте текста программы (в другом файле или дальше по тексту). Используется для создания переменных, доступных во всех модулях программы, в которых они объявлены;
- static – статические объекты. Время жизни постоянно. Инициализация переменных происходит один раз при первом выполнении оператора определения переменной. Могут быть локальными и глобальными;
- register – всё аналогично auto, но память по возможности выделяется в регистре.
Если при определении переменной начальное значение не задано явно, компилятор присваивает глобальным и статическим переменным нулевое значение соответствующего типа. Если автоматическая переменная не инициализируется, то выделенная для неё область памяти будет содержать грязь, т.е. непредсказуемое значение (пример 1.1).
Пример 1.1
#include
#include
using namespace std ;
int a ; //определение глобальной переменной a
//память выделяется в сегменте данных
//и обнуляется
extern int x_gl ; //объявление глобальной внешней переменной
//x_gl; память не выделяется
int main()
{int b_loc ; //определение локальной переменной b_loc
//память выделяется в сегменте стека
extern int x_loc ; //объявление внешней переменной x_loc
//память не выделяется
static int c ; //локальная статическая переменная c
//память выделяется в сегменте данных
//и обнуляется
setlocale(LC_ALL,"Russian") ;
printf("b_loc = %i ", b_loc) ; //выводится исходное значение - "грязь"
printf("x_gl = %i ", x_gl) ; //выводится значение после инициализации
printf("x_loc = %i ", x_loc) ; //выводится значение после инициализации
printf("c = %i ", c) ; //выводится исходное значение 0
int a ; //локальная переменная a
//память выделяется в сегменте стека
a = 10 ; //присваивание локальной переменной
++::a ; //изменение значения глобальной переменной
printf("a_loc = %i ", a) ; //вывод значения локальной переменной
printf("a_gl = %i ", ::a) ; //вывод значения глобальной переменной
return 0 ;
}
int x_gl = 5 ; //определение внешней переменной
//x_gl; выделение памяти и инициализация
int x_loc = 15 ; //определение внешней переменной x_loc;
//выделение памяти и инициализация
Результат работы программы
Лекция 5 Операции над объектами
В качестве операндов могут быть применены только скалярные объекты основных типов или ссылки. Операции над составными объектами запрещены, кроме операции присваивания для однотипных структур.
Многие операции не предъявляют жестких требований к типам операндов. Поэтому в зависимости от вида операции и типов операндов возможны автоматические преобразования значений операндов в форму другого типа.
Преобразования бывают:
- арифметические,
- преобразования при присваивании,
- явные.
Арифметические преобразования:
- если в выражении один из операндов long double, то остальные преобразуются в long double и результат long double;
- иначе, если один из операндов double, то остальные преобразуются в double и результат double;
- иначе, если один из операндов float, то остальные преобразуются в float и результат float;
- иначе, если один из операндов unsigned long, то остальные преобразуются в unsigned long и результат unsigned long;
- иначе, если один из операндов long, то остальные преобразуются в long и результат long;
- иначе, если один из операндов unsigned int, то остальные преобразуются в unsigned int и результат unsigned int;
- иначе, если один из операндов unsigned short или unsigned char, то они преобразуются в unsigned int и результат unsigned int;
- иначе все операнды преобразуются в тип int по правилам: unsigned char расширяется нулями, а signed char значением знакового разряда.
Арифметические операции
1) – и + унарные (инвертирование и сохранение знака операнда);
2) – и + бинарные (вычитание и сложение);
3) * и / (умножение и деление);
4) % (получение остатка отделения).
Операнды – выражения основных типов. Выполняются арифметические преобразования. При делении целых чисел дробная часть отбрасывается без округления. Знак остатка совпадает со знаком делимого.
Пример 1.2
5/2 даёт результат 2 5%2 даёт результат 1
-5/2 даёт результат -2 -5%2 даёт результат -1
5/-2 даёт результат -2 5%-2 даёт результат 1
-5/-2 даёт результат 2 -5%-2 даёт результат -1
Операции отношения
Сравнивают левый операнд с правым. Определены шесть операций:
> >= < <= == !=
Тип результата int. Операнды – выражения основных типов. Выполняются арифметические преобразования.
Лекция 4 Логические операции
! – логическое отрицание (унарное);
|| - логическое сложение (дизъюнкция);
&& - логическое умножение (конъюнкция).
Тип результата int. Операнды – выражения основных типов. Выражения, связанные операциями && и ||, вычисляются слева направо. Вычисление результата операции заканчивается при получении результата. Например, выражение_1||выражение_2. Если выражение_1 отлично от 0, то выражение_2 не вычисляется, т.к. результат операции || заведомо истина.
Поразрядные логические операции
~ - унарная операция поразрядного отрицания;
| - поразрядное логическое сложение (дизъюнкция);
^ - поразрядное логическое сложение по модулю 2;
& - поразрядное логическое умножение (конъюнкция).
Операнды – выражения целого типа. Длина операндов выравнивается, т.о. длина результата – по большему значению.
Операции сдвига
<< - сдвиг влево (выражение_1 << выражение_2);
>> - сдвиг вправо (выражение_1 >> выражение_2).
Операнды – выражения целого типа. При входе в операцию выражение_2 преобразуется в int. Результат того же типа, что выражение_1.
В результате сдвига влево освобождающиеся разряды значения левого операнда заполняются 0, при сдвиге вправо – значением знакового разряда. Если выражение_1 имеет тип unsigned, то свободные биты заполняются нулями. Результат операций не определён, если значение правого операнда отрицательное, а также равно или превышает количество разрядов левого операнда.
Понятие об L-значении (L-value)
L-значение (L-value) - это любое выражение, адресующее некоторый участок памяти. Название Lзн происходит от операции присваивания, в которой левая часть (LEFT) определяет, в какую область памяти поместить результат. Имена массивов и функций, а также объектов, объявленных с ключевым словом const, не являются L-значениями.
Операция присваивания
Два варианта записи:
- простое присваивание:
Lзн=выр //a=b+c-5;
допускается запись a=b=c=4; //присваивание идёт справа налево
- составная операция присваивания:
Lзн op= выр //op – любая бинарная операция + - * / % >> << & ^ |.
a op= выр эквивалентно a = a op выр //i+=2; ≡ i=i+2;
mas[выр]*=3; ≡ mas[выр]=mas[выр]*3; //в первом случае выр вычисляется один раз, во втором – дважды.
Преобразования при присваивании: тип результата выражения выр преобразуется к типу Lзн.
Преобразования могут быть:
long -> int – 2 старших байта отбрасываются;
long -> char – 3 старших байта отбрасываются;
int -> char – 1 старший байт отбрасывается;
double -> float – отбрасываются 4 байта младших разрядов мантиссы;
плавающие -> целые – дробная часть теряется без округления.
Если выполняется составная операция присваивания op=, то вначале выполняется операция op, а затем всё остальное.
Операция последовательного вычисления (запятая)
Связывает два выражения в одно и гарантирует, что левое выражение будет выполняться первым.
выражение_1, выражение_2
Тип и значение результата операции запятая определяет выражение_2.
Операции увеличения и уменьшения значения
++ - унарный инкремент (увеличить значение операнда на 1);
-- - унарный декремент (уменьшить значение операнда на 1).
Операнд должен быть переменной целого типа или ссылкой и быть Lзн.
Возможны две формы операций: префиксная ++n и постфиксная n++.
x++ ≡ x=x+1,x-1
x-- ≡ x=x-1,x+1
++x ≡ x=x+1
--x ≡ x=x-1
Пример 1.3
x=5;
n=++x; //присваивание после увеличения; поэтому x=6; n=6;
n=x++; //результат: x=7; n=6;
Условная операция
Операция тернарная.
выражение_1 ? выражение_2 : выражение_3
Сначала вычисляется выражение_1, которое должно быть арифметического типа или указателем. Если результат не равен 0, то вычисляется выражение_2, и это есть результат условной операции. Иначе (результат равен 0) вычисляется выражение_3, и это есть результат операции.
Пример: k=a>b?a:b //поиск максимума из a и b.
Необходимо отметить, что выражение_2 и выражение_3 могут быть разных типов. При входе в условную операцию выполняются арифметические преобразования. Поэтому, например, если выражение_2 имеет тип float, а выражение_3 – тип int, результат условной операции независимо от результата выражения_1 будет иметь тип float.
Лекция 5 Операция получения адреса объекта в основной памяти (взятие адреса) и операция получения содержимого объекта по его адресу (разадресация)
& - унарная операция получения адреса. Например, &a – адрес переменной a. Операндом может быть любое Lзн. Имена массивов и функций также могут быть операндами этой операции, но в этом случае знак операции является лишним, так как имена функций и массивов являются адресами.
* - унарная операция разадресации (или разыменования) предназначена для доступа к содержимому объекта, адрес которого хранится в операнде. Тип результата операции определяется типом величины, адресуемой указателем. Результат не определён, если значение указателя равно 0.
Пример 1.4
int main()
using namespace std ;
{int x, y, *p ; //p-указатель на объект типа int
x = 10 ;
p = &x ; //в p заносится адрес переменноё x
y = *p ; //выбор значения по адресу p, т.е. y=10
*p = 0 ; //занесение 0 по адресу, хранящемуся в p (x=0)
*p += 1 ; //увеличение значения x на 1, т.е. x=1
return 0 ;
}
Переменные, используемые для хранения адресов областей памяти, называются указателями. В C++ различают три вида указателей: указатели на объект, на функцию и на void, которые различаются свойствами и набором допустимых операций. Указатель не является самостоятельным типом, он всегда связан с каким-либо другим конкретным типом.
Указатель на объект содержит адрес области памяти, в которой хранятся данные определённого типа.
Объявление указателя:
тип * имя;
где тип может быть любым, причём он может быть к этому моменту только объявлен, но ещё не определён (например, в качестве поля структуры может быть указатель на структуру того же типа).
Звёздочка в объявлении указателя относится непосредственно к имени:
int *a, b, *c; //здесь a и c – указатели на тип int, b - переменная типа int.
Указатель на функцию содержит адрес в сегменте кода, по которому располагается исполняемый код функции, т.е. адрес, по которому передаётся управление при вызове функции. Указатели на функцию используются для косвенного вызова функции (не через её имя, а через обращение к переменной, хранящей её адрес), а также для передачи имени функции в другую функцию в качестве параметра. Указатель имеет тип «указатель на функцию, возвращающую значение заданного типа и имеющую аргументы заданного типа». Объявление такого указателя имеет вид:
тип (*имя)(список_типов_аргументов);
Например: int (*func)(int, int);
Здесь объявлен указатель с именем func на функцию, возвращающую значение типа int и имеющую два аргумента типа int.
Указатель на void используется в тех случаях, когда конкретный тип объекта, адрес которого надо хранить, не определён (например, если в одной и той же переменной в разные моменты времени требуется хранить адреса объектов различных типов). Для того чтобы выполнять какие-либо действия с таким указателем или объектом, который он адресует в данный момент, надо явно задать текущий тип операцией приведения типов.
Указатель может быть константой или переменной, а также указывать на константу или переменную.
Пример 1.5
#include
#include
using namespace std ;
int main()
{int i = 5, //переменная типа int
j = 10, //переменная типа int
*p ; //p-указатель на объект типа int
const int A = 1 ; //константа типа int
const int B = 2 ; //константа типа int
const int *p_co = &A ; //указатель на константу типа int
int *const co_p = &i ; //указатель-константа на объект типа int
const int *const co_co = &A ; //указатель-константа на конст. типа int
setlocale(LC_ALL,"Russian") ;
printf("Результат работы программы примера 1.5\n") ;
printf("p_co=%i ", *p_co) ; //вывод значения константы A
// *p_co += 5 ; //запрещено изменять значение константы
p_co = &j ; //действие разрешено
p_co = &B ; //действие разрешено
printf("p_co=%i ", *p_co) ; //вывод значения константы B
printf("co_p=%i ", *co_p) ;
// co_p = &j ; //запрещено изменять значение указателя
*co_p = A * B ; //действие разрешено (i=2)
*co_p = j ; //действие разрешено (i=10)
printf("i=%i ", i) ;
printf("co_co=%i\n", *co_co) ;
// co_co = &B ; //запрещено изменять значение указателя
// *co_co += 5 ; //запрещено изменять значение константы
return 0 ;
}
Модификатор const, находящийся между именем указателя и звёздочкой, относится к самому указателю и запрещает его изменение, а const слева от звёздочки задаёт постоянство значения, на которое он указывает. Для инициализации указателей использована операция получения адреса &.
Операции, которые можно выполнять с указателями:
- * - разадресация или косвенное обращение к объекту;
- присваивание;
- сложение и вычитание с целым числом – при этом значение указателя увеличивается (уменьшается) на произведение числа на размер объекта, адресуемого указателем;
- инкремент (++) и декремент (--) – при этом значение указателя увеличивается (уменьшается) на размер объекта, адресуемого указателем;
- сравнение, при этом использование операций отношения имеет смысл, если оба указателя адресуют один и тот же объект (например, массив);
- приведение типов.
Присваивание указателей допускается, если:
- тип указателя слева и справа одинаков;
- указатель слева типа void*.
Присваивание значений указателей на объекты указателям на функции (и наоборот) недопустимо. Запрещено присваивать значение указателям-константам. Присваивать значения указателям на константу и переменным, на которые ссылается указатель-константа, допускается.
Операция приведения типов
(имя_типа)выражение
Результат выражения преобразуется к заданному типу.
(double)a+b //преобразуется тип выражения a
(double)(a+b) //преобразуется тип выражения (a+b)
Лекция 6 Операция размер
Два вида операции.
sizeof(имя_типа) – возвращает размер памяти в байтах для области, отводимой под объект указанного типа.
Операция константная. Выполняется на этапе трансляции. Результат имеет тип int.
sizeof выражение – возвращает в байтах результат выражения. Если в качестве выражения указать имя массива, то возвращает размер памяти, отводимой под массив.
Первичные операции
. –> [] ()
. – обращение к элементу структуры или объединения по сложному имени (например, str.cod)
–> – обращение к элементу структуры или объединения по ссылке на структуру или объединение (например, p–>cod ≡ (*p).cod).
[] – индексирование (обращение к элементу массива).
() – если записано имя(), то это вызов функции.
Понятие о выражении
Выражение – это объединение операндов, операций и скобок. Каждое выражение имеет конкретное значение определённого типа. Каждый операнд, в свою очередь, является выражением или одним из его частных случаев – константой или переменной.
Любой операнд может быть заключён в скобки, которые не влияют на тип и значение выражения, заключённого в скобки.
Чтобы определить значение выражения надо выполнить содержащиеся в нём операции в порядке, определённом уровнем старшинства (приоритета). Порядок вычисления подвыражений в выражении не определён. Он устанавливается разработчиком конкретного компилятора и пользователю не известен.
Пример 1.6
выражение значение выражения
-4+6 2
c=3+8 11
6+(c=3+8) 17 (это выражение состоит из двух подвыражений,
каждое из которых имеет значение)
sin(x+2)+cos(y+1) нельзя считать, что обращение к sin будет раньше,
чем обращение к cos, и что результат (x+2)
вычислится раньше (y+1).
Константные выражения, операндами которых являются константы, вычисляются на этапе трансляции. Их можно использовать везде, где допускается использовать константу. Операндами константных выражений могут быть целые константы, символьные константы, константы с плавающей точкой, выражения приведения типов, выражения с операцией sizeof и другие. В константных выражениях нельзя использовать операции присваивания и последовательного вычисления.
Порядок вычисления операций в выражении
Всего 16 приоритетов. Внутри каждой группы приоритет одинаков. Стрелка указывает на порядок выполнения операций одного приоритета.
1) Первичные операции: . –> [] () (→)
2) Унарные операции: + - ++ -- ! ~ & * sizeof(имя_типа) sizeof выр (←)
3) Мультипликативные операции: * / % (→)
4) Аддитивные операции: бинарные + - (→)
5) Операции сдвига: >> << (→)
6) Операции отношения: > >= < <= (→)
7) Операции сравнения на равенство: == != (→)
8) Операция поразрядного логического умножения: & (→)
9) Операция поразрядного логического сложения по модулю 2: ^ (→)
10) Операция поразрядного логического сложения: | (→)
11) Операция логического умножения: && (→)
12) Операция логического сложения: || (→)
13) Условная операция: ?: (←)
14) Операции присваивания: = op= (←)
15) Операция присваивания структуры (←)
16) Операция последовательного вычисления (запятая). (→)
Операторы языка
В языке C используются следующие операторы:
1) оператор-выражение – ключевого слова не имеет;
2) оператор перехода – goto;
3) условный оператор – if;
4) оператор разрыва – break;
5) оператор возврата в вызывающую функцию – return;
6) операторы цикла – while, do…while, for;
7) оператор перехода на начало следующей итерации оператора цикла – continue;
8) оператор-переключатель – switch;
9) пустой оператор;
10) составной оператор.
Каждый оператор заканчивается точкой с запятой, которая входит в состав оператора.
Все операторы могут быть помечены. Метка – это имя оператора, по которому ему можно передать управление. Метка строится по правилам идентификатора. Она отделяется от помечаемого оператора двоеточием.
Лекция 7 Оператор-выражение
Вид: выражение;
Примеры: x=0; i++; j=x&&++y;
Оператор перехода
Вид: goto метка;
Пример: goto met1;
Управление передаётся оператору с указанной меткой. Область действия метки – функция. Не рекомендована передача управления внутрь составного оператора.
Условный оператор
Используется для принятия решения в зависимости от результата некоторого выражения.
I тип: if(выражение) оператор_1; else оператор_2;
Реализует базовую управляющую алгоритмическую структуру «выбор»
не 0 =0
II тип: if(выражение) оператор;
Реализует дополнительную управляющую алгоритмическую структуру «обход»
=0
не 0
В качестве оператора, оператора_1 и оператора_2 может быть любой оператор языка. Если это новый условный оператор – получаем вложенные условные операторы. В этом случае else относится к ближайшему условию if, у которого нет своего else. Поскольку оператор if проверяет (выражение) на истинность, то при проверке выражения на равенство или неравенство нулю вместо if(выражение != 0) правильнее писать if(выражение).
Оператор разрыва
Оператор break вызывает досрочный выход из непосредственно охватывающего его циклического оператора или оператора-переключателя.
Оператор возврата в вызывающую функцию
Оператор возврата в вызывающую функцию завершает выполнение функции и передаёт управление в точку её вызова. Бывают двух видов:
1) return; - если тип возвращаемого функцией значения описан как void;
2) return выражение; - выражение должно быть основного типа.
Функция может иметь несколько операторов return. Это определяется потребностями алгоритма. Если функция описана как void, то оператор return можно опускать, если возврат из функции происходит непосредственно перед её закрывающейся фигурной скобкой. Выражение, указанное после return, неявно преобразуется к типу возвращаемого функцией значения.
Лекция 8 Операторы цикла
Используются для организации многократно повторяющихся действий. Реализованы три оператора цикла: с предусловием (while), с постусловием (do..while) и с параметром (for).
Оператор цикла с предусловием
Синтаксис: while (выражение) оператор;
Предполагает многократное исполнение некоторого оператора (тела цикла) при сохранении начальной истинности (ненулевого значения) заданного выражения. Реализует базовую управляющую алгоритмическую структуру:
=0
не 0
Если при входе в цикл результат выражения равен 0, то оператор тела цикла не выполняется ни разу.
Во избежание зацикливания значение выражения должно меняться либо само по себе, например. while(++x != 7), либо оператором тела цикла.
Оператор цикла с постусловием
Синтаксис: do оператор while (выражение);
Реализует дополнительную управляющую алгоритмическую структуру:
не 0
=0
Оператор тела цикла выполняется хотя бы раз всегда. Такие циклы на практике используются реже: здесь и удобство чтения, и целесообразность вначале проверить необходимость выполнения оператора тела цикла.
Оператор цикла с параметром
Синтаксис: for (выражение_1; выражение_2; выражение_3) оператор;
выражение_1 задаёт начальные значения управляющих переменных цикла; вычисляется только один раз.
выражение_2 аналог выражения в операторе while.
выражение_3 вычисляется в конце каждой итерации.
Этот циклический оператор полностью аналогичен конструкции:
выражение_1;
while (выражение_2){
оператор;
выражение_3;
}
Из этого видно, что выражение_1 и выражение_3 могут быть опущены (при этом точка с запятой в записи сохраняется). Получаем полный аналог оператора while. Разрешается опускать и выражение_2. В этом случае считается, что его результат отличен от 0, и, таким образом, цикл бесконечен. Такая конструкция for(;;){…} часто используется для вывода на экран меню.
Из тела любого цикла можно выйти досрочно операторами goto, break, return.
По оператору goto можно выйти в охватывающий цикл или в область вне тела цикла.
Оператор break вызывает выход из непосредственно охватывающего его циклического оператора.
По оператору return просто завершается выполнение функции.
Лекция 8 Оператор перехода на начало следующей итерации оператора цикла
По оператору continue пропускается оставшаяся часть итерации и начинается следующая. Если это операторы while(выражение) или do…while(выражение), то после выполнения оператора continue управление передаётся на выражение.
Если это оператор for (выражение_1; выражение_2; выражение_3), то управление передаётся на выражение_3.
Непосредственно к оператору-переключателю оператор continue не применим. Однако, если оператор continue находится в теле оператора-переключателя, который в свою очередь расположен внутри тела оператора цикла, то управление по оператору continue будет передаваться на следующую итерацию оператора цикла.
Оператор-переключатель
Используется в случае необходимости выбора одного из нескольких вариантов вычисления.
Синтаксис: switch(выражение)
{case к_выражение_1: [оператор_1]
case к_выражение_2: [оператор_2]
…
case к_выражение_n: [оператор_n]
[default: оператор]
}
где: выражение должно быть любого целого типа;
к_выражение_i – константа или константное выражение любого целого типа;
default – переводится как «иначе» или «за отсутствием»; эта метка не обязательна и может находится в любом месте, не обязательно в конце.
Реализует дополнительную управляющую алгоритмическую структуру:
к_выражение_1
к_выражение_2
…
к_выражение_n
default
Результаты константных выражений всех префиксов case должны быть разными. В случае синтаксической ошибки в слове default сообщение об ошибке не выдаётся, поскольку компилятор воспринимает это слово как допустимую метку соответствующего оператора.
Работа оператора: вычисляется выражение. Его результат последовательно сравнивается с результатами константных выражений. Если сравнение произошло, то управление передаётся соответствующему оператору. Если за ним следует оператор break, то все встречающиеся после него префиксы case и default игнорируются до фигурной скобки. Если оператор break отсутствует, то будут выполняться все операторы, начиная с оператора, помеченного данной меткой, до фигурной скобки. Затем выход из оператора switch.
Если сравнение значения выражения и меток вообще не произошло, то управление передаётся оператору с меткой default (при его наличии). В противном случае произойдёт переход управления к оператору, следующему за оператором switch.
Допускается придавать любому оператору несколько префиксных меток:
case к_выражение_1:
case к_выражение_2:
case к_выражение_3: [оператор_3]
Оператор-переключатель завершается:
- когда выполняется до конца (до });
- по оператору break;
- по оператору goto с выходом на метку вне тела оператора-переключателя;
- по оператору return с возвратом в вызывающую функцию.
Лекция 9 Пустой оператор
Синтаксис: ;
Используется в тех случаях, когда по синтаксису языка следует разместить оператор, а по схеме алгоритма он не нужен.
Примеры:
met:;} передача управления на конец составного оператора;
for(…); пустое тело цикла.
Составной оператор
Вид: {объявления
операторы
}
Чаще всего используется в случаях, когда по синтаксису языка должен быть только один оператор, а по схеме алгоритма их несколько. Это единственный оператор языка, в конце которого нет точки с запятой.
Директивы препроцессора
Препроцессором называется первая фаза компиляции. Инструкции препроцессора называются директивами. Они начинаются символом #, перед которым могут находиться только пробельные символы.
Директива #include
Директива #include<имя_файла> вставляет содержимое указанного файла в ту точку исходного файла, где она записана. Поиск файла, если не указан полный путь, ведётся в стандартных каталогах включаемых файлов. Вместо угловых скобок могут использоваться кавычки (“имя_файла”). В этом случае поиск файла ведётся в каталоге, содержащем исходный файл, а затем уже в стандартных каталогах.
Эта директива является простейшим средством обеспечения согласованности объявлений в различных файлах. Она позволяет включить в них информацию интерфейсов заголовочных файлов.
Директива #define
Эта директива определяет подстановку в тексте программы. Она используется для определения:
1) символических констант:
#define имя текст_подстановки
Принято писать имя заглавными буквами.
В таком виде директива используется для того, чтобы связать идентификатор с константой, ключевыми словами, часто используемыми операторами и выражениями. Идентификаторы, которые представляют константы, называются символическими константами, а идентификаторы, представляющие операторы и выражения, называются макрокомандами.
В тексте исходного файла, следующем за директивой #define, все вхождения идентификатора имя заменяются текстом подстановки, если идентификатор образует лексему. Идентификатор не заменяется текстом подстановки, если он является частью строки или другого идентификатора.
Текст подстановки отделяется от идентификатора одним или более пробелами, которые не являются частью текста подстановки.
Текст подстановки может быть опущен. В этом случае удаляются все вхождения идентификатора имя из текста исходной программы. Тем не менее, идентификатор считается определённым и принимает значение 1 (истина), если проверяется директивой условной компиляции #if.
Препроцессор не выполняет проверки синтаксической правильности текста подстановки. Если ошибки есть, то они будут обнаружены на этапе трансляции.
Пример 1.7
#include
#include
#define AND &
#define OR |
#define EQ ==
#define PI 3.1415
#define RAZ 10
#define VOVA "Владимир Иванович"
#define PRINTZ printf("z=%i\n", z)
using namespace std ;
int main()
{unsigned char x = 15,
y = 7,
z = 0 ;
if(x EQ y)printf(VOVA) ;
else z = x AND y OR RAZ ;
PRINTZ ; //печатается z=15
return 0 ;
}
Символические константы унаследованы из языка C и при написании программ на C++ их следует избегать, используя вместо них типизированные именованные константы (например, const int RAZ=10;).
2) директива используется для определения макросов:
#define имя(список_параметров) текст_подстановки
Это позволяет определить макрокоманды с аргументами. При этом список_параметров содержит один или более формальных параметров, разделённых запятыми. Формальные параметры должны иметь уникальные имена.
Каждое вхождение имя(список_параметров) в тексте программы заменяется на текст_подстановки с заменой формальных параметров на фактические. Имена формальных параметров в тексте подстановки показывают места, куда должны быть подставлены фактические значения. Имя каждого формального параметра может встретиться в тексте подстановки более одного раза в любом порядке.
Препроцессор не выполняет вычислений, а только заменяет строку, поэтому если определение выглядит как
#define SQUARE(x) x*x
а в тексте записан вызов:
z=SQUARE(y+2); то после подстановки получим:
z=y+2*y+2; //а не z=(y+2)*(y+2);
В этом конкретном случае надо было записать директиву так:
#define SQUARE(x) (x)*(x)
Пример 1.8
#include
#include
#define MIN(x,y) ((x) < (y) ? (x) : (y))
#define MAX(x,y) ((x) < (y) ? (y) : (x))
#define ABS(x) ((x) < 0 ? -(x) : (x))
#define PRINTZ printf("z=%i\n", z)
using namespace std ;
int main()
{int x, y, z ;
setlocale(LC_ALL,"Russian") ;
printf("Результат работы программы примера 1.8\n") ;
printf("Введите x и y -> ") ;
scanf("%i%i", &x, &y) ;
z = MIN(x,y) ;
PRINTZ ;
z = MAX(x,y) ;
PRINTZ ;
return 0 ;
}
Обработка макроопределений не приводит к вызову функции со связанными с этим накладными расходами по передаче аргументов, распределением памяти, возврату значений. Кроме того, макрокоманда работает с любыми типами аргументов, в то время как для функции выполняется приведение типов.
Лекция 10 Статические массивы
Основные понятия
Массив – это именованная совокупность объектов одного типа и одного класса памяти, доступ к которым осуществляется не по их именам, а по их порядковому номеру (индексу) внутри массива.
При объявлении статического массива в квадратных скобках указывается количество элементов массива, нумерация которых всегда начинается с 0. Количество элементов массива задаётся целым положительным константным выражением. Даже если какой-либо конкретный компилятор разрешает задавать размер массива при его объявлении с использованием целочисленной переменной, делать это категорически запрещается.
Все массивы рассматриваются как одномерные, но при необходимости можно использовать рекурсии, т.е. массив массивов. Максимальная разрешенная вложенность зависит от конкретной реализации.
Последний элемент массива имеет номер, на единицу меньше заданного при описании размера. При обращении к элементам массива автоматический контроль выхода индекса за границы массива не производится, что может привести к ошибкам.
Массивы могут быть любого класса памяти кроме регистрового класса.
Если при описании массива не указан его размер, то в описании должен присутствовать инициализатор, задающий значения элементов массива. При этом компилятор выделит память под массив по количеству инициализированных значений.
В общем случае константное выражение при объявлении статического массива может быть опущено, если:
- массив при объявлении инициализируется;
- массив объявлен как формальный параметр функции;
- массив объявлен как ссылка на массив, явно определённый в другом месте.
При инициализации список значений заключается в фигурные скобки.
Пример 1.9
int a[]={1,2,3}; //т.е a[0]=1, a[1]=2, a[2]=3
int b[4]={2,3}; //т.е. b[0]=2, b[1]=3, b[2]=0, b[3]=0
Указывать большее число начальных значений, чем объявленное количество элементов массива, нельзя. Если инициализируется только часть значений элементов массива, значения остальных элементов обнуляются.
Размер массива предпочтительнее задавать с помощью именованных констант. Рекомендуемое объявление массива выглядит так:
Пример 1.10
const int RAZ=10; //определяется именованная константа
typedef int telem; //определяется тип элементов массива telem
typedef telem tmas[RAZ]; //определяется новый тип tmas
При этом исключаются возможные ошибки, которые могут возникнуть при необходимости изменить максимальный размер массива и тип его элементов (изменения потребуются только в одном месте текста программы).
При инициализации многомерных массивов они могут представляться либо как массив массивов (в этом случае каждый массив заключается в свои фигурные скобки), либо список значений задаётся в том порядке, в котором элементы располагаются в памяти.
Пример 1.11
int y[3][4]={1, 2, 3, 4, 5, 6, 7, 8}; //1 2 3 4
//5 6 7 8
//0 0 0 0
int x[3][3]={{1, 2}, {4, 5, 6}, {7, 8}}; //1 2 0
//4 5 6
//7 8 0
Нельзя инициализировать элементы в средине и конце массива, не задав значений его предыдущих элементов.
Индексные выражения
Элемент массива определяется индексным выражением вида:
выражение_1[выражение_2]
Здесь квадратные скобки являются элементом синтаксиса. Тип индексного выражения определяется типом элементов массива, а значением индексного выражения является величина, адрес которой вычисляется суммированием выражения_1 и выражения_2. Чаще всего выражение_1 – это указатель (которым является имя массива), а выражение_2 – целая величина. Это не является обязательным требованием: выражение_2 может быть указателем, а выражение_1 – целой величиной. Но в любом случае правое выражение должно быть заключено в квадратные скобки.
Связь указателей и массивов
Результат индексного выражения как ссылки на одномерный массив вычисляется как сумма целой величины и значения указателя с последующей разадресацией (*). При сложении используются правила адресной арифметики: целая величина вначале преобразуется в адресное представление путём умножения её на размер памяти, соответствующий типу элемента массива. Например, пусть объявлен массив:
int mass[10];
Для получения результата индексного выражения mass[4] целая величина 4 умножается на число байт, отводимых под значение типа int. Эта величина представляет собой смещение 4-го элемента массива от начала массива mass. Она складывается со значением указателя mass (хранящего адрес начала массива). В результате получаем адрес 4-го элемента массива mass. К полученному значению адреса применяется операция разадресования. Таким образом, результат индексного выражения mass[4] является значением 4-го элемента массива mass. Таким образом, можно записать соотношения:
mass[4]=*(mass+4)=*(mass+4*sizeof(int))
На самом деле компилятор языка преобразует все индексные выражения в адресные выражения (например, mass[4] преобразуется в *(mass+4)).
Таким образом, для доступа к i-му элементу массива mass можно использовать любое из выражений:
mass[i] i[mass] *(mass+i)
Лекция 11 Функции
Функция – это именованная последовательность описаний и операторов, выполняющая какое-либо законченное действие. Любая программа на языке C/C++ состоит из функций, одна из которых должна иметь имя main(): с неё начинается исполнение программы. Выполнение других функций начинается с момента их вызова.
Любая функция может быть объявлена и определена. Объявление функции должно находиться в тексте программы раньше её вызова для того, чтобы компилятор мог проверить правильность вызова.
Объявление (прототип, заголовок, сигнатура) функции задаёт её имя, тип возвращаемого значения и список передаваемых параметров.
Определение содержит, кроме объявления, тело функции, представляющее последовательность описаний и операторов в фигурных скобках. Вид определения функции:
[класс] тип имя([список_параметров])
{тело функции}
Необязательный модификатор класс используется для явного задания области видимости функции с использованием ключевых слов extern и static:
extern – глобальная видимость во всех модулях программы;
static – видимость в пределах модуля, в котором она определена.
Тип возвращаемого значения может быть любым, кроме массива и функции. Если функция не должна возвращать никакого значения, указывается тип void.
Список параметров определяет величины, которые надо передать в функцию при её вызове. Для каждого параметра указывается тип и имя (в объявлении имена параметров можно опускать). В определении, объявлении и при вызове одной и той же функции типы и порядок следования параметров должны совпадать. На имена ограничений не накладывается.
Вызов функции – это указание её имени со списком передаваемых аргументов. Вызов может находиться в любом месте текста программы, где по синтаксису допустимо выражение того типа, которое возвращает функция. Если тип возвращаемого значения не void, то вызов может входить в состав выражения.
Все величины, описанные внутри функции, а также её параметры, являются локальными: область их действия – функция. При вызове функции в сегменте стека выделяется память под локальные автоматические переменные. При выходе из функции соответствующий участок стека освобождается, поэтому значения локальных переменных не сохраняются. Если этого требуется избежать, при объявлении используется модификатор static.
Пример 1.12
#include
#include
using namespace std ;
void funk(int) ;
int main()
{setlocale(LC_ALL,"Russian") ;
cout << "Результат работы программы примера 1.12" ;
funk(2) ;
funk(3) ;
return 0 ;
}
void funk(int a)
{int c = 0 ;
cout << endl << "b c d" ;
while(a--)
{static int b = 0 ;
int d = 0 ;
cout << endl << b++ << " " << c++ << " " << d++ ;
}
}
В приведённом примере статическая переменная b размещается в сегменте данных и инициализируется один раз при первом выполнении оператора, содержащего её определение. Автоматическая переменная c инициализируется при каждом входе в функцию. Автоматическая переменная d инициализируется при каждом входе в блок цикла while.
В результате работы программы на экран будет выведено:
Обмен информацией между функциями можно осуществить с помощью глобальных переменных, через параметры и через возвращаемое функцией значение.
Использование глобальных переменных для передачи данных между функциями не рекомендуется (в лабораторных работах по дисциплине – запрещено). Функция должна быть максимально независима, а её интерфейс должен определяться прототипом.
Возвращаемое значение реализуется оператором return выражение, но при этом можно вернуть в вызывающую функцию только одно значение.
Параметры функции являются основным способом обмена информацией между вызываемой и вызывающей функциями. При вызове функции в сегменте стека выделяется память под формальные параметры функции в соответствии с их типом и каждому из них присваивается значение соответствующего фактического параметра.
Лекция 12
Существует два способа передачи параметров в функцию: по значению и по адресу. При передаче по значению в стек заносятся копии значений фактических параметров, и операторы функции работают с этими копиями. Доступа к исходным значениям в этом случае нет, поэтому нет возможности их изменить.
При передаче по адресу в стек заносятся копии адресов фактических параметров, поэтому функция имеет доступ к памяти по этим адресам и может менять находящиеся там значения.
Пример 1.13
#include
#include
using namespace std ;
int funk(int, int*, int&) ;
int main()
{int a = 0, b = 1, c = 2, d = 3 ;
setlocale(LC_ALL,"Russian") ;
cout << "Результат работы программы примера 1.13" << endl ;
cout << "a b c d" << endl ;
cout << a << " " << b << " " << c << " " << d << endl ;
d = funk(a, &b, c) ;
cout << a << " " << b << " " << c << " " << d << endl ;
return 0 ;
}
int funk(int a, int *b, int &c)
{a++ ;
(*b)++ ;
c++ ;
return (*b + c);
}
Параметр a передаётся по значению. Его изменение в функции funk не влияет на исходное значение.
Параметр b передаётся по адресу с помощью указателя, при этом для передачи в функцию funk адреса фактического параметра используется операция &. В самой функции funk для получения значения по этому адресу используется операция разыменования *.
Параметр c передаётся по адресу с помощью ссылки. При этом в функцию funk передаётся адрес указанного при вызове фактического параметра. Внутри функции funk все обращения к параметру неявно разыменовываются (т.е. осуществляется доступ к содержимому по адресу). Поэтому использование ссылок вместо указателей улучшает читаемость программы, избавляя от необходимости применять операции получения адреса и разыменования.
Переменная d получает значение по оператору return.
Если требуется запретить изменение параметра внутри функции, используется модификатор const:
int f(const char*);
char *t(char *a, const *b);
Таким образом, данные, которые не должны меняться в функции, предпочтительнее передавать ей в виде константных ссылок.
Передача массивов в качестве параметров
При использовании в качестве параметра массива в функцию передаётся указатель на его головной элемент (т.е. массив передаётся по адресу). При этом информация о количестве элементов массива теряется. Поэтому необходимо передавать размер массива через отдельный параметр.
Пример 1.14
//Найти сумму элементов введенного с клавиатуры массива целых чисел
#include
#include
using namespace std ;
const int RAZ = 10 ; //предельный размер массива
typedef int telem ; //определение типа элементов массива
typedef telem tmas[RAZ] ; //определение типа массива
telem summa(const tmas a, const int n) ;
void inputmas(tmas a, const int n) ;
int main()
{tmas a ; //массив
int n ; //размер массива
telem sum ; //сумма элементов массива
setlocale(LC_ALL,"Russian") ;
cout << "\n Найти сумму элементов массива целых чисел" << endl ;
//Ввод исходных данных
cout << " Введете размер массива <= " << RAZ << " -> " ;
cin >> n ;
//Контроль ввода размера массива
if(n > RAZ || n <= 0)
{cout << " Введено недопустимое значение размера массива" << endl ;
return 1 ;
}
inputmas(a, n) ;
//Поиск суммы значений элементов массива
sum = summa(a, n) ;
cout << " Сумма элементов массива = " << sum << endl ;
return 0 ;
}
telem summa(const tmas a, const int n)
{telem sum = 0 ; //текущее значение суммы элементов
for(int i = 0 ; i < n ; i++) sum += a[i] ;
return sum ;
}
void inputmas(tmas a, const int n)
{cout << "\n Введите в одной строке элементы массива, состоящего из";
cout << "\n " << n << " целых чисел, и нажмите " << endl;
for(int i = 0 ; i < n ; i++)
cin >> a[i] ;
}
Результат работы программы:
Лекция 13 Перегрузка функций
Для того чтобы сделать программу более понятной, удобно сделать так, чтобы функции, реализующие однотипные задачи для различных типов данных, имели одно и то же имя. Использование в тексте программы нескольких функций с одним и тем же именем, но с разными типами параметров, называется перегрузкой функций. Компилятор определяет, какую именно функцию надо вызвать, по типу параметров. Этот процесс называется разрешением перегрузки и основан на сложном наборе правил, позволяющих выбрать функцию с наиболее подходящими аргументами или выдать сообщение об её отсутствии.
Пример 1.15
//Реализованы три функции с одним именем:
//-функция, возвращающая значение;
//-функция с дополнительным аргументом-указателем;
//-функция с дополнительным аргументом-ссылкой.
#include
#include
#include
using namespace std ;
double tg(double);
void tg(double, double*);
void tg(double, double&);
int main()
{double angle, rez ;
setlocale(LC_ALL,"Russian") ;
cout << "Введите значения угла (в радианах): " ;
cin >> angle ;
cout << "Функция, возвращающая значение" << endl ;
cout << "Тангенс угла равен " << tg(angle) << endl << endl ;
cout << "Функция с дополнительным аргументом-указателем" << endl ;
cout << "Тангенс угла равен " ;
tg(angle, &rez) ;
cout << rez << endl << endl ;
cout << "Функция с дополнительным аргументом-ссылкой" << endl ;
cout << "Тангенс угла равен " ;
tg(angle, rez) ;
cout << rez << endl << endl ;
return 0 ;
}
double tg(double psi)
{return sin(psi) / cos(psi) ;
}
void tg(double psi, double *t)
{*t = sin(psi) / cos(psi) ;
}
void tg(double psi, double &t)
{t = sin(psi) / cos(psi) ;
}
Перегруженные функции должны быть в одной области видимости, иначе возникнет ситуация, аналогичная одинаковым именам переменных во вложенных блоках. Кроме того, необходимо иметь в виду, что возможно появление неоднозначности, например, при преобразовании типов.
Стандартный форматный ввод-вывод
В языке C/C++ все действия по вводу-выводу выполняются с помощью функций библиотеки. Ввод-вывод может быть реализован либо с использованием функций библиотеки языка C, либо с помощью потоков языка C++. Каждый из этих вариантов имеет свои плюсы и минусы.
Потоки C++ легче в использовании в простых случаях ввода-вывода, не требующих форматирования. Ввод-вывод в стиле C удобнее использовать при форматированном выводе в программах, не использующих объектно-ориентированную технологию. Смешивать эти два способа в одной программе не рекомендуется.
Лекция 14 Вывод в стиле C
В этом случае все данные рассматриваются как поток отдельных байтов. Для пользователя поток представляет собой либо файл (именованную область внешней памяти), либо физическое устройство (например, дисплей, клавиатура), рассматриваемое как частный случай файла. Функции ввода-вывода позволяют обрабатывать данные различных размеров и форматов от одиночных символов до структур данных.
Для использования функций ввода-вывода потока в программе необходимо директивой #include подключить файл или , в котором содержатся объявления функций ввода-вывода, определения констант, типов и структур, используемых функциями потока.
Вывод осуществляется в файл stdout (т.е. на экран), а ввод – из файла stdin (т.е. с клавиатуры). При запуске программы эти файлы открываются автоматически. При выводе информация преобразуется из внутренней формы памяти компьютера в символьную на носителе. При вводе – обратные преобразования. Такая передача с преобразованием называется форматной.
Вывод осуществляется с использованием функции:
int printf(fmt [, arg1, arg2,…])
где fmt – форматная строка.
Аргументов (выводимых значений) может быть ноль или больше. Аргументами могут быть выражения скалярных типов или ссылки на символьную строку. Значения аргументов преобразуются в символьную форму согласно спецификациям, указанным в форматной строке. При этом должно быть полное соответствие между типами и количеством аргументов и спецификациями форматной строки.
Функция printf возвращает число сгенерированных символов, поэтому в тексте программы можно писать:
i= printf(…); где i – число сгенерированных символов.
В общем случае fmt – это ссылка на символьную строку, поэтому возможна запись вида:
char *p; //p – ссылка на символьную переменную
p=”……..”;
printf(p, ….);
Форматная строка может содержать обычные символы, которые просто копируются в выводной поток, и спецификации преобразований [СП] значений аргументов, которые начинаются с символа %:
[символы] [СП1] [символы] [СП2]…
В общем случае вид спецификации преобразования при выводе:
%[флажок][ширина поля][.точность][h|l]символ_преобразования
Каждое поле спецификации преобразования является одиночным символом или числом.
Ширина поля (количество позиций, в которых будет размещено преобразованное выводимое значение) задаётся в виде целой положительной константы. Если окажется, что выводимое значение не помещается в отведённое для него поле, то поле автоматически расширяется до длины (количества символов) выводимого значения. Таким образом, информация не теряется, если даже не указать ширину поля вообще. Если это поле указано с начальным нулём, то к выводимому значению добавляются ведущие нули до заданного минимального размера поля.
Поле точность задаётся целым положительным числом с предшествующей точкой и определяет количество выводимых символов, количество десятичных позиций или количество значащих цифр. Для значений в форме с плавающей точкой точность задаёт число выводимых цифр справа от десятичной точки. Для строки символов точность означает максимальное выводимое число символов. Для целых значений поле задаёт минимальное количество выводимых цифр.
Префиксы размера ожидаемого аргумента:
[h] – используется как префикс для целых типов d, i, o, x, X, u для указания, что тип аргумента short;
[l] - используется как префикс для указания, что тип аргумента long.
Символы флажка управляют выравниванием вывода и печатью знака числа, пробелов, десятичной точки, префиксов восьмеричной и шестнадцатеричной систем счисления. Количество задаваемых флажков произвольное. В качестве флажков могут быть символы [-] [+] [пробел] [#].
[-] – если он присутствует, то выводимое значение выравнивается по левому краю отведённого поля вывода; если не указан – по правому краю;
[+] – используется для числовых значений; если он указан, то выводится знак выводимого числа (- или +); если не указан – то знак только у отрицательных чисел;
[пробел] – аналогичен [+], но для положительных чисел вместо знака плюс выводится пробел;
[#] – используется при выводе значения в восьмеричном или шестнадцатеричном формате; если он указан, то перед восьмеричным значением печатается 0, а перед шестнадцатеричным – 0x или 0X.
Символ преобразования - один из: d, i, u, o, x, X, c, s, f, e, E, g, G
d, i – формат вывода - десятичное целое со знаком;
u - формат вывода - десятичное целое без знака;
o - формат вывода - восьмеричное целое без знака;
x - формат вывода - шестнадцатеричное целое без знака, при этом в качестве шестнадцатеричных цифр используются строчные буквы “abcdef”;
X – аналогично, но буквы прописные “ABCDEF”;
c – выводится одиночный символ; соответствующий аргумент может иметь тип char или int; для типа int выводится байт с большим адресом;
s – вывод символьной строки, при этом аргумент – ссылка на символьную строку или имя символьного массива. Символы строки выводятся до первого символа пустой ноль (‘\0’) или до достижения значения заданной точности;
f – аргумент типа с плавающей точкой; выводимое значение имеет форму вещественной константы без буквы e: [-]ццц.ццц, где ццц – одна или более десятичных цифр. После точки по умолчанию выводится 6 цифр, иначе – сколько указано в поле точности. Если точность указана нулевой или если само значение не имеет дробной части, то десятичная точка не выводится;
e – соответствующий аргумент типа с плавающей точкой; выводимое значение имеет формат: [-]ц.цццe[+|-]ццц. Первая цифра – значащая. После точки по умолчанию выводится 6 цифр, иначе – сколько указано в поле точности.
E – аналог e, но вместо e печатается E.
g – значение со знаком печатается в формате f или e в зависимости от того, что более компактно для данного значения и точности. При этом формат e используется в случае, если значение экспоненты меньше -4 или больше заданной точности. Конечные нули отсекаются, а десятичная точка выводится только в случае, если за ней есть хотя бы одна цифра.
G – аналогичен g, но при выводе вместо e печатается E.
Лекция 15
Пример 1.16:
//Форматный вывод
#include
#include
using namespace std ;
int main()
{setlocale(LC_ALL,"Russian") ;
int i, j, k, m ;
short s, h, r ;
long l, n, g ;
unsigned int ui, uj, uk ;
char c, cc,
cs[] = "Усталые, но довольные пионеры возвращались домой" ;
i = 23 ;
j = -134 ;
k = 0117 ;
m = 0xA3B ;
//Вывод значений типа int в десятичном формате
printf("Вывод значений типа int в десятичном формате\n") ;
printf(" i = %+.5d\tj = %i\tk = %d\tm = %i\n", i, j, k, m) ;
s = 40 ;
h = -157 ;
r = -015 ;
//Вывод значений типа short в десятичном формате
printf("Вывод значений типа short в десятичном формате\n") ;
printf(" s = %d\th = %i\tr = %d\n", s, h, r) ;
l = -25L ;
n = -555 ;
g = 147483647 ;
//Вывод значений long в десятичном формате
printf("Вывод значений типа long в десятичном формате\n") ;
printf(" l = %ld\tn = %li\tg = %ld\n", l, n, g) ;
ui = 157 ;
uj = 0xFF ;
uk = -15 ;
//Вывод беззнаковых целых значений в десятичном формате
printf("Вывод беззнаковых целых значений в десятичном формате\n") ;
printf(" ui = %u\tuj = %u\tuk = %u\n", ui, uj, uk) ;
//Вывод беззнаковых целых значений в восьмеричном формате
printf("Вывод беззнаковых целых значений в восьмеричном формате\n") ;
printf(" ui = %#o\tuj = %o\n", ui, uj) ;
//Вывод беззнаковых целых значений в шестнадцатеричном формате
printf("Вывод беззнаковых целых значений в шестнадцатеричном формате\n") ;
printf(" ui = %x\tuj = %#X\n", ui, uj) ;
c = 'A' ;
cc = 65 ;
//Вывод значений типа char в десятичном формате
printf("Вывод значений типа char в десятичном формате\n") ;
printf(" c = %i\tcc = %d\n", c, cc) ;
//Вывод значений типа char в символьном формате
printf("Вывод значений типа char в символьном формате\n") ;
printf(" c = %c\tcc = %c\n", c, cc) ;
//Вывод символьных массивов
printf("Вывод символьных массивов\n") ;
printf(" cs = %s\n", cs) ;
printf(" cs = %.21s\n", cs) ;
float f, a, t ;
double d, b ;
f = 1.75e3 ;
a = -1.75E-3 ;
t = .0000175 ;
//Вывод значений типа float в десятичном формате
printf("Вывод значений типа float в десятичном формате\n") ;
printf(" f = %f\ta = %f\tt = %+f\n", f, a, t) ;
//Вывод значений типа float в экспоненциальном формате
printf("Вывод значений типа float в экспоненциальном формате\n") ;
printf(" f = %#e\ta = %.4E\tt = %+e\n", f, a, t) ;
d = 3. ;
b = -123.456789e15 ;
//Вывод значений типа double в десятичном формате
printf("Вывод значений типа double в десятичном формате\n") ;
printf(" d = %+f\tb = %.3f\n", d, b) ;
//Вывод значений типа double в экспоненциальном формате
printf("Вывод значений типа double в экспоненциальном формате\n") ;
printf(" d = %+.3e\tb = %E\n", d, b) ;
return 0 ;
}
Результат работы программы:
Лекция 16 Ввод в стиле C
Ввод осуществляется с использованием функции:
int scanf(fmt [, par1, par2,…])
где fmt – форматная строка.
Функция вводит строку параметров par1, par2,… в формате, определённом форматной строкой fmt. Функция возвращает число переменных, которым присвоено значение.
Параметров (вводимых значений) может быть ноль или больше. Каждый параметр должен быть указателем на переменную, тип которой соответствует типу, указанному в форматной строке.
Форматная строка может содержать обычные символы и спецификации преобразований [СП] значений параметров, которые начинаются с символа %:
[символы] [СП1] [символы] [СП2]…
Спецификации преобразований указывают, что из входного потока надо читать символы и преобразовывать их в значение указанного типа. Это значение присваивается параметру в списке параметров, который следует за форматной строкой. Значение первого входного поля преобразуется в соответствии со спецификацией преобразования и сохраняется в области памяти, указанной первым аргументом за форматной строкой. Значение второго входного поля преобразуется в соответствии со следующей спецификацией преобразования и сохраняется в следующем аргументе и так до конца форматной строки.
Входное поле образуют все символы вплоть до первого пробельного символа или до первого символа, который не может быть преобразован в соответствии со спецификацией формата, или фиксированное количество символов, если оно указано (всё зависит от того, что произойдёт раньше).
Если за форматной строкой параметров больше, чем спецификаций преобразования, лишние параметры игнорируются. Если для спецификаций преобразования параметров недостаточно, результат не определён.
В общем случае вид спецификации преобразования при выводе:
%[*][длина][h|l]символ_преобразования
Каждое поле спецификации преобразования является одиночным символом или числом. Символ преобразования указывает, как интерпретируется входное поле.
Звёздочка (*) за символом % подавляет присваивание следующего входного поля, которое интерпретируется как поле указанного типа. Поле читается, но не сохраняется.
Поле длина в виде положительного десятичного целого числа задаёт максимальное число символов, которое может быть прочитано из входного потока, если только раньше не встретится пробельный символ.
Префикс l означает, что соответствующий параметр должен быть указателем на объект типа long или double.
Префикс h означает, что соответствующий параметр должен быть указателем на объект типа short.
Символ преобразования - один из: d, o, x, i, u, U, e, E, f, g, G, c, s
d – формат ввода - десятичное целое со знаком;
o - формат ввода - восьмеричное целое без знака;
x - формат ввода - шестнадцатеричное целое без знака;
i - формат ввода – десятичное, восьмеричное или шестнадцатеричное целое со знаком;
u - формат ввода - десятичное целое без знака;
U – формат ввода - десятичное целое длинное без знака;
c – вводится одиночный символ; если во входном потоке встретится пробельный символ, то он будет считан, а не пропущен;
s – ввод символьной строки. Символы строки вводятся до первого пробельного символа;
f, e, E, g, G – значение с плавающей точкой вида:
[+|-]цццц [E|e [+|-] цц], где цццц – одна или более десятичных цифр, возможно содержащих десятичную точку; цц – целое.
Лекция 17
Пример 1.17:
//Форматный ввод
#include
#include
using namespace std ;
int main()
{setlocale(LC_ALL,"Russian") ;
int id, io, ix ;
unsigned int ui, uo ;
char c, cc,
cs[50] = "",
ss[50] = "";
//Ввод значений типа int
printf(" Ввод значений типа int\n") ;
printf(" Введите числа в 10-ом, 8-ом и 16-ом форматах:\n ") ;
scanf("%i%o%x", &id, &io, &ix) ;
printf(" Были введены значения:\n") ;
printf(" id = %d\tio = %#o\tix = %#X\n", id, io, ix) ;
//Ввод беззнаковых целых значений
printf(" Ввод беззнаковых целых значений\n") ;
printf(" Введите два числа в 10-ом формате:\n ") ;
scanf("%u%u", &ui, &uo) ;
printf(" Были введены как беззнаковые значения:\n") ;
printf(" ui = %u\tuo = %u\n", ui, uo) ;
//Ввод значений типа char
printf(" Ввод значений типа char\n") ;
printf(" Введите два символа:\n ") ;
scanf("%*1c%1c%1c", &c, &cc) ;
printf(" Были введены символы:\n") ;
printf(" c = %c\tcc = %c\n", c, cc) ;
//Ввод символьных массивов
printf(" Ввод символьных массивов\n") ;
printf(" Введите строку символов:\n ") ;
scanf("%s%c%20c", cs, &c, ss) ; //имя массива является указателем
//считываем слово, после первого слова считываем пробел, затем символы
printf(" cs = %s\n", cs) ;
printf(" ss = %s\n", ss) ;
float fd, fe ;
//Ввод значений типа float
printf(" Ввод значений типа float\n") ;
printf(" Введите числа в 10-ом и экспоненциальном форматах:\n ") ;
scanf("%f%e", &fd, &fe) ;
printf(" Были введены значения:\n") ;
printf(" fd = %+.2f\tfe = %+e ", fd, fe) ;
return 0 ;
}
Результат работы программы:
Для ввода и вывода строк в стиле языка C лучше использовать функции:
- char* gets(char*st); - читает со стандартного устройства ввода (клавиатуры) строку символов, которая может содержать пробелы, и помещает её в строку st; возвращает указатель на st.
- int puts(char*st); -выводит на стандартное устройство вывода (экран) строку символов st и переводит курсор в начало следующей строки. Возвращает неотрицательное значение при успехе или EOF при ошибке.
Ввод и вывод с использованием потоковых объектов cin и cout языка C++ будут рассмотрены во второй части учебного пособия.