- •СПбГУТ им. проф. М.А. Бонч-Бруевича Кафедра программной инженерии и вычислительной техники (ПИ и
- •1. Общая характеристика языка Си
- •Рейтинг TIOBE Index
- •Рейтинг IEEE Spectrum
- •Рейтинг Stack Overflow
- •Общая характеристика языка Си
- •Структура программы на языке Си
- •Структура программы на языке Си
- •Компиляция и интерпретация
- •Структура программы на языке Си
- •2. Директивы препроцессора
- •Директивы препроцессора. Макроопределения и макровызовы
- •3. Понятие о функции
- •Понятие о функции
- •Понятие о функции
- •Простейшие средства ввода-вывода
- •5. Простейшие средства ввода-вывода
- •Простейшие средства ввода-вывода
- •Простейшие средства ввода-вывода
- •Справочно: Форматированный вывод данных. Функция printf( )
- •Справочно: Форматированный ввод данных. Функция scanf( )
Структура программы на языке Си
Теперь, когда мы закончили разбор программы «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