Учебно-методическое пособие Тольятти тгу 2011 удк 004. 3(075) ббк 32. 97

Вид материалаУчебно-методическое пособие

Содержание


2.3.1Функции, библиотеки функций, переменные, операторы, операции
1.0, затем вычисленное значение записывается в переменную x
2.3.2Пример простой программы
После текста комментария надо поставить звёздочку, а затем – косую
Введите текущий год и нажмите ENTER.
Выражение1 Выражение2 ... ВыражениеN
2000. Это происходит после того, как пользователь напечатает «2000
2.3.3Базовые типы данных char, int, long, float, double
2.3.4Модификатор unsigned
2.3.5Массивы: описание, определение, способы формирования и особенности работы
Hours_array hours
Сколько часов отработал сотрудник номер 1?: 38
Сколько часов отработал сотрудник номер 5?: 38
2.3.6Классификация операций: арифметические, сравнения, логические, машинно-ориентированные, адресные, присваивания. Порядок и н
Операция ИЛИ
Операция присваивания «=»
Результатом операции
Операция присваивания, соединенная с одной из бинарных операций
A[i++] +=b; // эквивалентно A[i] = A[i] + b; i++
2.3.7Классификация операторов – выражения с «;», составной оператор (последовательность), условие, цикл, переход
...
Полное содержание
Подобный материал:
1   2   3   4   5   6   7   8   9   10

2.3.1Функции, библиотеки функций, переменные, операторы, операции


Функция является основной структурной единицей языка Си. В других языках функции иногда называют подпрограммами. Функция – это фрагмент программы, который может вызываться из других программ. Функция обычно выполняет алгоритм, который описывается и реализуется отдельно от других алгоритмов. При вызове функции передаются аргументы, которые могут быть использованы в теле функции. В результате своей работы функция возвращает значение некоторого типа. Например, функция sin, прототип которой задается строкой

double sin(double x);

имеет один аргумент x типа double (вещественное число). Результат функции также имеет тип double. При вызове функция sin вычисляет синус числа, переданного ей в качестве фактического аргумента, и возвращает вычисленное значение в вызывающую программу.
Вызов функции происходит в результате использования её имени в выражении. За именем функции следуют круглые скобки, внутри которых перечисляются фактические значения её аргументов. Даже если аргументов нет, круглые скобки с пустым списком аргументов обязательно должны присутствовать.

После вызова функции значение, возвращенное в результате её выполнения, используется в выражении (имя функции как бы заменяется возвращенным значением).

Пример:

x = sin(1.0);

Здесь в результата вызова функции sin вычисляется синус числа 1.0, затем вычисленное значение записывается в переменную x при выполнения оператора присваивания «=». Другой пример:

f();

Вызывается функция f, не имеющая параметров. Значение, возвращённое в результате выполнения функции f, не используется. Программа на Си состоит из функций. Работа программы всегда начинается с функции с именем main.

Для выполнения определённых действий функция main обычно обращается к другим функциям, часть из которых находится в той же самой программе, а часть – в библиотеках, содержащих ранее написанные функции.

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

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

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

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

age = 32;

salary = 25000.75;

distance_to_the_moon = 238857;

Замечание: значения, присваиваемые переменным, не должны содержать запятые (например, 25,000.75 и 238,857). Если вы включаете запятые, компилятор C++ будет генерировать и выводить сообщения о синтаксических ошибках.

Фрагмент следующей программы сначала объявляет переменные, а затем использует оператор присваивания, чтобы присвоить переменным значения:

#include

void main(void)

{
int age;


float salary;

long distance_to_the_moon;

age = 32;

salary = 25000.75;

distance_to_the_moon = 238857;

}

При объявлении переменной часто удобно присваивать ей начальное значение. Чтобы упростить такую процедуру, C++ позволяет присваивать значение во время объявления переменной, как показано ниже:

int age = 32;

float salary = 25000.75;

long distance_to_the_moon = 238857;

Несмотря на ограниченный набор базовых типов данных (целые и вещественные арифметические данные и строковые литералы) в языке Си++ определён обширный набор операций над данными, часть из которых непосредственно соответствует машинным командам. Как и во всех языках программирования, операции служат для построения выражений. Выражение представляет собой последовательность операндов и знаков операций и служит для вычисления некоторого значения.

В качестве операндов в выражении выступают идентификаторы переменных, константы, и строковые литералы, являющиеся первичными выражениями. Выражение, заключённое в круглые скобки, также рассматривается как первичное. Каждая операция предполагает использование определённых типов операндов (целых, вещественных, указателей). Операция присваивания в Си++ также является выражением, в связи с этим различаются операнды, которым можно присвоить новое значение и операнды, значение которых не может меняться. Чтобы операнду можно было присвоить значение, ему должна соответствовать область памяти и компилятору должен быть известен адрес этой памяти. Такие операнды называют L-выражениями (от английского left – левый), так как они могут быть записаны в левой части оператора присваивания.

Результат вычисления выражения зависит от приоритетов операций. В Си++ сложная система приоритетов операций, включающая 16 уровней. В таблице 3 приведён перечень операций Си++ с указанием их приоритетов, назначения и схемы записи.

Операторы – это синтаксические конструкции, определяющие действия, выполняемые программой. В Си/Си++ имеются следующие типы операторов: операторы-выражения, операторы выбора, операторы цикла и оператор перехода. Синтаксис некоторых операторов содержит выражения, играющие роль условий, в зависимости от выполнения или невыполнения которых выбирается та или иная последовательность действий. Поскольку в Си нет булевых выражений, в качестве условий используются любые выражения, дающие скалярные значения, и условие считается выполненным, если это значение отлично от нуля, и невыполненным, если оно равно нулю.

Перед любым оператором может быть записана метка в виде идентификатора, отделённого от помечаемого оператора двоеточием.

В определении языков Си и Си++ отсутствуют операторы ввода-вывода, операции над строковыми данными и многие другие средства, имеющиеся в других языках программирования. Этот недостаток компенсируется добавлением в системы программирования Си и Си++ библиотек функций, подключаемых к рабочим программам при редактировании связей и называемых библиотеками времени выполнения. Отделение этих библиотек от компилятора позволяет в необходимых случаях использовать различные варианты этих библиотек, например, для различных моделей ЭВМ или операционных систем.

Часть функций из библиотек времени выполнения стандартизована, в стандарте зафиксированы имя функции, ее назначение, перечень и типы параметров и тип возвращаемого значения. Другие функции ориентированы на конкретные модели ЭВМ и операционные системы, способы их вызова и использования могут быть различными в системах программирования разных фирм.

Чтобы обеспечить возможность контроля правильности вызова функций при компиляции программы, в систему программирования входят файлы заголовков функций времени выполнения. В файлах заголовков определяются прототипы библиотечных функций, а также константы и типы данных, используемые этими функциями.

2.3.2Пример простой программы


Ниже приведён исходный текст простой программы на Си++.

 // В языке Си++ с двойной косой черты начинаются комментарии

// (например, как эта строка). Компилятор игнорирует комментарии,

// начиная от первой черты и до конца строки.

/* Второй способ записи комментариев – после косой черты со звёздочкой.

После текста комментария надо поставить звёздочку, а затем – косую

черту. Комментарии, записанные подобным образом, могут занимать

больше одной строки. */

/* В программе ОБЯЗАТЕЛЬНО должно быть достаточное количество

комментариев! */

/* Эта программа запрашивает у пользователя текущий год, возраст

пользователя и еще один год. Затем программа вычисляет возраст

пользователя, который будет у него во втором введённом году.*/

#include

int main()

{

int year_now, age_now, another_year, another_age;

cout << "Введите текущий год и нажмите ENTER.\n";

cin >> year_now;

cout << "Введите свой возраст (в годах).\n";

cin >> age_now;

cout << "Введите год, для которого вы хотите узнать свой возраст.\n";

cin >> another_year;

another_age = another_year - (year_now - age_now);

if (another_age >= 0)

{

cout << "В " << another_year << " году вам будет ";

cout << another_age << "\n";

}

else

{

cout << "В " << another_year << " вы еще не родились!\n";

}

return 0;

}

Некоторые свойства приведённой программы являются обычными для большинства программ на Си++. Программа начинается (после комментариев) с оператора

#include

Директива include предназначена для включения в исходный текст содержимого другого файла. Например, в приведённую программу включается файл iostream.h, содержащий описания функций стандартной библиотеки ввода/вывода для работы с клавиатурой и экраном.

Алгоритм, записанный в приведённой программе, очень простой. Поэтому структуру программы легко представить в виде списка последовательно выполняемых команд (операторов). Схематично программу, содержащуюся после директивы #include, можно представить в виде:

int main()

{

Первый оператор;

...

...

Последний оператор;

return 0;

}

 Подобная структура является общей для всех программ на Си++. Каждый оператор в теле программы завершается точкой с запятой. В хорошо разработанной большой программе большинство операторов являются обращениями (вызовами) к подпрограммам, которые записываются после функции main() или в отдельных файлах. Каждая подпрограмма (функция) имеет структуру, подобную функции main(). Но функция main() в каждой программе только одна. Именно с нее начинается выполнение программы. (Подробнее функции будут рассматриваться далее.)

В конце функции main() записана строка:

return 0;

Эта строка значит «вернуть операционной системе в качестве сигнала об успешном завершении программы значение 0». Оператор возврата return применяется не только при завершении программы, но и при завершении отдельных подпрограмм. В любом случае этот оператор возвращает определённое значение на более высокий уровень управления.

В программе-примере используются четыре переменные:

year_now, age_now, another_year и another_age

Переменные в программировании отличаются от математических переменных. Они используются как символические имена «фрагментов оперативной памяти компьютера». При выполнении программы в различные моменты времени переменные могут хранить различные значения. В приведённой программе первое упоминание четырёх переменных содержится в строке с оператором описания переменных:

int year_now, age_now, another_year, another_age;

Этот оператор уведомляет компилятор, что для хранения четырёх переменных типа «целое число» (integer – int) требуется выделить необходимое количество памяти. Эта область памяти будет зарезервирована в течение выполнения оставшейся части программы. Переменные всегда должны быть описаны до первого использования. В программировании хорошим стилем считается описание всех переменных, используемых в подпрограмме, в начале этой подпрограммы. В Си++ есть несколько различных типов переменных, и они будут обсуждаться немного позже.

 После компиляции программы её можно запустить на выполнение. Результат выполнения на экране будет выглядеть примерно так:

Введите текущий год и нажмите ENTER.

2000

Введите свой возраст (в годах).

21

Введите год, для которого вы хотите узнать свой возраст.

2017

В 2017 году вам будет 38

Первая, третья, пятая и седьмая строки выдаются на экран программой с помощью следующего оператора:

cout << Выражение1 << Выражение2 << ... << ВыражениеN;

Этот оператор выводит на экран сообщение:

Выражение1 Выражение2 ... ВыражениеN

Последовательность операторов

cout << Выражение1;

cout << Выражение2;

...

...

cout << ВыражениеN;

приводит к аналогичному результату. Если между выражениями требуется вставить пробелы или новые строки, то их нужно указать явно, с помощью символов "" и "\n" соответственно.

Числа, показанные выше в примере выдачи на экран полужирным шрифтом, были напечатаны пользователем. В показанном примере оператор

cin >> year_now;

приводит к тому, что переменной year_now присваивается значение 2000. Это происходит после того, как пользователь напечатает «2000» и нажмет клавишу Enter. В программе есть ещё места, где переменным присваиваются значения, в том числе оператор присваивания:

another_age = another_year - (year_now - age_now);

Операция «=» означает «присвоить переменной, стоящей слева от знака равенства, значение, указанное справа». Проверка на равенство в Си++ обозначается двойным символом: «==».

В нескольких последних строках программы (до строки «return 0») записано:

if (another_age >= 0)

{

cout << "В " << another_year << " году вам будет ";

cout << another_age << "\n";

}

else

{

cout << "В " << another_year << " вы еще не родились!\n";

}

 Оператор ветвления (условный оператор) «if...else...» выглядит примерно одинаково во всех процедурных языках программирования. В Си++ он называется просто оператором if, и его общая структура такова:

if (условие)

{

Оператор1;

...

...

ОператорN;

}

else

{

ОператорN+1;

...

...

ОператорN+M;

}

Часть «else (иначе)» в операторе if необязательна. Более того, если после «if (условие)» стоит только один оператор, то можно опустить фигурные скобки и записать оператор так:

if (условие)

Оператор1;

В программах условные операторы часто встречаются группами, например:

...

...

if (total_test_score < 50)

cout << "Вы не прошли тест. Выучите материал как следует.\n";

else if (total_test_score < 65)

cout << "Вы прошли тест со средним результатом.\n";

else if (total_test_score < 80)

cout << "Вы хорошо выполнили тест.\n";

else if (total_test_score < 95)

cout << "Вы показали отличный результат.\n";

else

{

cout << "Вы сдали тест нечестно!\n";

total_test_score = 0;

}

...

...

Приведённый фрагмент программы может показаться довольно сложным. Тем не менее, он соответствует правилам Си++.

При обработке приведённого фрагмента программы компилятор Си++ трактует весь текст, выделенный ниже полужирным шрифтом, как один оператор после первого слова else.

...

...

if (total_test_score < 50)

cout << "Вы не прошли тест. Выучите материал как следует.\n";

else if (total_test_score < 65)

cout << "Вы прошли тест со средним результатом.\n";

else if (total_test_score < 80)

cout << "Вы хорошо выполнили тест.\n";

else if (total_test_score < 95)

cout << "Вы показали отличный результат.\n";

else

{

cout << "Вы сдали тест нечестно!\n";

total_test_score = 0;

}

...

...

2.3.3Базовые типы данных char, int, long, float, double


Тип переменной указывает тип значения, хранимого в переменной, а также набор операций (таких как сложение, умножение и другие), которые программа может выполнять над значением переменной. Большинство программ на C++ будут использовать типы переменных, перечисленные в табл. 1.

Таблица 1. Типы переменных C++

Тип

Хранимые значения

char

Значения в диапазоне от -128 до 127. Обычно используется для хранения букв алфавита

int

Значения в диапазоне от -32768 до 32767

unsigned

Значения в диапазоне от 0 до 65535

long

Значения в диапазоне от -2147483648 до 2147483647

float

Значения в диапазоне от -3.4 x 10-38 до 3.4 x 1038

double

Значения в диапазоне от 1.7х 10-308 до 1.7х 10308

Прежде чем вы сможете использовать переменную, ваша программа должна её объявить. Другими словами, вам следует представить переменную компилятору C++. Чтобы объявить переменную в программе, вам следует указать тип переменной и её имя, по которому программа будет обращаться к данной переменной. Указывайте тип и имя переменной после открывающей фигурной скобки главной программы, как показано ниже:

тип_переменной имя_переменной;

Как правило, тип переменной будет одним из типов, перечисленных в табл. 1. Выбираемое вами имя переменной должно нести смысловую нагрузку, которая описывает (для всех, кто читает вашу программу) использование переменной. Например, ваша программа могла бы использовать переменные, такие как employee_name, employee_age и т.д. Обратите внимание на точку с запятой, которая следует за именем переменной. В C++ объявление переменной считается оператором. Поэтому вы должны поставить после объявления точку с запятой.

Фрагмент следующей программы объявляет три переменные, используя типы int, float и long.

#include

void main(void)

{
int test_score;


float salary;

long distance_to_mars;

}

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

float salary, income_tax, retirement_fund;

Каждая создаваемая вами переменная должна иметь уникальное имя. Чтобы сделать свои программы более лёгкими для чтения и понимания, следует использовать смысловые имена переменных. Например, следующий оператор объявляет три переменных с именами х, y и z:

int х, у, z;

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

int student_age, test_score, grade;

При выборе имен переменных можно использовать комбинацию букв, цифр и подчёркивания (_). Первый символ в имени переменной должен быть буквой или подчёркиванием. Нельзя начинать имя переменной с цифры. Кроме того, в C++ буквы нижнего и верхнего регистров считаются разными. Сначала для имен своих переменных используйте только буквы нижнего регистра. Как только вы освоитесь в C++, можете комбинировать буквы верхнего и нижнего регистров для получения смысловых имён, как показано ниже:

float MonthlySalary, IncomeTax;

При создании имён переменных необходимо знать, что в C++ слова, перечисленные в табл. 2, резервируются в качестве ключевых слов, имеющих специальное значение для компилятора. Вы не имеете права использовать ключевые слова C++ в качестве имён переменных.

Таблица 2. Ключевые слова C++.

asm

auto

break

case

catch

char

class

const

default

delete

do

double

else

enum

extern

float

friend

goto

if

inline

int

long

new

operator

protected

public

register

return

short

signed

sizeof

static

switch

template

this

throw

try

typedef

union

unsigned

void

volatile

while

continue

for

private

struct

virtual

2.3.4Модификатор unsigned


Типы intshort и long представляют целые числа со знаком. Для типа char стандарт Си не устанавливает явно наличие знака, однако большинство компиляторов трактуют элементы типа char как целые числа со знаком в диапазоне от -128 до 127. Если необходимо трактовать целые числа как неотрицательные, или беззнаковые, следует добавить модификатор unsigned при описании переменных. Примеры:

unsigned char c = 255;

unsigned short s = 65535;

unsigned int i = 1000000000;

unsigned j = 1;

При описании типа «unsigned int» слово «int» можно опускать, что и сделано в последнем примере.

Следует по возможности избегать беззнаковых типов, поскольку арифметика беззнаковых чисел не на всех компьютерах реализована одинаково и из-за этого при переносе программы с одной платформы на другую могут возникнуть проблемы. По этой причине в языке Java беззнаковые числа запрещены.

Имеется также модификатор signed (знаковый). Его имеет смысл использовать на тех платформах, в которых тип char является беззнаковым. Пример описания:

signed char d = (-1);

2.3.5Массивы: описание, определение, способы формирования и особенности работы


В программировании часто возникают задачи, связанные с обработкой больших объёмов данных. Для постоянного хранения этих данных удобно пользоваться файлами. Например, в программе для ввода и сортировки длинных числовых списков данные можно ввести с клавиатуры один раз и сохранить в файле для последующего многократного использования. Но до сих пор не было рассмотрено удобного способа представления больших объемов данных внутри программ. Для этой цели в Си++ часто применяются массивы – простейшая разновидность структурных типов данных.

Массив – это набор переменных одного типа («int», «char» и др.). При объявлении массива компилятор выделяет для него последовательность ячеек памяти, для обращения к которым в программе применяется одно и то же имя. В то же время массив позволяет получить прямой доступ к своим отдельным элементам.

Оператор описания массива имеет следующий синтаксис:

<тип данных> <имя переменной>[<целое значение>];

Допустим, в программе требуется обрабатывать данные о количестве часов, отработанных в течение недели группой из шести сотрудников. Для хранения этих данных можно объявить массив:

int hours[6];

или, лучше, задать численность группы с помощью специальной константы:

const int NO_OF_EMPLOYEES = 6;

int hours[NO_OF_EMPLOYEES];

Если подобные массивы будут часто встречаться в программе, то целесообразно определить новый тип:

const int NO_OF_EMPLOYEES = 6;

typedef int Hours_array[NO_OF_EMPLOYEES];

Hours_array hours;

Hours_array hours_week_two;

В любом из трёх перечисленных вариантов, в программе будет объявлен массив из 6 элементов типа «int», к которым можно обращаться с помощью имён:

hours[0] hours[1] hours[2] hours[3] hours[4] hours[5]

Каждое из этих имён является именем элемента массива. Числа 0, ..., 5 называются индексами элементов. Отличительная особенность массива заключается в том, что его элементы – однотипные переменные – занимают в памяти компьютера последовательные ячейки памяти (рис. 2).



Рис. 2. Расположение элементов массива в оперативной памяти (направление сверху вниз соответствует возрастанию адресов ячеек памяти).

С элементами объявленного массива можно выполнять все действия, допустимые для обычных переменных этого типа (выше был приведен пример целочисленного массива, т.е. типа «int»). Например, возможны операторы присваивания наподобие:

hours[4] = 34;

hours[5] = hours[4]/2;

или логические выражения с участием элементов массива:

if (number < 4 && hours[number] >= 40) { ...

Присвоить значения набору элементов массива часто бывает удобно с помощью циклов «for» или «while». В приведённой ниже программе в цикле у оператора запрашивается количество часов, отработанных каждым сотрудником группы за неделю. Хотя более естественной может показаться нумерация сотрудников от 1 до 6, а не от 0 до 5, но необходимо помнить, что индексация массивов в Си++ начинается с 0. Поэтому приведённая ниже программа вычитает 1 из порядкового номера сотрудника, чтобы вычислить индекс соответствующего элемента массива.

#include

const int NO_OF_EMPLOYEES = 6;

typedef int Hours_array[NO_OF_EMPLOYEES];

int main()

{

Hours_array hours;

int count;

for ( count = 1; count <= NO_OF_EMPLOYEES; count++ )

{

cout << "Сколько часов отработал сотрудник";

cout << " номер " << count << "?: ";

cin >> hours[count - 1];

}

return 0;

}

В типичном сеансе работы приведённая выше программа выведет на экран подобные сообщения:

Сколько часов отработал сотрудник номер 1?: 38

Сколько часов отработал сотрудник номер 2?: 42

Сколько часов отработал сотрудник номер 3?: 29

Сколько часов отработал сотрудник номер 4?: 35

Сколько часов отработал сотрудник номер 5?: 38

Сколько часов отработал сотрудник номер 6?: 37

На рис. 3. показано состояние целочисленного массива после ввода этих данных.



Рис. 3. Состояние массива после присвоения значений его элементам.

Полезно разобраться, что произошло бы, если бы в программе внутри цикла «for» в операторе «cin ...» отсутствовало бы вычитание 1 из переменной «count». Компилятор Си++ (в отличие, например, от Паскаля) не обнаруживает ошибки выхода за пределы массива, поэтому участок памяти компьютера с массивом и сразу за ним оказался бы в состоянии, показанном на рис. 4.



Рис. 4. Ошибка выхода за пределы массива.

Другими словами, значение «37» было бы размещено в ячейке памяти, достаточной для хранения целого числа, которая расположена сразу после массива «hours». Это чрезвычайно нежелательная ситуация, потому что компилятор может зарезервировать эту ячейку памяти для другой переменной (например, для переменной «count»).

Массивы могут быть любого типа, не обязательно типа «int». Ниже приведена программа, в которой символьный («char») массив применяется для печати собственного исходного файла на экране в обратном порядке.

#include

#include

const int MAX_LEN = 1000;

typedef char File_array[MAX_LEN];

int main()

{

char character;

File_array file;

int count;

ifstream in_stream;

in_stream.open("prg6_1_2.cpp");

in_stream.get(character);

for ( count = 0; !in_stream.eof() && count < MAX_LEN; count++ )

{

file[count] = character;

66

in_stream.get(character);

}

in_stream.close();

while (count > 0)

cout << file[--count];

return 0;

}

В заголовке цикла «for» обратите внимание на условие «... && count < MAX ; ...», специально предусмотренное для предотвращения выхода за пределы массива.

2.3.6Классификация операций: арифметические, сравнения, логические, машинно-ориентированные, адресные, присваивания. Порядок и направление выполнения


Если посмотреть полный перечень операций в Си (табл. 3), то первое, что бросается в глаза, это их многочисленность. Второе, что к операциям относятся такие действия, которые в других языках программирования считаются операторами (например, присваивание). И третье, что все они имеют очень «компактный» синтаксис, то есть при пропуске или добавлении лишнего символа одна операция превращается в другую.

Всё это требует внимательного и осознанного отношения к операциям в Си. Для начала перечислим их особенности.

Операции разбиты на 16 групп по приоритетам их выполнения. Внутри каждой группы задается направление выполнения операций для последовательности операций одного приоритета. Для большинства из них имеет место естественное направление слева направо. Однако для операций присваивания, условной операции и унарных операций, у которых знак операции находится перед операндом (слева), направление операций противоположное – справа налево. Операнд – переменная, константа, выражение, участвующие в операции. Унарная операция – операция с одним операндом. Бинарная – операция с двумя операндами.

Большинство операций совместимо по результатам. Это значит, что результат любой операции может быть операндом любой другой операции, то есть их можно комбинировать между собой как угодно, даже в самых «диких» сочетаниях.

Для бинарных операций определены правила неявного преобразования типов операндов. Эти правила позволяют однозначно определить, результат какого типа получится при её выполнении и какими преобразованиями операндов это будет достигнуто.

Обычно результат операции представляет собой новое, самостоятельное значение, которое затем может использоваться в качестве операнда в последующих операциях. Входные операнды при этом остаются без изменения.

Таблица 3

 Приоритет

Знак операции

Назначение

Схема

1

.

Обращение к члену структуры по имени структуры

имя_структуры . имя_члена_структуры

1

[ ]

Обращение к элементу массива

указатель [индекс]

1

( )

Преобразование типа данного

имя_типа (выражение) или (тип) выражение

1

( )

Вызов функции

функция (аргументы)

2

++

Автоувеличение

++ L-значение или
L-значение++

2

--

Автоуменьшение

-- L-значение или
L-значение--

2

!

Логическое отрицание

! выражение

2

-

Одноместный минус

- выражение

2

+

Одноместный плюс

+ выражение

2

sizeof

Размер данного

sizeof выражение

2

Размер типа данного

sizeof (имя типа )

3

*

Умножение

выражение * выражение

3

/

Деление

выражение / выражение

3

%

Остаток от деления нацело

выражение % выражение

4

->*

Обращение к члену структуры по указателю

указатель_на_структуру ->* имя_члена_структуры-указателя

4

.*

Обращение к члену структуры по имени структуры

имя_структуры .*
имя_члена_структуры-указателя

5

+

Сложение

выражение + выражение

5

-

Вычитание

выражение - выражение

6

<<

Сдвиг влево

целое_выражение << целое_выражение

6

>>

Сдвиг вправо

целое_выражение >> целое_выражение

7

<

Меньше

выражение < выражение

7

<=

Меньше или равно

выражение <= выражение

7

>

Больше

выражение > выражение

7

>=

Больше или равно

выражение >= выражение

8

==

Равно

выражение == выражение

8

!=

Не равно

выражение != выражение

9

&

Поразрядная конъюнкция

выражение & выражение

10



Отрицание равнозначности

выражение выражение

11

|

Поразрядная дизъюнкция

выражение | выражение

12

&&

Логическое "И"

выражение && выражение

13

| |

Логическое "ИЛИ"

выражение | | выражение

14

? :

Условное выражение

выражение ? выражение1 : выражение2

15

=

Простое присваивание

выражение = выражение

16

,

Операция следования

выражение, выражение

В Си схема может быть сложнее – операнды некоторых операций могут изменяться в процессе её выполнения.

Самый простой пример – операция присваивания:

a = b; // Действие над операндом: переменная a получает значение переменной b

// Результат: значение переменной a после присваивания

 Наличие у операции результата позволяет использовать её в контексте (окружении) других операций.

Например:

c = (a = b) + 5; // эквивалентно a = b; c = a + 5;

Более интересный случай представляют собой операции инкремента и декремента, в которых действие не совпадает с результатом, например:

a++; // Действие над операндом: переменная a увеличивается нa 1

// Результат: значение переменной до её увеличения

c = A[i++]; // эквивалентно c = A[i]; i = i + 1;

Итак, операция – это результат плюс действия над операндами.

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

- арифметические (+, -, *, /, %);

- логические (&&, ||, !);

- сравнения (<, >, >=, <=, ==, !=);

- машинно-ориентированные (операции над машинными словами, поразрядные (&, |, , ~, <<, >>);

- присваиваниe (=, ++, --, +=, -=, *-, /= и т.д.);

- работa с указателями и памятью (*, &, sizeof);

- выделение составляющего типа данных ((), *, [], ., ->);

- явноe преобразованиe типа ((тип));

- условная (?:);

- последовательность выражений («,» – запятая).

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

Операция «%» вычисляет остаток от деления первого операнда на второй. Она имеет также другой, содержательный смысл: второй операнд-константа выступает ограничителем возможных изменений первого операнда и называется модулем. Название такой операции звучит как «... по модулю ...». Например:

a = (a + 1) % 16; // a присвоить a+1 по модулю 16

Двуместные арифметические операции умножения (*), деления (/), получения остатка от деления нацело (%), сложения (+) и вычитания (-) имеют обычный смысл и обычный относительный приоритет. Если операнды арифметической операции имеют разные типы, предварительно выполняются стандартные арифметические преобразования и тип результата операции определяется общим типом операндов после стандартных преобразований. Следовательно, выражение 7/2 будет иметь значение 3 типа int, так как оба операнда имеют тип int, а выражение 7.0/2 даст результат 3.5 типа double, поскольку этот тип имеет первый операнд.

В Си отсутствует особый базовый тип данных для представления логических значений «истина» и «ложь». Для этой цели используются значения целой переменной. Значение 0 всегда является «ложью», значение 1 – «истиной». Такие значения дают операции сравнения и логические операции.

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

if (1) { A } else { B } // Всегда выполнять B

while (1) { ... } // "Вечный" цикл

if (k) { A } else { B } // Эквивалентно if(k !=0)

Все операции сравнения дают в качестве результата значения 1 или 0. Следовательно, их можно использовать совместно с арифметическими и другими операциями.

Например

a = b > c; // Запомнить результат сравнения

a = (b > c)* 2; // Принимает значения 0 или 2


Логические операции И (&&), ИЛИ (||) и НЕ (!) едины для всех языков программирования и соответствуют логическим функциям И, ИЛИ и НЕ для логических (булевых) переменных.

Операция И имеет результатом значение «истина» тогда и только тогда, когда оба её операнда истинны, то есть по отношению к операндам-утверждениям звучит как «одновременно оба».

Операция ИЛИ имеет результатом значение «истина», когда хотя бы один из операндов истинен, то есть характеризуется фразой «хотя бы один». Например:

if (a < b & & b < c) // если ОДНОВРЕМЕННО ОБА a < b и b < c, то...

if (a==0 || b > 0) // если ХОТЯ БЫ ОДИН a==0 или b > 0, то...

Логические операции И и ИЛИ имеют ещё одно свойство: если в операции И первый операнд имеет значение «ложь», а в операции ИЛИ –«истина», то вычисление выражения прекращается, потому что значение его уже становится известным («ложь» – для И, «истина» – для ИЛИ). Поэтому возможны выражения, где в первом операнде операции И проверяется корректность некоторой переменной, а во втором – она же используется с учётом этой корректности:

if (a > =0 & & sin(sqrt(a)) > 0) ...

В данном примере второй операнд, включающий в себя функцию вычисления квадратного корня, не вычисляется, если первый операнд – «ложь».

Особо следует отметить операцию логической инверсии (отрицания) – «!». Значение «истина» она превращает в «ложь» и наоборот. Если считать значением «истина» любое ненулевое значение целой переменной, то эту операцию для целых следует понимать как проверку на 0. Например:

while(!k) {...} // эквивалентно while(k==0) {...}

К операциям присваивания относятся все операции, которые меняют значение одного из операндов. В Си их целых три группы:

1) обычное присваивание (=);

2) присваивание, соединённое с одной их бинарных операций (+=, -=, *=, /=, %=, <<=, >>=, &=, |=, =);

3) операции инкремента и декремента (увеличения и уменьшения на 1).

Операция присваивания «=» сохраняет значение выражения, стоящего в правой части, в переменной, а точнее, в адресном выражении, стоящем в левой части.

Термин адресное выражение используется для обозначения тех выражений, которым соответствуют объекты (переменные) в памяти программы. Например, это простые переменные и элементы массивов.

При выполнении операции присваивания тип выражения в правой части преобразуется к типу адресного выражения в левой. Результатом операции является значение левой части после присваивания, соответственно, тип результата - это тип левой части. Кроме того, присваивание – одна из немногих операций с направлением выполнения «справа налево».

Из сказанного следует возможность многократного присваивания «справа налево», в котором результат каждого из них используется как правая часть последующего:

 long a; char b; int c;

a = b = c; // эквивалентно b = c; a = b;

В данном случае при первом (правом) присваивании тип int преобразуется к char, а результатом операции является значение переменной b типа char после выполнения этого присваивания.

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

a +=b; // эквивалентно a = a + b;

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

A[i++] +=b; // эквивалентно A[i] = A[i] + b; i++;

Операции инкремента и декремента увеличивают или уменьшают значение единственного операнда до или после использования его значения в выражении. Например:

int a; // Эквивалент Интерпретация

a++; // Rez=a; a=a+1; Увеличить на 1 после использования

++a; // a=a+1; Rez=a; Увеличить на 1 до использования

a--; // Rez=a; a=a-1; Уменьшить на 1 после использования

--a; // a=a-1; Rez=a; Уменьшить на 1 до использования

2.3.7Классификация операторов – выражения с «;», составной оператор (последовательность), условие, цикл, переход


Все операторы языка Си могут быть условно разделены на следующие категории:

- условные операторы, к которым относятся оператор условия if и оператор выбора switch;

- операторы цикла (for, while, do while);

- операторы перехода (break, continue, return, goto);

- другие операторы (оператор-выражение, пустой оператор).

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

Все операторы языка СИ, кроме составных операторов, заканчиваются точкой с запятой «;».

Наиболее простым является оператор-выражение, представляющий собой полное выражение, заканчивающееся точкой с запятой, например,

x = 3; y = (x +1) * t; i++;

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

Составной оператор – это группа операторов, заключённых в фигурные скобки. Составной оператор – основная структурная единица, из которой строится большая программа. Функция также представляет собой составной оператор.

Как правило, составной оператор используется в качестве тела других операторов, например, таких как do, for, if. Независимое использование составного оператора возможно, но это вряд ли действительно может понадобиться.

Составные операторы могут вкладываться друг в друга, но не могут пересекаться. Конечно, говорить о пересечении собственно составных операторов нет смысла. Но надо помнить, что составной оператор, как правило, оказывается привязанным к тому или иному ключевому слову, образует вполне определённую конструкцию, и в этих ситуациях говорить о пересечении уже есть смысл. Ниже пример ошибочной конструкции:

do

{

while(условие)

{

}

while (условие);

}

Здесь очевидное пересечение двух составных операторов, и этот фрагмент не получится даже откомпилировать.

Операторы выбора в Си/Си++ представлены условным оператором и переключателем. Условный оператор аналогичен условным операторам других языков программирования и может использов1аться в сокращённой и полной формах, которым соответствуют схемы:

if (выражение-условие) оператор

if (выражение-условие) оператор-1 else оператор-2

В сокращённой форме условного оператора вычисляется выражение-условие и, если его значение отлично от нуля, выполняется следующий за условием оператор, в противном случае не производится никаких действий.

В полной форме условного оператора при ненулевом значении выражения-условия выполняется оператор-1 с последующим переходом к следующему оператору программы, а при нулевом значении выражения условия выполняется оператор-2 с переходом к следующему оператору программы.

Предположим, необходимо присвоить некоей переменной (пусть её имя b) значение 2, но только в том случае, если другая переменная (пусть её имя d) равна 1, и в то же время третья переменная (например, r) равна 5. Это условие возможно записать так:

if (d==1)

if (r==5) b=2;

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

if (d==1 && r==5) b=2;

Но объединение условий посредством логических связок необходимо использовать осторожно. Не всегда это работает так просто. С появлением <оператора2>, выполняемого в случае ложности условия, ситуация существенно усложняется. Рассмотрим для иллюстрации следующую конструкцию:

if (d==1)

if (r==5) b=2;

else b=3;

Эта конструкция говорит о том, что для определения значения b величина d обязательно должна иметь значение 1. В противном случае наша конструкция из двух вложенных условий вообще не окажет никакого влияния на вычисление величины b. А если переменная d все-таки равна 1, то тогда выбор между двумя значениями осуществляется в зависимости от значения величины r. Если же мы вложенный выбор заменим одним со сложным условием, то смысл проверяемого условия существенно изменится.

if (d==1 && r==5) b=2; else b=3;

Новое выражение говорит о том, что выбор между двумя значениями будет сделан обязательно в зависимости от того, ложно или истинно сложное условие. Здесь величина d не имеет особенной роли, она равноправна с величиной r. В то время, как в предыдущей конструкции при величине d, не равной единице, значение r не имеет никакого значения.

Вероятно, уже понятно, что ключевое слово else принадлежит ближайшей конструкции if. Далее для иллюстрации приведём чуть более сложный пример:

if (d==1)

if (r==5) b=2;

else b=3;

else b=4;

Здесь отступами показана сопринадлежность if и else. В этом примере значение величины b определяется так: если d не равно единице, то b=4. Если же равно, то значение b определяется из сравнения r==5. Если r равно 5, то выполняется b=2. Если же нет, то выполняется b=3.

Поставим теперь такую задачу. Пусть переменная d также играет определяющее значение и таким же образом участвует в определении величины b. Пусть также необходимо, чтобы при r=5 переменная b принимала значение 2. А при r<>5 величина b пусть имеет то же самое значение, которое она имела до входа в конструкцию выбора. Иными словами, необходимо выбросить else, входящий во внутреннюю конструкцию. То есть, сделать так:

if (d==1)

if (r==5) b=2;

else b=4;

Но это совсем не то, что нам нужно. В таком варианте запись

else b=4;

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

if (d==1)

{ if (r==5) b=2; }

else b=4;

Внутренняя конструкция условного оператора превращается в составной оператор, а запись else b=4; остается составной частью внешней конструкции выбора.

И последнее, и <оператор1>, и <оператор2>, указанные в синтаксисе, – это один, но возможно составной оператор.

Язык Си, как и любой другой достаточно развитый язык, предлагает несколько способов организации циклов. Конструкции циклов по своим основным свойствам можно характеризовать как циклы с параметром или циклы по условию, а также как циклы с предусловием (т.е. такие, в которых условие цикла проверяется перед выполнением тела цикла) и как циклы с постусловием (т.е. условие цикла проверяется после выполнения тела цикла).

Однако необходимо заметить, что название «цикл с параметром» – не совсем точное название для той конструкции, которую даёт в распоряжение Си. Те, кто имеет опыт программирования на языках Бейсик или Паскаль, очень быстро заметят разницу. Классический цикл с параметром – скорее, частный случай того, что предлагает язык С. Рассмотрим пример:

for (int i=1;i<=N;i++) cin >> A[i];

Здесь есть тело цикла, состоящее из одной команды ввода, и заголовок, в котором записано начальное значение параметра, его конечное значение и шаг изменения. Это действительно не более, чем частный случай, потому как трём частям заголовка присвоен конкретный смысл, привязанный к термину «параметр». Чтобы лучше понять сказанное, рассмотрим синтаксис:

for ([<выражение1>]; [<выражение2>]; [<выражение3>])

<команда>

Здесь <команда> выполняется до тех пор, пока <выражение2> не примет значение 0 (в смысле логического значения).

<выражение1> вычисляется перед выполнением первого шага цикла. Обычно оно используется для инициализации цикла, но это лишь частный случай.

После каждого шага цикла вычисляется <выражение3>. Обычно это выражение используется для изменения параметра цикла, но и это не обязательно.

Обратите внимание, что все три выражения заключены в квадратные скобки []. Это означает, что все три выражения не являются обязательными и даже цикл for (;;;) с полностью пустым заголовком будет синтаксически правильным. Такое обобщенное понимание компонентов цикла даёт большую свободу. Например, выражения, отвечающие за инициализацию и изменения в ходе работы цикла, могут быть сложными. Например, такими:

for (i=0, t=50; i < 40 && t>0; i++, t--)

Как видите, здесь две переменные играют роль параметров, и условие продолжения работы выглядит достаточно сложным.

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

Синтаксис цикла с предусловием выглядит следующим образом:

while <условие> <команда>

Здесь <команда> может быть как одной командой, так и составным оператором. <условие> – любое арифметическое выражение, значение которого воспринимается, как логическое. Как уже было сказано в начале главы, тело цикла этой формы выполняется после проверки условия, следовательно, если перед первым шагом цикла условие окажется ложным, тело цикла не будет выполнено ни одного раза. Цикл работает до тех пор, пока условие истинно. Напомним, что условие (так, как его понимает Си) – это арифметическое выражение, следовательно, условием может быть почти всё, что угодно, в том числе и сложное выражение, состоящее из нескольких элементарных, перечисленных через запятую.

Синтаксис цикла с постусловием:

do

{ <последовательность_команд>

}

while <условие>

Цикл с постусловием также работает, пока истинно условие. Но тело цикла выполняется до проверки условия. Следовательно, данная форма цикла предпочтительна, если тело необходимо выполнить хотя бы один раз независимо от результата проверки условия, или если проверять условие до отработки команд цикла нет смысла. Так бывает, если, например, входящие в условие величины формируются в теле цикла. Но в значительной степени все три формы организации цикла решают одни и те же задачи и, следовательно, вполне взаимозаменяемы.

Оператором goto следует пользоваться очень аккуратно. Его чрезмерное участие в программе может сделать логику слишком запутанной и непрозрачной. Как правило, его можно убрать из программы, немного изменив ее структуру. С этим оператором тесным образом связано понятие метки. А именно, оператор передаёт управление на оператор с меткой. В примере, приведённом ниже, показано, как это делается.

#include

void main()

{ int sum=0;

for (int i=1;i<=10;i++)

{ sum+=i;

if (i==5) goto metka;

}

cout <<"Вывод";

metka:

cout <

}

В результате действия оператора goto вычисление суммы прекращается несколько раньше, и выполнение передаётся на оператор, следующий за идентификатором.

2.3.8Вопросы для самоконтроля


  1. Какие символы могут содержать имена переменных?
  2. Какие типы переменных допустимы на языке Си++?
  3. Что такое оператор?
  4. Какие типы операторов допустимы на Си++?
  5. Для чего используются комментарии в программе?
  6. Перечислите основные логические операции.
  7. Какие циклы можно организовать на языке Си++?
  8. Формат описания переменных. Привести пример.
  9. Запись целых констант.
  10. Выражения.
  11. Арифметические операции.
  12. Операции присваивания.
  13. Приоритеты логических операций.
  14. Структура простейшей программы на Си++.
  15. Напишите формат и пример условного оператора в полной форме.
  16. Напишите формат и пример условного оператора в краткой форме.
  17. Формат оператора цикла с параметром, постусловием, предусловием.
  18. Формат описания массива.