
- •Происхождение языка с
- •Язык среднего уровня
- •Структурированный язык
- •Язык программирования
- •Компиляторы против интерпретаторов
- •Вид программ на с
- •Библиотеки и компоновка
- •Раздельная компиляция
- •Карта памяти с-программы
- •Переменные, константы, операторы и выражения
- •Идентификаторы
- •Типы данных
- •Модификаторы типов
- •Модификаторы доступа
- •Объявление переменных
- •Локальные переменные
- •Формальные параметры
- •Глобальные переменные
- •Спецификаторы хранения
- •Статические переменные
- •Статические локальные переменные
- •Статические глобальные переменные
- •Регистровые переменные
- •Оператор присваивания
- •Многочисленное присваивание
- •Преобразование типов при присваивании
- •Инициализация переменных
- •Константы
- •Символьные константы с обратным слэшем
- •Операторы
- •Арифметические операторы
- •Увеличение и уменьшение
- •Операторы отношения и логические операторы
- •Битовые операторы
- •Оператор ?
- •Операторы указания & и *
- •Оператор 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
- •Операторы препроцессора # и ##
- •Предопределенные макросы
- •Комментарии
Сопоставление классического и современного объявления параметров
Ранние версии использовали другой метод объявления параметров функции, который иногда называется классическим. Фактически очень мало можно найти программ, написанных с использованием классического объявления параметров функций. С++ поддерживает только современный способ. Тем не менее, важно знать классический способ, поскольку много старых программ, написанных на С, используют его.
Классический способ объявления параметров функций состоит из двух частей: списка параметров, находящихся в круглых скобках, следующих за именем функции, и собственно объявления параметров, которое происходит между закрывающей круглой и открывающей фигурной скобкой. Ниже приведен общий вид классического определения параметров: тип имя_функции (параметр1, параметр2, ..., параметрN) тип параметр1; тип параметр2; ... тип параметрN; { код функции }
Ниже показан пример современного объявления: char *f(char *str1, int count, int index) { .... }
При классическом объявлении эта же функция будет выглядеть так: char *f(str1, count, index) char *str1; int count, index; { ... } Обратим внимание, что в классическом способе может присутствовать более одного параметра после имени.
Надо помнить, что хотя классический способ и устарел, Borland С++ по-прежнему может корректно компилировать написанные на С программы, использующие его. Следовательно, не стоит волноваться при компиляции С-программы, использующей классическое объявление. Но программы С++ должны использовать современный способ.
Указатели на функции
Чаще всего к ошибкам приводит использование указателей па функцию. Хотя функция - это не переменная, она по-прежнему имеет физическое положение в памяти, которое может быть присвоено указателю. Адрес, присвоенный указателю, является входной точкой в функцию. Указатель может использоваться вместо имени функции. Он также позволяет передавать функции как обычные аргументы в другие функции.
Чтобы понять, как работают указатели на функции, необходимо понять, как компилируются и вызываются функции в С. По мере компиляции функций исходный код преобразуется в объектный и устанавливается точка входа. При вызове функции происходит обращение к данной точке входа. Поэтому указатель на функцию может использоваться для вызова функции.
Адрес функции получается при использовании имени функции без каких-либо скобок или аргументов. (Очень похоже на массивы, где адрес получается с использованием имени массива без индексов.) Рассмотрим следующую программу, предназначенную для демонстрации нюансов объявления: #include <stdio.h> #include <string.h> void check(char *a, char *b, int (*cmp) (const char *, const char *)); int main(void) { char s1 [80], s2[80]; int (*p) (const char*, const char*); p = strcmp; /* получение адреса strcmp() */ gets(s1); gets (s2); check(s1, s2, p); return 0; } void check (char *a, char *b, int (*cmp) (const char *, const char *)) { printf("Testing for equality.\n"); if(!(*cmp) (a, b)) printf("Equal"); else printf("Not equal"); }
Когда вызывается check(), ей передаются два указателя на символы и один указатель на функцию. В функции check() аргументы объявляются как указатели на символы и указатель на функцию. Надо обратить внимание на способ объявления указателя на функцию. Следует использовать аналогичный метод при объявлении других указателей на функцию, за исключением тех случаев, когда отличается возвращаемый тип или передаваемые параметры. Скобки вокруг *cmp необходимы для правильной интерпретации компилятором данного выражения.
При объявлении указателя на функцию можно по-прежнему использовать прототип, как показано в предыдущей программе. Тем не менее в большинстве случаев имена настоящих параметров неизвестны. Поэтому можно оставить имена пустыми или можно использовать любые понравившиеся имена.
Рассмотрим работу функции strcmp() в функции check(). Оператор if (!(*cmp) (a, b) ) printf("Equal"); осуществляет вызов функции, в данном случае strcmp(), с помощью cmp, который указывает на данную функцию. Вызов происходит с аргументами a и b. Данный оператор демонстрирует общий вид использования указателя на функцию для вызова функции, на которую он указывает. Круглые скобки вокруг *cmp необходимы вследствие наличия приоритетов. На самом деле можно напрямую использовать cmp, как показано ниже: if (!cmp(a, b)) printf("Equal"); Данная версия также вызывает функцию, на которую указывает cmp, но она использует нормальный синтаксис. Использование ( cmp) помогает всем, читающим программу, понять, что указатель на функцию используется для вызова функции вместо вызова функции cmp. Возможно вызвать напрямую check(), используя strcmp, как показано ниже: check(s1, s2, strcmp); Данный оператор устраняет необходимость наличия дополнительной переменной-указателя.
Можно задаться вопросом, почему кто-то хочет написать программу таким способом. В данном примере ничего не достигается, но появляются большие проблемы. Тем не менее иногда бывают моменты, когда выгодно передавать функции в процедуры или хранить массивы функций. Следующий пример демонстрирует использование указателей на функции. При написании интерпретатора наиболее типично использование вызовов функций для различных подпрограмм поддержки, типа синус, косинус и тангенс. Вместо использования большого списка для оператора switch, можно использовать массив указателей на функции, в котором доступ к необходимой функции осуществляется с помощью индекса. В данной программе check() может использоваться для проверки как алфавитного, так и численного равенства простым вызовом различных функций сравнения. #include <stdio.h> #include <ctype.h> #include <string.h> #include <stdlib.h> void check(char *a, char *b, int (*cmp) (const char *, const char *)); int numcmp (const char *a, const char *b) ; int main(void) { char s1[80], s2 [80]; gets (s1); gets (s2); if(isalpha(*s1)) check (s1, s2, strcmp); else check(s1, s2, numcmp); return 0; } void check(char *a, char *b, int (*cmp) (const char *,const char *)) { printf("Testing for equality.\n"); if(!(*cmp) (a, b)) printf ("Equal"); else printf("Hot equal"); } int numcmp (const char *a, const char *b) { If(atoi(a)==atoi(b)) return 0; else return 1; }