- •Происхождение языка с
- •Язык среднего уровня
- •Структурированный язык
- •Язык программирования
- •Компиляторы против интерпретаторов
- •Вид программ на с
- •Библиотеки и компоновка
- •Раздельная компиляция
- •Карта памяти с-программы
- •Переменные, константы, операторы и выражения
- •Идентификаторы
- •Типы данных
- •Модификаторы типов
- •Модификаторы доступа
- •Объявление переменных
- •Локальные переменные
- •Формальные параметры
- •Глобальные переменные
- •Спецификаторы хранения
- •Статические переменные
- •Статические локальные переменные
- •Статические глобальные переменные
- •Регистровые переменные
- •Оператор присваивания
- •Многочисленное присваивание
- •Преобразование типов при присваивании
- •Инициализация переменных
- •Константы
- •Символьные константы с обратным слэшем
- •Операторы
- •Арифметические операторы
- •Увеличение и уменьшение
- •Операторы отношения и логические операторы
- •Битовые операторы
- •Оператор ?
- •Операторы указания & и *
- •Оператор sizeof
- •Оператор «запятая»
- •Операторы [ ] u ()
- •Приоритеты в с
- •Выражения
- •Преобразование типов в выражениях
- •Принудительные преобразования
- •Пробелы и круглые скобки
- •Сокращенные операторы в с
- •Операторы управления программой
- •Истина и ложь в с
- •Операторы выбора
- •Вложенные if
- •Лесенка if-else-if
- •Оператор ?
- •Вложенные операторы switch
- •Вариации цикла for
- •Бесконечный цикл
- •Циклы for без тела
- •Метки и goto
- •Функции
- •Оператор return
- •Выход из функции
- •Возвращаемые значения
- •Значения, возвращаемые функцией main()
- •Правила видимости для функций
- •Аргументы функции
- •Передача по значению и передача по ссылке
- •Создание передачи по ссылке
- •Передача массивов в функции
- •Аргументы функции main()
- •Функции, возвращающие нецелые значения
- •Использование прототипов функции
- •Прототипы стандартных библиотечных функций
- •Создание прототипов функций, не имеющих параметров
- •Возврат указателей
- •Рекурсия
- •Сопоставление классического и современного объявления параметров
- •Указатели на функции
- •Особенности реализации
- •Параметризированные функции и функции общего назначения
- •Эффективность
- •Массивы
- •Одномерный массив
- •Создание указателя на массив
- •Передача одномерных массивов в функции
- •Двумерные массивы
- •Массивы строк
- •Многомерные массивы
- •Индексация с помощью указателей
- •Размещение массивов
- •Инициализация массива
- •Инициализация безразмерных массивов
- •Пример программы игры в крестики-нолики
- •Указатели
- •Указатели - это адреса
- •Переменные-указатели
- •Операторы для работы с указателями
- •Выражения с указателями
- •Присваивание указателей
- •Арифметические действия с указателями
- •Сравнение указателей
- •Динамическое выделение и указатели
- •Указатели на константы
- •Указатели на константы
- •Указатели на константы
- •Указатели и массивы
- •Указатели на символьные массивы
- •Массивы указателей
- •Указатели на указатели - многочисленное перенаправление
- •Инициализация указателей
- •Указатели на функции
- •Проблемы, связанные с указателями
- •Структуры, объединения и определяемые пользователем типы
- •Структуры
- •Доступ к членам структуры
- •Присваивание структур
- •Массивы структур
- •Программа инвентаризации
- •Передача структур в функции
- •Передача членов структур в функции
- •Передача всей структуры в функцию
- •Указатели на структуры
- •Объявление указателя на структуру
- •Использование указателей на структуру
- •Массивы и структуры в структурах
- •Битовые поля
- •Объединения
- •Перечисления
- •Использование sizeof для обеспечения переносимости
- •Ввод, вывод, потоки и файлы
- •Потоки и файлы
- •Текстовые потоки
- •Двоичные потоки
- •Консольный ввод/вывод
- •Чтение и запись символов
- •Чтение и запись строк: gets() и puts()
- •Форматированный консольный ввод/вывод
- •Печать символов
- •Вывод чисел
- •Вывод адресов
- •Спецификатор %n
- •Модификаторы формата
- •Спецификатор минимума ширины поля
- •Спецификатор точности
- •Выровненный вывод
- •Работа с другими типами данных
- •Модификаторы * u #
- •Спецификаторы формата
- •Ввод чисел
- •Ввод беззнаковых целых
- •Чтение отдельных символов с помощью scanf()
- •Чтение строк
- •Ввод адреса
- •Спецификатор %n
- •Использование множества сканирования
- •Пропуск нежелательных специальных символов
- •Обычные символы в управляющей строке
- •В scanf() следует передавать адреса
- •Модификаторы формата
- •Подавление ввода
- •Файловая система ansi с
- •Указатель на файл
- •Открытие файла
- •Запись символа
- •Чтение символа
- •Использование fopen(), getc(), putc() и fclose()
- •Использование feof()
- •Две расширенные функции: getw() и putw()
- •Работа со строками: fgets() и fputs()
- •Fseek() и произвольный доступ
- •Удаление файлов
- •Работа с консолью
- •Препроцессор и комментарии
- •Директивы условной компиляции
- •Использование defined
- •Операторы препроцессора # и ##
- •Предопределенные макросы
- •Комментарии
Удаление файлов
Функция remove() удаляет файлы. Она имеет следующий прототип:
int remove(const char *имя_файла);
В случае удачного выполнения она возвращает ноль, а в случае неудачного - не ноль.
Следующая программа использует remove() для удаления файла, определяемого пользователем:
/* пример использования remove() */ #include <stdio.h> int main(void) { char fname[80]; printf ("Name of file to remove: "); gets(fname); if(remove(fname)) { printf("Error removing file"); return 1; } else return 0; }
ferror() и rewind()
Функция ferror() используется для определения, привела ли выполненная операция к ошибке. Функция ferror() имеет следующий прототип:
int ferror(FILE *fp) где fp - это допустимый указатель на файл. Она возвращает истину, если в результате выполнения последней операции произошла ошибка. В противном случае она возвращает ложь. Поскольку каждая файловая операция изменяет состояние ошибки, ferror() должна вызываться после каждой файловой операции, иначе ошибка может потеряться.
Функция rewind() сбрасывает маркер файла на начало для файла, указанного в аргументе. Она имеет прототип:
void rewind(FILE *fp) где fp - это допустимый файловый указатель.
Работа с консолью
Независимо от того, когда программа начнет выполнение, всегда открыты 5 потоков. Это stdin, stdout, stderr, stdaux и stdprn. Поскольку это файловые указатели, они могут использоваться системой ввода/вывода ANSI С, использующей указатели. Например, функция putchar() может быть определена следующим образом: int putchar(int с) { return putc(c, stdout); }
Как показывает данный пример, С проводит не очень заметную границу между консольным и файловым вводом/выводом. В принципе функции консольного ввода/вывода являются версиями функций для работы с файлами с тем отличием, что ввод или вывод неявно направляется на консоль. Единственная причина их существования - это удобство. В целом можно использовать stdin, stdout и stderr в качестве файловых указателей в функциях, использующих в качестве параметра переменную типа FILE *.
В системах, где разрешается перенаправление ввода/вывода, stdin и stdout можно перенаправлять. Это означает, что они могут быть связаны с устройством, отличным от экрана или клавиатуры. Например, рассмотрим программу: #include <stdio.h> int main(void) { char str[80]; printf("Enter a string: "); gets(str); printf(str); return 0; }
Предположим, что данная программа называется TEST. При обычном выполнении TEST она выводит на экран подсказку, читает с клавиатуры строку и выводит данную строку на экран. Как stdin, так и stdout могут быть перенаправлены в файл. Например
TEST > OUTPUT вывод программы TEST будет происходить в файл OUTPUT. Выполнение команды
TEST < INPUT > OUTPUT приведет к тому, что программа будет читать из файла INPUT, а записывать в файл OUTPUT.
Как можно видеть, консольный ввод/вывод и файловый ввод/вывод - это два немного различающихся взгляда на одну и ту же вещь.
Препроцессор и комментарии
Исходный код программы на С (или С++) может содержать различные инструкции компилятору. Не являясь частью языка, директивы препроцессора расширяют область применения языка. В данной главе рассматривается препроцессор. Также рассматриваются стандартные макросы и некоторые дополнения, внесенные в препроцессор фирмой Borland.
Стандарт ANSI С определяет следующие директивы препроцессора: #if #ifdef #ifndef #else #elif #endif #include #define #undef #line #error #pragma
Все директивы препроцессора начинаются со значка #, и каждая директива должна находиться в своей собственной строке. Например: /* не будет работать */ #include <stdio.h> #include <stdlib.h> не будет работать.
#define
Директива #define определяет идентификатор и последовательность символов, которой будет замещаться данный идентификатор при его обнаружении в тексте программы. Идентификатор также называется именем макроса, а процесс замещения называется подстановкой макроса. Стандартный вид директивы следующий:
#define имя_макроса последовательность_символов Обратим внимание, что в данном операторе отсутствует точка с запятой. Между идентификатором и последовательностью символов может быть любое число пробелов. Макрос завершается только переходом на новую строку.
Например, если необходимо использовать TRUE для значения 1, a FALSE для 0 то можно объявить следующие два макроса: #define TRUE 1 #define FALSE 0 В результате, если компилятор обнаружит в тексте программы TRUE или FALSE, то он заменит их на 1 и 0 соответственно. Например, следующая строка выводит на экран «0 1 2»: printf ("%d %d %d", FALSE, TRUE, TRUE + 1);
В случае, если макрос определен, он может использоваться для определения других макросов. Например, следующий код сопоставляет с именами ONE, TWO и THREE их численные значения: #define ONE 1 #define TWO ONE + ONE #def ine THREE ONE + TWO
В результате макроподстановки идентификаторы замещаются указанными строками. Если необходимо определить стандартное сообщение об ошибке, то можно написать что-то вроде следующего: #define E_MS "Standart error on input.\n" /*...*/ printf(E_MS);
Если компилятор обнаруживает идентификатор E_MS, то он замещает его строкой «Standart error on input.» На самом деле компилятор увидит оператор в виде printf("Standart error on input.\n"); Если идентификатор находится в строке, то подстановка не происходит. Например: #define XYZ this is a test /*...*/ printf("XYZ"); выведет не «this is a test», a «XYZ».
Если строка не вмещается в одной строке, то ее можно продолжить на следующей строке, поместив в конце строки обратный слэш, как показано в следующем примере: #define LONG_STRING "This is a very long" \ string that is used as an example." Программисты, пишущие на С, часто используют заглавные буквы для определения идентификаторов. Данное соглашение помогает любому человеку, читающему программу, бросив на нее один взгляд, узнать, что он имеет дело с макросом. Также вce #define лучше помещать в начале файла или вообще в отдельный заголовочный файл.
Очень часто макросы используют для определения «магических чисел», используемых в программе. Например, программа может определять массив и иметь несколько процедур для работы с ним. Вместо того, чтобы жестко кодировать размер массива, лучше определить макрос, соответствующий размеру массива, и использовать его в тех местах, где необходимо использование размера. Таким образом, если необходимо изменить размер массива, единственное, что требуется сделать, — это изменить оператор #define и перекомпилировать программу. Везде, где использовался данный макрос, произойдут автоматические изменения. Рассмотрим пример: #define MAX_SIZE 100 /*...*/ float balance[MAX_SIZE]; /*...*/ float temp[MAX_SIZE]; Для изменения размеров обоих массивов просто изменим определение MAX_SIZE.
Директива #define имеет еще одну возможность: макрос может иметь аргументы. Каждый раз при встрече такого макроса аргументы макроса будут замещаться реальными аргументами программы. Такой тип макроса называется макрос типа функция. Например: #include <stdio.h> #define MIN(a,b) ((a)<(b)) ? (a) : (b) int main(void) { int x, y; x = 10; у = 20; printf("The minimum is: %d", MIN(x, y) ); return 0; } При компиляции программы вместо MIN(a, b) подставляется указанное выражение, причем вместо фиктивных параметров а и b подставляются реальные х и у. Таким образом, в результате подстановки функция printf() примет следующий вид: printf ("The minimum is: %d",((x) < (y) ) ? (x) : (y) );
Надо быть осторожным при определении макросов, получающих аргументы, или можно получить несколько неожиданные результаты. Например, рассмотрим следующую короткую программу, использующую макрос для определения четности значения:
/* программа выдает неправильный результат */ #include <stdio.h> #define EVEN(a) a%2==0 ? 1 : 0 int main(void) { if (EVEN(9+1) ) printf("is even"); else printf ("is odd"); return 0; } Из-за способа подстановки данная программа работает неправильно. В результате компиляции программы EVEN(9 + 1) расширится до 9 + 1% 2 == 0 ? 1 : 0
Как известно, оператор взятия по модулю имеет более высокий приоритет, чем оператор сложения. Это означает, что сначала выполнится взятие по модулю с числом 1, а затем результат прибавится к 9, что, естественно, не может быть равно 0. Для устранения данной проблемы следует заключить а в макросе EVEN в круглые скобки, как показано в следующей правильной версии программы: #include <stdio.h> #define EVEN(a) (a)%2==0 ? 1 : 0 int main(void) { if(EVEN(9 + 1) ) printf("is even"); else printf("is odd"); return 0; } Обратим внимание, что 9+1 вычисляется до взятия по модулю. В целом заключение параметров макроса в скобки — это достаточно хорошая идея, и она позволяет избежать множества проблем. Использование макроподстановок вместо реальных функций имеет одно большое преимущество — существенно увеличивается скорость работы программы, поскольку нет необходимости тратить время на вызов функции и возврат из нее. Тем не менее, за данное увеличение скорости работы следует платить увеличением размера исполнимого кода программы, поскольку программа вынуждена дублировать код макроса.
#error
Директива #error указывает компилятору в случае ее обнаружения остановить компиляцию. Как правило, она используется для отладки. Общий вид директивы следующий: #error сообщение_об_ошибке сообщение_об_ошибке не заключается в двойные кавычки. Когда компилятор обнаруживает директиву, он выводит сообщение в следующем виде и завершает компиляцию: Fatal: имя_файла номер_строки: Error directive: сообщение_об_ошибке Здесь имя_файла — это имя файла, где была найдена директива #error, номер_строки — это номер строки директивы, а сообщение_об_ошибке — это собственно само сообщение.
#include
Директива #include предлагает компилятору включить другой исходный файл, имя которого указывается после директивы. Имя файла заключается в двойные кавычки или в <>. Например, следующие две директивы указывают компилятору на необходимость подключить заголовочный файл стандартной библиотеки ввода/вывода: #include "stdio.h" #include <stdio.h>
Подключаемые файлы также могут иметь директивы #include. Если это имеет место, то говорят о вложенных подключениях. Например, следующая программа подключает файл, который, в свою очередь, подключает другой файл:
/* файл программы */ #include <stdio.h> int main(void) { #include "one" return 0; }
/* подключаемый файл ONE */ printf("This is from the first include file.\n"); #include "two"
/* подключаемый файл TWO */ printf("This is from the second include file.\n");
Если подключаемый файл указан в <>, то поиск будет происходить в стандартных каталогах, предназначенных для хранения заголовочных файлов. В случае, если подключаемый файл заключен в двойные кавычки, поиск будет происходить в текущем рабочем каталоге. Если файл не найден, то поиск продолжается в стандартных каталогах.
