Обратим внимание на то, что функции ceil и floor, хотя и вычисляют (или, как принято говорить у программистов, "возвращают") целочисленные значения, имеют вещественный тип. Вообще для определения типа любой функции и типов ее аргументов рекомендуется просмотреть информацию об этой функции через справочную систему способом, описанным выше. Напомним еще раз, что использование математических функций требует подключения заголовочного файла math.h.
Выражения присваивания и ввод данных в программу
Теперь перед нами стоит важная задача - научиться вводить в программу извне исходную информацию. Разумеется, начальные данные для решения конкретной задачи могут быть заданы в выражениях описания переменных или с помощью выражений присваивания, как мы это делали до сих пор. Понятно, однако, что на этом пути мы далеко не продвинемся, поскольку при этом изменения вводимых в программу величин потребуют изменений в тексте, перекомпиляции и т. д. Конечно, в С есть средства более удобного общения пользователя с программой, но сначала мы все же более подробно поговорим о выражениях присваивания. В простейшем виде выражение присваивания имеет структуру:
ИмяПеременной=Выражение;
Слева от знака равенства должно стоять именно имя переменной и ничто другое. Справа стоит любое выражение, в том числе, возможно, снова выражение присваивания. Работает выражение присваивания так. Сначала вычисляется значение выражения в правой части, затем это значение приводится к типу, с которым была описана переменная в левой части, и после этого переменная получает соответствующее значение. На основании такого определения ясно, что странная, с точки зрения алгебры, запись Р=Р+1 является совершенно нормальной конструкцией языка С. Означает такое выражение присваивания то, что после его выполнения переменная Р будет иметь значение, увеличенное на единицу по сравнению с тем значением, которое было у нее до присваивания. Также на основании определения можно легко понять, что в результате "каскадного" присваивания типа а=b=с=3.1415927 все три переменные будут иметь значение пифагорова числа. Подчеркнем, что присваивание здесь будет выполняться справа налево. Сначала с получит значение этой константы, затем b примет значение, равное величине переменной с, а затем и а не останется обойденной вниманием. Для любопытных дадим повод к размышлению. А что будет, если переменные в "каскадном" присваивании имеют разные типы? Тут, как говорится, возможны варианты.
Кроме обычного выражения присваивания в языке С реализованы сложные присваивания, имеющие вид:
Имя< операция> =Выражение;
Имя - имя переменной. В качестве конструкции <операция> может стоять любой знак арифметического действия или операции сдвига +, -, *, /, %, >>, <<. Такое сложное присваивание дает тот же результат, что и простое присваивание в виде Имя=Имя<операция> Выражение, однако сложное присваивание, использованное там, где оно может заменить простое, выполняется быстрее. В этой связи в оптимально написанной программе обычно широко используются сложные присваивания. Типы данных, допустимые в этих выражениях, зависят от типов операций. Выражение а*=r эквивалентно выражению а=а*r и допустимо для любых типов данных. Выражение а%=b присваивает переменной а новое значение, равное остатку от целочисленного деления старого значения а на b и применимо, очевидно, к целым. Целочисленным является и выражение а>>=2, результат которого новое значение а есть старое значение а, целочисленно деленное на 4. Разумеется, такое присвоение выполняется быстрее, и существенно быстрее, чем выражения а=а/4.
Как уже говорилось, задание переменным значений с помощью присваивания не решает проблему ввода данных в программу в целом. Если вы хотите многократно использовать одну программу с различными входными данными, то вам поможет функция scanf. Продемонстрируем ее работу в следующей программе:
#include<stdio.h>
#include<math.h>
#include<conio.h>
Viod main()
{
double a.b,c,xl,x2;
printf ("a="); scanf ("%lf ",&a);
printf ("b="); scanf ("%lf ",&b);
printf ("c="); scanf("%lf",&c);
xl=(-b+(c=sqrt(b*b-4.0*a*c)))/(a+a);
x2=(-b-c)/(a+a);
printf ("xl=%f\n",xl);
printf ("x2=%f\n",x2);
getch();
}
Функция scanf в какой-то степени напоминает по своей структуре функцию printf, но по своему назначению скорее противоположна ей, поскольку предназначена для придания переменным значений, вводимых с консоли. Структура функции такова:
Scanf(“строка.", Указатель1, Указатель2,...);
В строке в кавычках мы рекомендуем начинающим писать только форматные выражения, полностью аналогичные форматным выражениям в функции printf. Количество форматных выражений, которые советуем писать подряд через разделители, например, запятые, должно быть равным количеству элементов в конструкции после закрывающих строку кавычек и запятой. Функция устроена так, что в ней перечисляются (через запятую) не сами имена переменных, которым нужно придать значения, а так называемые указатели на имена или адреса этих переменных. Разговор об указателях отложим до более подходящего момента, ограничившись пока тем пояснением, что адрес переменной r (указатель на r) записывается как &r, что мы и видим в примере программы. Важнейшим обстоятельством является то, что программист должен очень строго следить за совпадением типа вводимой переменной с типом соответствующего форматного выражения. Так, если в функции printf вы печатаете переменную типа double по формату %f, то значение выведется на экран правильно, однако, если вы по этому формату попытаетесь ввести переменную типа double функцией scanf, то результат будет плачевным, вплоть до полного "зависания" системы - для типа double строго требуется формат %lf. Сочетание букв соответствует типу long float, что и есть double. Можно ли между знаком % и форматными буквами писать что-нибудь типа 6.3? Да, можно. Однако извольте и число, вводимое с клавиатуры, задавать именно в такой форме: две позиции в целой части, десятичная точка, три позиции в дробной части. Для полного согласования типов вводимых значений и типов форматных выражений автор настоятельно советует читателю еще раз обратиться к справочной системе Builder, вызвав справку по функции scanf. Отметим также еще раз, что если функция printf может печатать значение переменной, значение выражения, значение, возвращаемое какой-либо функцией, то функция scanf только присваивает переменной значение, вводимое с клавиатуры. Если ввод осуществляется выражением:
scanf("%lf,%lf,%lf",&a,&b,&c);
то нужно ввести на экран первое число, затем запятую, затем второе число, затем запятую, затем третье число и только потом нажать клавишу ввода Enter. В примере программы ввод организован иначе. Сначала печатается "подсказка" а=. Теперь курсор ввода находится правее знака равенства, и вы должны напечатать вводимое число и нажать Enter. Все. Первая функция ввода проработала, и переменная а получила введенное значение. Функция scanf, закончив работать, всегда осуществляет переход на новую строку так, что следующая подсказка будет уже на новой строке, и все повторяется теперь для переменной b, а затем и для с.
Читатель уже понял, что программа вычисляет корни квадратного уравнения ax2 + Ьх + с = 0. Здесь могут возникнуть неприятности, если a равно нулю или если дискриминант отрицателен. Будем считать, что коэффициенты введены такими, что неприятностей не возникнет. Далее в программе реализованы расчетные формулы для корней. Здесь снова продемонстрирована интересная новинка. Обратите внимания на то, что прямо внутри арифметического выражения помещено выражение присваивания: c=sqrt(b*b-4.0*a*c), что вполне допустимо. Тогда переменная с получает новое значение, вычисленное с использованием ее старого значения. Разумеется, это старое значение после такого присваивания исчезает, но в данном примере оно уже и не нужно, что и позволило сэкономить немного машинной памяти - обойтись без введения и описания новой переменной. Если бы мы не присвоили переменной с значения корня из дискриминанта, в следующем выражении нам снова пришлось бы вычислять этот корень. Поскольку именно вычисление значений математических функций занимает наибольшее процессорное время, здесь мы оптимизировали программу по быстродействию. И еще один момент. В последних выражениях программы мы снова пишем а+а вместо 2*а, заменяя медленную операцию умножения быстрой операцией сложения.
Условные выражения
До сих пор мы жили в скучном мире, где программы выполнялись последовательно, выражение за выражением от начала до конца, не задавая себе вопросов, а значит, и не пытаясь находить ответы. Да простит нам читатель такое "очеловечивание" программы, но суть материала данного раздела действительно связана с тем, что в программе могут анализироваться те или иные условия, и в зависимости от того, выполняются ли эти условия, программа работает по-разному. Таким образом, реализуются разветвляющиеся алгоритмы - сценарий работы может развиваться по одному из нескольких вариантов в зависимости от условий. Мы рассмотрим лишь некоторые средства языка С, позволяющие программировать разветвляющиеся алгоритмы. Хотя другие средства могут оказаться весьма удобными для какой-то конкретной задачи, мы остановимся только на условных выражениях, которые, впрочем, полностью решают задачу организации разветвлений.
Начнем сразу с примера программы:
#include<stdio.h>
Void main()
{
int a,b;
printf ("ВВЕДИ A="); scanf("%d",&a);
printf ("ВВЕДИ В="); scanf("%d",&b);
if(a%b==0)
рrintf("Число %d делится на %d нацело\n",а,b);
Else
{
printf ("Число %d не делится на %d нацело\n",а,b);
printf ("Остаток деления %d на %d равен %d\n",a,b,a%b);
}
}
Программа делает простые вещи. Принимает с консоли два целых числа и проверяет, делится ли первое число на второе нацело. В первом случае на экран выводится одно сообщение, во втором - два других.
Условные выражения, обеспечивающие альтернативные пути выполнения программы, могут быть двух типов. Простое условное выражение имеет структуру;
if(Выраженне1) Выражение2;
Само служебное слово if означает ЕСЛИ. Если Выражение1 имеет значение 0, то Выражение2 пропускается, и программа выполняется дальше. Если Выражение1 не равно 0, то выполняется Выражение2, и только после этого программа работает дальше. Сложное условное выражение имеет вид:
if(Выражение1) Выраженпе2; else ВыражениеЗ;
Служебное слово else означает ИНАЧЕ. Если Выражение1 имеет значение отличное от нуля, то выполняется Выражение2, пропускается ВыражениеЗ, и программа выполняется дальше. Если же Выражение1 имеет значение 0, то, наоборот, пропускается Выражение2, выполняется ВыражениеЗ, программа работает дальше. Любое из выражений может, в свою очередь, быть условным выражением. Необходимо только следить за тем, чтобы служебное слово if не оказалось оторванным от своего else. Это значит, что между if и else формально должно стоять только одно выражение.
А что делать, если необходимо, чтобы в качестве Выражения2 или ВыраженияЗ выполнялась целая группа действий? В этом случае вам нужно "обмануть" систему, замаскировать группу выражений под одно выражение. Делается это очень просто. Группу выражений объединяют в так называемое составное выражение (блок) - заключают эту группу в фигурные скобки {}. Тогда группа в фигурных скобках и в условных выражениях и в других специальных конструкциях будет восприниматься как одно выражение.
Теперь, очевидно, нам никак не обойтись без введения в программу таких понятий, как "больше", "не равно" и так далее. Выражения, содержащие такого сорта сравнения, естественно назвать выражениями отношений. Сразу отметим, что выражения отношений имеют целый, беззнаковый числовой тип. Более того, отношения могут принимать только одно из двух возможных значений: 0 или 1. Первое можно рассматривать как "ЛОЖЬ", а второе как "ИСТИНА", хотя такая трактовка чужда языку С.
В языке допустимы следующие знаки сравнения:
= - равно;
< - меньше;
!= - не равно;
>= - не меньше;
> - больше;
<= - не больше.
Смысл обозначений совершенно ясен и не требует пояснений. Приведем примеры. Значение выражения 3<=7 имеет значение 1, поскольку "ЭТО ПРАВИЛЬНО", а выражение 0.2<.0 равно нулю, поскольку "ЭТО НЕПРАВДА". Выражение а!=b равно нулю, если а и b имеют равные значения. В противном случае выражение имеет значение 1. При этом можно сравнивать не только числовые значения, но и, например, символьные переменные. Самое интересное заключается в том, что во многих случаях вы имеете право сравнивать значения разных типов. Так, переменная а может быть целой, a b символьной. Догадываетесь, что сравнивается в этой ситуации?
Важную роль в условных выражениях играют логические операции. Таких операций три. Логическое отрицание !а имеет значение 1, если а равно нулю и имеет значение 0 при любых других значениях а. Логическое умножение А&&В имеет значение 1 в том и только том случае, когда и А и В не равны нулю, и имеет значение 0, когда либо А, либо B, либо и А и В равны пулю. Логическое сложение P||Q имеет значение 0 в том и только том случае, когда и Р и Q равны нулю, и имеет значение 1 при любых других вариантах.
В сложных логических выражениях надо ориентироваться на приоритеты операций. В первую очередь выполняются операции отрицания, во вторую очередь - логические умножения и в последнюю - логические сложения. Во избежание неприятностей рекомендуется пользоваться круглыми скобками. Читателю предлагается убедиться в том, что
(3==7)||(2!=4) есть 1,
(!(2!=0)||((!2)==5) есть 0,
!(4.5>1.0)&&(3!=3) есть 0,
(!(1&&2))||((3>2) есть 1.
Теперь вернемся к нашей последней программе. Здесь в условии после if вычисляется остаток от деления a на b и результат сравнивается с нулем. Если остаток от деления равен нулю, то есть, если а делится на b нацело, то выполняется выражение после if(). В противном случае выполняется составное выражение в фигурных скобках, следующее за else. Цель достигнута, но не самым лучшим способом. Действительно, для достижения этой же цели в скобках после if достаточно было бы написать !(а%b). А можно было бы поступить и еще проще. В скобках пишем просто а%b, и выражения перед и после else меняем местами.
Для закрепления усвоенного составим еще одну программу, которая снова решает квадратное уравнение, но предварительно страхует нас от случая комплексных корней, когда дискриминант отрицателен:
#include<stdio.h>
#include<math.h>
Void main()
{
double a,b,c;
printf("ВВЕДИТЕ a,b,c\n");
scanf("%lf,%lf,%lf”,&а,&Ь,&с);
if((c=b*b-4.0*a*c)<0.)
printf("НЕТ РЕШЕНИЯ");
Else
{
printf ("x1=%f\n",(-b+(c=sqrt (c))) /a+=a);
printf ("x2=%f\n",(-b-c)/a);
}
}
В программе снова применены некоторые маленькие хитрости, обеспечивающие оптимизацию вычислений. Поскольку уважаемый читатель стал уже достаточно опытным, предоставим ему возможность самостоятельно разобраться с ними.
Циклические выражения
Циклические выражения, или просто циклы, предназначены для многократного выполнения однотипных действий. В С используются три типа циклов. Рассмотрим первый из них, называемый циклом while. Сразу начнем с примера программы:
#include<stdio.h>
Void nain()
{
int i,j=1,k=0;
printf("Введите i="); scanf("%d".&i);
while(j<=i)
{ k+=j; j++; }
printf('Cyммa ряда равна %d",k);
}
Цикл while имеет следующую структуру:
while(Выражение1)Выражение2;
Само слово while означает ПОКА. Более корректно трактовать это слово в форме ДО ТЕХ ПОР ПОКА. Работает цикл следующим образом. Вычисляется Выражение1. Если его значение не 0, то выполняется Выражение2, которое может быть составным выражением. Затем снова вычисляется Выражение1, и если оно не 0, снова выполняется Выражение2. Так продолжается до тех пор, пока Выражение1 не примет значение, равное нулю. Разумеется, в процессе выполнения цикла что-то в выражении в скобках должно меняться. Иначе произойдет зацикливание программы. Кстати, в случае зацикливания или возникновения некоторых других аварийных ситуаций можно попробовать прервать работу программы? нажав Ctrl + C, или дважды нажав комбинацию клавиш Ctrl+Pause.
Наша последняя программа вычисляет сумму первых i натуральных чисел 1 + 2 + 3 +... + i, где i - целое число, вводимое с консоли. Условие, проверяемое в цикле j<=i, перестанет выполняться (примет значение 0), как только j станет больше i. Это событие рано или поздно наступит, поскольку в составном выражении, выполняемом в цикле, каждый раз j увеличивается на единицу. Кроме того, в составном выражении в переменной k накапливается сумма членов ряда. С учетом того, что в описании переменных j и k им присвоены необходимые значения, можно убедиться "вручную" проследив работу цикла, что поставленная задача здесь решается. Если введено число i, равное нулю, то цикл не проработает ни одного раза и переменная k останется со своим начальным значением 0, что также верно. Заметим, что в данном варианте цикл записан не вполне оптимально. Составное выражение можно записать более компактно и более оптимально с точки зрения быстродействия: while(j<=i) k+=j++; Можно видеть, что необходимость в фигурных скобках отпала, поскольку выражение перестало быть составным.
Рассмотрим еще одну программу, использующую цикл while:
Void main()
{
int st=1,i=0,n;
scanf("%d",&n);
while(i++<n) st<<=1;
printf("2^n=%d",st);
}
Эта программа вычисляет в переменной st n-ю степень двойки. Начальное значение st равно 1. В цикле каждый раз производится сдвиг двоичного кода величины st на одну позицию влево, то есть выполняется умножение на 2. Тем самым и определяется степень двойки. Успокоим того дотошного читателя, который разумно заподозрил, что мы делаем лишнюю работу. Эта программа только демонстрирует работу цикла. На самом деле вместо всего цикла достаточно было бы записать одно выражение:
st<<=n;
Другой тип цикла называется циклом do-while и имеет следующую структуру:
do Выражение1; while(Выражение2);
Такую конструкцию следует понимать так: ВЫПОЛНЯТЬ Выражение1, ПОКА Выражение2 есть НЕ НОЛЬ. Отличие от цикла while состоит в том, что сначала выполняется первое выражение, а затем проверяется второе и так происходит до тех пор, пока последнее не примет значение 0. В цикле while, наоборот, сначала, проверялось условие, а потом выполнялось выражение. Как мы видели, может иметь место такая ситуация, когда цикл while не выполняется ни разу. Цикл do-while выполняется, как минимум, один раз. И здесь также Выражение1 может быть составным.
С использованием цикла do-while напишем программу, вычисляющую сумму п + 1 первых членов так называемого гармонического ряда 1+1/2+1/3+1/4+... +1/n.
Void main()
{
int n,j=1; double a=.0;
scanf("%d",&n); n++;
do a+=1.0/j++; while(j<n);
printf("%lf".a);
}
Программа будет работать правильно, но она снова не оптимальна. Можно обойтись без переменной j. Цикл в этом случае следует записать в виде:
do a+=1.0/n- -; while(n);
При этом из программы следует убрать выражение инкремента п++; после функции scanf. Можно видеть, что сумма ряда теперь вычисляется в обратном порядке - от конца к началу, и переменная n каждый раз уменьшается на единицу (декрементируется). Упростилось и условие выхода из цикла. Действительно, цикл нужно выполнять до тех пор, пока n не станет равным нулю, что и записано в скобках после while.
Третий тип циклических выражений - циклы for. Структура цикла в общем виде может быть представлена так:
for(Выражения1; Выражение2; Выражения3)Выражение4;
Сразу обратим внимание на то, что при использовании цикла for вы можете воспользоваться некими исключениями из правил, которые только в этом цикле и допустимы. Выражения1 и Выражения3 не зря написаны здесь во множественном числе. На их месте могут быть записаны несколько выражений присваивания, перечисленные не через точку с запятой, как обычно, а через запятую. Тогда эти группы будут представлять собой своеобразные составные выражения, не нуждающиеся, однако, в фигурных скобках. Выражение2 представляет собой обычное условие, принимающее значение 0 или не 0. Выражение4, которое может быть составным уже в обычном виде, является выражением, выполняющимся в цикле. Интересно то, что Выражения1, Выражения3 и даже Выражение4 могут отсутствовать, если в них нет нужды. Правильнее будет сказать, что они могут быть пустыми выражениями, от которых остались только их точки с запятой. Более того, для Выражений3 имеет место совсем нестандартная ситуация. Если они присутствуют, то они не должны оканчиваться символом ";". Все эти исключения, по мнению автора, только портят общее представление о языке С. С этим, однако, приходится мириться, поскольку цикл for с его "причудами" очень широко используется программистами. Здесь уместно отметить, что все три цикла взаимозаменяемы.
Работа цикла for заключается в следующем. Сначала, причем только один раз, выполняются Выражения1, если они имеются. Понятно, что эти выражения целесообразно использовать для выполнения предваряющих цикл присваиваний, для установки некоторых начальных значений переменных. Затем вычисляется Выражение2. Если оно равно 0, то на этом работа цикла заканчивается и программа работает дальше. Если Выражение2 не есть 0, то выполняется Выражение4, которое, как говорилось выше, может быть и пустым. Затем выполняются Выражения3 (если они есть), снова проверяется условие в Выражении2 и так далее, до тех пор пока в Выражение2 не образуется 0.
Проиллюстрируем работу цикла for на программе, вычисляющей факториал числа n! = 1*2*3*…*n:
Void main()
{
int n,i.j;
scanf(“%d”.&n);
for(i=1,j=1; i<=n; i++,j*=i);
printf("%d!=%d",n,j);
}
В данном примере как раз отсутствует Выражение4, поскольку все, что необходимо, выполняется в Выражениях3, которых два. Эти два выражения лучше превратить в одно: j*=++i. Убедитесь, что знаки ++ здесь должны стоять именно перед i (в префиксной форме), а не после i. Сначала надо увеличить i на единицу, а затем использовать, как это более явно видно из неисправленной программы. Выражения1 также не мешает преобразовать к виду i=j=1;
А теперь перепишем программу в другой форме:
Void main()
{
int n,i=1,j=1;
scanf("%d".&n);
for(;i<=n;)j*=i++;
printf(“%d!=%d”,n,j);
}
Здесь, наоборот, невостребованными оказались Выражения1, поскольку начальные присваивания сделаны при описании переменных. Отсутствуют и Выражения3, так как все необходимые действия перенесены в Выражение4.
Поскольку циклы являются важнейшими элементами программ, рассмотрим еще несколько примеров. Следующая программа выводит на экран столбец случайных целых чисел:
#include<stdio.h>
#include<stdlib.h>
Void main()
{
int i;
for(i=10; i- -;) printf("%d\п",rand());
}
Здесь фигурирует новый заголовочный файл stdlib.h, имеющий отношение к функциям из так называемой стандартной библиотеки. В данном случае одной из таких функций является генератор случайных чисел rand(). Эта функция при каждом к ней обращении возвращает очередное целое случайное число в диапазоне от 0 до 32 766 включительно. Что это за магическое число? Это максимальное целое число типа int. С помощью генератора rand можно легко получать последовательности и вещественных чисел. Так, в результате операции rand()/32766.0 будет сгенерировано случайное вещественное число в диапазоне от 0.0 до 1.0. Подчеркнем, что число в знаменателе должно быть записано именно с десятичной точкой, как вещественное число. В противном случае будет выполнено целочисленное деление, и будет почти всегда генерироваться.... Что? Только очень редко, примерно один раз из 30 000 будет попадаться... Что?
Ну, а сам цикл for в последней программе будет выполнен 10 раз. На экране будет столбик из 10 случайных чисел. Обращаем внимание на то, что цикл записан весьма компактно и оптимален по быстродействию.
Теперь составим программу, решающую следующую задачу. Нужно найти среднее арифметическое всех случайных чисел, генерируемых функцией rand. в последовательности до первого числа, превышающего значение 20 000.
#include<stdio. h>
#include<stdlib.h>
Void main()
{
int i=0,a.z;
long s=0;
double sred;
printf("3ATPABKA="); scanf("%d".&z);
srand(z);
while((a=rand())<=20000) {i++; s+=a;}
sred=(double)s/i;
printf("СРЕДНЕЕ %d ЧИСЕЛ = %lf",i,sred);
}
В программе вызывается еще одна функция из стандартной библиотеки srand(z) от целого аргумента z. Число z вводится с консоли и является "затравкой" для последовательности случайных чисел, генерируемых функцией rand. Можно считать, что z является номером случайной последовательности. Каждому z будет соответствовать своя последовательность. Чтобы получать каждый раз один и тот же ряд случайных чисел, нужно вводить одно и то же z, и, наоборот, вводя каждый раз новое z, вы получите новый ряд. В переменной s накапливается сумма случайных чисел для определения среднего значения. Поскольку эта сумма может легко превысить максимально допустимую для типа int величину 32 767, мы задали переменной s тип "длинного целого" long. Понятно, что начальное значение s должно быть нулевым. Целая переменная i является счетчиком числа выполнений цикла. Она также нужна для вычисления среднего арифметического. В цикле while, непосредственно при проверке условия, переменная а получает очередное случайное значение. Цикл выполняется до тех пор, пока очередное встретившееся случайное число не превысит 20 000. В составном выражении в цикле счетчик увеличивается каждый раз на единицу и к сумме добавляется очередное число. После выхода из цикла вещественная переменная sred получает значение среднего арифметического. Для этого надо сумму разделить на счетчик. Но и s и i имеют целый тип, и операция s/i выполнится целочисленно, что было бы неверно, поскольку среднее арифметическое - вещественное число. В этой связи в программе применена новая операция, которую можно назвать приведением к нужному типу. Мы знаем, что для того, чтобы операция деления выполнилась в вещественной форме или, как говорят, в виде операции с плавающей точкой, достаточно привести числитель или знаменатель к вещественному типу. В нашей программе в числителе написано (double)s. Это и означает, что значение переменной s будет преобразовано в вещественное число (с двойной точностью). Теперь все стало на свои места, и нам осталось вывести на экран вычисленное среднее. Заметим, что переменная sred, вообще говоря, не нужна — можно было бы сразу в выражении печати использовать (double)s/i или s/(double)i.
Операция приведения к типу, конечно, универсальна. Так, если переменная Р была вещественного типа, то (int)P дает целое типа int, полученное из Р округлением, a (char)P дает символ, код которого равен округленному значению Р, если, конечно, такой символ вообще существует.
Массивы
Если вам приходится работать с большими объемами данных, то неизбежно возникает проблема задания большого количества имен переменных, с которыми обращается программа. Однако эта проблема еще не проблема по сравнению с тем, что нужно найти способ компактного описания однотипных действий с большим количеством однотипных данных. В математике эти проблемы давно решены с помощью введения переменных с индексами - векторов, тензоров. Этим же путем пошли и разработчики многих языков программирования, включая С.
Итак, массивом называется совокупность переменных с одинаковым именем и снабженных индексами. Сразу уточним, что все компоненты массива имеют один и тот же тип. Сами же массивы могут быть любых типов, как и простые переменные. Проиллюстрируем способ описания массивов и методы работы с массивами в языке С на программе, вычисляющей скалярное произведение двух векторов. Вектор является типичным представителем массивов. В трехмерном пространстве, например декартовом, вектор R есть совокупность трех компонентов: Rx,Ry,Rz. Ничто не мешает нам заменить буквенные обозначения индексов на числовые. Для того чтобы ближе подойти к правилам описания и использования массивов в С, обозначим компоненты вектора как R0,R1,R2.
Как известно читателю, скалярное произведение двух векторов c = ab есть сумма произведений соответствующих компонентов:
a0b0+a1b1+a2b2.
Тогда программа, выполняющая поставленную задачу, может выглядеть следующим образом:
Void main()
{
int i;
double c=0.,a[3],b[3];
for(i=0; i<3; i++)
{
printf("a[%d]="); scanf(“lf”,&a[i]);
printf("b[%d]="); scanf("&lf".&b[i]);
}
for(i=0; i<3; i++) c+=a[i]*b[i];
printf(“c=%lf”,c);
}
Описание массива аналогично описанию переменной, с той лишь разницей, что после имени массива следуют прямые скобки, в которых указывается число элементов в массиве. В данном случае описаны два массива: а и b, элементы которых есть вещественные числа типа double. Каждый массив состоит из трех чисел. Теперь подчеркнем важнейший момент, который начинающие, как правило, упускают из вида, что приводит к серьезным неприятностям при выполнении программы. Автор настоятельно советует читателю несколько раз повторить про себя, что ЭЛЕМЕНТЫМАССИВОВ НУМЕРУЮТСЯ, НАЧИНАЯ С 0, а не с 1, хотя последнее, возможно, более привычно. В нашем примере массив а состоит из трех элементов: а[0], а[1], а[2]. Уясните себе, что если массив описан, например, как Р[5], то в нем последним элементом является Р[4] и вовсе нет элемента Р[5], зато есть элемент Р[0]. Автору не хотелось бы показаться занудой, но он вынужден вновь и вновь возвращать читателя на несколько строчек назад, к выделенному заглавными буквами тексту. Основная беда здесь заключается в том, что компилятор (по крайней мере, в Builder) спокойно пропускает ситуацию, когда программист допускает "выход за границы массива" - система ошибки не обнаруживает. При этом во время выполнения программы возникает конфликт в оперативной памяти компьютера, который довольно трудно распознать. Очень часто именно по этой причине возникает фатальное "зависание" системы, и единственным выходом из такой ситуации является перезагрузка компьютера.
Последняя программа показывает, как следует обращаться к отдельным элементам массива: пишется имя массива, а в прямых скобках его индекс. Очевидно, что использование массивов в сочетании с циклами позволяет реализовывать однотипные операции над большими объемами данных.
В предыдущей программе заполнение массивов конкретными значениями - инициализация массивов делалось через ввод значений каждого из элементов с консоли с использованием приглашения и функции scanf. Однако аналогично тому, как начальная инициализация простых переменных может быть выполнена непосредственно в описаниях, точно так и массивы можно инициализировать в описаниях. Следующая программа, также выполняющая скалярное произведение векторов, демонстрирует начальную инициализацию массивов:
Void main()
{
int i=2;
double a[]={0.23,-1.45,2.04},
b [] ={-3.12.0.03,4.66}, c=0.0;
for(;i>=0;) c+=a[i]*b[i- -];
printf("c=%lf",c);
}
При задании компонентов массивов в описаниях присваиваемые значения перечисляются в фигурных скобках через запятую. Как видно из текста программы, в этом случае количество элементов в массиве - длину массива можно не указывать и в описании после имени массива оставить пустые прямые скобки. Действительно, количество инициализирующих значений внутри скобок {} автоматически задает длину массива. Если же вы укажете длину массива явно, а в инициализирующем списке напишете значений меньше, чем длина, то будут заданы первые элементы массива, а остальные останутся неопределенными.
Теперь рассмотрим следующую задачу. Сформируем два массива целых чисел по 100 элементов в каждом. Заполним массивы случайными целыми числами в диапазоне от 0 до 500. Выведем на экран номера (индексы) и значения попарно совпадающих элементов массивов, продвигаясь по массивам "сверху вниз", то есть, начиная с последних элементов массивов. Задача может быть реализована следующей программой:
Void main()
{
const double f=l./32767*500;
int i,a[100],b[100];
for(i=0; i<1