Скачиваний:
3
Добавлен:
03.01.2024
Размер:
1.18 Mб
Скачать

Структура программы на языке Си

Теперь, когда мы закончили разбор программы «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 может либо отсутствовать, либо не возвращать никакого значения:

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() вызываем дважды

}

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

Простейшие средства ввода-вывода

Табличный вывод

 

 

 

 

 

Ввод информации

 

 

 

 

 

явным образом указать общее

Функция форматированного

ввода данных с клавиатуры

При указании формата можно

количество знакомест и количество знакомест, занимаемых

 

scanf() выполняет чтение данных, вводимых с клавиатуры,

дробной частью:

 

преобразует их во внутренний формат и передает

 

 

 

 

 

 

 

вызывающей функции.

#include <stdio.h>

 

 

 

При этом программист задает правила интерпретации

int main()

 

 

входных данных с помощью спецификаций форматной

{

 

 

 

 

 

 

строки.

 

 

 

 

 

 

Общая форма записи функции scanf( ):

float x = 1.2345;

 

 

 

 

 

scanf ("CтрокаФорматов", адрес1, адрес2,...);

printf("x=%10.5f\n", x);

 

 

 

 

 

 

Строка форматов аналогична функции printf().

 

 

 

 

 

 

 

 

 

 

 

getchar();

 

 

 

Для формирования адреса переменной используется символ

return 0;

 

 

 

 

 

амперсанд ‘&’:

 

 

 

адрес = &объект

}

 

 

 

 

 

 

Строка форматов и список аргументов для функции

 

 

 

 

 

 

обязательны.

 

 

 

 

 

 

В приведенном примере:

 

 

 

 

 

#define _CRT_SECURE_NO_WARNINGS /* для возможности

10 — общее количество

знакомест, отводимое под значение

 

использования scanf */

переменной;

#include <stdio.h>

5 — количество позиций после разделителя целой и

#include <stdlib.h> // для перехода на русский язык

дробной части (после десятичной точки).

int main()

В указанном примере количество знакомест в выводимом

{

 

 

 

 

числе меньше 10, поэтому свободные знакоместа слева от

float y;

числа заполняются пробелами.

printf("Введите y: "); // выводим сообщение

Такой способ форматирования часто используется для

scanf("%f", &y); // вводим значения переменной y

построения таблиц.

printf("Значение переменной y=%f", y); /* выводим значение

 

 

 

 

 

 

переменной y */

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

getchar(); getchar();

 

 

 

 

 

 

 

 

return 0;

 

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Простейшие средства ввода-вывода

Функция 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

Соседние файлы в папке Лекции