Условия. Операторы ветвления. Операторы цикла
Для решения задач, которые ставятся перед программистом, линейных операторов не достаточно. Поэтому в любом языке программирования существуют операторы ветвления и операторы цикла. Эти операторы наряду с линейными операторами позволяют запрограммировать любую задачу. Они позволяют изменить ход выполнения программы в зависимости от значения "условия".
Условие – это выполняемое выражение, результат вычисления которого имеет тип bool.
Чаще всего условия формируются про помощи операций сравнения. Существует шесть фундаментальных операций для сравнения двух доступных значений:
Операция Отношение
< Первый операнд меньше, чем второй операнд
> Первый операнд больше, чем второй операнд
<= Первый операнд меньше или равен второму операнду
>= Первый операнд больше или равен второму операнду
= = Первый операнд равен второму операнду
! = Первый операнд не равен второму операнду
Операция сравнения "равно" состоит из двух подряд знаков равенства. Это не то же самое, что операция присваивания, которая состоит лишь из одного знака равенства. Распространенная ошибка связана с использованием операции присваивания вместо операции сравнения, ее допускаю чаще всего программисту ранее использующие в своей работе язык программирования Паскаль.
Каждая из этих операций сравнивает значения двух своих операндов и возвращает одно из двух возможных значений типа bool: true – если сравнение истинно, и false – если нет. Работу этих операций можно рассмотреть на следующих примерах. Предположим, что объявлены целочисленные переменные i и j со значениями 10 и -5 соответственно. Все представленные ниже выражения возвращают значение true:
i>j i!=j j>-8 i<=j+15
Далее предположим, что были определены следующие переменные:
char first=‘A’, last=‘Z’;
Вот несколько примеров сравнений, использующих эти символьные переменные:
first == 65 first<last ‘E’<=first first!=last
Все четыре выражения сравнивают значения кодов ASCII. Первое выражение возвращает true, потому что first инициализировано символом ‘А’, что эквивалентно десятичному числу 65. Второе выражение проверяет, меньше ли значение first, которое равно ‘А’, чем значение last, которое равно ‘Z’. Если рассмотреть таблицу кодов символов ASCII в приложении, то можно увидеть, что латинские заглавные буквы представлены последовательными числовыми величинами — от 65 до 90, при чем 65 представляет ‘А’, а 90 — ‘Z ‘, поэтому второе сравнение также вернет true. Третье же выражение вернет false, потому что ‘Е’ больше, чем значение first. Последнее выражение вернет true, поскольку ‘А’ определенно не равно ‘Z’.
Теперь рассмотрим несколько более сложные сравнения чисел. Дело в том, что в качестве операндов сравнения можно использовать выражения, возвращающие числовые значения.
Имея переменные, определенные следующим образом:
int i = -10, j = 20;
double x = 1.5, у = -0.25E-10;
рассмотрим такие выражения:
-1<у j<(10-i) 2.0*x>= (3+у)
Приоритет операций сравнения ниже, чем приоритет у математических операций, поэтому скобки не являются совершенно необходимыми, однако они помогают сделать выражения яснее. Первое сравнение истинно, поэтому возвращает значение true. Переменная у содержит очень малое отрицательное число, – 0,000000000025, а потому оно больше, чем -1. Второе сравнение возвращает false. Выражение 10-i равно 20, то есть тому же, что и j. Третье выражение возвращает true, потому что 3+у чуть меньше, чем 3.
Можете использовать операции отношений для сравнения значений любого фундаментального типа, но необходимо помнить, что желательно сравнивать операнды одного типа. Дело в том, что из-за разности хранения целочисленных и дробных чисел формально 0 не равняется 0.f или 0.f поэтому желательно либо принудительно приводить операнды к одному типу либо использовать операнды одного типа.
Условие может быть выражением, возвращающим значение любого из фундаментальных типов данных. Когда условное выражение возвращает числовое значение вместо bool, то компилятор вставляет автоматическое приведение результата такого выражения к типу bool. Приведение к bool ненулевого значения дает true, а нулевого— false. Поэтому такой ситуации следует избегать, если программист не в состоянии гарантировать однозначность подобной интерпретации. Примером подобного « условия» можно считать операцию % : a%10, то есть если переменная а кратна 10 результат false, иначе true, следовательно, условие на проверку кратности переменной а будет выглядеть так: !(а%10) или а%10==0, наглядность второго варианта выше, и поэтому, желательно использовать именно его. Начинающий программист, скорее всего, допустит ошибку, используя первый вариант в виде а%10.
Простое и четкое решение для создания сложных условий обеспечивают логические операции. Применяя логические операции, можно комбинировать серии сравнений в одно логическое выражение так, что все сведется в конечном итоге к выбору из двух альтернатив (true или false).
Этих операторов всего три: && (и), || (или) и !(не). Логические операторы нельзя путать с похожими побитовыми операциями &, | и ^ , рассмотренными в главе 3, результат работы их различен.
Операция логического И (&&) применяется тогда, когда есть два условия, и оба должны вернуть результат true, чтобы общий результат был равен true. Как и ранее, условия, комбинируемые логическими операциями, могут возвращать числовые значения. Необходимо помнить, что все ненулевые значения приводятся к true, а нулевые – к false.
Операция логического ИЛИ (| |) применяется тогда, когда имеются два условия и нужно получить результат true, если любое из них или оба возвращают true.
Третья логическая операция – НЕ (!) – принимает один операнд типа bool и инвертирует его значение. Поэтому если значением переменной test является true, то !test получит значение false. Если же test равно false, то !test будет равно true.
Результаты работы всех логических операций можно представить в таблице истинности (табл. 7).
Таблица 7
Таблица истинности для логических операций
Первый операнд (А) | Второй операнд (В) | A&&B И | A || B или | !A не |
true | true | True | true | false |
true | false | False | true | |
false | true | False | true | true |
false | false | False | rate |
В действительности таблица истинности ни так уж и нужна, поскольку все достаточно просто: операция && возвращает true, только тогда, когда оба операнда равны true, а операция || имеет результат false тогда и только тогда, когда оба ее операнда равны false.
Здесь также результат может быть выражен очень просто: результат false получается только тогда, когда оба операнда операции | | равны false.
Задача о пересечение двух множеств (рис. 30) можно реализовать с помощью сложного условия: x<10&&x>-2.
-2 |
10 |
Рис. 30. Иллюстрация задачи о пересечении двух множеств
Приоритет логической операции ниже, чем операции сравнения, поэтому скобки не нужны, но для наглядности их можно использовать: (x<10)&&(x>-2).
Операции && и || являются бинарными, а операция ! – унарной. Типы первого и второго операндов могут быть различными, но необходимо помнить, что любой из типов будет приведён к типу bool, каким образом, как уже упоминалось выше: 0 – false, любое другое значение — true. Предположим, что имеется переменная float rate=3.2 При желании выяснить является ли эта переменная 0 есть возможность написать это двумя разными способами: !(rate) или rate==0. Опять же наглядность выше у второго варианта.
Операнды логических выражений вычисляются слева направо. Если значения первого операнда достаточно, чтобы определить результат операции, то второй операнд не вычисляется.
Приоритеты операций отношения и логических операций от высшего к низшему (на одном уровне располагаются одноранговые операции, в этом случае выполнение происходит по порядку слева на право, если нет ( ) ):
!
> >= < <=
== !=
&&
||
Для реализации выбора в языке С предусмотрена тернарная операция – это условная операция "?:".
Она имеет следующее синтаксическое представление:
<operand 1> ? <operand 2> : <operand 3>
Выражение <operand 1> вычисляется с точки зрения его эквивалентности нулю. Если <operand 1> имеет ненулевое значение, то вычисляется <operand 2> и результатом условной операции является значение выражения <operand 2>. Если <operand 1> равен нулю, то вычисляется <operand 3> и результатом является значение выражения <operand3>. Заметим, что вычисляется один из операндов <operand 2> или <operand 3>, но не оба.
Тип результата зависит от типов второго и третьего операндов следующим образом: если второй и третий операнды имеют целый или вещественный тип (их типы могут быть отличны), то выполняются обычные арифметические преобразования. Типом результата является тип операнда после преобразования.
Задача вида может быть записана следующим образом:
y=x<0 ? 2*x : x/2;
Для наглядности можно расставить скобки
y=(x<0) ? (2*x) : (x/2);
Или пример 2. z = (a>b) ? a : b; В данном случае переменной z присваивается максимальное значение из двух переменных: а и b. Если а>b , то z присваивается а, в противном случае z присваивается b.
Выбор пути выполнения программы реализуется в языке С при помощи оператора ветвления. Базовый оператор if позволяет программировать выполнение единственного оператора или блока операторов, заключенных в фигурные скобки, если данное условное выражение оценено как истинное, или же пропустить оператор или блок операторов, если условие оценено как ложное. Это показано на рис. 31.
условие |
условие == true |
Оператор 1 |
Оператор 2 |
Рис. 31. Блок-схема работы оператора if
Конструкция базового оператора if имеет вид:
if(условие)
оператор;
При этом, если требуется выполнить несколько операторов внутри if, то из них формируется блок при помощи { …}. Следует обратить внимание, что заголовок оператора ветвления (if ( )) не заканчивается ;.
Оператор, который должен быть выполнен в случае истинности условия if, также может быть еще одним оператором if. Такая организация называется вложенным if. Условие вложенного if проверяется только в случае истинности условия внешнего if. Вложенный if, в свою очередь, может также содержать еще один вложенный if. В языке С реализована возможность вкладывать операторы if друг в друга на любую глубину, но более наглядным и удобным является составление сложных условий при помощи логических операций. Уже рассмотренный пример пресечения множеств можно реализовать при помощи двух разных реализаций оператора ветвления:
if(x<10)
if(x>-2) …// при помощи вложенного if
или
if(x<10&&x>-2.) …//при помощи сложного условия
Базовый оператор if, выполняет оператор или блок, если указанное условие возвращает true. Затем выполнение программы продолжалось со следующего оператора по порядку. Но есть и другая версия if, которая позволяет выполнить один оператор или блок, когда условие if возвращает true, и другой – когда оно возвращает false. После этого выполнение программы продолжается со следующего оператора по порядку. Такой оператор называют расширенным (рис. 32).
Условие == |
true |
Оператор 1 |
Оператор 2 |
false |
Оператор 3 |
Рис. 32. Блок-схема работы расширенного оператора if
Конструкция расширенного оператора if-else имеет вид:
if(условие)
оператор1;
else
оператор2;
Если требуется выполнить несколько операторов внутри if и/или else, то из них формируется блок при помощи { …}.
Ключевое слово else пишется без точки с запятой, так же, как заголовок оператора if.
Расширенный оператор if можно вкладывать в другие операторы if, причет не важно используется расширенный или базовый операторы. То есть можно вкладывать операторы if-else внутрь операторов if, а операторы if– внутрь if-else. Здесь существует некоторая возможность путаницы, поэтому необходимо рассмотреть несколько примеров с иллюстрацией их блок-схемами.
| Блок-схеме на рис. 33 соответствуют следующие операторы: if(условие 1) { if(условие 2) оператор 1; else оператор 2; оператор 3; } else оператор 4; оператор 5; Так как if(условие 2) … и оператор 3; выполняются по одной ветке первого ветвления, то их необходимо объединить блоком, иначе компилятор укажет ошибку. | |||||||||||||
Рис. 33. Блок-схема № 1 Блок-схеме на рис. 34 соответствует конструкция языка: if(условие 1) { if(условие 2) оператор 1; else оператор 2; оператор 3; } оператор 4; |
Рис. 34. Блок-схема № 2 | |||||||||||||
| Если в этом случае не поставить {…}, то компилятор не будет указывать на ошибку, но блок-схема изменится принципиально и будет отражать процесс, представленный на рис. 35. Поэтому нельзя игнорировать выделение блоков, если это необходимо для правильной реализации задачи. | |||||||||||||
Рис. 35. Измененная блок-схема № 2 Рассмотрим следующий пример ветвления, отраженный на рис. 36. Соответствующая конструкция будет иметь вид: if(условие 1) if(условие 2) { оператор 1; оператор 2; } else { оператор 3; оператор 4; } else { оператор 5; оператор 6; } } к чему? оператор 7; Отсутствие {…} приведет к ошибкам компиляции, связанным с неправильным формированием конструкции if-else. |
Рис. 36. Блок-схема № 3 | |||||||||||||
| Если изменить блок-схему № 3, приведя её к виду, представленному на рис. 37 , то реализация на языке С будет выглядеть следующим образом if(условие 1) { if(условие 2) { оператор 1; оператор 2; } } else { оператор 5; оператор 6; } } ??? оператор 7; |
В этом случае отсутствие {…} между первым if и else приведет принципиальному изменению алгоритма. Конструкции языка будет выглядеть так: if(условие 1) if(условие 2) { оператор 1; оператор 2; } else { оператор 5; оператор 6; } } ??? оператор 7; А соответствующая блок-схема без этих скобок примет вид, показанный на рис. 38. |
Рис. 38. Измененная блок-схема № 3 (без скобок) |
Без этих скобок оператор else по умолчанию соединяется с последним (более близким к нему) заголовком ветвления, а в этом примере это if (условие 2).
Проиллюстрированные примеры призваны подчеркнуть важность использования блоков во вложенных ветвлениях. При неправильном использовании блоков может возникнуть ситуация искажения алгоритма без отражения его в ошибках компиляции, поэтому такая ошибка будет локализована только на этапе тестирования программы. И потребуется использовать процесс отладки для поиска места ошибки. При недостаточном тестировании подобные ошибки могут быть вообще не обнаружены программистом, и проявиться уже на этапе эксплуатации программы заказчиком, что недопустимо, и потребует бесплатных усилий на их исправления.
Примером полностью написанной программы на языке С, использующей ветвление можно считать реализацию на языке С поиска корней квадратного уравнения, алгоритм которого рассмотрен в главе 1.
#include <iostream>
#include <math.h>
using namespace std;
int main(void)
{double a,b,c,x1,x2,d;
cout<<"vvedite koeffichient's a*x^2+b*x+c=0\n";
cin>>a>>b>>c;
d=b*b-4.*a*c;
if(d<0.) cout<<"resheniy net\n";
else
if(d==0.)
{
x1=x2=-b/(2.*a);
cout<<"odin koren="<<x1<<endl;
}
else
{
x1=(-b+sqrt(d))/(2.*a);
x2=(-b-sqrt(d))/(2.*a);
cout<<"dva koren's\n x1="<<x1<<"\tx2="<<x2<<endl;
}
return 0;
}
Результат работы программы примет вид, представленный на рис. 39.
Рис. 39. Результат работы программы
Алгоритм и ход рассуждений полностью приведены в главе 1, поэтому здесь не требуется их повторять.
Иногда возникает ситуация, когда необходимо принять одно из множества решений. Эту ситуацию можно запрограммировать при помощи множества вложенных циклов (если всего решений N, то вложений будет N-1), что не очень удобно. В такой ситуации используется оператор многовариантного выбора switch.
Оператор switch дает возможность выбирать из множества вариантов, основываясь на наборе фиксированных значений заданного выражения. В языке С переменная, которая может участвовать в работе оператора switch должна быть целочисленной.
Синтаксис записи оператора switch:
switch(целое выражение или переменная)
{
case константа_1: операторы_1;
…
case константа_n : операторы_n;
default : операторы;
}
В операторе switch выбор определяется значением указанного выражения. Программист устанавливает возможные позиции switch одним или более case-значений, одно из которых выбирается, если значение выражения совпадает с ним. Каждому возможному значению выражения оператора switch соответствует одно case-значение, причем эти case-значения должны отличаться друг от друга.
Если значение выражения switch не соответствует ни одному из case-значений, то switch автоматически выбирает ветвь default. Если не использовать ветвь default, то когда выражение switch не соответствует ни одному из case-значений, по умолчанию не делается ничего, а управление просто передается оператору, следующему за завершающей фигурной скобкой switch. В то же время разные case не обязаны вести к уникальным действиям. Несколько ветвей case могут разделять одни и те же действия.
Рассмотрим всё выше сказанное на примере программы:
#include <iostream>
#include <math.h>
using namespace std;
// Множество действий case
int main(void)
{
char letter =0;
cout <<endl<<"Vvtlbnt ,bookvy po anglisky : ";
cin >>letter;
switch (letter* (letter >= 'a' && letter <= 'z'))
{
case 'a':
case 'e':
case 'i':
case 'o':
case 'u' : cout <<endl <<"It is glasnay.\n";
case 0: cout <<endl << "It is't propisnay bookva\n.";
default: cout <<endl <<"It is soglasnay.\n";
}
return 0;
}
Результат выпонения представленной программы можно увидеть на рис. 40.
Рис. 40. Результат работы программы
Рассмотрим подробнее описание полученных результатов. Несмотря на то, что была введена прописная гласная буква, отработали все возможные верви оператора switch. Чтобы этого не происходило, в конец каждой ветви case имеющей действия необходимо добавит оператор прерывания break;, то есть некоторые строки программы преобретут вид:
case 'u' : cout <<endl <<"It is propisnay glasnay.\n";break;
case 0: cout <<endl << "It is't propisnay bookva\n.";break;
default: cout <<endl <<"It is soglasnay.\n"; break;
Результат работы станет однозначным (рис. 41).
Рис. 41. Результат работы измененной программы
В данном примере использовано более сложное выражение в switch() – switch (letter* (letter >= 'a' && letter <= 'z')). Если введенный символ не является буквой в нижнем регистре, то выражение: (letter >= 'a' && letter <= 'z') даст в результате false, иначе – true. Поскольку letter умножается на это выражение, значение логического выражения приводится к целому (0, если выражение ложно, и 1 – если истинно). Таким образом, выражение switch равно 0, если введенный символ не является буквой нижнего регистра, в противоположном случае оно равно самому введенному символу. То есть оператор, следующий за case 0, выполняется всякий раз, когда код символа, сохраненного в letter, не является прописной буквой нижнего регистра.
Если же введена прописная буква, то выражение switch в результате вычисления дает само значение этой буквы, поэтому для того, чтобы выдать сообщение о том, что введенная буква – гласная, оператор вывода помещается после серии меток case, каждая из которых сравнивает значение с одной из гласных букв. Один и тот же оператор выполняется для любой гласной, потому что когда обнаруживается подходящая метка case, то после этого выполняются все последующие операторы – до тех пор, пока не встретится break. Если же будет введена согласная буква, то выполнится оператор, следующий за меткой default.
Еще одной возможностью изменить последовательность выполнения операторов обладает оператор безусловного перехода goto. Оператор if предоставляет возможность гибко выбирать один или другой набор операторов, в зависимости от указанного условия, то есть последовательность выполнения операторов варьируется в зависимости от данных программы. В отличие от этого, оператор goto – грубый инструмент. Он позволяет перейти к определенному оператору программы безо всяких условий. Оператор, на который выполняется переход, должен быть идентифицирован меткой — идентификатором, определенным в соответствии с теми же правилами, которым подчиняются имена переменных. Метка должна быть снабжена двоеточием и помещена перед оператором, к которому выполняется переход. Ниже представлен пример помеченного оператора.
myLabel: cout <<"Use goto in myLabel\n";
Оператор имеет метку myLabel, и безусловный переход к этому оператору в программе записывается так:
goto myLabel;
Где только возможно, необходимо избегать в программах применения goto. Это порождает запутанный код, который чрезвычайно трудно отследить.
Поскольку теоретически goto не является необходимым в программах– применению goto всегда существует альтернатива – большая часть программистов считает, что использовать его вообще никогда не следует. Но, в конце концов, это – законный оператор языка, и бывают случаи, когда применить его удобно. Однако использовать этот оператор рекомендуется, так чтобы он использовался тогда и только тогда, когда очевидно его преимущество перед другими возможными вариантами организации кода. В противном случае существует риск получить запутанный, подверженный ошибкам код, который трудно понять и еще труднее сопровождать.
Циклы
Возможность повторить какие-либо действия даёт программе выполнение оператора цикла. Такой процесс называется циклическим или итерационным, а одно повторение называют итерацией. В языке С для реализации циклов используются три оператора while (цикл с предусловием), do-while (цикл с постусловием) и for (цикл со счетчиком).
Все эти операторы позволяют программировать итерационные процессы, но каждый имеет определённые особенности, которые делают их особенно удобными в одних видах циклов, и не очень удобными в других. Программист обязан знать эти особенности и использовать циклы наиболее подходящие в каждом конкретном случае.
Цикл выполняет последовательность операторов до тех пор, пока истинно (или ложно) определенное условие. Можно написать цикл, используя лишь те операторы C++, которые известны к настоящему времени. Для этого понадобится только if и "страшный" goto. Следующий пример иллюстрирует данное утверждение.
// Создание цикла средствами if и goto
#include <iostream>
using namespace std;
int main()
{
int i = 0, sum = 0;
const int max = 10;
i = 1;
loop: // Метка
sum += i; // Добавить текущее значение i к sum
if (++i <= max) goto loop; // Возвращаться к loop
// до тех пор, пока i меньше 11
cout << endl<<"sum = " << sum<<endl<< "i = "<< i<< endl;
return 0;
}
Программа осуществляется накопление суммы целых чисел от 1 до 10. При первом выполнении последовательности операторов i== 1, добавляется к переменной sum, которая поначалу равна 0. В операторе if переменная i увеличивается до 2 и, до тех пор, пока она меньше или равна max, выполняется безусловный переход к метке loop и значение i – на этот раз 2 – прибавляется к sum. Программа продолжает работать, увеличивая i и прибавляя его к sum до тех пор, пока i не вырастет до 11, и поскольку условие if станет ложным, очередной возврат к loop выполнен не будет, и цикл завершится. Результат выполнения программы примет следующий вид (рис. 42).
Рис. 42. Результат работы программы создания цикла средствами if и goto
Ту же самую задачу можно реализовать при помощи цикла for:
// Создание цикла средствами for
#include <iostream>
using namespace std;
int main()
{
int i = 0, sum = 0;
const int max = 10;
for(i=1;i<=max;i++)
sum+=i;
cout << endl<<"sum = " << sum<<endl<< "i = "<< i<< endl;
return 0;
}
Результат работы этого примера полностью совпадает с результатом работы примера предыдущего (рис. 42).
Синтаксис записи цикла for выглядит следующим образом:
for (список_инициализации ;условие_работы; список_изменений_счетчиков)
оператор_внутри_цикла;
Логика работы оператора for изображена на рис. 43.
Конечно же, оператор_внутри_цикла может быть отдельным оператором или блоком операторов в фигурных скобках. Выражения, управляющие циклом for, очень гибки. Можете написать два или более выражений, разделенных запятыми, вместо любого из трех управляющих операторов цикла for. Это дает широчайшие возможности в его применении. Необходимо учитывать, что в случае применения ‘,’ в блоке «условие_выполнения» полученный результат может отличаться от ожидаемого, поэтому желательно при наличии нескольких условий соединять их в одно при помощи логических операций.
В большинстве случаев выражения в цикле for используются довольно стандартным способом: первое из них инициализирует один или более счетчиков цикла, второе проверяет, должен ли цикл продолжаться, а третье увеличивает или уменьшает значение одного или более счетчиков. Однако вы не обязаны использовать выражения именно таким образом – на самом деле, возможны несколько вариаций.
Условие_работы == |
Выполнить список_инициализации |
выполнить следующий оператора |
Выполнить оператор_внутри_цикла |
Выполнить список_измен-ений_счетчиков |
true |
Рис. 43. Блок-схема работы цикла for
Выражение инициализации цикла for может также включать объявление переменной цикла. В предыдущем примере можно написать цикл так, чтобы в первом его управляющем выражении содержалось объявление переменной i.
for(int i = 1; i <= max; i++) // Спецификация цикла
sum += i;// Оператор цикла
Естественно, исходное объявление i теперь должно быть исключено из программы. Если внести эти изменения в последний пример, то обнаружится, что программа не компилируется, потому что переменная цикла i исчезает после завершения цикла, и не возможно обращаться к ней в операторе вывода. Цикл имеет область видимости, распространяющуюся от управляющих выражений for до конца его тела, которое может быть либо блоком кода в фигурных скобках, либо единственным оператором. Счетчик i теперь объявлен внутри области видимости цикла, и не может использоваться в операторе вывода, потому что он находится за пределами этой области. По умолчанию компилятор C++ придерживается стандарта ISO/ANSI C++, который утверждает, что переменная, определенная внутри условия цикла, не может быть доступна вне этого цикла.
Можно вообще исключить инициализирующее выражение из цикла. Если инициализировать i в отдельном операторе объявления, то цикл можно написать так:
int i = 1;
for( ; i <= max; i++) // Спецификация цикла
sum += i; // Оператор цикла
Тут по-прежнему нужна точка с запятой, отделяющая выражение инициализации от проверочного условия цикла. Фактически обе точки с запятой всегда должны присутствовать, независимо от того, пропущено ли какое-то одно из управляющих выражение, или даже все сразу. Если пропустить первую точку с запятой, то компилятор не сможет понять, какое именно из трех управляющих операторов отсутствует, или даже какой именно точки с запятой не хватает.
Цикл for может быть пустым. Например, можно поместить оператор цикла из предыдущего примера в выражение инкремента. Тогда цикл будет выглядеть так:
for(i = 1; i <= max; sum +=, i++); // Полный цикл for
Точка с запятой после закрывающей скобки необходима, чтобы сообщить компилятору, что оператор цикла теперь пуст. Если ее пропустить, то оператор, следующий непосредственно после этой строки, будет интерпретирован как тело цикла.
Второй вид циклов C++ – цикл while. В то время как цикл for предназначен главным образом для повторения оператора или блока операторов определенное количество раз, цикл while служит для выполнения оператора или блока до тех пор, пока указанное условие остается истинным.
Синтаксическая форма записи цикла while:
while (условие)
оператор_внутри_цикла ;
Здесь оператор_внутри_цикла выполняется повторно до тех пор, пока выражение условие имеет значение true. После того, как условие становится равным false, программа выходит из цикла и переходит к оператору, следующему за ним. Как всегда, единственный оператор_внутри_цикла может быть заменен блоком операторов в фигурных скобках.
Логика работы цикла while представлена на рис. 44.
Если выражение или переменная, которая входит в условие не изменяется в оператор_внутри_цикла ;, то цикл зацикливается, то есть становится бесконечным. Программист обязан следить чтобы при выполнении тела цикла условие изменялось.
Условие == |
выполнить следующий оператора |
Выполнить оператор_внутри_цикла |
true |
Рис. 44. Блок-схема работы цикла while
Предыдущий пример с использованием этого цикла будет иметь вид:
// Создание цикла средствами while
#include <iostream>
using namespace std;
int main()
{
int i = 0, sum = 0;
const int max = 10;
i=0;
while(i<=max)
{
sum+=i;
i++;
}
cout << endl<<"sum = " << sum<<endl<< "i = "<< i<< endl;
return 0;
}
Окно консольного приложения программы имеет точно такой же вид, что и в двух придыдущих реализациях.
Третий цикл do-while подобен циклу while в том, что он выполняется до тех пор, пока указанное условие остается истинным. Главное отличие его состоит в том, что здесь условие проверяется в конце цикла – это и отличает его от циклов for и while, где оно проверяется в начале. Как следствие, оператор внутри цикла do-while всегда выполняется, по меньшей мере, один раз.
Синтаксическая форма записи цикла do-while имеет вид:
do
оператор_внутри_цикла ;
while (условие);
Логика этой формы цикла показана на рис. 45.
true |
Условие == |
выполнить следующий оператора |
Выполнить оператор_внутри_цикла |
// Создание цикла средствами do-while
#include <iostream>
using namespace std;
int main()
{
int i = 0, sum = 0;
const int max = 10;
i=0;
do
{
sum+=i;
i++;
}while(i<=max);
cout << endl<<"sum = " << sum<<endl<< "i = "<< i<< endl;
return 0;
}
Рис. 45. Блок-схема цикла do-while
Результат выполнения программы не отличается от трех предыдущих реализаций. Необходимо отметить, то цикл do-while используется в ситуациях, когда результат условия цика при первом вхождении в цикл неизвестен. То есть он требует расчетов полностью совпадающих с операторами стоящими в теле цикла. Если в такой ситуации использовать цикл while эти операторы должны быть продублированы до цикла. Поэтому использование do-while позволяет написать более экономный и эффетивный код программы. А в остальном все виды операторов цикла взаимозаменяемы.
Циклы можно вкладывать друг в друга. Никаких проблем это не создает, если помнить, что цикл состоит из заголовка и тела, ограниченного {…}. Заголовок нельзя отрывать от тела цикла.
Внутри операторов цикла могут использоваться два оператора прерывания: оператор break; – прерывающий выполнение оператора цикла полностью и оператор continue;, который прерывает только текущую итерацию.
Пример работы этих операторов можно рассмотреть на примере расчета суммы чисел изменяющихся от 1 до 300, которые кратны 3 и не являются четными (не делятся нацело на 2):
#include <iostream>
using namespace std;
int main()
{
int i = 0, sum = 0;
const int max = 10;
for(i=i; ;i++)
{
if(i%2==0) continue;
if(i%3==0)
{
sum+=i;
cout << "i = "<< i<< endl;
}
if(i>30) break;
}
cout << endl<<"sum = " << sum<<endl<< "i = "<< i<< endl;
return 0;
}
Описание программы: выход из цикла for реализован не при помощи условия, а с использование оператора break;, если i четное, то оно пропускается при помощи операторо continue. Для наглядности работы программы, каждое значение, добавляемое в сумму выводится на экран. Окно консольного приложения примера имеет вид (рис. 46).
Рис. 46. Результат работы программы,
иллюстрирующей применение операторов continue и goto
Контрольные вопросы и задания
1) Что такое условие в языке С?
2) Какие операции можно использовать для сравнения двух значений?
3) Какие логические операции позволяют создать сложные условия?
4) Приведите приоритеты операций отношения и логических операций от высшего к низшему.
5) Что такое условная операция, для чего она нужна? Приведите пример использования условной операции.
6) Какие операторы ветвления Вам известны?
7) Опишите синтаксис оператора if – else. В чем отличие операции условия (условной операции) от оператора if – else?
8) Как организуется вложенность операторов if – else?
9) Как реализовать в программе пользовательское меню, используя переключатель switch?
10) Напишите программу на языке С, которая определяет большее из 3-х заданных чисел.
11) Чем отличается оператор цикла с предусловием от оператора цикла с постусловием?
12) Какие возможности предоставляет оператор цикла for?
13) Перечислите порядок передачи управления при выполнении оператора цикла for.
14) Для чего и каким образом используются в операторах цикла операторы передачи управления break и continue?
15) Каким образом можно организовать цикл, используя оператор goto? В чем заключается особенность использования данного оператора?
16) Составьте фрагмент блок-схемы алгоритма, соответствующий следующему фрагменту программы:
for (i=1; i<n; i=i+1)
p*=i;
Реализуйте данный алгоритм, используя сначала циклы while, а потом do-while.
ГЛАВА 7
ФУНКЦИИ
Язык С является функционально-ориентированным языком. Это подразумевает, что любая задача, поставленная перед программистом, может быть разбита на конечное множество подзадач, решение которых приведёт к решению исходной задачи. Сам синтаксис языка ориентирован на использование программистом аппарата функций, как написанных самостоятельно (пользовательских) так и созданных разработчиками M S VS 2008 для С (стандартных).
Одно из главных преимуществ, предоставляемых функцией, состоит в том, что она может быть выполнена столько раз, сколько необходимо, в разных точках программы. Без такой возможности упаковывать блоки кода в функции, программы были бы намного больше, поскольку тогда пришлось бы повторять один и тот же код везде, где он может понадобиться. Но реальная необходимость в функциях вызвана тем, чтобы можно было разбивать программу на легко управляемые фрагменты для независимой разработки и тестирования.
Если необходимо написать действительно большую программу – скажем, в миллион строк кода, то программу такого размера практически невозможно создать без использования функций. Функции позволяют сегментировать программу так, что ее можно писать по частям, и тестировать каждую часть независимо, прежде чем соединять ее с прочими частями. Это также дает возможность распределить работу между членами команды разработчиков, где каждый член команды отвечает за четко определенную часть программы, с хорошо определенным функциональным интерфейсом для остального кода.
Таким образом, если программист рассчитывает писать программы на языке С на достаточно высоком профессиональном уровне, то он не сможет обойтись без использования функций.
Для начала рассмотрим общие принципы работы функций.
Функция – это изолированный блок кода, имеющий определенное специфическое назначение. Функция обладает именем, которое как идентифицирует ее, так и служит для вызова на выполнение внутри программы.
Имя функции глобально, но не обязательно уникально в C++. Однако функции, которые выполняют различные действия, обычно должны иметь разные имена.
Имена функций подчиняются тем же правилам, что и имена переменных. То есть имя функции – это последовательность букв и цифр, начинающаяся с буквы, причем знак подчеркивания тоже считается буквой. Имя функции должно обычно отражать то, что она делает.
Передача информации функции происходит при помощи аргументов, специфицированных при ее вызове. Эти аргументы должны соответствовать параметрам, появляющимся в определении функции, так называемым формальным параметрам. Когда функция выполняется, то указанные аргументы (фактические параметры) заменяют формальные параметры, использованные в ее определении. Код функции выполняется так, как будто он был написан с применением значений фактических аргументов.
Структура функции ничем не отличается от структуры функции main(). Любая функция состоит из заголовка функции, который идентифицирует ее, а за ним следует тело функции, заключенное в фигурные скобки и содержащее ее исполняемый код.
Если вспомнить задачу о поиске корней квадратного уравнения (алгоритм решения был рассмотрен в главе №1, само решение приведено в главе №4 при изучении ветвления) то теперь можно рассмотреть решение в виде функции. Сложность задачи заключается в том, что при положительном дискриминанте необходимо вычислить два корня, а если вспомнить вызов стандартных функций языка С, функция может вернуть только одно значение.
Это ограничение можно обойти, если использовать две функции каждую для нахождения одного корня, но это не оптимальное решение. При желании можно самостоятельно разработать подобную версию программы. Второй вариант решения более удачен с точки зрения оптимальности кода. Он и будет рассмотрен ниже.
#include <iostream>
#include <math.h>
#include<iomanip>
using namespace std;
double korny_2D(double a1,double a2,double a3,int k); // прототип функции
double korny_2D(double a,double b,double c,int k) // заголовок функции
{
// коэффициен к определяет знак в вычислениях корня, если 1 то +, если -1 ‑ -
double d,x;
d=b*b-4.*a*c;
x=(-b+k*sqrt(d))/(2.*a);
return x;
}
int main(void)
{
double a,b,c,x1,x2,d;
cout<<"Vvedite kooficienty a*x^2+b*x+c+0\n";
cin>>a>>b>>c;
d=d=b*b-4.*a*c;
cout.setf(ios::showpos);
cout<<a<<"*x^2"<<b<<"*x"<<c<<"=0\n";
if(d<0.) cout<<"korney net\n";
else
if(d==0.)
{
x1=korny_2D(a,b,c,1);
cout<<"koren 1="<<x1<<endl;
}
else
{
cout<<"2 korny\n";
x1=korny_2D(a,b,c,1);
cout<<"koren 1="<<x1<<endl;
x2=korny_2D(a,b,c,-1);
cout<<"koren 2="<<x2<<endl;
}
return 0;
}
Результат работы программы будет выглядеть так (рис. 47).
Рис. 47. Результат работы программы с применением функций
Необходимо более полно рассмотреть код вышенаписанной программы.
Заголовок функции
Сначала рассмотрим в этом примере заголовок функции.
double korny_2D (double a,double b,double c,int k)
Тип возвращаемого Имя Список формальных значения функции параметров, заключенный в ( )
Возвращаемое значение является результатом работы функции. Это значение подставляется в выражение из которого функция была вызвана после того как работа функции закончена.
Функция из примера имеет три параметра: a,b,c – значения типа double являются коэффициентами квадратного уравнения, и int k определяет знак в вычислениях корня, если 1 то +, если -1 – -. Эти формальные параметры участвуют в вычислениях, выполняемых функцией, вместе с другими переменными ‑ d и x, объявленными в ее теле.
Формальные переменные инициализируются значениями фактических переменных по очереди, то есть первое соответствует первому, второе второму и так далее, если типы формального и фактического значения не совпадают, то происходит принудительное приведение к типу формального параметра, даже если это происходит с потерей точности.
Следует обратить внимание, что в конце заголовка функции и после закрывающей фигурной скобки ее тела точка с запятой не требуется
Общая форма заголовка функции может быть записана следующим образом:
тип_возврата имя_функции (список_параметров)
тип_возврата может быть любым легальным типом. Если функция не возвращает значения, то тип возврата указывается ключевым словом void. Ключевое слово void также применяется для обозначения отсутствия параметров, поэтому функция, которая не имеет параметров и не возвращает значения, должна иметь следующую форму заголовка:
void my_function(void)
Пустой список параметров также означает, что функция не имеет аргументов, поэтому можно пропустить ключевое слово void между скобками:
void my_function ()
Функция с типом возврата void не должна использоваться в составе выражений в вызывающей программе. Поскольку она не возвращает значения, то, по сути, не может быть значимой частью выражения, поэтому применение ее в таком виде заставит компилятор генерировать сообщение об ошибке.
Тело функции
Все необходимые вычисления функции выполняются операторами в ее теле, которое следует за заголовком. Тело функции из последнего примера начинается с объявления переменной. Переменная d локальна по отношению к функции, как и все автоматические переменные, объявленные в ее теле. Это значит, что переменные d и x прекращают существование после того, как функция завершит работу.
Оператор return
Оператор return возвращает значение x в точку вызова функции. Общая форма оператора return такова:
return выражение;
где выражение должно вычисляться как значение типа, специфицированного в заголовке функции для возврата значения. Выражение может быть любым, условие одно – результат имеет значение требуемого типа. Выражение может включать вызовы функций – даже вызов той же самой функции, в которой оно появляется.
Если тип возврата функции специфицирован как void, то за оператором return не должно следовать никакого выражения, либо оператор можно просто не писать. Оно должна записываться очень просто: return;