Программная инженерия. 1 курс 1 семестр / Лекции / L-03.YazikSi.Nachalniesvedeniya
.pdfСтруктура программы на языке Си
Теперь, когда мы закончили разбор программы «Hello, world», самое время попробовать её запустить.
Итак, включаем компьютер, входим в систему, запускаем текстовый редактор Vim (или Nano, или MC Edit); учтите, что ваш файл должен иметь суффикс «.c», поскольку вы собираетесь в нём набрать программу на Си.
Например, имя hello.c для вашего файла вполне подойдёт.
Набираем ровно такой текст программы, как показано на предыдущих слайдах, и сохраняем его.
Теперь нам нужно запустить компилятор, который в данном случае называется gcc.
Компилятор gcc поддерживает множество разнообразных флажков командной строки, из которых нам потребуются по меньшей мере два:
-Wall, который включает все разумные предупреждения,
и -g, который заставляет компилятор сгенерировать
отладочную информацию.
Эти флажки мы при запуске gcc указываем всегда, то есть вообще всегда, следует выработать привычку к этому на уровне, как говорят, спинного мозга: отсутствие любого из этих флажков может очень дорого обойтись.
Не указывать эти флаги можно разве что тогда, когда компилятор запускает не программист, а конечный пользователь, чтобы из присланных ему исходных текстов получить исполняемую программу.
В этом случае предполагается, что программа уже полностью готова и отлажена, а даже если она и содержит ошибки, то конечный пользователь всё равно не станет их исправлять. Но это ситуация не наша: мы пользуемся компилятором как инструментом программиста.
Ещё один полезный флаг, -o, позволяет задать имя файла, в который будет записан результат компиляции.
Если этот флажок не указать, итоговый исполняемый файл будет иметь имя a.out, что не всегда удобно.
Как мы помним, обычно исполняемые файлы в ОС Unix не имеют расширения(суффикса).
Именно так мы поступим и сейчас — отправим результат компиляции в файл hello:
…$ gcc -Wall -g -o hello hello.c
Полностью эта команда означает следующее:
«Возьми исходный файл hello.c, откомпилируй его, выдавая при этом все разумные предупреждения, добавь в полученный модуль отладочную информацию, потом вызови редактор связей, «скормив» ему наш модуль и стандартную библиотеку языка Си (!), а результат пусть он запишет в файл hello».
Промежуточный объектный файл нам в этот раз не нужен, поскольку наша программа состоит из одного модуля; компилятор поместит объектный код во
временный файл, который по завершении работы сотрёт.
11
2. Директивы препроцессора
Если всё сделать правильно, компилятор отработает полностью молча, не выдав ни слова, а в текущей директории появится файл hello, который можно будет запустить:
avst@host:~/work$ ./hello
Hello, world avst@host:~/work$
Директивы препроцессора
В языке Си присутствует концепция макропроцессора
и, более того, мы с самого начала используем его во всех наших примерах программ, хотя и не обращаем на это особого внимания.
Дело в том, что хорошо знакомая нам директива #include как раз является частью макропроцессора.
Общая идея макропроцессирования состоит в том, что текст (в данном случае текст программы) в какой-то момент подаётся на вход специальной программе или подсистеме компилятора, которая называется
макропроцессором.
Текст, подаваемый на вход макропроцессору, как правило, содержит определённые указания по его преобразованию, выраженные в виде макродиректив и макровызовов.
Результатом работы макропроцессора становится опять-таки текст, но уже не содержащий макродиректив и не требующий дальнейшего преобразования.
Препроцессором называется программа, которая вызывается компилятором и обрабатывает исходную программу перед ее компиляцией.
Препроцессор различает специальные команды – директивы.
Препроцессор обеспечивает подключение текстов исходных модулей, формирование макроопределений, планирование условной компиляции.
Препроцессорные директивы начинаются с символа #,
за которым следует наименование директивы, указывающее текущую операцию препроцессора.
#include < ID_файла.h>
#include <stdio.h> – вставляет в программу текст из файла stdio.h, который находится в стандартных каталогах (указанных компилятору, т.е. заданных в среде разработки).
Примеры файлов *.h:
stdio.h – содержит стандартные функции файлового
ввода-вывода;
conio.h – функции для работы с консолью (клавиатура,
экран монитора);
math.h – математические функции.
#include "My_file.h"
Вставляет в программу текст из файла My_file.h, который находится в текущем каталоге проекта.
12
Директивы препроцессора. Макроопределения и макровызовы
Второе основное назначение препроцессора – обработка макроопределений.
Макроподстановка имеет общий вид:
#define < ID > <строка> Например: #define PI 3.1415927
В ходе препроцессорной обработки программы появление в тексте идентификатора PI везде заменяется значением 3.1415927.
Макроопределение представляет собой фрагмент исходного текста программы, в котором вводится новое имя (идентификатор), предназначенное к обработке макропроцессором.
Примеры:
#define BUFFERSIZE 1024
#define HELLOMSG "Hello, world\n"
#define MALLOCITEM malloc(sizeof(struct item))
Здесь BUFFERSIZE, HELLOMSG и MALLOCITEM —
макроимена или просто макросы; встретив любой из этих идентификаторов в дальнейшем тексте программы, компилятор заменит первый из них на число 1024, второй
— на строковую константу "Hello, world\n", третий — на вызов функции malloc.
Можно ожидать теперь появления в программе чего-то вроде следующего:
char buffer[BUFFERSIZE]; /* ... */ puts(HELLOMSG);
/* ... */
struct item *p = MALLOCITEM;
и так далее.
Это, собственно говоря, и есть макровызовы.
Тело макроса не обязано быть законченным выражением или даже законченной конструкцией; вполне можно описать, например, такое:
#define IF if( #define THEN ) { #define ELSE } else { #define FI }
и затем в программе изобразить что-то вроде
IF a > b THEN
printf("the first was greater then the second\n"); b = a; ELSE
printf("the second was greater\n"); a = b; FI
Иной вопрос, что конкретно вот так делать всё же не стоит (хотя и можно), но в более сложных случаях
подобный синтез языковых конструкций из макросов
вполне может себя оправдать.
13
3. Понятие о функции
Подпрограмма (ПП) – это поименованный или иным образом идентифицированный фрагмент компьютерной программы, которому можно передать управление (вызвать) в любой её точке и который имеет возможность вернуть управление в точку, следующую за точкой своего вызова
Ранее (60-е годы ХХ века): Уменьшение размера памяти, занимаемой кодом программы – почти неактуально в наст. вр.
Структуризация программы с целью удобства её понимания и сопровождения
Исправление ошибок, оптимизация, расширение функциональности в ПП автоматически отражается на всех её вызовах
Вынесение в ПП даже однократно выполняемого набора действий делает программу более понятной и обозримой
Механизм вызова ПП (продолжение):
Каждому вызову ПП соответствует отдельная область памяти – стековый кадр
Функция — это самостоятельная единица программы, которая спроектирована для реализации конкретной подзадачи.
Функция является подпрограммой, которая может содержаться в основной программе, а может быть создана отдельно (в библиотеке). Каждая функция выполняет в программе определенные действия.
Сигнатура функции определяет правила использования функции. Обычно сигнатура представляет собой описание функции, включающее имя функции, перечень формальных параметров с их типами и тип возвращаемого значения.
Семантика функции определяет способ реализации функции. Обычно представляет собой тело функции.
Определение функции
Механизм вызова ПП: |
Каждая функция в языке Си должна быть определена, то |
|||
есть должны быть указаны: |
||||
Вызов ПП делится на |
||||
|
тип возвращаемого значения; |
|||
|
Подготовительные служебные действия |
|
||
|
имя функции; |
|||
|
вызывающей программы |
|||
|
|
информация о формальных аргументах; |
||
Собственно работу ПП |
||||
|
|
тело функции. |
||
Заключительные служебные действия |
вызывающей программы
14
Понятие о функции
Определение функции имеет следующий синтаксис:
Тип ИмяФункции(СписокФормальныхАргументов)
{
ТелоФункции;
...
return(ВозвращаемоеЗначение);
}
// Пример: Функция сложения двух вещественных чисел float function(float x, float z)
{
float y; y=x+z; return(y);
}
В указанном примере возвращаемое значение имеет
тип float.
В качестве возвращаемого значения в вызывающую функцию передается значение переменной y.
Формальными аргументами являются значения
переменных x и z.
Если функция не возвращает значения, то тип возвращаемого значения для нее указывается как void.
При этом операция return может быть опущена.
Если функция не принимает аргументов, в круглых скобках также указывается void.
Различают:
системные (в составе систем программирования)
собственные функции.
Системные функции хранятся в стандартных библиотеках, и пользователю не нужно вдаваться в подробности их реализации. Достаточно знать лишь их сигнатуру.
Примером системных функций, используемых ранее, являются функции printf() и scanf().
Собственные функции — это функции, написанные пользователем для решения конкретной подзадачи.
Разбиение программ на функции дает следующие
преимущества:
Функцию можно вызвать из различных мест программы, что позволяет избежать повторения программного кода.
Одну и ту же функцию можно использовать в разных программах.
Функции повышают уровень модульности программы и облегчают ее проектирование.
Использование функций облегчает чтение и понимание программы и ускоряет поиск и исправление ошибок.
С точки зрения вызывающей программы функцию можно представить как некий «черный ящик», у которого есть несколько входов и один выход.
В языке Си нельзя определять одну функцию внутри другой.
Функции могут определяться как до вызывающей
функции, так и после нее. |
15 |
Понятие о функции
Возврат в вызывающую функцию
По окончании выполнения вызываемой функции осуществляется возврат значения в точку ее вызова.
Это значение присваивается переменной, тип которой должен соответствовать типу возвращаемого значения функции.
Функция может передать в вызывающую программу только одно значение.
Для передачи возвращаемого значения в вызывающую функцию используется оператор return в одной из форм:
return(ВозвращаемоеЗначение); return ВозвращаемоеЗначение;
Действие оператора следующее: значение выражения,
заключенного в скобки, вычисляется и передается в вызывающую функцию.
Возвращаемое значение может использоваться в вызывающей программе как часть некоторого выражения.
Оператор return также завершает выполнение функции и передает управление следующему оператору в вызывающей функции.
Оператор return не обязательно должен находиться в конце тела функции.
Функции могут и не возвращать значения, а просто выполнять некоторые вычисления.
В этом случае указывается пустой тип возвращаемого значения void, а оператор return может либо отсутствовать, либо не возвращать никакого значения:
Пример: Посчитать сумму двух чисел.
#define _CRT_SECURE_NO_WARNINGS /* для возможности использования scanf */
#include <stdio.h>
// Функция вычисления суммы двух чисел
int sum(int x, int y) /* в функцию передаются два целых числа */
{
int k = x + y; // вычисляем сумму чисел и сохраняем в k
return k; |
// возвращаем значение k |
} |
|
int main() |
|
{ |
|
int a, r; // описание двух целых переменных printf("a= ");
scanf("%d", &a); // вводим a, %d (десятичное целое) r = sum(a, 5); // вызов функции: x=a, y=5
printf("%d + 5 = %d", a, r); // вывод: a + 5 = r getchar(); getchar(); // мы использовали scanf(), return 0; // поэтому getchar() вызываем дважды
}
return; |
16 |
|
Простейшие средства ввода-вывода
Основной задачей программирования является обработка информации, поэтому любой язык программирования имеет средства для ввода и вывода информации.
В языке Си нет операторов ввода-вывода.
Ввод и вывод информации осуществляется через функции стандартной библиотеки.
Прототипы рассматриваемых функций находятся в файле
stdio.h.
Эта библиотека содержит функции: printf() — для вывода информации scanf() — для ввода информации.
Вывод информации
Функция printf() предназначена для форматированного вывода.
Она переводит данные в символьное представление и выводит полученные изображения символов на экран.
При этом у программиста имеется возможность форматировать данные, то есть влиять на их представление на экране.
Общая форма записи функции printf(): printf("Строка_Форматов", объект1, объект2, ..., объектn);
Строка_Форматов состоит из следующих элементов:
управляющих символов;
текста, представленного для непосредственного вывода;
форматов, предназначенных для вывода значений переменных различных типов.
Объекты могут отсутствовать.
Управляющие символы не выводятся на экран, а управляют расположением выводимых символов.
Отличительной чертой управляющего символа является наличие обратного слэша ‘\’ перед ним.
Пример основных управляющих символов: ‘\n’ — перевод строки;
‘\t’ — горизонтальная табуляция; ‘\v’ — вертикальная табуляция; ‘\b’ — возврат на символ;
‘\r’ — возврат на начало строки; ‘\a’ — звуковой сигнал.
Форматы нужны для того, чтобы указывать вид, в котором информация будет выведена на экран.
Отличительной чертой формата является наличие символа
процент ‘%’ перед ним:
%d — целое число типа int со знаком в десятичной системе счисления;
%u — целое число типа unsigned int;
%x — целое число типа int со знаком в шестнадцатеричной системе счисления;
%o — целое число типа int со знаком в восьмеричной системе счисления;
%hd — целое число типа short со знаком в десятичной системе счисления;
%hu — целое число типа unsigned short;
%hx — целое число типа short со знаком в шестнадцатеричной системе счисления;
%ld — целое число типа long int со знаком в десятичной системе счисления;
%lu — целое число типа unsigned long int;
17
5. Простейшие средства ввода-вывода
Форматы нужны для того, чтобы указывать вид, в котором информация будет выведена на экран. Отличительной чертой формата является наличие символа процент ‘%’ перед ним:
%lx — целое число типа long int со знаком в шестнадцатеричной системе счисления;
%f — вещественный формат (числа с плавающей точкой типа float);
%lf — вещественный формат двойной точности (числа с плавающей точкой типа double);
%e — вещественный формат в экспоненциальной форме (числа с плавающей точкой типа float в экспоненциальной форме);
%c — символьный формат; %s — строковый формат.
Строка форматов содержит форматы для вывода значений. Каждый формат вывода начинается с символа %.
После строки форматов через запятую указываются имена переменных, которые необходимо вывести.
Количество символов % в строке формата должно совпадать с количеством переменных для вывода.
Тип каждого формата должен совпадать с типом переменной, которая будет выводиться на это место.
Замещение форматов вывода значениями переменных происходит в порядке их следования.
#include <stdio.h> int main()
{
int a = 5; float x = 2.78;
printf("a=%d\n", a); printf("x=%f\n", x); getchar();
return 0;
}
То же самое с использованием одного вызова printf:
#include <stdio.h> int main()
{
int a = 5; float x = 2.78;
printf("a=%d\nx=%f\n", a, x); getchar();
return 0;
}
18
Простейшие средства ввода-вывода
Табличный вывод
При указании формата можно явным образом указать общее количество знакомест и количество знакомест, занимаемых дробной частью:
#include <stdio.h> int main()
{
float x = 1.2345; printf("x=%10.5f\n", x); getchar();
return 0;
}
В приведенном примере:
10 — общее количество знакомест, отводимое под значение переменной;
5 — количество позиций после разделителя целой и дробной части (после десятичной точки).
В указанном примере количество знакомест в выводимом числе меньше 10, поэтому свободные знакоместа слева от числа заполняются пробелами.
Такой способ форматирования часто используется для построения таблиц.
Ввод информации
Функция форматированного ввода данных с клавиатуры scanf() выполняет чтение данных, вводимых с клавиатуры, преобразует их во внутренний формат и передает вызывающей функции.
При этом программист задает правила интерпретации входных данных с помощью спецификаций форматной строки.
Общая форма записи функции scanf( ):
scanf ("CтрокаФорматов", адрес1, адрес2,...);
Строка форматов аналогична функции printf().
Для формирования адреса переменной используется символ амперсанд ‘&’:
адрес = &объект
Строка форматов и список аргументов для функции обязательны.
#define _CRT_SECURE_NO_WARNINGS /* для возможности использования scanf */
#include <stdio.h>
#include <stdlib.h> // для перехода на русский язык int main()
{
float y;
printf("Введите y: "); // выводим сообщение scanf("%f", &y); // вводим значения переменной y
printf("Значение переменной y=%f", y); /* выводим значение переменной y */
getchar(); getchar(); return 0;
} |
19 |
Простейшие средства ввода-вывода
Функция scanf( ) является функцией незащищенного ввода, т.к. появилась она в ранних версиях языка Си.
Поэтому чтобы разрешить работу данной функции в современных компиляторах необходимо в начало программы добавить строчку
#define _CRT_SECURE_NO_WARNINGS
Другой вариант — воспользоваться функцией защищенного ввода scanf_s( ), которая появилась несколько позже, но содержит тот же самый список параметров.
#include <stdio.h> int main()
{
int a; printf("a = ");
scanf_s("%d", &a); printf("a = %d",a); getchar(); getchar(); return 0;
}
20