Читайте также:
|
|
Поскольку было сказано, что программа на Си состоит только из функций, возникает вопрос: с какого же места начинается выполнение программы? Для этого существует зарезервированное имя функции: main. Эту функцию вызывает операционная система[14]. Функция main имеет тип int. Она возвращает код ошибки.
Все ошибки выполнения программы, которые могут привести к её непредвиденному завершению, были определены, и каждой из них был присвоен свой уникальный код – код ошибки. Он позволяет узнать, что привело к завершению программы, и таким образом может помочь понять, где была ошибка в коде. Корректному завершению программы соответствует код ошибки 0. Поэтому в конце функции main нужно ставить строку
return 0;
У функции main есть и аргументы. Как несложно догадаться, это какие-то параметры, которые её передаёт операционная система. Однако, об этом позднее.
Итак, пользуясь полученными знаниями, можно написать первую работающую программу.
int even(int); //returns 1 if a is even and 0 if a is odd
int main()
{
int b, a=10;
b=even(a);
return 0;
}
int even(int a)
{
int e;
(a%2)&&(e=0)||!(a%2)&&(e=1);
return e;
}
Первая строка – это прототип функции even. Дело в том, что функция может вызывать другую функцию, но только описанную ранее. Соблюдать при этом правильный порядок очень сложно, а порой невозможно. Поэтому в самом начале кода целесообразно поместить прототипы всех функций – типы, имена и типы аргументов (без указания их имён). Это позволит любую функцию использовать в любом месте[15].
В функции main объявлены две переменные типа int: a и b. При объявлении a сразу инициализируется значением 10. Инициализацией называется первое присваивание переменной конкретного значения. До этого в ней находится мусор из оперативной памяти[16].
Скомпилируем и запустим эту программу:
user@dhcppc1:~> gcc even.c -o even
user@dhcppc1:~>./even
user@dhcppc1:~>
Программа была скомпилирована без ошибок, запущена, однако о факте своего запуска и завершения не дала знать никаким образом.
Для взаимодействия с пользователем существуют функции ввода-вывода: getchar() и putchar(char). Функция getchar() имеет тип int и возвращает значение предпоследнего[17] символа в буфере клавиатуры. По сути – считывание одного символа, после ожидания нажатия клавиши Enter[18]. Функция имеет тип void, её аргументом является ASCII-код символа, который будет выведен в поток вывода stdout (по умолчанию – текстовая видеопамять).
Поскольку запоминать коды всех символов трудно, а их употребление снизило читаемость программ, в языке Си введёно такое понятие как символьные константы, которые заключаются в одинарные кавычки. Обозначение ‘<символ>’ преобразуется в ASCII-код символа.
Модифицируем тело функции main следующим образом:
int main()
{
int a=10, b;
even(a)&&putchar('1')||!even(a)&&putchar('0');
b=5;
even(b)&&putchar('1')||!even(b)&&putchar('0');
return 0;
}
Результат выполнения этой программы таков:
user@dhcppc1:~>./even
10user@dhcppc1:~>
Значение переменной a – 10 – число чётное, а потому первым выведенным символом будет 1. Переменная b имеет значение 5 – нечётное – после 1 будет выведен 0. Ясно, что в конец программы неплохо бы добавить символ перехода на новую строку, чтобы следующие команды в консоли не смешивались с результатами работы программы. Как это сделать? Конструкция
putchar('
');
где в одинарных кавычках содержится символ перехода на новую строку, является ошибочной, о чём и сообщает компилятор:
user@dhcppc1:~> gcc even.c –o even
even.c:14:10: warning: missing terminating ' character
even.c: In function ‘main’:
even.c:14: error: missing terminating ' character
even.c:15:1: warning: missing terminating ' character
even.c:15: error: missing terminating ' character
even.c:16: error: expected expression before ‘return’
even.c:17: error: expected ‘;’ before ‘}’ token
user@dhcppc1:~>
Можно запомнить, что код символа перехода на новую строку – 13, и писать его. Но это не единственный символ, код которого невозможно получить с помощью кавычек. Поэтому так называемые управляющие символы имеют собственные обозначения.
Символ | Значение |
\n | Переход на новую строку (от англ. new) |
\t | Табуляция (от англ. tab) |
\v | Вертикальный отступ (от англ. vertical) |
\b | Возврат на один символ назад (от англ. backspace) |
\r | Возврат в начало строки (от англ. rewind) |
\\ | Символ \ (если написать один раз – игнорируется или вызывает ошибки) |
\' | Символ ' |
\" | Символ " |
\0 | Нуль-символ (символ с кодом 0) |
Таким образом, функция main приобретает следующий вид:
int main()
{
int a=10, b;
even(a)&&putchar('1')||!even(a)&&putchar('0');
b=5;
even(b)&&putchar('1')||!even(b)&&putchar('0');
putchar('\n');
return 0;
}
Результат её выполнения выглядит так:
user@dhcppc1:~>./even
user@dhcppc1:~>
Естественно, такой способ вывода совершенно неприемлем для практического написания программ. Существуют удобные и эффективные функции, которые можно найти в библиотеках функций. Возникает вопрос – как включить эти функции в свою программу. Можно было бы добавлять реализации этих функций прямо в код программы, однако это бы неоправданно увеличивало их длину, особенно в случае коротких. Поэтому для подключения библиотек к программе используется препроцессор.
Препроцессор – это программа, осуществляющая обработку кода программы до компиляции, рассматривая код как текст. В частности, именно препроцессор убирает из кода, передаваемого компилятору, комментарии. Команды, которые управляют поведением препроцессора, называются препроцессорными директивами.
#include <файл> [19]– директива, включающая файл в текст программы. Именно таким способом подключаются библиотеки[20]. Если имя файла заключено в треугольные скобки, то файл с таким именем ищется в папке со стандартными библиотеками функций, а если в двойные кавычки – то оно должно представлять собой полный путь к включаемому файлу.
#define <замещаемая лексема> [<заместитель>] – директива, после появления которой в тексте программы, все замещаемые лексемы, кроме расположенных внутри двойных кавычек, меняются на заместители (поле <заместитель> может быть пустым, а потому написано в квадратных скобках). Такая замена во многих случаях может быть очень удобной. Например, в программе часто придётся использовать число π. Каждый раз писать 3.141592165358979 нецелесообразно. К тому же, можно ошибиться. Но можно один раз, желательно в начале программы, написать
#define PI 3.14159265358979
и в нужных местах писать просто PI. Директива #define позволяет создавать и более сложные объекты, называемые макросами. Макрос – это определённая в программе и действующая только для неё форма записи команды или последовательности команд. Если в замещаемой лексеме есть круглые скобки, то символы, содержащиеся в скобках, препроцессор воспринимает как аргументы, которые можно использовать, но уже иным образом, в заместителе.
Например, мы знаем, что в программе придётся часто иметь дело с последовательностями из двадцати одинаковых лексем. Вместо того чтобы каждый раз писать их, можно написать макрос:
#define twenty(a) a a a a a a a a a a a a a a a a a a a a
Теперь встретившийся в программе код twenty(putchar('\n');) преобразуется в двадцать строк putchar('\n');. Можно использовать и такую конструкцию:
#define twenty(a) a a a a a a a a a a a a a a a a a a a a
#define many(a) twenty(twenty(twenty(a)))
#define do(a, b) many(a(b);)
Макрос do(a, b) 8000 раз вызовет функцию a от аргумента b. Например, do(putchar, 's') 8000 раз выведет на экран символ s.
#undef <лексема> – директива, отменяющая после своего появления действие директивы #define, определяющей макрос с данным именем.
#if <выражение>, #ifdef <имя макроса>, #else, #endif – директивы условной компиляции. В конструкции
/*текст1*/ #if <выражение> /*текст2*/ #endif /*текст3*/
Текст2 подвергается дальнейшей обработке и компиляции только в случае если выражение является истинным. В конструкции
/*текст1*/ #if <выражение> /*текст2*/ #else /*текст3*/ #endif /*текст4*/
Текст2 подвергается дальнейшей обработке и компиляции, а текст3 игнорируется только в случае если выражение является истинным, в противном случае всё наоборот. В конструкции
/*текст1*/ #ifdef <имя макроса> /*текст2*/ #endif /*текст3*/
Текст2 подвергается дальнейшей обработке и компиляции только если макрос с данным именем был ранее определён. В конструкции
/*текст1*/ #ifdef <имя макроса> /*текст2*/ #else /*текст3*/ #endif /*текст4*/
Текст2 подвергается дальнейшей обработке и компиляции, а текст3 игнорируется только если макрос с данным именем был ранее определён, в противном случае всё наоборот.
Условная компиляция на первый взгляд может показаться ненужной, однако следует учесть, что компиляторы по умолчанию определяют несколько пустых макросов, описывающих среду компиляции. Например, при компиляции под операционными системами Windows всегда определяется макрос WIN32. А это уже мощное средство создания кроссплатформенных (то есть, работающих под разными компиляторами, операционными системами, архитектурах) приложений. Если какая-то функция по-разному выглядит для ОС Windows и Linux, то можно написать такой код:
<тип> <имя функции>(<аргументы>)
{
#ifdef WIN32
/*реализация под Windows*/
#endif
#ifdef LINUX
/*реализация под Linux*/
#endif
}
После чего к функции можно обращаться, не задумываясь о том, под какую операционную систему пишется программа.
Дата добавления: 2015-11-13; просмотров: 57 | Нарушение авторских прав
<== предыдущая страница | | | следующая страница ==> |
Часть 2: функции. | | | Лекция 6: массивы и строки, библиотечные функции ввода-вывода. |