Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Прикладное программирование на CC++ с нуля до мультимедийных и сетевых приложений
.pdf
Массивы 31
Массивы
Если вам приходится работать с большими объемами данных, то неизбежно возникает проблема задания большого количества имен переменных, с которыми обращается программа. Однако это еще не проблема по сравнению с
тем, что нужно найти способ компактного описания однотипных действий с
большим количеством однотипных данных. В математике эти проблемы давно
решены с помощью введения переменных с индексами (номерами) — векторов, тензоров. Этим же путем пошли и разработчики многих языков программирования, включая С.
Итак, массивом называется совокупность переменных с одинаковым именем и снабженных индексами. Сразу уточним, что все компоненты массива
имеют один и тот же тип. Сами же массивы могут быть любых типов, как и
простые переменные. Проиллюстрируем способ описания массивов и методы
работы с массивами в языке С на программе, вычисляющей скалярное произведение двух векторов. Вектор является типичным представителем массивов.
В трехмерном пространстве, например декартовом, вектор R есть совокупность трех компонентов: R
x,Ry,Rz
ные обозначения индексов на числовые. Для того чтобы ближе подойти к
правилам описания и использования массивов в С, обозначим компоненты
вектора как R
0,R1,R2
.
Как известно читателю, скалярное произведение двух векторов c=abåñòü
сумма произведений соответствующих компонентов:
. Ничто не мешает нам заменить буквен-
a
0b0+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). Каждый массив состоит из трех чисел. Теперь подчеркнем важнейший момент, который начинающие, как правило, упускают из вида, что приводит к серьезным неприятностям при выполнении программы. Автор настоятельно советует читателю
несколько раз повторить про себя, что ЭЛЕМЕНТЫ МАССИВОВ НУМЕРУ
ЮТСЯ, НАЧИНАЯ С НУЛЯ, а не с единицы, хотя последнее, возможно, бо
-
-

32 Глава 1. Язык программирования С
лее привычно. В нашем примере массив à состоит из трех элементов: à[0],
à[1], à[2]. Уясните себе, что если массив описан, например, как Ð[5], òî â
нем последним элементом является Ð[4] и вовсе нет элемента Ð[5], зато есть
элемент Ð[0]. Автору не хотелось бы показаться занудой, но он вынужден
вновь и вновь возвращать читателя на несколько строчек назад, к выделенному заглавными буквами тексту. Основная беда здесь заключается в том, что
компилятор (по крайней мере, в TURBO Ñ) спокойно пропускает ситуацию,
когда программист допускает «выход за границы массива» — система ошибки
не обнаруживает. При этом во время выполнения программы возникает конфликт в оперативной памяти компьютера, который довольно трудно распознать. Очень часто именно по этой причине возникает фатальное «зависание»
системы, и единственным выходом из такой ситуации является перезагрузка
компьютера.
Последняя программа показывает, как следует обращаться к отдельным
элементам массива: пишется имя массива, а в прямых скобках его индекс.
Очевидно, что использование массивов в сочетании с циклами позволяет реализовывать однотипные операции над большими объемами данных.
В предыдущей программе заполнение массивов конкретными значениями — инициализация массивов делалась через ввод значений каждого из элементов с консоли с использованием приглашения и функции 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. Выведем на экран номера (индексы) и зна
чения попарно совпадающих элементов массивов, продвигаясь по массивам
-

Массивы 33
«сверху вниз», то есть начиная с последних элементов массивов. Задача может
быть реализована следующей программой:
void main()
{const double f=l./32767*500;
int I,a[100],b[100];
for(i=0; i<100; i++)
{ a[i]=rand()*f; b[i]=rand()*f; }
for(; - -i>==0;) if(a[i]==b[i])
printf("a[%d]=b[%d]=%d \n",i,i,a[i]); }
Сначала в программе задается глобальная вещественная константа f, которая, будучи умноженной на функцию rand(), дает вещественное случайное
число в нужном диапазоне. В первом цикле for элементам массивов à è b придаются случайные значения. Поскольку массивы описаны как целые, правые
части выражений присваивания автоматически будут приведены к целому
типу. Если же вы (как и ваш покорный слуга) с трудом переносите такое
вольное обращение с типами данных, то пишите a[i]=(int) (rand()*f). Программа от этого ничего не потеряет, хотя ровным счетом ничего и не приобретет. Из первого цикла программа выходит при значении r, равном 100. Следующий цикл должен просматривать массивы, начиная с 99-х элементов.
В этой связи в проверке условия в цикле используется предварительная декрементация переменной i. Напомним, что выражение --i>==0 сначала уменьшает i на единицу, а затем производит сравнение уменьшенного значения с
нулем.
До сих пор мы говорили о числовых массивах. Массивы, однако, как и
простые переменные, могут быть произвольного типа. Определенные особенности имеют место для символьных массивов. В этой связи остановимся на
массивах типа char более детально.
В принципе обращение с символьными массивами может ничем не отличаться от обращения с числовыми массивами. Вот пример инициализации
массива символов путем ввода значения каждого элемента с клавиатуры:
viod main()
{char a[5],z; int i;
for(i=0; i< 5; i++) scanf("%c".&z); a[i]=z;}
Инициализация при описании тоже может быть выполнена обычным образом:
void main()
{ char s[]={'a','b','c'};
printf("%c",s[2]); }
Напомним, что символьная константа — это символ, заключенный в
апострофы. В результате работы этой программы на экран будет выведен
символ ñ.
Теперь обратим внимание на смысл понятия «массив символов». Это ведь
не что иное, как строка в самом житейском понимании. Не правда ли? Такая

34 Глава 1. Язык программирования С
интерпретация символьных массивов породила в С некоторые особые моменты. Начальная инициализация строки при описании, наряду со стандартным
способом, может быть выполнена несколько проще:
void main()
{ int i=0; char S[]="This is string";
while(S[i++]!='\0');
printf("%d",- -i); }
Массив S инициализирован в виде строки, заключенной в кавычки. Определенные таким образом символьные массивы, которые будем называть
строками, несколько отличаются от массивов, определенных стандартным
способом. Во-первых, для них существует целый набор функций, описанных
в заголовочном файле string.h, о которых мы поговорим позже. Во-вторых, в
конец строки всегда добавляется специальный символ '\0' — признак конца
строки. Программисту не нужно заботиться об этом символе, поскольку он
вставляется системой автоматически, но помнить о его существовании в конце массива следует. Его присутствие может оказаться полезным, как это использовано в нашей программе. Цикл while здесь каждый раз проверяет, достигли ли мы конца строки, и увеличивает счетчик i на единицу. После выхода
из цикла счетчик имеет значение, равное количеству символов в строке, с
учетом символа конца строки. Таким образом, в результате работы программы
будет напечатана длина строки — в данном случае 14, число символов, помещенных между кавычками (включая два символа пробела).
Укажем, что числовой эквивалент символа '\0' есть 0. По этой причине
понятно, что условие в цикле можно упростить до вида S[i++]! = 0. Автор
был бы безмерно счастлив, если бы в этот момент внимательный читатель
воскликнул: «А почему не просто while(S[i++])?». Он прав! Так и сделайте.
В упомянутом заголовочном файле string.h имеется довольно много строковых функций. Рассмотрим некоторые из них. Функция gets(Èìÿ) осуществляет ввод строки с консоли. Здесь Èìÿ есть имя символьного массива, в который будет записана вводимая с клавиатуры строка. Сам ввод осуществляется
следующим образом. Набирается строка целиком так, как она должна выглядеть. После набора нажимается клавиша Enter. Простейшая программа, инициализирующая строку вводом с клавиатуры, выглядит так:
#include< string.h >
void main()
{ char S[100]; gets(S); }
Здесь максимальная длина вводимой строки — 100 символов. При попытке ввода более длинной строки никакой ошибки система не обнаружит, но
возникнет уже описанная ситуация с выходом за границы массива с непредсказуемыми последствиями.
Функция puts(Èìÿ) выводит на экран строку, описанную символьным
массивом, названным как Имя. Независимо от объявленной длины массива,

Массивы 35
строка выводится до символа конца строки. Следующая программа производит вывод на экран строки, инициализированной при описании:
#include< string.h >
void main()
{ char SYM[]="QWER'\n'qwer";
puts(SYM); }
В результате работы программы сначала печатается часть строки QWER,
затем печатается символ перевода строки, то есть просто происходит переход
на новую строчку, где печатается остальная часть qwer.
Еще одна полезная функция из заголовочного файла string.h позволит определить длину строки без «самодельных» ухищрений, которые мы предпринимали ранее. Это функция strlen(Èìÿ), возвращающая целое число, равное
длине строки, опять же от начала до признака конца. Теперь вместо составленной ранее программы можно предложить более простую:
void main()
{ char S[]="This is string";
printf("%d",strlen(S)); }
Для операций с матрицами в математике используются переменные со
многими индексами. В С для этой цели используются так называемые многомерные массивы, или массивы массивов. В принципе ограничений на количество индексов нет, но мы ограничимся рассмотрением двухмерных массивов.
Описание int x[2][3]; можно рассматривать как два массива, каждый из которых есть набор трех целых чисел. Нам более привычно рассматривать такую
конструкцию просто как матрицу — набор компонентов с двумя индексами:
xxx
00 01 02
xxx
10 11 12
В памяти компьютера многомерные массивы располагаются последовательно так, что в первую очередь меняется последний индекс, а в последнюю — первый. Таким образом, элементы указанного массива расположены в
памяти последовательно в следующем порядке:
x[0][0], x[0][1], x[0][2], x[1][0], x[1][1], õ[1][2]
Работу с двухмерными массивами проиллюстрируем простой программой,
которая формирует единичную матрицу и выводит ее на экран:
void main()
{ int i.j, Ì[5][5];
for(i=0; i<5; i++)
for(j=0; j<5; j++) if(i==j) M[i][j]=l; else M[i][j]=0;
for(i=0; i<5; i++)
{for(j=0; j<5; j++) printf("%d ",M[i][j]);
printf("\n"); } }

36 Глава 1. Язык программирования С
Как известно, в единичной матрице по главной диагонали стоят единицы,
а все остальные элементы равны нулю. Два первых вложенных друг в друга
цикла for позволяют перебрать все возможные комбинации индексов, а условное выражение во внутреннем цикле выполняет соответствующее присваивание. Можно предложить более элегантное, хотя и менее наглядное выражение
вместо конструкции if — else. Необходимый результат дает присваивание
M[i] [j]=(i==j);. Вторая пара вложенных циклов обеспечивает печать матрицы
на экран именно в виде таблицы из пяти строк и пяти столбцов. Внешний
цикл работает пять раз. Пять раз выполняется составное выражение, в котором, в свою очередь, два выражения. Первое выражение — печать значений
элементов матрицы. Печать выполняется в одну строчку — печатаются пять
чисел, разделенных пробелом. Затем выполняется второе выражение, а это
есть переход на новую строку. Далее снова печатается пять чисел и снова производится перевод на новую строку, где, наконец, печатаются последние числа. Заметим, что в данной программе можно было бы обойтись только одной
парой циклов, совместив инициализацию матрицы с ее печатью. Разделение
на два этапа сделано исключительно для большей наглядности.
Адреса и указатели
Этот материал выделен автором в самостоятельный раздел, несмотря на
его небольшой объем, поскольку является крайне важным для дальнейшего,
теперь уже достаточно профессионального изучения языка С.
Начать следует с напоминания того факта, что все данные, необходимые
для выполнения программы, такие как переменные, константы, массивы и
многое другое, включая и сам код программы, размещаются в оперативной
памяти компьютера. Память представляет собой совокупность большего числа
элементарных ячеек, каждая из которых имеет свой номер или, выражаясь
профессионально, адрес. Такая минимальная адресуемая ячейка называется
байтом. Байт, в свою очередь, представляет собой совокупность восьми двоичных разрядов (битов), в которых записываются 0 или 1. Мы уже говорили о
том, что различные типы данных занимают различное число байтов. Так,
один символ размещается в одном байте, целое типа int занимает уже два байта и так далее. После загрузки исполняемого кода программы в память каждая
переменная обзаводится своим адресом, номером первого из группы занимаемых ею байтов. Адреса принято представлять в шестнадцатеричной системе
счисления, и для операций с адресами в С существует специальный формат ð
(от английского слова pointer — указатель), а для размещения адресов существует специальный тип переменных — указатели. В зависимости от того, адреса каких данных помещаются в указатели, изменяются и сами типы указателей. Могут существовать указатели на символы, на целые типа int, на вещественные типа double и многие другие. Указатели в программе описываются так:
Òèï *Èìÿ. При этом указатели можно объединять с описаниями других данных под общим названием типа. Например, возможно такое описание:
double u=7.65,*a;. Здесь описана и инициализирована вещественная перемен-

Адреса и указатели 37
ная двойной точности u и указатель a на объект типа double, пока не инициализированный, то есть не имеющий значения. Для получения адреса объекта
в С существует специальная операция &, знак которой помещается перед
именем объекта. Таким образом, &u есть адрес переменной u. Понятно, что
инициализация указателя (придание ему конкретного значения) может быть
сделана простым присваиванием. После выполнения выражения à=&u указатель à имеет значение адреса переменной u. Существует операция, обратная
операции получения адреса. Такая операция называется разадресацией и имеет вид *Указатель. Применение такой операции позволяет получить значение,
находящееся по адресу, на который указывает указатель. В нашем примере *à
есть число, находящееся по адресу à. В свою очередь, à есть адрес переменной u, значение которой равно 7.65. В итоге *à — это просто число 7.65. Поскольку & è * — взаимно обратные операции, очевидно, что &*M åñòü Ì è
*&N åñòü N,
что бы ни представляли собой переменные Ì è N.
Может возникнуть вопрос, ответ на который не столь тривиален, как это
может показаться. Вопрос простой: в каких единицах измеряется значение
указателя и какова величина этих единиц. Сказать, что указатели выражаются
в байтах, будет правильно, но мало. Сначала рассмотрим программу:
void main()
{int c=4,*a,*b;
a=&c; b=a+l;
printf("%p %d",a,(int)b-(int)a); }
Здесь описана и инициализирована целая переменная и два указателя на
целые (int) переменные. Указатель à получает значение адреса переменной, а
указатель b — значение на единицу большее. Затем печатаются адрес переменной и разность указателей, каждый из которых приведен к целому числовому
типу. Казалось бы, раз b на единицу больше à, то разность между ними есть 1.
При печати вы, однако, увидели бы не 1, а 2. Если заменить в описаниях int
íà long, то напечаталось бы 4. Дело в том, что здесь печатается разность номеров байтов, начиная с которых размещаются переменные соответствующего
типа, и если переменная à размещена в байтах номер N и номер N+1 (òèï int
занимает два байта), то следующая переменная, указатель на которую увеличен на единицу, будет размещена в байтах N+2 è N+3. Поэтому разность указателей и составляет 2 байта, а для типа long — 4 байта.
Принципиальный момент для языка С: условились считать, что имя массива (без индексов) само уже является указателем на начало этого массива, то
есть если описан массив t[15], òî t åñòü &t[0]. Тогда понятно, что *t есть значение начального элемента массива и вообще *(t+i) åñòü íå ÷òî èíîå, êàê t[i].
Более того, еще до начала компиляции программы препроцессор, о котором
мы вскользь упоминали раньше, производит замену в тексте всех конструкций типа u[i] на конструкции *(u+i). Из этого, в частности, следует, что совершенно чудовищная, на первый взгляд, конструкция 3[ð] будет совершенно
безболезненно пропущена компилятором, и если вы имели в виду ð[3], то все
в программе будет в порядке. Тогда, если вы хотите слегка облегчить работу

38 Глава 1. Язык программирования С
системы и уменьшить время обработки (но не исполнения) программы, пишите всегда *(ð+4) вместо ð[4].
В заключение параграфа приведем еще одну программу:
viod main()
{double m[]={0.6,0.2,1.4,3.3,5.2,7.1,9.2},*p;
for(p=m+2; p<m+5; p++) printf("lf\n" ,*p) ; }
Автор тешит себя надеждой, что читатель уже достаточно опытен для того,
чтобы понять, что эта программа выведет на экран три числа 1.4, 3.3 и 5.2.
Функции
До сих пор в наших программах мы использовали одну единственную «самодельную» функцию с именем main — главная. При необходимости программа может содержать достаточно большое количество функций, среди которых,
однако, присутствие функции main обязательно, поскольку именно с нее начинается выполнение любой программы. Описание и использование «самодельных» функций демонстрирует следующая программа:
#include<stdio.h>
/*Это - первая функция*/
double sqr(double х)
{ return х*х; }
/*Это - вторая функция*/
double cub(double x)
{ return sqr(x)*x; }
/*Это - главная функция*/
void main(void)
{ double у;
for(y=0.; y<10.5; y++)
printf("y=%3.1lf y^2=%3.11f y^3=%3.11f\n", y.sqr(y),cub(y)); }
Начнем с того, что не имеет отношения к описаниям функций, но впервые появилось в нашей программе. Речь идет о так называемых комментариях. Комментарием является все, что стоит между открывающей конструкцией
/* и закрывающей конструкцией */. Присутствие в программе комментариев
никакого влияния ни на что не оказывает. Система вообще не видит комментариев, и они предназначены только для человека, читающего программу.
Зато этому человеку комментарии могут сообщить полезную информацию,
как это и сделано в последней программе. Комментарий в одной строке можно организовать и по-другому: напишите конструкцию //. Все, что находится
после этой конструкции до конца строки, будет комментарием.
Теперь в нашей программе описана функция с именем sqr. Общая форма
описания функции имеет вид:
ТипФункции ИмяФункции(ТипАргумента1 Аргумент1, ТипАргумента2 Аргумент2,...)
{Тело функции};

Функции 39
Если назначение функции заключается в вычислении одного значения, то
есть, как говорят гуру от программирования, если функция возвращает значение, то тип функции должен быть указан, и таким типом является тип возвращаемого значения. В нашей программе функция имеет тип double, стало быть,
функция возвращает вещественное число с двойной точностью. Если тип
функции не указан, то, по умолчанию, она считается целочисленной функцией (типа int). Если функция не возвращает никакого значения, например знакомая нам функция clrscr, то она имеет тип void — никакой. Несмотря на отсутствие типа, слово void в описании должно присутствовать. Во всех наших
программах функции main не возвращали значений и описывались как void.
Функции, возвращающие значение, обязаны содержать в своем теле хотя бы
одно так называемое выражение возврата:
return ВозвращаемоеЗначение;
Слово return как раз и означает ВЕРНУТЬ, и после него следует выражение, вычисляющее возвращаемое значение. В функциях сложной структуры,
имеющих, например, несколько альтернативных путей выполнения, возможно присутствие нескольких выражений return. Нужно помнить, что выполнение любого из выражений возврата приводит к немедленному завершению
выполнения функции, передаче возвращаемого значения «наружу» и переходу
к участку программы, непосредственно следующему за обращением к этой
функции. Нетрудно видеть, что функция sqr возвращает квадрат своего аргумента. В некоторых языках программирования именно под таким именем существует именно такая функция, вычисляющая квадрат своего аргумента, но
не «самодельная», а стандартная. Там она ничем не отличается от других стандартных функций: sin, åxð, sqrt и так далее. В С такой стандартной функции
нет. Так что если вам непременно хочется ее иметь, то придется изготовить ее
своими руками, что в последней программе и сделано.
В скобках после имени функции находится список аргументов, разделенных запятыми. Для каждого аргумента указывается его тип. Даже если в списке есть несколько аргументов одного типа, их нельзя объединять под одним
словом описания типа, как это, например, можно было делать при объявлении переменных. Такая «причуда» имеет своей целью, в частности, уберечь
программиста от злоупотребления большим числом аргументов функций.
В нашем примере список аргументов состоит из одного параметра, но это
только частный случай. Необходимо обратить внимание на одно крайне важное обстоятельство. Принимая свой аргумент, функция делает с него копию и
работает уже с этой копией, а не с самим значением. По этой причине, если
вы в теле функции измените, как вам кажется, значение аргумента, то после
выхода из функции в вызывающую ее часть программы вы с удивлением можете обнаружить, что значение аргумента не изменилось. У такой особенности построения функций в С есть свои преимущества и свои недостатки, которые мы обсудим несколько позднее.
Функция может и не иметь ни одного аргумента. Ну, действительно, за
чем параметры могут понадобиться функции clrscr, если ее назначение —
-

40 Глава 1. Язык программирования С
очистка экрана? Она ведь даже не возвращает значения, не имеет типа. В случае отсутствия у функции аргументов вы можете при описании воспользоваться пустыми скобками (). Для тех, кто любит во всем порядок, как автор
этих строк, посоветуем избавить себя от дискомфорта при взгляде на эти пустые скобки и писать (void). Вот мы и полностью разобрались с непонятной
поначалу строкой из самой первой нашей программы void main(void). Как видите, автор выполняет свои обещания.
Тело функции — это запрограммированный рецепт выполнения некоторых действий. Говорить особо о построении тела функции нет смысла, поскольку до сих пор мы только этим и занимались. Разве что еще раз напомним, что функция, имеющая любой тип, отличный от void, должна возвращать значение с помощью выражения возврата. В функции sqr тело до
крайности простое. Оно даже не содержит ни одного выражения, кроме выражения возврата return x*x. С последним все ясно. Вы, конечно, догадываетесь,
что такая простая ситуация скорее исключение, чем правило.
Уже следующая функция в нашей программе выглядит чуть сложнее. Заметьте, что вторая функция cub находится в программе после функции sqr и
поэтому знает, что функция sqr собой представляет. Аналогично функция
main знает обе функции: и sqr,ècub. Такую естественную последовательность
рекомендуется соблюдать в программе, хотя несложные средства, демонстрируемые далее, позволят вам изменить этот порядок.
Функция cub, как это следует из ее имени, вычисляет куб своего аргумента. Если бы выражение возврата в ней мы записали в виде return x*x*x;
(что было бы не хуже представленного выше варианта), то нам было бы нечего добавить к тому, что говорилось о функции sqr. Однако здесь функция
возведения в куб использует функцию возведения в квадрат, то есть при своей работе функция cub обращается к уже известной ей функции sqr. В свою
очередь, главная функция обращается к описанным ранее функциям и успешно распечатывает таблицу квадратов и кубов чисел от 0 до 10 с шагом 1.
Мы вполне сознаем, что такую таблицу можно было бы изготовить значительно проще. Программу следует рассматривать только как учебный пример
и не более того.
В приведенном примере внутри «самодельных» функций sqr è cub, в отличие от функции main, нам не пришлось описывать никаких дополнительных
переменных. Однако и это скорее исключение, чем правило. Следующая программа демонстрирует более реалистичную ситуацию:
#include<stdio.h>
void main(void)
{ int i; double x,STEP();
printf("x="); scanf("%lf'.&x);
printf("ï="); scanf("%d",&i);
printf ("%lf %d=%lf",x,i.STEP(x,i)); }
double STEP(double x, int n)
{ int i; double s=1.0;
for(i=l; I<=n; i++) s*=x;
return s; }
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
