§6 Выражения. Операции. Инструкции. Случайные числа

Синтаксис и семантика

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

Выражения

Выражение (expression) – это произвольная комбинация констант, переменных и операций приводящая к вычислению некоторого значения. В простейшем случае выражение может быть представлено одним литералом или переменной. Например:

0
-12.4
(x + 5) / 2
cout << ++num
c == 2 && (c < 5 || d > sqrt(3 * a))

Примечание: выражение и инструкция (см. ниже) – это не одно и тоже!

Операнды

Операнд (operand) ― это аргумент операции. В выражении один или несколько операндов связаны (по аналогии с математическим способом записи) операциями. В простейшем случае операнд может быть литералом, константой или переменной. В более общем случае – это выражение (в котором тоже свои операнды). В одно выражение могут входить операнды разных числовых типов. Преобразования типов мы рассмотрели на предыдущем уроке.
9

Операции

Операции (operator) – выполняют определенные действия над операндами. Полный список операций представлен в методичке «Краткий справочник по языку С++» (таблица 3). В соответствии с количеством операндов операции делятся на унарные (один операнд), бинарные (два операнда) и, единственную, тернарную (в которой три операнда). Операция вызова функции может содержать неограниченное количество операндов. Фактически, операция — это та же функция, но записываемая особым образом. В C++ имеется возможность определять операции для произвольных типов как функции (методы) — чтобы можно было работать с ними точно так же, как и с фундаментальными типами. Эта возможность называется «перегрузка операций». Операции имеют приоритет и ассоциативность.
Приоритет (precedence) определяет старшинство операции. Несколько операций могут иметь равный приоритет. В этом случае включается порядок вычисления – ассоциативность (associativity). Ассоциативность может быть либо слева-направо, либо справа-налево. Приоритет и ассоциативность определяют направление вычислительного процесса. Например:

2 + 4 * 6 / 3 
// результат 10, а не 12!
10 - 5 - 2
// результат 3, а не 7!

Первое выражение будет равносильно: (2 + ((4 * 6) / 3))
Второе выражение будет равносильно: ((10 - 5) - 2)
Для изменения порядка вычисления используются ().
Примечание: иногда в переводной литературе допускается неверный перевод, что приводит к некоторой путанице. Operator (операция) переводится как оператор, но в отечественной литературе за оператором (исторически, но не повсеместно) закреплено понятие statement (инструкция). В нашем курсе понятие оператор использоваться не будет.

Инструкция

Инструкция (statement) – представляет собой законченное предложение на языке C++. Инструкция указывает компьютеру выполнить некоторые действия. Чаще всего используется инструкция выражение (expression statement). Такая инструкция всегда завершается точкой с запятой;, даже если она находится в конце блока (см. ниже). Инструкции делятся на неисполняемые (например, для описания данных) и исполняемые (например, присваивание, ввод, вывод). Иногда, требуется использовать пустую инструкцию, состоящую из одиночной ";". Такая ситуация возникает, например, в циклах, когда все выражения заключены в заголовке цикла.

int a;
a = a + 1;
a++;

Составная инструкция. Область видимости

Если группа инструкций заключена в фигурные скобки {}, она должна рассматриваться как одна – составная инструкция. Составная инструкция обычно называется блоком. Блок не завершается ";", за исключением блока класса (class) или структуры (struct). Переменные описанные в блоке (локальные переменные) становятся доступными от места описания и до конца блока. Вне блока переменные не доступны, но доступны в блоках, вложенные в данный. Если переменные определены, но не инициализированы внутри блока, то они имеют неопределенные значения. Переменные объявленные вне блока (но не инициализированные) неявно получают значения по умолчанию (для числовых типов 0). Если переменная описанная вне блока имеет тоже имя, что и переменная в блоке, то действие глобальной переменной внутри блока будет отключаться, чтобы не возникало конфликта имен.
Программа 6.1

//==============================================================//
// Программа в которой две переменные - локальная и глобальная  //
// имеют одинаковые имена                                       //
//==============================================================//
#include <iostream>
using namespace std;

int main() {
	int a = 1;
	if (true) {
		int a = 2;
		cout << a << endl;
	}
	cout << a << endl;
	return 0;
}
2
1

Использовать в программе одинаковые имена не желательно, так как это может привести к путанице, особенно при работе со сложными структурами данных.

Решение задач линейной структуры

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

  • Описание переменных и констант
  • Реализация диалога с компьютером
  • Инструкции реализации задачи
  • Вывод результатов

Разработка программ такого типа позволяет получить навыки работы с различными типами данных, умение создавать дружественные интерфейсы программ и составлять арифметические выражения на языке программирования C++.
Рассмотрим пример типичной задачи линейной структуры:
В треугольнике известны три стороны a, b и c; найти (в градусах) углы этого треугольника, используя формулы:
 cos(A)=\frac{b^2+c^2-a^2}{2 a b},\qquad sin(B)=\frac{b sin(A)}{a},\qquad C=180\textdegree-(A+B)
Примечание: тригонометрические функции возвращают значение угла в радианах. Формула для перевода рад <-> град:
 \alpha[\textdegree] = \alpha[rad]\times(180/\pi)
Программа 6.2

//==============================================================//
// Нахождение углов треугольника по трем сторонам               //
//==============================================================//
#include <iostream>
#include <cmath>
using namespace std;

int main() {
	double a, b, c, A, B, C;
	const double RadToGre = 180 / M_PI;

	cout << "a = "; cin >> a;
	cout << "b = "; cin >> b;
	cout << "c = "; cin >> c;

	A = acos((b * b + c * c - a* a) / (2 * b * c));
	B = asin(b * sin(A) / a);
	C = 180 - (A + B) * RadToGre;
	A *= RadToGre;
	B *= RadToGre;

	cout << "A = " << A << endl;
	cout << "B = " << B << endl;
	cout << "C = " << C << endl;

	return 0;
}
a = 2.3
b = 5
c = 4.7
A = 27.2069
B = 83.6799
C = 69.1133

Процессор случайных чисел

В программах может возникнуть необходимость получения значения какого-либо арифметического типа случайным образом, согласно заданной функции распределения вероятности. Для создания наборов случайных чисел в C++ существует библиотека random. В этой библиотеке определены два типа взаимосвязанных классов:

  • random-namber engine – процессоры случайных чисел
  • random-namber distribution – распределения случайных чисел

В библиотеке random несколько процессоров случайных чисел. По умолчанию используется процессор default_random_engine.
Для определения диапазона генерации случайных чисел необходимо использовать распределение случайных чисел. Наиболее часто используется нормальное распределение. Для такого типа распределения определены классы для целых и действительных типов:

  • uniform_int_distribution<type>
  • uniform_real_distribution<type>

Процесс создания последовательности случайных чисел будет состоять из следующих этапов:

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

Посмотрим на примере.
Программа 6.3 Получить два массива (20 элементов в каждом) случайных чисел: целых в интервале [10, 99] и вещественных в интервале [0, 1].

#include <iostream>
#include <random>
#include <array>
using namespace std;

int main() {
	array<int, 20> ar_int;
	array<double, 20> ar_dbl;
	default_random_engine rnd;
	uniform_int_distribution<unsigned> d1(10, 99);
	uniform_real_distribution<double> d2(0, 1);
	for (size_t i = 0; i < ar_int.size(); i++) {
		ar_int[i] = d1(rnd);
		ar_dbl[i] = d2(rnd);
	}
	for (auto &r : ar_int) cout << r << " ";
	cout << "\n";
	cout.precision(2);
	for (auto &r : ar_dbl) cout << r << " ";
	cout << endl;
	return 0;
}

Возможный вывод:

10 51 14 94 84 57 44 71 86 68 91 14 66 42 75 16 34 52 42 90 
0.76 0.22 0.68 0.52 0.053 0.0077 0.42 0.93 0.092 0.7 0.26 0.33 0.99 0.98 0.65 0.88 0.77 0.27 0.49 0.061 

Процессор генерирует машинно-зависимую последовательность элементов. Это означает, что при каждом запуске программы будет генерироваться одна и та же последовательность. Для того, чтобы процессор выдавал всегда различные наборы случайных чисел необходимо установить начальное число последовательности, например с помощью библиотечной функции time (заголовок ctime), которая возвращает количество секунд прошедшее от начала заданной эпохи:

// как аргумента конструктора:
default_random_engine rnd(time(0));
// или как аргумент метода seed
rnd.seed(time(0));

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

static default_random_engine rnd(time(0));
static uniform_int_distribution d1(10, 99);
static uniform_real_distribution d2(0, 1);
Домашняя работа
  1. В треугольнике известны три стороны a, b и c; найти радиус описанной окружности и угол А (в градусах)
  2. Найти площадь кольца, внутренний радиус которого равен r, а внешний R (R > r)
Вопросы
  1. В чем отличие синтаксиса от семантики?
  2. Объясните понятия операнд, оператор, операция
  3. Что такое приоритет операции; зачем он нужен?
  4. В каком порядке выполняются операции, если они имеют одинаковый приоритет?
  5. Как изменить порядок вычислений в выражениях?
  6. Для чего предназначен составной оператор? Что такое локальная переменная?
Учебник

§56

Презентация к уроку

PDF
ODP

Литература
  1. Прата, Стивен. Язык программирования C++. Лекции и упражнения, 6-е изд.: Пер. с англ. — М.: ООО «И.Д. Вильямс», 2012
  2. Стенли Липпман, Жози Лажойе, Барбара Му. Язык программирования С++. Вводный курс. — Вильямс: 2007
  3. Павловская Т. А. C/C++. Программирование на языке высокого уровня. – СПб.: Питер, 2003.
  4. Стиль, разработка, эффективность, отладка и испытание программ
  5. Операция (программирование)


Print Friendly, PDF & Email

Добавить комментарий