Министерство образования и науки Российской Федерации 3 страница
/*prog11.c*/
#include<stdio.h>
main()
{
char x = ‘A’, y = 97;
printf (“Численное значение переменной х равно %d, в ней хранится символ %c \n Численное значение переменной у равно %d, в ней хранится символ %с \n”, x, x, y, y);
return 0;
}
Откомпилировав и выполнив программу мы увидим, что буква ‘А’ имеет ASCII-код 65, а ASCII-коду 97 соответствует строчная буква ‘а’.
Таким образом, мы можем присваивать целочисленные значения переменным типов int и char, выполнять с ними обычные арифметические действия, представлять в десятичной системе счисления. Однако переменные этих типов занимают разный объем памяти. Операция
sizeof (имя_переменной)
определяет количество байтов, которые операционная система выделила указанной переменной.
/*prog12.c*/
#include<stdio.h>
main()
{
int x = 97;
char y = 97;
printf (“Переменной х типа int присвоено значение %d, она занимает %d байт Переменной у типа char присвоено значение %d, она занимает %d байт ”, x, sizeof(x), y, sizeof(y));
return 0;
}
При вызове функции printf сначала вычисляются значения всех её аргументов - x, sizeof(x), y, sizeof(y) – а затем операционная система выводит их на экран.
Строка в языке С представляет собой массив, образованный следующими друг за другом переменными типа char. Они нумеруются целыми числами (индексами массива), начиная с нуля. Спецификация формата ввода-вывода строки - %s. Строка не обязательно должна занимать весь массив – ее конец отмечает элемент с нулевым значением.
/*prog13.c*/
#include<stdio.h>
main()
{
char str[20] = “I am a student”;
str[6] = 0;
printf( “%s \n”, str );
return 0;
}
Прежде всего, обратим внимание на объявление массива str. Сначала идёт имя типа элемента массива – char, затем имя массива – str и модификатор типа – квадратные скобки. В языке С квадратные скобки интерпретируются как операция доступа к элементу массива: str[6] = 0 – элементу массива str с индексом 6 присвоить значение 0. Объявление массива следует понимать так: выполнив с переменной str операцию доступа к элементу массива, мы получим значение типа char. Число в квадратных скобках при объявлении массива равно количеству его элементов; так как индекс начального элемента 0, то индекс последнего элемента массива равен 19. В строке “I am a student” 11 букв и три пробела – всего 14 символов, да еще символ с нулевым значением, завершающий строку – получается, что длина строки составляет 15 символов, последний байт массива не используется. В теле функции элементу с индексом 6 присваивается значение 0, следовательно, строка сокращается. Откомпилировав и выполнив программу prog5.c, мы увидим, что она выводит на экран лишь 6 символов строки: “I am a”.
Есть еще один способов объявления массива. На самом деле в переменной str хранится адрес начального элемента массива, т.е. str – это ссылка на переменную str[0].
Значение адреса можно получить с помощью операции разадресации, она обозначается символом *.
/*prog14.c*/
#include<stdio.h>
main()
{
char *str = “I am a student”;
str[7]=‘S’;
printf(“%s \n”, str);
return 0;
}
После выполнения программы на экране появляется сообщение “I am a Student”. Таким образом, объявления
char str[20];
char *str;
можно считать равноправными. На самом деле это не так – в первом случае мы точно знаем, что нам выделено место в памяти для размещения двенадцати элементов, во втором дело обстоит гораздо сложнее.
В программах prog9.c и prog10.c мы присваивали строке значение при помощи инициализации, а затем изменяли ее отдельные символы. Использовать операцию = для изменения самой строки в теле функции нельзя, это делает специальная библиотечная функция. Зато при вводе строки не надо выполнять операцию ссылки:
scanf(“%s”, str);
/*prog15.c*/
/* Программа выводит на экран строку (без пробелов), которую ввёл пользователь */
#include <stdio.h>
#define N 1000
main()
{
char str[ N ];
scanf ( “%s”, str);
printf(“%s \n”, str);
return 0;
}
Переменная str в программе prog15 является буфером – областью памяти для временного хранения введённой информации. Обычно объём требуемой памяти для буфера известен лишь приблизительно, поэтому его выделяют с некоторым запасом. Числовые значения, которые используются в программе, следует задавать с помощью директивы препроцессору define, в этом случае уменьшается вероятность ошибок в случае их изменения.
/*prog16.c*/
/* Программа подсчитывает количество символов в строке (без пробелов), которую ввёл пользователь */
#include<stdio.h>
#define N 1000
main()
{
unsigned int i = 0, count = 0; /* переменная i будет индексом массива символов (строки), а переменная count представляет собой счётчик, в ней будет накапливаться количество символов в строке, введённой пользователем */
char c, str[ N ];
scanf ( “%s”, str);
c = str[ 0 ];
while ( c != 0 )
count++;
printf(“%u \n”, count );
return 0;
}
Задания
Составьте программы, для решения следующих задач:
<9.1> на вход поступают строка (без пробелов) и символ, программа подсчитывет, сколько раз символ встречается в строке;
<9.2> на вход поступают строка (без пробелов) и два символа, программа определяет, какой из них встречается в строке чаще;
ПЕРЕДАЧА ПАРАМЕТРОВ В КОМАНДНОЙ СТРОКЕ
Вызывая компилятор, мы передавали ему имя файла с исходным текстом программы в виде аргумента командной строки. Точно так же мы можем передавать строки программам, написанным на языке С. Рассмотрим пример:
/*prog17.c*/
#include<stdio.h>
main(int argc, char* argv[] )
{
printf(“Количество слов в командной строке равно %d \n”, argc);
if ( argc > 1)
printf( “Первый аргумент программы - %s \n”, argv[ 1 ] );
else
printf ( “Вы запустили программу %s без аргументов \n”, argv[0] );
return 0;
}
Теперь функция main вызывается с двумя аргументами, их передает программе операционная система. Традиционно они называются argc и argv. Аргумент argc относится к типу int, это – целое число. Значением аргумента argc служит количество слов в командной строке при вызове программы, не считая приглашения командной строки. Слова отделяются друг от друга пробелами и знаками табуляции (Tab), а не запятыми, точками или другими знаками препинания. Первое слово – имя исполняемого файла, следовательно, значение argc всегда больше нуля. Команда
printf(“Количество слов в командной строке равно %d \n”, argc);
выводит на экран значение аргумента argc. Если программа запущена так:
prog6.c aa bb cc
то значение argc должно быть равно 4.
Аргумент argv объявлен при помощи ключевого слова char и двух модификаторов типа - * и []:
сhar* argv[];
Символ * можно отделять от слов char, argv – пробелами или писать слитно с тем или другим словом. Напомним, что модификаторы типа интерпретируются как операции, но в отличие от бинарных операций (сложение, умножение), у которых два операнда, эти операции являются унарными – у них только один аргумент. В языке С операции доступа к элементу массива ([]) и разадресации (*) выполняются справа налево. Таким образом, если мы осуществляем доступ к элементу массива argv, то получим значение типа char* - строку символов. Отсюда следует, что argv – массив строк, т.е. каждый элемент массива представляет собой цепочку символов, которая завершается нулевым значением. Количество элементов массива не указывается – его определяет операционная система. Значениями элементов массива argv служат слова командной строки: argv[0] - имя исполняемого файла, argv [1] – первый аргумент и т.д., к каждому слову операционная система добавляет завершающий нуль. Команда
printf( “Первый аргумент программы - %s \n”, argv[1] );
выводит на экран первый аргумент программы - argv[1].
Принцип надежности работы программы требует, чтобы при запуске программы без аргументов предыдущая команда не выполнялась. Для этого мы используем управляющую конструкцию с ключевым словом if – если ( ветвление). Программа проверяет истинность условия, в нашем случае – верно ли то, что argс > 1. Если условие истинно, то выполняется следующая за ней команда, если нет, и присутствует ключевое слово else – то выполняется команда, стоящей после этого слова. Затем управление передается команде, следующей за ветвлением. Команда
printf(“Вы запустили программу %s
без аргументов \n”, argv[0]);
выводит сообщение с указанием имени исполняемого файла.
Задание.
Составьте программу, которая выводит два первых аргумента, переданных ей при запуске слитно, раздельно и в столбик (один под другим). Не забудьте корректно обработать случай запуска программы с меньшим количеством аргументов.
МАТРИЦЫ
Матрицей называется квадратная или прямоугольная числовая таблица. Элементы матрицы объединяются в горизонтальные строки и вертикальные столбцы; в языке С строки и столбцы матрицы нумеруются целыми числами 0, 1, 2,.. Порядок матрицы представляют в виде m * n, где m – количество строк, n – количество столбцов. Элемент матрицы а, стоящий на пересечении i-той строки и j-того столбца обозначается символом a [ i ] [ j ]; если а – матрица порядка m * n, то i = 0, 1,.., m - 1, j = 0, 1,…, n - 1. Значения i и j называются индексами элемента a [ i ] [ j ].
Рассмотрим следующую программу:
/* matrix1
программа выделяет область памяти для матрицы а порядка 1000*1000; после запуска программы пользователь вводит значение элемента a [ 0 ] [ 0 ], которое затем выводится на экран*/
# include<stdio.h>
# define M 1000
# define N 1000
main()
{
int a [ M ] [ N ]; //объявляет матрицу а
int i = 0, j = 0;
scanf ( “%d”,&( a [ i ] [ j ] )); //читает значение элемента а [ i ] [ j ], i = j = 0
printf ( “ %d\n”, a [ i ] [ j ] ); // выводит на экран значение элемента а [ i ] [ j ], i = j = 0
return 0;
}
Количество строк и столбцов матрицы а определены при помощи директивы препроцессора # define; нулевые значения индексам i, j присвоены в ходе их объявления ( напомним, что этот механизм называется инициализацией переменных).
Задание. Откомпилируйте и выполните программу matrix1.
Большие программы очень трудно понять, поэтому в них всегда присутствуют комментарии, содержащие необходимые разъяснения. Мы уже отмечали, что комментарии располагаются между специальными символами /* и */; если комментарий занимает часть строки, то перед ним ставят символ //, конец комментария в этом случае совпадает с концом строки.
Как правило, программу составляют так, чтобы она обрабатывала матрицы разных порядков. В этом случае количество строк и столбцов вводит пользователь. Выполнять однотипные операции с элементами строки и столбца удобнее всего с помощью оператора цикла for.
/* matrix2
После запуска программы пользователь вводит количество строк и столбцов матрицы, а затем ее элементы; программа выводит на экран транспонированную матрицу (напомним, что операция транспонирования матрицы заменяет её строки столбцами)*/
// Директивы препроцессора
# include<stdio.h>
# define M 1000
# define N 1000
main()
{
// объявление переменных
int a [ M ] [ N ];
int m, n, i, j;
// программа читает количество строк и столбцов матрицы а
scanf ( “ %d %d”, &m, &n);
//читает значения элементов матрицы а
for (i = 0; i < m; i++)
for ( j = 0; j < n; j++)
scanf ( “ %d”, &( a [ i ] [ j ] ) );
// выводит элементы матрицы на экран
for ( j = 0; i < n; i++)
for ( i = 0; j < m; i++)
printf(“ %d ”, a [ i ] [ j ] );
return 0;
}
Как и в программе matrix1, программа выделяет область памяти, в которой можно разместить матрицу порядка 1000*1000. предполагается, что значения m и n, которые введет пользователь, меньше, чем 1000. Таким образом, часть выделенной памяти не используется. С помощью более сложных механизмов языка С можно каждый раз выделять такую область памяти, в которой можно разместить матрицу порядка m*n.
Оператор цикла for ( i = 0; i < m; i++) сначала присваивает переменной i значение 0, затем проверяет условие i < m. Если неравенство справедливо, выполняется команда, непосредственно следующую за конструкцией for. После этого оператор цикла увеличивает значение i на 1 (выполняет операцию i++, которая называется инкрементом), снова проверяет условие i < m и т.д. Как только условие i < m перестанет быть справедливым, выполнение команды, непосредственно следующей за конструкцией for, прекращается и управление передается команде, стоящей далее.
НЕ НАДО СТАВИТЬ ТОЧКУ С ЗАПЯТОЙ ПОСЛЕ КОНСТРУКЦИИ for !!!
Команда, непосредственно следующая за оператором for, также является циклом. С ее помощью программа повторяет команды – читает элементы строки матрицы а; когда строка прочитана, то внешний оператор цикла for ( i = 0; i < m; i++) осуществляет переход к чтению следующей строки. Когда оба цикла завершатся, будут прочитаны все строки исходной матрицы. Точно также организован вывод элементов матрицы на экран.
Отметим, что команда, следующая за оператором цикла, образует с ним единый блок, поэтому ее набирают с отступом на 2-4 интервала.
Задание. Откомпилируйте и выполните программу matrix2
Выполнив программу, мы убедимся, что результат представлен на экране в виде одной числовой строки. Для того, чтобы на экран выводилась матрица в прямоугольном виде, необходимо вывод каждой строки завершать символом переноса ‘\n’
for ( j = 0; j < m; j++)
{
for ( i = 0; i < n; i++)
printf ( “%d ”, a [ i ] [ j ] );
printf ( “ \n ”);
}
(после %d в команде printf обязательно оставьте пробел, иначе числа будут выводиться без разделителей).
Задание. Дополните программу и выполните ее.
Как говорилось выше, оператор цикла for управляет выполнением непосредственно следующей за ним команды. В нашем случае он должен обеспечить выполнение двух команд: вывод элементов строки матрицы и переход на новую строку. Для этого обе команды объединяются в одну при помощи фигурных скобок {…}.
Язык С предназначен в первую очередь для разработки программ, которые выполняются на компьютерах, обладающих очень высокой производительностью. Здесь машинное время очень дорого, поэтому ввод данных осуществляется не с клавиатуры, а из файла. Составьте текстовый файл mydata.txt, содержащий следующие данные
3 4
1 2 3 4
4 3 1 2
17 18 19 20
Первые два числа задают количество строк и столбцов матрицы, а далее следует сама матрица. Откомпилируйте программу matrix2; пусть исполняемый файл называется matrix2.exe. Запустите программу, набрав в командной строке
matrix2.exe < mydata.txt
Символ < указывает операционной системе, что стандартный канал ввода исполняемого файла matrix2.exe следует соединить с файлом mydata.txt
Задание. Выполните программу так, чтобы исходные данные были прочитаны из текстового файла.
РАЗРАЬРТКА ПРОГРАММЫ
Существует целый ряд способов разработки больших программ. один из них предлагает создавать программу в виде последовательности макетов, каждый из которых реализует какую-то часть функций программы, так, что завершающий макет представляет собой готовую программу.
Пусть наша задача формулируется следующим образом: после запуска программы пользователь вводит матрицу порядка m * n; программа изменяет порядок элементов каждого столбца на противоположный.
Программы matrix1, matrix2 могут служить макетами. Следующий макет – программа matrix3
/* matrix3
после запуска программы пользователь вводит количество строк и столбцов матрицы, а затем ее элементы;
программа меняет местами первый и последний элементы каждого столбца и результат выводит на экран*/
// Директивы препроцессора
# include<stdio.h>
# define M 1000
# define N 1000
main()
{
// объявление переменных
int a [ M ] [ N ];
int m, n, i, j;
int buf;
//выделяется буфер для временного хранения промежуточных результатов
scanf(“%d % d”,&m, &n);
//программа читает количество строк и столбцов матрицы,
// программа читает элементы матрицы
for ( i = 0; i < m; i++ )
for ( j = 0; j < n; j++ )
scanf ( “%d”,& ( a [ i ] [ j ] ));
// программа меняет местами первый и последний элемент каждого столбца
for ( j = 0; j < n; j++ )
{ buf = a [ 0 ] [ j ];
// значение первого элемента столбца помещаем в буфер buf
//значения первого и последнего элемента столбца j меняются местами
a [ 0 ] [ j ] = a [ m ] [ j ];
a [ m ] [ j ] = buf;
}
// программа выводит матрицу-результат на экран
for ( i = 0; i < m; i++ )
for ( j = 0; j < n; j++)
printf ( “%d ”, a [ i ] [ j ] );
return 0;
}
Задание. Откомпилируйте и выполните программу matrix3, проверьте правильность результата и внесите в её текст необходимые изменения.
Теперь мы можем составить завершающую программу-макет. Она меняет местами элементы a [ i ] [ j ] и a [ к ] [ j ], сначала i равно 0, а к равно n - 1, затем каждый раз значение i увеличивается на 1, а значение к уменьшается на 1 до тех пор, пока I будет меньше к.
НЕ ЗАБУДЬТЕ ОБЪЯВИТЬ НОВУЮ ПЕРЕМЕННУЮ к
…………………………………………………………
for ( j = 0; j < n; j++ )
for ( i = 0; к = n - 1; i < к; i++, к--)
{ buf = a [ i ] [ j ];
a [ i ] [ j ] = a [ к ] [ j ];
a [ к ] [ j ] = buf;
}
Напомним, что операции, которые отделяются друг от друга запятой, в языке С образуют единое целое; таким образом, в начале цикла for переменной i присваивается значение 0, а переменной к - значение n - 1. После каждого прохода цикла значение переменной i увеличивается на 1, а значение переменной к уменьшается на 1 (выполняется операция к--, которая называется декрементом).
ОПТИМИЗАЦИЯ И ДОКУМЕНТИРОВАНИЕ ПРОГРАММЫ
Пример. Натуральное число называется совершенным, если оно равно сумме всех своих делителей, включая 1 и исключая само число. Составьте программу для вычисления совершенных чисел.
Постановка задачи. На вход поступают два натуральных числа, a и b, программа находит все совершенные числа, заключённые между a и b.
В качестве имени файлов будем использовать аббревиатуру (сокращение слов) perfnum: по-английски совершенное число - perfect number.
Составление теста программы. Перебирая натуральные числа, замечаем, что число 6 является совершенным: 6 = 1 + 2+ 3. Файл, содержащий исходные данные:
a=2 b=10
назовём perfnum.txt
Результатом выполнения программы должно стать число 6.
Макеты программы
Макет № 1
/*perfnum1.c*/
/*программа читает данные, содержащиеся в тестовом файле и выводит их на экран*/
#include <stdio.h>
main()
{
int a,b;
scanf (" a=%d b=%d", &a, &b);
printf (" a=%d b=%d\n");
return 0;
}
Программа читает из входного потока содержимое файла. Команда запуска откомпилированной программы:
perfnum.txt > perfnum.exe
Макет № 2
/*perfnum2.c*/
/*программа читает числа a,b, содержащиеся в тестовом файле и выводит на экран все делители чисел a и b*/
#include <stdio.h>
main()
{
int a,b;
int j;
scanf (" a=%d b=%d", &a, &b);
printf ("divisors of a: \n")
for ( j = 1; j < a; j++)
if ( a % j == 0 )
printf (" %d ", j);
printf (" /n divisors of b: \n")
for ( j = 1; j < b; j++)
if ( b % j == 0)
printf ("%d ", j);
return 0;
}
Программа вычисляет остатки от деления числа a на 1, 2,..., a-1. Если остаток от деления a на j равен нулю, то число j является делителем a, оно выводится на экран. Аналогично вычисляются делители числа b.
Макет № 3
/*perfnum3.c*/
/*программа читает числа a,b, содержащиеся в тестовом файле и выводит на экран все совершенные числа, заключённые между a и b*/
#include <stdio.h>
main()
{
int a, b;
int i, j, s;
scanf ( " a=%d b=%d", &a, &b);
for ( i = a; i <= b; i++)
{ // перебираем числа, заключённые между a и b
s = 0; //инициализация сумматора s
for ( j = 1; j < i; j++)
if ( i % j == 0) s+=j;
/* После завершения цикла значение переменной s равно сумме всех делителей числа i */
if ( i == s)
printf ("%d ", i);
}
return 0;
}
Эта программа находит все совершенные числа, заключённые между a и b. Однако она обладает существенным недостатком. Программа, которая выполняется на компьютере, занимает его ресурсы - процессор, оперативную память, каналы ввода-вывода, файлы, внешние устройства и т.д. Основная адача разработчика - рациональное использование ресурсов. Обычно затраты ресурсов оцениваются относительно некоторого характерного параметра программы. Будем считать, что a=1, число b будет характерным параметром, обозначим его символом n. Для того, чтобы найти все совершенные числа, не превосходящие n, программа выполняет 1+2+...+n-1 проходов цикла: она перебирает числа от 1 до n и у k-го числа проверяет k-1 чисел, выбирая из них делители k;1+2+...+n-1 = n * ( n – 1) / 2, следовательно, программа выполняет порядка n * n операций.
Решая задачу, человек обычно автоматически старается делать как можно меньше усилий. Поэтому разработчики часто 'проигрывают' программу - один из них имитирует решение задачи, а другой тщательно фиксирует все его действия.
Проверим, является ли число 255 совершенным. Число 1 является делителем любого числа, поэтому сразу можно поместить в переменную s значение 1; 255 - нечётное число, следовательно, оно не имеет чётных делителей, так что будем искать делители только среди нечётных чисел. 255 делится на 3, значит оно имеет ещё один делитель – 255 / 3 = 85; s = 1+ 3 + 85 + ... Cледующая пара делителей - 5 и 51: s = 1 + 3 + 85 + 5 + 51 +... Точно так же находим ещё два делителя - 15 и 17. Следующий делитель 17 у нас уже есть, перебор прекращается, s = 1 + 3 + 85 + 5 + 51 + 15 + 17 = 177 < 255, число 255 не является совершенным.
Теперь проверим, является ли совершенным число 16: это число чётное, оно делится на 2 и 4, 16 / 2 = 8, 16 / 4 = 4, два делителя совпадают, поиск делителей прекращается.
Обобщая наши исследования, можно сказать, что в том случае, когда число p является делителем числа q, то q делится без остатка на q / p, один из этих делителей не превосходит корня квадратного из q, а другой не меньше этого значения. Следующий макет программы выглядит так:
Макет № 4
/*perfnum4.c*/
/*программа читает числа a,b, содержащиеся в тестовом файле и выводит на экран все совершенные числа, заключённые между a и b*/
#include <math.h> /*заголовочный файл math.h содержит описания математических функций */
#include <stdio.h> /*файл содержит описания средств ввода-вывода*/
main()
{
int a, b;
int i, j, k,s;
int p, q;
float m;
scanf ( " a = %d b = %d", &a, &b);
if ( b < 6 )
{
printf ("NO PERFECT NUMBERS\n");
return 0;
}
if ( a % 2 ==0 ) // Если a чётное число
{ p = a; q = a+1; }
else
{ q = a; p = a +1; }
for ( i = p; i <= b; i = i+2 )
{ // перебираем чётные числа, заключённые между a и b
s = 1; //инициализация сумматора s
m = sqrt ( i ) + 1; // будем искать те делители числа i, которые не превосходят корня квадратного из i
for ( j = 2; j <= m ; j++)
if ( i % j == 0)
{
s+= j;
if ( (k = i / j) < j) s+= k;
}
/* После завершения цикла значение переменной s равно сумме всех делителей числа i */
if ( i == s)
printf ("%d ", i);
}
for ( i = q; i <= b; i = i + 2 )
{ // перебираем нечётные числа, заключённые между a и b
s = 1; //инициализация сумматора s
m = sqrt ( i ) + 1; // будем искать те делители числа i, которые не превосходят корня квадратного из i
for ( j = 3; j <= m; j = j + 2) /*ищем только нечётные делители*/
if ( i % j == 0)
{
s+= j;
if ( (k = i / j) < j) s+=k;
}
/* После завершения цикла значение переменной s равно сумме всех делителей числа i */
if ( i == s)
printf ("%d ",i);
}
return 0;
}
Вычисление корня квадратного требует значительного времени, поэтому мы введём две целых переменных - m и sqm. Когда начинается выполнение программы, их значения равны, оответственно, (int)sqrt (a) + 1 и m * m. Как только i становится больше sqm, программа прибавляет к sqm 2 * m + 1 и увеличивает m на единицу. Таким образом, m равно наименьшему натуральному числу, квадрат которого больше i.
Макет № 5
/*perfnum5.c*/
/*программа читает числа a,b, содержащиеся в тестовом файле и выводит на экран все совершенные числа, заключённые между a и b*/
#include <math.h> /*файл содержит описания математических функций */
#include <stdio.h> /*файл содержит описания средств ввода-вывода*/
main()
{
unsigned int a,b;
unsigned int i, j, k,s;
unsigned int p,q;
unsigned int m, sqm;
scanf ( " a = %d b = %d", &a, &b);
if ( b < 6 )
{
printf ("NO PERFECT NUMBERS\n");
return 0;
}
if ( a % 2 ==0 ) // Если a чётное число
{ p=a; q=a+1; }
else
{ q=a; p=a+1; }
m = (int) sqrt (p) + 1; sqm = m * m;
for (i = p; i <= b; i = i+2 )
{ // перебираем чётные числа, заключённые между a и b
s = 1; //инициализация сумматора s
for ( j = 2; j <= m ; j++)
if ( i % j == 0 )
{
s+= j;
if ( ( k = i / j) < j ) s+= k;
}
/* После завершения цикла значение переменной s равно сумме всех делителей числа i */