- •Кетков ю.Л.
- •Раздел 5. Системные данные текстового типа 33
- •Раздел 6. Основные синтаксические конструкции языка c 46
- •Раздел 7. Указатели и ссылки 59
- •Раздел 8. Функции и их аргументы 62
- •Раздел 9. Работа с массивами. 74
- •Раздел 10. Пользовательские типы данных. 95
- •Раздел 11. Работа с файлами 104
- •Раздел 12. Библиотеки стандартных и нестандартных функций 118
- •Раздел 15. Классы. Создание новых типов данных 131
- •Раздел 16. Классы как средство создания больших программных комплексов 150
- •Раздел 17. Прерывания, события, обработка исключений 167
- •Введение
- •Раздел 1. Немного истории
- •Раздел 2. Структура программы на языке c
- •Раздел 3. Среда программирования
- •Раздел 4. Системные данные числового типа
- •4.1. Типы числовых данных и их представление в памяти эвм
- •4.1.1. Внутреннее представление целочисленных данных
- •4.1.2. Однобайтовые целочисленные данные
- •4.1.3. Двухбайтовые целочисленные данные
- •4.1.4. Четырехбайтовые целочисленные данные
- •4.1.5. Восьмибайтовые целочисленные данные
- •4.2. Внутреннее представление данных вещественного типа
- •4.3. Внешнее представление числовых констант
- •4.4. Объявление и инициализация числовых переменных
- •4.5. Ввод числовых данных по запросу программы
- •4.5.1. Потоковый ввод данных числового типа
- •4.5.2. Форматный ввод
- •4.6. Вывод числовых результатов
- •4.6.1. Форматный вывод
- •4.6.2. Потоковый вывод
- •4.7. Примеры программ вывода числовых данных
- •4.8. Операции над числовыми данными целого типа
- •4.9. Операции над числовыми данными вещественного типа
- •Раздел 5. Системные данные текстового типа
- •5.1. Символьные данные и их представление в памяти эвм
- •5.2. Строковые данные и их представление в памяти эвм
- •5.3. Ввод текстовых данных во время работы программы
- •5.3.1. Форматный ввод
- •5.3.3. Потоковый ввод
- •5.3.4. Специальные функции ввода текстовых данных
- •5.4. Вывод текстовых данных
- •5.4.1. Форматный вывод
- •5.5.2. Операции над строковыми данными
- •5.6. Управление дисплеем в текстовом режиме
- •Раздел 6. Основные синтаксические конструкции языка c
- •6.1. Заголовок функции и прототип функции
- •6.2. Объявление локальных и внешних данных
- •6.3. Оператор присваивания
- •6.4. Специальные формы оператора присваивания
- •6.5. Условный оператор
- •6.6. Оператор безусловного перехода
- •6.7. Операторы цикла
- •6.8. Дополнительные операторы управления циклом
- •6.9. Оператор выбора (переключатель)
- •6.10. Обращения к функциям
- •6.11. Комментарии в программах
- •Раздел 7. Указатели и ссылки
- •7.1. Объявление указателей
- •7.2. Операции над указателями
- •7.3. Ссылки
- •Раздел 8. Функции и их аргументы
- •8.1. Параметры-значения
- •8.2. Параметры-указатели
- •8.3. Параметры-ссылки
- •8.4. Параметры-константы
- •8.5. Параметры по умолчанию
- •8.6. Функции с переменным количеством аргументов
- •8.7. Локальные, глобальные и статические переменные
- •8.8. Возврат значения функции
- •8.9. Рекурсивные функции
- •8.10. Указатели на функцию и передача их в качестве параметров
- •8.11. "Левые" функции
- •Раздел 9. Работа с массивами.
- •9.1. Объявление и инициализация массивов.
- •9.2. Некоторые приемы обработки числовых массивов
- •9.2. Программирование задач линейной алгебры
- •9.2.1. Работа с векторами
- •9.2.2.Работа с матрицами
- •9.3. Поиск
- •9.3.1. Последовательный поиск
- •9.3.2. Двоичный поиск
- •9.4. Сортировка массивов.
- •9.4.1. Сортировка методом пузырька
- •9.4.2. Сортировка методом отбора
- •9.4.3. Сортировка методом вставки
- •9.4.4. Сортировка методом Шелла
- •9.4.5.Быстрая сортировка
- •9.5. Слияние отсортированных массивов
- •9.6. Динамические массивы.
- •Раздел 10. Пользовательские типы данных.
- •10.1. Структуры
- •10.1.1. Объявление и инициализация структур
- •10.1.2. Структуры – параметры функций
- •10.1.3.Функции, возвращающие структуры
- •10.2. Перечисления
- •10.3. Объединения
- •Раздел 11. Работа с файлами
- •11.1.Файлы в операционной системе
- •11.1. Текстовые (строковые) файлы
- •11.2. Двоичные файлы
- •11.3. Структурированные файлы
- •11.4. Форматные преобразования в оперативной памяти
- •11.5. Файловые процедуры в системе bcb
- •11.5.1. Проверка существования файла
- •11.5.2. Создание нового файла
- •11.5.3. Открытие существующего файла
- •11.5.4. Чтение из открытого файла
- •11.5.5. Запись в открытый файл
- •11.5.6. Перемещение указателя файла
- •11.5.7. Закрытие файла
- •11.5.8. Расчленение полной спецификации файла
- •11.5.9. Удаление файлов и пустых каталогов
- •11.5.10. Создание каталога
- •11.5.11. Переименование файла
- •11.5.12. Изменение расширения
- •11.5.13. Опрос атрибутов файла
- •11.5.14. Установка атрибутов файла
- •11.5.15. Опрос и изменение текущего каталога
- •11.6. Поиск файлов в каталогах
- •Раздел 12. Библиотеки стандартных и нестандартных функций
- •12.2. Организация пользовательских библиотек
- •12.3. Динамически загружаемые библиотеки
- •13.1. Препроцессор и условная компиляция
- •13.2. Компилятор bcc.Exe
- •13.3. Утилита grep.Com поиска в текстовых файлах
- •14.1. Переопределение (перегрузка) функций
- •14.2. Шаблоны функций
- •Раздел 15. Классы. Создание новых типов данных
- •15.1. Школьные дроби на базе структур
- •15.2. Школьные дроби на базе классов
- •15.3. Класс на базе объединения
- •15.4. Новые типы данных на базе перечисления
- •15.5. Встраиваемые функции
- •15.6. Переопределение операций (резюме)
- •15.8. Конструкторы и деструкторы (резюме)
- •Раздел 16. Классы как средство создания больших программных комплексов
- •16.1. Базовый и производный классы
- •16.1.1.Простое наследование
- •16.1.2. Вызов конструкторов и деструкторов при наследовании
- •16.1.3. Динамическое создание и удаление объектов
- •16.1.4. Виртуальные функции
- •16.1.5. Виртуальные деструкторы
- •16.1.6. Чистые виртуальные функции и абстрактные классы
- •16.2. Множественное наследование и виртуальные классы
- •16.3. Объектно-ориентированный подход к созданию графической системы
- •Раздел 17. Прерывания, события, обработка исключений
- •17.1. Аппаратные и программные прерывания
- •17.2. Исключения
12.3. Динамически загружаемые библиотеки
В довольно ранних версиях операционных систем наряду со статическими библиотеками объектных модулей появились динамически загружаемые библиотеки с расширением .dll (от Dynamic-link libraries). Динамически загружаемые библиотеки Windows могут иметь и другие расширения – .exe или .drv.
Основная разница между статическими и динамическими библиотеками заключается в следующем. Если используется статическая библиотека, то на стадии редактирования связей в состав исполняемого модуля включаются все функции, для которых обнаружено обращение из текста исходной программы. В отличие от этого вызов модуля из динамической библиотеки происходит только на стадии выполнения программы. При таком подходе библиотечные функции не включаются в состав исполняемого модуля, его размеры становятся меньше и, тем самым, экономится место, занимаемое исполняемыми файлами на диске.
У каждого из этих подходов есть свои плюсы и минусы. При использовании статических библиотек размер исполняемого модуля возрастает, т.к. к нему подключаются все функции, упомянутые в программе. Однако такой модуль можно выполнить на любом компьютере независимо от того, установлена ли там соответствующая система программирования или нет. При использовании динамически загружаемых библиотек размер исполняемого модуля не так велик, но для его работы требуется присутствие в оперативной памяти динамической библиотеки, из которой в случае необходимости потребуется запустить тот или иной модуль. Поэтому при переносе программы на другой компьютер придется кроме исполняемого модуля захватить и всю цепочку задействованных динамических библиотек. Правда, в разумных системах программирования предусмотрен режим компиляции с включением всех вызываемых функций в состав исполняемого модуля.
Раздел 13. Дополнительные сведения о системе программирования Borland C++ 3.1
13.1. Препроцессор и условная компиляция
В состав системы программирования BC 3.1 входит препроцессор cpp.exe, который выполняет следующую подготовительную работу перед компиляцией программы:
включает в программу тексты указанных файлов;
исключает из программы фрагменты, не удовлетворяющие заданным условиям;
осуществляет предусмотренную замену (макроподстановка);
заменяет Esc-последовательности их числовыми кодами;
объединяет смежные символьные строки и устраняет символы переноса строк.
Все действия препроцессора диктуются директивами, которые программист включает в текст своей программы. Первым символом директивы является символ #.
Для включения в текст программы указанных файлов используется директива #include (от англ. – включить), допускающая два следующие формата:
#include <file_name>
#include "file_name"
Угловые скобки являются указанием препроцессору, что поиск файла с заданным именем надо начинать с системного каталога (например, с каталога c:\bc\include). Если в указанном каталоге файл file_name не обнаружен, то поиск продолжается сначала с текущего каталога, а затем по всем каталогам, перечисленным в директиве PATH операционной системы. Если имя файла заключено в двойные кавычки, то поиск начинается с текущего каталога.
Обычно, с помощью директивы #include к программе подключаются системные и пользовательские заголовочные файлы с расширением .h (от header – заголовок). Однако точно так же к программе можно подключить ранее заготовленный фрагмент исходного кода, оформленный в виде текстового файла с расширением .inc.
Замена одной цепочки символов в тексте программы на другую цепочку символов реализуется с помощью макроподстановки #define (от англ. – определить):
#define s1s2s3...sn q1q2...qm
При этом цепочка символов s1s2s3...sn в тексте исходной программы будет заменена на цепочку q1q2...qm . Пробелы перед замещающей цепочкой и в ее конце игнорируются. Замене не подвергаются значения строк и комментарии. Заменяющий фрагмент может оказаться и многостроковым. В этом случае в конце каждой строки помещается символ переноса – "\". В приведенной ниже программе содержится несколько наиболее характерных примеров использования директивы #define:
#include <stdio.h>
#include <conio.h>
#define Nmax 100
#define max(a,b) ((a)>(b))?(a):(b)
#define print(a) printf("\n%s=%d\n",#a,a);\
getch()
void main()
{ int x=5,y=8;
int z=Nmax;
int w=max(x*y,z);
print(x);
print(y);
print(z);
print(w);
}
//=== Результат работы ===
x=5
y=8
z=100
w=100
Обратите внимание на некоторые тонкости в приведенных подстановках.
Во-первых, аргументы макроопределения-функции max в замещающем выражении заключены в круглые скобки. Это позволяет использовать в конкретных обращениях нормальные арифметические выражения. Представим себе, что в программе достаточно часто приходится использовать операцию возведения в квадрат. Если для этой цели макроподстановку Square(x) определить без использования скобок (#define Square(x) x*x), то для арифметического выражения Square(1+z) результат такой подстановки даст 1+z*1+z=2*z+1, т.е. заведомо неправильное значение.
Наличие круглых скобок тоже не является стопроцентной гарантией правильности результата подстановки. Например, попытка обратиться к макросу max необычным образом сообщений об ошибках не вызовет, но и результат может оказаться далеким от истины:
w=max(x+=2,y+=3); //результат подстановки w=14
w=max(x+2,y+3); //результат правильный w=11
В бесскобочном макроопределении первое обращение привело бы к синтаксической ошибке (Lvalue required – требуется левое значение).
Во-вторых, макроопределение max не зависит от типа используемых данных.
В-третьих, в макроопределении print использована довольно редко описываемая возможность вывода имени переменной (#a – интерпретируется препроцессором как имя переменной a). Если бы мы включили эту переменную в форматную строку (printf("\na=%d",a);), то ничего хорошего из этого бы не вышло. Так как на содержимое строк макроподстановка не распространяется, то при каждом обращении вместо имени очередной переменной выводился бы символ 'a'.
В операторе макроподстановки иногда используется операция склейки лексем (аналог того, что при работе со строками называют конкатенацией):
#define Paste(a,b) a##b
В результате такой подстановки строка Paste(x,4) будет заменена на x4.
Две группы следующих директив используются для организации "условной компиляции":
#define...#ifdef...ifndef...#undef
#if...#elif...#elif...#else...#endif
На самом деле препроцессор компиляцией не занимается, он просто включает или отключает фрагменты исходной программы, которые в дальнейшем будут или не будут обрабатываться компилятором.
Если заглянуть в любой заголовочный файл из каталога ...BC\INCLUDE, например, в файл math.h, то в самом его начале (исключая комментарий по поводу авторских прав) находятся следующие строки:
#ifndef __MATH_H
#define __MATH_H
#if !defined(___DEFS_H)
#include <_defs.h>
#endif
В чем смысл двух первых строк? Сначала проверяется, была ли ранее объявлена подстановка для идентификатора __MATH_H (этот идентификатор является уникальным, т.к. он повторяет имя заголовочного файла). Если такого указания еще не было, то следующая строка объявляет о необходимости такой подстановки в строках программы, следующих ниже по тексту (не важно, что замещающее выражение пусто), и все остальное содержимое файла, который мы присоединяем по директиве #include <math.h>, будет включено в текст наше программы. Но если такая подстановка ранее была заявлена, то повторное подключение файла math.h не произойдет. Это позволяет избежать дублирования констант и других макроопределений, которые могли бы появиться из-за повторения заголовочного файла.
Примерно такую же функцию выполняют три следующие строки. Первая из них проверяет, не состоялось ли ранее присвоение значения идентификатору ___DEFS_H. Если такого действия еще не было, то файл defs.h будет загружен в оперативную память. В противном случае фрагмент программы до строки #endif не будет передан компилятору. Кстати, подобная тройка строк присутствует во многих заголовочных файлах, и такая проверка предупреждает повторную загрузку файла defs.h.
Более детально, первая группа директив препроцессора выполняет следующие действия:
Таблица 13.1
Директива |
Пояснение |
#define name value |
Объявляет о необходимости замены выражения name на значение value в строках программы, расположенных ниже |
#ifdef name |
Проверяет, была ли объявлена замена выражения name (true, если была) |
#ifndef name |
Проверяет, была ли объявлена замена выражения name (true, если не была) |
#undef name |
Отменяет указание о замене выражения name в строках программы, расположенных ниже |
Вторая группа директив предназначена для включения или отключения фрагментов программы пользователя средствами, напоминающими действие условного оператора if – then – else:
#if (условие_1) //если выполнено условие_1
фрагмент_1
#elif (условие_2) //если выполнено условие_2
фрагмент_2
#elif (условие_3) //если выполнено условие_3
фрагмент_3
.............
#else //если не выполнено ни одно из предшествующих условий
фрагмент_k
#endif //конец проверок
Если результат проверки дал положительный результат, то следующий за ним фрагмент программы будет передан компилятору. В противном случае это фрагмент исключается из текста исходной программы (не затирается, а просто не поступает на вход компилятора). Условия, которые задаются в директивах проверки, могут выполняться только над константными выражениями:
#define name 1
.............
#if (name==1)
Одним из достаточно частых применений группы #if...#endif является подключение или отключение отладочных выдач.