/*prog14.c
на вход программы поступают вещественные числа, программа вычисляет их сумму*/
#include <stdio.h>
main()
{
float x, s = 0; /* переменная s является сумматором, в ней будет накапливаться сумма чисел, которые вводит пользователь; в ячейке x хранится очередное число, прочитанное программой */
int flag; /* в переменную flag помещается значение, которое возвращает функция scanf */
flag = scanf ("%f", &x);
/*программа читает данные из файла, содержимое которого направляется в её входной поток; когда прочитан весь файл, функция scanf возвращает значение EOF, оно определяется в файле stdio.h*/
while (flag!= EOF)
{
s = s + x;
flag = scanf ("%f", &x);
}
printf (“ сумма введённых чисел равна %f\n”, s);
return 0;
}
Составим текстовый файл mydata.txt, содержащий следующие вещественные числа:
121.45 12.78 151.11 777.76
Откомпилируем программу prog14.c и запустим её, набрав в командной строке:
prog14.exe < mydata.txt
Символ < указывает операционной системе, что стандартный канал ввода исполняемого файла prog14.exe следует соединить с файлом mydata.txt. Программа выводит на экран сумму чисел, содержащихся в файле mydata.txt.
Составьте программы для решения следующих задач:
(8.1) на вход поступают два целых числа, программа определяет, равны они или нет;
(8.2) на вход поступают два разных вещественных числа, программа выводит на экран снечала большее число, а под ним меньшее;
(8.3) на вход поступают три вещественных числа, программа определяет, есть ли среди них положительные числа;
(8.4) на вход поступают три вещественных числа, программа определяет, есть ли среди них отрицательные числа;
(8.5) на вход поступают три целых числа, программа определяет, есть ли среди них совпадающие числа;
(8.6) на вход поступают три вещественных числа, программа определяет, могут ли они быть сторонами треугольника;
(8.7) на вход поступают три вещественных числа, программа определяет, могут ли они быть сторонами треугольника;
(8.8) на вход поступает целое положительное число n, программа вычисляет произведение чисел 1, 2,..., n (значение n! - n факториал);
(8.9) на вход поступают вещественные числа, программа вычисляет их среднее арифметическое;
(8.10) на вход поступают ненулевые целые числа, программа подсчитывет, сколко среди них положительных чисел и сколько отрицательных чисел;
(8.11) на вход поступают вещественные числа, программа определяет, упорядочены они по возрастанию или нет;
(8.12) на вход поступают вещественные числа, программа определяет, упорядочены они по убыванию или нет;
9. СИМВОЛЫ И СТРОКИ
Символьные константы в языке С относятся к типу char (character - буква). Их можно представлять как в виде числовых значений ASCII-кода, так и в виде соответствующих символов, в исходном тексте программы эти символы следует заключать в апострофы (одинарные кавычки). Для вывода символьной константы в виде символа используют спецификацию формата %с.
/*prog15.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 (имя_переменной)
определяет количество байтов, которые операционная система выделила указанной переменной.
/*prog16.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. Строка не обязательно должна занимать весь массив – ее конец отмечает элемент с нулевым значением.
/*prog17.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, следовательно, строка сокращается. Откомпилировав и выполнив программу prog16.c, мы увидим, что она выводит на экран лишь 6 символов строки: “I am a”.
Есть еще один способов объявления массива. На самом деле в переменной str хранится адрес начального элемента массива, т.е. str – это ссылка на переменную str[0].
Значение адреса можно получить с помощью операции разадресации, она обозначается символом *.
/*prog18.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;
можно считать равноправными. На самом деле это не так – в первом случае мы точно знаем, что нам выделено место в памяти для размещения двенадцати элементов, во втором дело обстоит гораздо сложнее.
В программах prog17.c и prog18.c мы присваивали строке значение при помощи инициализации, а затем изменяли ее отдельные символы. Использовать операцию = для изменения самой строки в теле функции нельзя, это делает специальная библиотечная функция. Зато при вводе строки не надо выполнять операцию ссылки:
scanf(“%s”, str);
/*prog19.c*/
/* Программа выводит на экран строку (без пробелов), которую ввёл пользователь */
#include <stdio.h>
#define N 1000
main()
{
char str[ N ];
scanf (“%s”, str);
printf(“%s \n”, str);
return 0;
}
Переменная str в программе prog19 является буфером – областью памяти для временного хранения введённой информации. Обычно объём требуемой памяти для буфера известен лишь приблизительно, поэтому его выделяют с некоторым запасом. Числовые значения, которые используются в программе, следует задавать с помощью директивы препроцессору define, в этом случае уменьшается вероятность ошибок в случае их изменения.
/*prog20.c*/
/* Программа подсчитывает количество символов в строке (без пробелов), которую ввёл пользователь */
#include<stdio.h>
#define N 1000
main()
{
char c, str[ N ]; /* в массиве str будет храниться строка, которую введёт пользователь, прочитанный символ строки программа будет помещать в ячейку c */
unsigned int i; /* переменная i будет индексом символа строки */
scanf (“%s”, str);
i = 0;
c = str[ i ]; /*начальный символ строки помещается в ячейку c*/
while (c!= 0)
{
i++;
c = str [ i ];
}
/*после выхода из цикла значение переменной i будет равно индексу последнего элемента строки, значение этого элемента равно нулю; индекс начального элемента строки равен нулю, следовательно, значение переменной i после завершения цикла будет совпадать с количествомтех символов, которые ввёл пользователь*/
printf(“%u \n”, i);
return 0;
}
Задания
Составьте программы, для решения следующих задач:
<9.1> составьте программу, подсчитывающую количество символов в строке, которую ввёл пользователь (аналогичную программе prog20.c), причём вместо оператора цикла while используйте оператор цикла for;
<9.2> на вход поступают строка (без пробелов) программа выводит символы строки в обратном порядке;
<9.3> на вход поступают строка (без пробелов), программа выводит символы строки ‘в столбик’;
<9.4> на вход поступают строка (без пробелов), программа выводит символы строки, разделённые пробелами, например:
пользователь вводит: aabbc,
на экране появляется: a a b b c;
<9.5> на вход поступают строка (без пробелов) и символ, программа выводит на экран символы строки ‘лесенкой’, например:
пользователь вводит: aabbc,
на экране появляется:
a
a
b
b
c;
<9.6> на вход поступают строка (без пробелов) и символ, программа подсчитывет, сколько раз символ встречается в строке;
<9.7> на вход поступают строка (без пробелов) и два символа, программа определяет, какой из них встречается в строке чаще;
<9.8> на вход поступает последовательность слов, содержащихся в текством файле, программа подсчитывет, количество слов;
10 ПЕРЕДАЧА ПАРАМЕТРОВ В КОМАНДНОЙ СТРОКЕ
Вызывая компилятор, мы передавали ему имя файла с исходным текстом программы в виде аргумента командной строки. Точно так же мы можем передавать строки программам, написанным на языке С. Рассмотрим пример:
/*prog20.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 операций.
Решая задачу, человек обычно автоматически старается делать как можно меньше усилий. Поэтому разработчики часто 'проигрывают' программу - один из них имитирует решение задачи, а другой тщательно фиксирует все его действия.