-> косвенный выбор компонента структурированного объекта, адресуемого указателем. При использовании операции требуется, чтобы с объектом был связан указатель. В этом случае формат применения операции имеет вид:
указатель_на_структурированный_объект-> имя_компонента;
Так как операции выбора компонентов структурированного объекта используются со структурами, объединениями и классами, то пояснения к этим операциям будут приведены позже после введения перечисленных понятий.
Операции с компонентами классов:
.* прямое обращение к компоненту класса по имени объекта и указателю на компонент;
->* косвенное обращение к компоненту класса через указатель на объект и указатель на компонент.
Комментарии по этим операциям будут даны позже после введения понятия класса.
:: операция указания области видимости имеет две формы: бинарную и унарную. Бинарная форма применяется для доступа к компоненту класса. Унарная операция:: позволяет получить доступ к внешней для некоторой функции именованной области памяти.
Запятая в качестве операции:
, несколько выражений, разделенных запятыми, вычисляются слева направо. В качестве результата сохраняются тип и значение самого правого выражения. Таким образом, операция «запятая» группирует вычисления слева направо. Тип и значение результата определяется самым правым из разделенных запятыми выражений. Значения всех левых выражений игнорируются. Например, в выражении (d = 4, f=5, d*2-f;) действия выполняются в таком порядке: переменной d присваивается 4; переменной f присваивается 5; результатом всего выражения в скобках является значение d*2-f т.е. 3.
Круглые () и квадратные [ ] скобки играют роль бинарных операций при вызове функций и индексировании элементов массивов. Таким образом, вызов функции, который имеет формат
имя_функции (список фактических параметров);
- это операция. Обращение к элементу массива, например, ar[1] -это тоже операция, которую можно записать и в таком виде ar+1.
Условная операция
В отличие от унарных и бинарных операций условная операция используется с тремя операндами. В изображении условной операции два размещённых не подряд символа?: и три операнда выражения:
выражение_1? выражение_2: выражение_3
Первым вычисляется значение выражения_1. Если оно истинно, т.е. не равно нулю, то вычисляется значение выражения_2, которое становится результатом выполнения условной операции. Если при вычислении выражение_1 получится 0, то в качестве результата берётся значение выражения_3. Классический пример:
x<0? –x: x;
Выражение возвращает абсолютное значение переменной х.
Операция явного преобразования (приведения) типа в языке Си++ имеет две различные формы. Каноническая форма, унаследованная от языка Си, имеет следующий формат:
(имя_типа) операнд
и позволяет преобразовывать значение операнда к нужному типу. В качестве операнда может быть использована переменная, константа или любое выражение, заключённое в круглые скобки.
Кроме рассмотренной канонической операции преобразования типа, в языке Си++ введена ещё одна возможность приведения типов, которую обеспечивает функциональная форма преобразования типа:
имя_типа (операнд)
Она может использоваться только в тех случаях, когда тип имеет простое (несоставное) наименование. Например:
long (2); //Всё правильно ошибок нет. Создаётся неименованная константа типа //long, которая инициализируется значением 2
unsigned long (2); // Ошибка. Имя типа составное.
Результатом выполнения операции преобразования типа является создание неименованного объекта типа имя_типа, который инициализируется значением операнда. При этом ни тип, ни значение операнда не изменяются.
Операции new и delete для динамического распределения памяти. Эти две унарные операции используются для динамического распределения памяти. Операция
new имя_типа;
либо
new имя_типа инициализатор;
позволяет выделить и сделать доступным свободный участок в памяти, размеры которого соответствуют типу данных, определяемому именем типа. В выделенный участок заносится значение, определяемое инициализатором. В случае успешного выполнения операция new возвращает адрес начала выделенного участка памяти. Если участок нужных размеров не может быть выделен (нет памяти), то операция new возвращает нулевое значение адреса (NULL). Возвращаемый операцией new адрес необходимо сохранить в указателе для дальнейшего доступа к полученному фрагменту памяти. Поэтому обычно операция new используется в выражении
тип* имя_ указателя = new имя_типа инициализатор;
или
тип* имя_ указателя = new имя_типа;
где имя_указателя – это идентификатор.
Для явного освобождения выделенного операцией new участка памяти используется оператор
delete имя_указателя;
где имя_указателя адресует освобождаемый участок памяти, ранее выделенный с помощью операции new. Повторное применение операции delete к тому же указателю даёт неопределенный результат. Также непредсказуем результат применения этой операции к указателю, получившему значение без использования операции new. Однако применение delete к указателю с нулевым значением (NULL) не запрещено, хотя и не имеет особого смысла.
Таблица 2.7
Ранг операций. Ниже приведена таблица рангов (приоритетов) операций.
| Ранг | Операции | Ассоциатив- ность |
| 1 | () [] ->::. | ® |
| 2 | ! ~ + - ++ -- & * sizeof (унарные операции) new delete (тип) тип() | |
| 3 | .* ->* | ® |
| 4 | * / % (мультипликативные бинарные операции) | ® |
| 5 | + - (аддитивные бинарные операции) | ® |
| 6 | << >> | ® |
| 7 | < <= >= > | ® |
| 8 | ==!= | ® |
| 9 | & | ® |
| 10 | ^ | ® |
| 11 | | | ® |
| 12 | && | ® |
| 13 | || | ® |
| 14 | ?: (условная операция) | |
| 15 | = *= /= %= += -= &= ^= |= <<= >>= | |
| 16 | , (операция запятая) | ® |
При вычислении выражений операции выполняются последовательно друг за другом в строгом соответствии с их рангом. В первую очередь в выражении выполняются операции с наивысшим 1-м рангом, потом 2-м рангом и т.д.. Последними выполняются операции с наинизшим 16-м рангом. Если в выражении имеются операции одного ранга, то они выполняются последовательно друг за другом в соответствии с направлением ассоциативности для этого ранга (слева направо ® или справа налево). Например, в выражении (13-4>5 && 3+5<4 && 4+5>3) восемь операций. Наивысшим ранг в данном случае пятый у аддитивных операций (-, +). Ассоциативность для этого ранга направлена слева направо поэтому операции будут выполнены друг за другом в следующем порядке (13-4), (3+5), (4+5). Найдем результат этого выражения, выполняя последовательно операции в соответствии с их рангом и ассоциативностью:
(13-4>5 && 3+5<4 && 4+5>3)
(9>5 && 3+5<4 && 4+5>3)
(9>5 && 8<4 && 4+5>3)
(9>5 && 8<4 && 9>3)
(1 && 8<4 && 9>3)
(1 && 0 && 9>3)
(1 && 0 && 1)
(0 && 1)
0
Таким образом, правильно используя таблицу рангов операций можно найти значение выражения любой сложности.
Разделители
Разделители, или знаки пунктуации, входят в число лексем языка:
[] () {},;: … * = # &
Как видно одни и те же лексемы языка в зависимости от места их использования в программе могут выступать в качестве знака операции или в качестве разделителя.
Квадратные скобки [] являются разделителями (а не операцией) при определении размерности определяемого массива
int A [] = { 1,2,3,4,5}; //квадратные скобки выступают в качестве разделителя
int s [2][5]; //квадратные скобки выступают в качестве разделителя
Круглые скобки ():
· входят как обязательный элемент в операторы цикла и условия;
· входят как обязательный элемент в определение, описание (в прототип) и вызов любой функции, где выделяют соответственно список формальных параметров, список спецификаций параметров и список фактических параметров;
· обязательны в определении указателя на функцию;
· группируют выражение, изменяя естественную последовательность выполнения операций;
· являются необходимым элементом при выполнении операции преобразования типа;
· применение круглых скобок настоятельно рекомендуется в макроопределениях.
Фигурные скобки {} обозначают начало и конец составного оператора или блока. Они используются также для выделения списка компонентов в определениях типов структур, объединений, классов и инициализации массивов и структур при их определении.
Запятая, разделяет элементы списков, используется в описаниях и определениях объектов одного типа, участвует в качестве разделителя в заголовке оператора цикла for.
В качестве ещё одной области применения запятой как разделителя нужно отметить описание производного класса, в котором используется список базовых классов и список вызываемых конструкторов. Кроме того, в списке однотипных компонентов класса они отделяются друг от друга запятыми.
Точка запятой; завершает каждый оператор, каждое определение (кроме определения функции) и каждое описание. Любое допустимое выражение, за которым следует ‘;’, воспринимается как оператор. Это справедливо и для пустого выражения, т.е. отдельный символ «точка с запятой» считается пустым оператором.
Двоеточие: служит для отделения метки от помечаемого ею оператора:
метка: оператор;
Метка – это идентификатор. Таким образом, допустимы, например, такие помеченные операторы:
XYZ: a = b+c –d; vv: x*=10; // XYZ и vv метки
Второе применение двоеточия – описание производного класса, где имя класса отделяется от списка базовых классов двоеточием.
Многоточие ‘…’ - это три точки без пробелов между ними. Оно используется для обозначения переменного числа параметров у функции при её описании и определении.
Звездочка * не является операцией при описании и определении указателей, например:
int * point; // звездочка не выполняет роль операции при описании указателя
char ** refer; // аналогично
Знак ‘=’ является разделителемпри определении объекта, отделяя определение объекта от списка его инициализации:
int D = 62; //
В списке формальных параметров функции знак ‘=’ указывает на выбираемое по умолчанию значение аргумента (фактического параметра).
Символ # используется для обозначения директив препроцессора. Если этот символ является первым отличным от пробела символом в строке программы, то строка воспринимается как директива препроцессора.
Символ & играет роль разделителя при определении переменных типа ссылки:
int Bl;
int &a = B1;
Отметив использование символа & в качестве разделителя при описании ссылок, отложим подробное рассмотрение ссылок.
2.7. Первоначальные сведения о функциях языка Си++
До этого момента все приведённые выше программы содержали только одну функцию – main(). Как уже говорилось любая программа, написанная на языке Си++, должна обязательно содержать функцию main(), и эта функция должна быть в программе единственной, так как именно с выполнения операторов этой функции и начинается выполнение программы. Синтаксис определения любой функции, в том числе и функции main(), задаётся следующими правилами:
тип_функции имя_функции (список формальных параметров){
тело функции
}
где тип_функции – тип возвращаемого в точку вызова функцией значения; имя_функции – в общем случае определяется пользователем; список формальных параметров - список типов данных, передаваемых функции; тело функции – последовательность, заключённых в фигурные скобки, операторов, реализующих алгоритм работы программы на языке Си++. Совокупность
тип_функции имя_функции (список формальных параметров);
называют ещё сигнатурой функции.
Приведем пример определения функции, которая суммирует два числа:
int sum(int z, int g) {
int s = z+g;
return s;
}
где int – тип функции; sum – имя функции; (int z, int g) – список формальных параметров (в данном случае z - первый формальный параметр типа int, g – второй формальный параметр типа int); в фигурные скобки {} заключено тело функции (состоящее в данном случае из двух операторов); int sum(int z, int g) – сигнатура функции sum().
Само по себе наличие определения функции в программе ничего не даёт, для того чтобы функция начала работать (т.е. начали выполняться операторы тела функции), должен обязательно присутствовать вызов функции в программе. Под вызовом функции понимают выражение
имя_функции(список_фактических_параметров)
где имя_функции – имя вызываемой функции; список_фактических_параметров – список констант, переменных или выражений записанных через запятую. Если фактический параметр представлен выражением, то сначала вычисляется значение выражения, и только потом осуществляется вызов функции. Для примера несколько вызовов функции sum() можно записать следующим образом:
sum(2, 1); sum(3+5,3-7); sum(f+2, d); sum(2, 2*sum(3,5))
в первом вызове первый фактический параметр равен 2, второй – 1; во втором вызове два фактических параметра представлены выражениями, только после нахождения значений которых осуществляется вызов функции; в третьем вызове первый фактический параметр является выражением, в котором используется переменная f, значение которой должно было быть определено выше в теле программы до вызова функции, вторым фактическим параметром является значение переменной d.
Что же происходит при вызове функции? При вызове функции формальным параметрам присваивается значение фактических параметров в строгом соответствии с их положением в списке. Другими словами, первому формальному параметру присваивается значение первого фактического параметра, второму формальному параметру – значение второго фактического параметра и т.д. После этого начинают выполняться операторы тела вызванной функции. Таким образом, через аппарат формальных и фактических параметров данные передаются в вызванную функцию. В приведенных выше примерах в первом вызове формальные параметры получают следующие значения z= 2, g = 1; во втором вызове z= 8, g = -5; в третьем вызове (если предположить, например, что f = 1, d = 6) z= 3, g = 6.
Функция имеет не только механизм получения данных из вызывающей программы, но может и вернуть данные в точку вызова. Для этого используется оператор return в теле функции. В общем виде его можно записать так:
return выражение;
С помощью этого оператора в точку вызова возвращается значение выражения, тип которого соответствует типу функции. Другими словами, результатом выполнения операции вызова функции является появление на месте вызова неименованного объекта с типом, совпадающим с типом функции, и значением равным значению выражения в операторе return.
В приведенных выше примерах вызова функции sum() в первом вызове на месте sum(2, 1) после выполнения операторов тела функции появится неименованный объект типа int с значением 3; во втором вызове на месте sum(3+5, 3-7) - появится неименованный объект типа int с значением 4; в третьем вызове на месте sum(f+2, d) - появится неименованный объект типа int с значением 9 (если предположить, например, что f = 1, d = 6). Именно потому, что функция sum() имеет тип int, т.е. возвращает один из арифметических типов данных, можно выполнять вложенные вызовы или использовать эту функцию в составе выражения. Например, выражение:
4+sum(2,1)/3+ sum(sum(1,2), 4); // (4+2+1)/3+ 1+2+4
является правильным и равно 12. Причем приоритет операции вызова функции в соответствии с таблицей рангов операций самый высокий, поэтому в выражении сначала выполнятся вызовы функций (в соответствии с направлением ассоциативности первым выполнится вызов sum(2,1), вторым sum(1,2) так как вызов может быть осуществлен только после вычисления значения фактических параметров и, наконец, sum(3, 4)) и только потом будут выполняться все остальные операции.
Приведем для примера полный текст программы на языке Си++, которая вызывает функцию sum(), и в которой определим ещё одну функцию, возвращающую максимум из двух чисел.
// Программа 2.1
#include "stdafx.h"
#include <iostream>
double max(double c, double k);/*1.Описание функции max()*/
int sum(int z, int g) { /*2. Определение функции sum()*/
int s = z+g; //3.
return s; //4.
} //5.
//-----------------------------------------
void main(){ /*6. Определение функции main()*/
int f = 1, d = 6; //7.
int z = -10, s; //8.
s = sum(3, 4); //9.
std::cout<<"\n s = "<<s; //10.
s = sum(f - d, s) - max(f+s,d); //11.
std::cout<<"\n s = "<<s; //12.
getchar();
} //13.
//-------------------------------------------
double max(double c, double k){ /*14. Определение функции max()*/
return c>k? c: k; //15.
}
Итак, рассмотрим последовательность, в которой будут выполняться строки программы. Первыми будут выполнены строки 6-8, так как выполнение программы всегда начинается с функции main(). В 9-й строке программы присутствует вызов функции sum(), следовательно, будут выполнены строки 2-5, т.е. операторы тела функции sum(), и затем будет закончено выполнение 9-й строки присвоение переменной s значения 7. Далее в 10-й строке значение переменной s будет выведено на экран монитора. При выполнении 11-ой строки вызываются функции sum() и max(), т.е. выполняются строки 2-5 и 14-16, затем переменной s присваивается новое значение. В 12-й строке программы новое значение s выводится на экран монитора. При выполнении 13-ой строки программа завершает своё выполнение.
Таким образом, строки 2-5 выполнялись два раза (так как два раза вызывалась функция sum()), но каждый раз формальные параметры получали разные значения и функция sum() естественно возвращала разные значения. Поэтому можно сказать, что в виде функции удобно оформлять фрагменты повторяющегося кода программы, а с помощью аппарата фактических и формальных параметров обеспечивать разные начальные условия.
Единственная строка программы, которая не выполнялась ни разу, это 1-я строка. Это строка является описанием (прототипом) функции max(). Описание необходимо в программе в том случае, если вызов функции по тексту программы предшествует её определению. В нашем случае вызов функции max() находится в 11-й строке, а определение начинается с 14-й. Синтаксис описания функции приведен ниже:
тип_функции имя_функции(список формальных параметров);
Как видно описание – это сигнатура функции с точкой запятой на конце. С помощью описания функции программист сообщает компилятору о том, что где-то далее в тексте программы будет вызвана функция с таким-то именем, с таким-то количеством и типом формальных параметров и с таким-то типом возвращаемого значения. Если же определение предшествует вызову, то компилятор получает эту информацию из него, что и продемонстрировано на примере функции sum(). Поэтому описание для функции sum() можно опустить.
Кроме функций, определенных программистом, есть огромное количество библиотечных функций, которыми постоянно приходится пользоваться. Например, необходимо в программе вычислить sin(x), для этого конечно можно написать свою собственную функцию, но проще воспользоваться готовой библиотечной. Для этого необходимо в справочнике или в помощи среды программирования найти описание функции и имя заголовочного файла, в котором это описание находится. Для функции sin() описание выглядит так double sin(double x) и находится оно в заголовочном файле math.h. В файле помощи к этой функции также сказано, что аргумент задаётся в радианах. Этой информации достаточно, чтобы можно было написать программу, в которой используется вызов библиотечной функции:
//Программа 2.2
#include "stdafx.h"
#include <iostream>
#include <math.h>
void main(){
double z;//Определение переменной z.
std::cout<<"\n Input x for sin(x), x = ";
std::cin>>z;//Ввод с клавиатуры значения в переменную z.
std::cout<<"\n Output sin(x) = "<<3*sin(z); /*Вызов библиотечной функции sin()*/
getchar();
}
Из описания функции sin() видно, что она имеет один формальный параметр типа double и возвращает в точку вызова значение функции в формате типа double. Описание функции находится в файле math.h, поэтому его необходимо с помощью директивы include включить в текст программы. Определение функции находится в виде двоичного кода в одном из файлов с расширением lib, оно будет включено компоновщиком в текст программы во время компоновки программы в среде программирования.
Таким образом, в этом параграфе были изложены основные понятия: определение, описание и вызов, связанные с функциями языка Си++. Безусловно, это не всё. Несколько позднее по мере освоения материала будут рассмотрены функции с переменным количеством параметров, рекурсивные функции, встроенные функции и т.д.






