Fmod(x,y) - остаток от вещественного деления x на у.




 

Обратим внимание на то, что функции 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



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2016-02-16 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: