Содержание
Введение
1. Установка программ под Windows
1.2. Добавление пути в переменную PATH
1.3. Работа с командной строкой
1.4. Установка MinGW и MSYS
1.5. Установка MinGW-W64
1.6. Установка MSYS2 и MinGW-W64
1.7. Установка и настройка редактора Eclipse
1.8. Создание проектов в редакторе Eclipse
2. Первые шаги
2.2. Создание пустого проекта в редакторе Eclipse
2.3. Добавление в проект файла с программой
2.4. Добавление в проект заголовочного файла
2.5. Компиляция и запуск программы в редакторе Eclipse
2.6. Структура программы
2.7. Комментарии в программе
2.8. Вывод данных
2.9. Ввод данных
2.9.3. Ввод строки
2.10. Интерактивный ввод символов
2.11. Получение данных из командной строки
2.12. Предотвращение закрытия окна консоли
2.13. Настройка отображения русских букв в консоли
2.14. Преждевременное завершение выполнения программы
3. Переменные и типы данных
3.2. Именование переменных
3.3. Типы данных
3.4. Целочисленные типы фиксированного размера
3.5. Оператор sizeof и тип size_t
3.6. Инициализация переменных
3.7. Оператор typedef
3.8. Константы
3.9. Спецификаторы хранения
3.10. Области видимости переменных
3.11. Массивы
3.12. Строки
3.13. Указатели
3.14. Динамическое выделение памяти
3.15. Структуры
3.16. Битовые поля
3.17. Объединения
3.18. Перечисления
3.19. Приведение типов
4. Операторы и циклы
4.2. Побитовые операторы
4.3. Операторы присваивания
4.4. Оператор запятая
4.5. Операторы сравнения
4.6. Приоритет выполнения операторов
4.7. Оператор ветвления if
4.8. Оператор ?:
4.9. Оператор выбора switch
4.10. Цикл for
4.11. Цикл while
4.12. Цикл do...while
4.13. Оператор continue: переход на следующую итерацию цикла
4.14. Оператор break: прерывание цикла
4.15. Оператор goto
5. Числа
5.2. Основные функции для работы с числами
5.3. Округление чисел
5.4. Тригонометрические функции
5.5. Преобразование строки в число
5.6. Преобразование числа в строку
5.7. Генерация псевдослучайных чисел
5.8. Бесконечность и значение NAN
6. Массивы
6.2. Определение количества элементов и размера массива
6.3. Получение и изменение значения элемента массива
6.4. Перебор элементов массива
6.5. Доступ к элементам массива с помощью указателя
6.6. Массивы указателей
6.7. Динамические массивы
6.8. Многомерные массивы
6.9. Поиск минимального и максимального значений
6.10. Сортировка массива
6.11. Проверка наличия значения в массиве
6.12. Копирование элементов из одного массива в другой
6.13. Сравнение массивов
6.14. Переворачивание массива
7. Символы и C-строки
7.2. Настройка локали
7.3. Изменение регистра символов
7.4. Проверка типа содержимого символа
7.5. Объявление и инициализация C-строки
7.6. Доступ к символам внутри C-строки
7.7. Определение длины строки
7.8. Перебор символов C-строки
7.9. Основные функции для работы с C-строками
7.10. Поиск и замена в C-строке
7.11. Сравнение С-строк
7.12. Форматирование С-строк
8. Широкие символы и L-строки
8.2. Вывод и ввод широких символов
8.3. Изменение регистра символов
8.4. Проверка типа содержимого широкого символа
8.5. Преобразование широких символов в обычные и наоборот
8.6. Объявление и инициализация L-строки
8.7. Доступ к символам внутри L-строки
8.8. Определение длины L-строки
8.9. Перебор символов L-строки
8.10. Вывод и ввод L-строк
8.11. Преобразование C-строки в L-строку и наоборот
8.12. Преобразование кодировок
8.13. Основные функции для работы с L-строками
8.14. Поиск и замена в L-строке
8.15. Сравнение L-строк
8.16. Преобразование L-строки в число
8.17. Преобразование числа в L-строку
8.18. Типы char16_t и char32_t
9. Работа с датой и временем
9.2. Форматирование даты и времени
9.3. «Засыпание» программы
9.4. Измерение времени выполнения фрагментов кода
10. Пользовательские функции
10.2. Расположение объявлений и определений функций
10.3. Способы передачи параметров в функцию
10.4. Передача массивов и строк в функцию
10.5. Переменное количество параметров
10.6. Константные параметры
10.7. Статические переменные и функции
10.8. Способы возврата значения из функции
10.9. Указатели на функции
10.10. Передача в функцию и возврат данных произвольного типа
10.11. Рекурсия
10.12. Встраиваемые функции
11. Обработка ошибок
11.2. Предупреждающие сообщения при компиляции
11.3. Переменная errno и вывод сообщения об ошибке
11.4. Способы поиска ошибок в программе
11.5. Отладка программы в редакторе Eclipse
12. Чтение и запись файлов
12.2. Указание пути к файлу
12.3. Режимы открытия файла
12.4. Запись в файл
12.5. Чтение из файла
12.6. Чтение и запись двоичных файлов
12.7. Файлы произвольного доступа
12.8. Создание временных файлов
12.9. Перенаправление ввода/вывода
12.10. Работа с буфером ввода и вывода
13. Низкоуровневые потоки ввода и вывода
13.2. Чтение из файла и запись в файл
13.3. Файлы произвольного доступа
13.4. Создание временных файлов
13.5. Дескрипторы потоков ввода/вывода
13.6. Преобразование низкоуровневого потока в обычный
13.7. Создание копии потока
13.8. Перенаправление потоков
14. Работа с файловой системой
14.2. Переименование, перемещение и удаление файла
14.3. Проверка прав доступа к файлу и каталогу
14.4. Изменение прав доступа к файлу
14.5. Делаем файл скрытым
14.6. Получение информации о файле
14.7. Функции для работы с дисками
14.8. Функции для работы с каталогами
14.9. Перебор объектов, расположенных в каталоге
15. Потоки и процессы
15.1.2. Синхронизация потоков
15.2. Функции для работы с потоками, объявленные в файле process.h
15.3. Потоки POSIX
15.3.2. Синхронизация потоков
15.4. Запуск процессов
15.5. Получение идентификатора процесса
16. Создание библиотек
16.1.2. Создание статической библиотеки в редакторе Eclipse
16.2. Динамические библиотеки
16.2.2. Создание динамической библиотеки в редакторе Eclipse
16.2.3. Загрузка динамической библиотеки во время выполнения программы
16.2.4. Экспортируемые и внутренние функции
17. Прочее
17.2. Выполнение системных команд
17.3. Получение и изменение значений системных переменных
17.4. Директивы препроцессора
17.5. Создание значка приложения
Заключение
Приложение. Описание электронного архива
Предметный указатель
Текст
                    Николай Прохоренок
Санкт-Петербург
«БХВ-Петербург»
2020


УДК 004.438 C ББК 32.973.26-018.1 П84 Прохоренок Н. А. П84 Язык C. Самое необходимое. — СПб.: БХВ-Петербург, 2020. — 480 с.: ил. — (Самое необходимое) ISBN 978-5 -9775-4116-9 Описан базовый синтаксис современного языка C: типы данных, операторы, условия, циклы, работа с числами, строками, массивами и указателями, создание пользовательских функций, модулей, статических и динамических библиотек. Рас- смотрены основные функции стандартной библиотеки языка C, а также функции, применяемые только в операционной системе Windows. Для написания, компиля- ции и запуска программ используется редактор Eclipse, а для создания исполняе- мого файла — компилятор gcc.exe версии 8.2, входящий в состав популярной биб- лиотеки MinGW-W64. Книга содержит большое количество практических приме- ров, помогающих начать программировать на языке C самостоятельно. Весь материал тщательно подобран, хорошо структурирован и компактно изложен, что позволяет использовать книгу как удобный справочник. Электронный архив с примерами находится на сайте издательства. Для программистов УДК 004.438 C ББК 32.973.26-018.1 Группа подготовки издания: Руководитель проекта Евгений Рыбаков Зав. редакцией Екатерина Сависте Компьютерная верстка Ольги Сергиенко Дизайн серии Марины Дамбиевой Оформление обложки Карины Соловьевой Подписано в печать 05.12 .19 . Формат 70×100 1 /16. Печать офсетная. Усл. печ. л . 38,7. Тираж 1300 экз. Заказ No "БХВ-Петербург", 191036, Санкт-Петербург, Гончарная ул., 20. Отпечатано с готового оригинал-макета ООО "Принт-М", 142300, М .О ., г. Чехов, ул. Полиграфистов, д. 1 ISBN 978-5-9775-4116-9 © ООО "БХВ", 2020 © Оформление. ООО "БХВ-Петербург", 2020
Оглавление Введение ............................................................................................................................ 9 Глава 1. Установка программ под Windows ............................................................ 11 1.1. Создание структуры каталогов ..............................................................................................11 1.2 . Добавление пути в переменную PATH .................................................................................12 1.3. Работа с командной строкой ..................................................................................................14 1.4. Установка MinGW и MSYS ...................................................................................................14 1.5. Установка MinGW-W64 .........................................................................................................20 1.6. Установка MSYS2 и MinGW-W64 ........................................................................................24 1.7. Установка и настройка редактора Eclipse ............................................................................ 29 1.8. Создание проектов в редакторе Eclipse ................................................................................ 34 Глава 2. Первые шаги .................................................................................................. 44 2.1. Первая программа...................................................................................................................44 2.2. Создание пустого проекта в редакторе Eclipse .................................................................... 47 2.3. Добавление в проект файла с программой ...........................................................................49 2.4. Добавление в проект заголовочного файла .......................................................................... 50 2.5. Компиляция и запуск программы в редакторе Eclipse ........................................................ 53 2.6. Структура программы ............................................................................................................57 2.7. Комментарии в программе.....................................................................................................63 2.8. Вывод данных .........................................................................................................................65 2.9. Ввод данных ............................................................................................................................71 2.9 .1 . Ввод одного символа ...................................................................................................71 2.9 .2 . Функция scanf() ............................................................................................................72 2.9 .3 . Ввод строки ..................................................................................................................77 2.10. Интерактивный ввод символов............................................................................................ 80 2.11 . Получение данных из командной строки ........................................................................... 82 2.12 . Предотвращение закрытия окна консоли ........................................................................... 84 2.13. Настройка отображения русских букв в консоли .............................................................. 86 2.14 . Преждевременное завершение выполнения программы................................................... 89 Глава 3. Переменные и типы данных ....................................................................... 91 3.1. Объявление переменной ........................................................................................................91 3.2. Именование переменных .......................................................................................................92
4 Оглавление 3.3. Типы данных ...........................................................................................................................93 3.4. Целочисленные типы фиксированного размера .................................................................. 97 3.5. Оператор sizeof и тип size_t .. .. ... ... .. ... ... .. ... ... .. ... ... .. ... ... .. ... ... .. ... ... .. ... ... .. ... ... .. ... ... .. ... ... .. ... ... ..99 3.6. Инициализация переменных ................................................................................................100 3.7. Оператор typedef ...................................................................................................................100 3.8. Константы .............................................................................................................................101 3.9. Спецификаторы хранения ....................................................................................................104 3.10. Области видимости переменных .......................................................................................105 3.11 . Массивы ..............................................................................................................................107 3.12 . Строки..................................................................................................................................110 3.13. Указатели.............................................................................................................................111 3.14 . Динамическое выделение памяти .....................................................................................118 3.14 .1 . Функции malloc() и free() .......................................................................................118 3.14 .2 . Функция calloc() ......................................................................................................119 3.14 .3 . Функция realloc() ....................................................................................................122 3.15 . Структуры ...........................................................................................................................123 3.16. Битовые поля.......................................................................................................................126 3.17 . Объединения .......................................................................................................................127 3.18. Перечисления ......................................................................................................................129 3.19. Приведение типов ...............................................................................................................130 Глава 4. Операторы и циклы.................................................................................... 133 4.1. Математические операторы .................................................................................................133 4.2. Побитовые операторы ..........................................................................................................135 4.3. Операторы присваивания .....................................................................................................138 4.4. Оператор запятая ..................................................................................................................138 4.5. Операторы сравнения ...........................................................................................................139 4.6. Приоритет выполнения операторов ....................................................................................141 4.7 . Оператор ветвления if...........................................................................................................142 4.8. Оператор ?: ...........................................................................................................................146 4.9. Оператор выбора switch .......................................................................................................147 4.10. Цикл for ...............................................................................................................................149 4.11 . Цикл while............................................................................................................................152 4.12 . Цикл do...while.....................................................................................................................152 4.13. Оператор continue: переход на следующую итерацию цикла .........................................153 4.14 . Оператор break: прерывание цикла ................................................................................... 153 4.15 . Оператор goto......................................................................................................................154 Глава 5. Числа.............................................................................................................. 156 5.1. Математические константы .................................................................................................159 5.2. Основные функции для работы с числами .........................................................................160 5.3. Округление чисел .................................................................................................................164 5.4. Тригонометрические функции ............................................................................................165 5.5. Преобразование строки в число ..........................................................................................165 5.6. Преобразование числа в строку...........................................................................................174 5.7. Генерация псевдослучайных чисел .....................................................................................177 5.8 . Бесконечность и значение NAN ...........................................................................................179
Оглавление 5 Глава 6. Массивы ........................................................................................................ 181 6.1. Объявление и инициализация массива ...............................................................................181 6.2. Определение количества элементов и размера массива....................................................183 6.3 . Получение и изменение значения элемента массива.........................................................184 6.4. Перебор элементов массива.................................................................................................185 6.5. Доступ к элементам массива с помощью указателя ..........................................................186 6.6. Массивы указателей .............................................................................................................189 6.7. Динамические массивы ........................................................................................................189 6.8. Многомерные массивы ........................................................................................................190 6.9 . Поиск минимального и максимального значений .............................................................193 6.10. Сортировка массива ...........................................................................................................195 6.11 . Проверка наличия значения в массиве .............................................................................198 6.12 . Копирование элементов из одного массива в другой ......................................................201 6.13. Сравнение массивов ...........................................................................................................203 6.14 . Переворачивание массива ..................................................................................................204 Глава 7. Символы и C-строки .................................................................................. 206 7.1 . Объявление и инициализация отдельного символа ...........................................................206 7.2. Настройка локали .................................................................................................................212 7.3. Изменение регистра символов.............................................................................................216 7.4. Проверка типа содержимого символа .................................................................................218 7.5. Объявление и инициализация C-строки..............................................................................222 7.6. Доступ к символам внутри C-строки ..................................................................................224 7.7. Определение длины строки .................................................................................................225 7.8. Перебор символов C-строки ................................................................................................226 7.9. Основные функции для работы с C-строками ....................................................................227 7.10. Поиск и замена в C-строке .................................................................................................232 7.11 . Сравнение С-строк..............................................................................................................237 7.12 . Форматирование С-строк ...................................................................................................242 Глава 8. Широкие символы и L-строки ................................................................. 244 8.1 . Объявление и инициализация широкого символа .............................................................245 8.2. Вывод и ввод широких символов ........................................................................................247 8.3. Изменение регистра символов.............................................................................................249 8.4. Проверка типа содержимого широкого символа ...............................................................251 8.5. Преобразование широких символов в обычные и наоборот.............................................255 8.6. Объявление и инициализация L-строки ..............................................................................256 8.7. Доступ к символам внутри L-строки...................................................................................257 8.8. Определение длины L-строки ..............................................................................................258 8.9. Перебор символов L-строки ................................................................................................259 8.10. Вывод и ввод L-строк .........................................................................................................260 8.11 . Преобразование C-строки в L-строку и наоборот............................................................263 8.12 . Преобразование кодировок................................................................................................265 8.13. Основные функции для работы с L-строками ..................................................................271 8.14 . Поиск и замена в L-строке .................................................................................................277 8.15 . Сравнение L-строк ..............................................................................................................282 8.16. Преобразование L-строки в число.....................................................................................286 8.17 . Преобразование числа в L-строку .....................................................................................294 8.18. Типы char16_t и char32_t ... .. ... ... .. ... ... .. ... ... .. ... ... .. ... ... .. ... ... .. ... ... .. ... ... .. ... ... .. ... ... .. ... ... .. ... ...297
6 Оглавление Глава 9. Работа с датой и временем......................................................................... 300 9.1. Получение текущей даты и времени ...................................................................................301 9.2. Форматирование даты и времени ........................................................................................305 9.3. «Засыпание» программы ......................................................................................................309 9.4. Измерение времени выполнения фрагментов кода ...........................................................311 Глава 10. Пользовательские функции .................................................................... 312 10.1 . Создание функции и ее вызов............................................................................................312 10.2 . Расположение объявлений и определений функций .......................................................315 10.3 . Способы передачи параметров в функцию ......................................................................318 10.4 . Передача массивов и строк в функцию ............................................................................320 10.5 . Переменное количество параметров .................................................................................324 10.6 . Константные параметры ....................................................................................................325 10.7 . Статические переменные и функции ................................................................................326 10.8 . Способы возврата значения из функции........................................................................... 328 10.9 . Указатели на функции ........................................................................................................330 10.10. Передача в функцию и возврат данных произвольного типа ....................................... 331 10.11 . Рекурсия ............................................................................................................................332 10.12 . Встраиваемые функции ....................................................................................................333 Глава 11. Обработка ошибок .................................................................................... 336 11.1 . Типы ошибок.......................................................................................................................336 11.2 . Предупреждающие сообщения при компиляции .............................................................337 11.3 . Переменная errno и вывод сообщения об ошибке ........................................................... 338 11.4 . Способы поиска ошибок в программе ..............................................................................341 11.5 . Отладка программы в редакторе Eclipse ..........................................................................345 Глава 12. Чтение и запись файлов ........................................................................... 351 12.1 . Открытие и закрытие файла ..............................................................................................351 12.2 . Указание пути к файлу .......................................................................................................354 12.3 . Режимы открытия файла ....................................................................................................356 12.4 . Запись в файл ......................................................................................................................358 12.5 . Чтение из файла ..................................................................................................................360 12.6 . Чтение и запись двоичных файлов ....................................................................................363 12.7 . Файлы произвольного доступа ..........................................................................................365 12.8 . Создание временных файлов .............................................................................................367 12.9 . Перенаправление ввода/вывода.........................................................................................369 12.10. Работа с буфером ввода и вывода ...................................................................................372 Глава 13. Низкоуровневые потоки ввода и вывода ............................................. 374 13.1 . Открытие и закрытие файла ..............................................................................................374 13.2 . Чтение из файла и запись в файл....................................................................................... 377 13.3 . Файлы произвольного доступа ..........................................................................................380 13.4 . Создание временных файлов .............................................................................................381 13.5 . Дескрипторы потоков ввода/вывода .................................................................................382 13.6 . Преобразование низкоуровневого потока в обычный .....................................................383 13.7 . Создание копии потока ......................................................................................................384 13.8 . Перенаправление потоков ..................................................................................................384
Оглавление 7 Глава 14. Работа с файловой системой ................................................................... 386 14.1 . Преобразование пути к файлу или каталогу.....................................................................386 14.2 . Переименование, перемещение и удаление файла ..........................................................390 14.3 . Проверка прав доступа к файлу и каталогу ......................................................................391 14.4 . Изменение прав доступа к файлу ......................................................................................393 14.5 . Делаем файл скрытым ........................................................................................................394 14.6 . Получение информации о файле .......................................................................................395 14.7 . Функции для работы с дисками .........................................................................................399 14.8 . Функции для работы с каталогами ....................................................................................401 14.9 . Перебор объектов, расположенных в каталоге ................................................................403 Глава 15. Потоки и процессы.................................................................................... 407 15.1 . Потоки в WinAPI ................................................................................................................407 15.1 .1 . Создание и завершение потока ..............................................................................407 15.1 .2 . Синхронизация потоков .........................................................................................412 15.2 . Функции для работы с потоками, объявленные в файле process.h . ... .. ... ... ... .. ... ... .. ... ... ..417 15.3 . Потоки POSIX .....................................................................................................................420 15.3 .1 . Создание и завершение потока ..............................................................................420 15.3 .2 . Синхронизация потоков .........................................................................................423 15.4 . Запуск процессов ................................................................................................................426 15.5 . Получение идентификатора процесса...............................................................................429 Глава 16. Создание библиотек .................................................................................. 430 16.1 . Статические библиотеки ....................................................................................................430 16.1 .1 . Создание статической библиотеки из командной строки ...................................430 16.1 .2 . Создание статической библиотеки в редакторе Eclipse ......................................434 16.2 . Динамические библиотеки .................................................................................................438 16.2 .1 . Создание динамической библиотеки из командной строки ................................438 16.2 .2 . Создание динамической библиотеки в редакторе Eclipse ...................................440 16.2 .3 . Загрузка динамической библиотеки во время выполнения программы ............443 16.2 .4 . Экспортируемые и внутренние функции ..............................................................446 16.2 .5 . Функция DllMain() ..................................................................................................446 Глава 17. Прочее .......................................................................................................... 448 17.1 . Регистрация функции, выполняемой при завершении программы ................................448 17.2 . Выполнение системных команд ........................................................................................449 17.3 . Получение и изменение значений системных переменных ............................................451 17.4 . Директивы препроцессора .................................................................................................454 17.5 . Создание значка приложения ............................................................................................455 Заключение ................................................................................................................... 459 Приложение. Описание электронного архива ....................................................... 461 Предметный указатель .............................................................................................. 463
Введение Добро пожаловать в мир языка C! Язык C — это компилируемый язык программирования высокого уровня, предна- значенный для самого широкого круга задач. С его помощью можно обрабатывать различные данные, писать инструкции для микроконтроллеров, создавать драйве- ры, консольные, нативные и оконные приложения, и даже целые операционные системы. Язык C — настоящая легенда, один из старейших языков программирова- ния в мире, переживший многие другие языки и оказавший влияние на синтаксис современных языков программирования, таких как Java, C#, C++, PHP и др. C — язык кроссплатформенный, позволяющий создавать программы, которые будут работать во всех операционных системах, но для каждой операционной сис- темы компиляцию нужно выполнять отдельно. В этой книге мы рассмотрим осно- вы языка C применительно к 64-битной операционной системе Windows. Для соз- дания исполняемого файла (EXE-файла) мы воспользуемся компилятором gcc.exe версии 8.2, входящим в состав популярной библиотеки MinGW-W64. Для удобства написания и запуска программы будем пользоваться редактором Eclipse. Существует несколько стандартов языка C: C90 (ANSI C/ISO C), C99 и C11. Для того чтобы использовать правила конкретного стандарта, нужно в составе команды компиляции указать следующие флаги: -std=c90, -std=c99 или -std=c11. Мы не будем указывать эти флаги, т. к . станем изучать современный язык C, который включает возможности стандарта C11. Узнать используемый стандарт языка C внутри программы можно с помощью макроса __STDC_VERSION__: printf("%ld\n", __STDC_VERSION__); /* 201710 */ При указании флагов -std=c99 и -std=c11 результаты будут такими: - std=c99: 199901 - std=c11: 201112 Получить информацию о версии компилятора позволяет макрос __VERSION__: printf("%s\n", __VERSION__); /* 8.2.1 20181214 */ Давайте рассмотрим структуру книги. В главе 1 мы установим необходимое программное обеспечение под Windows: биб- лиотеку MinGW и редактор Eclipse.
10 Введение Глава 2 является вводной. Мы настроим среду разработки, скомпилируем и запус- тим первую программу — как из командной строки, так и из редактора Eclipse. Кроме того, вкратце разберемся со структурой программы, а также научимся выво- дить результат работы программы и получать данные от пользователя. В главе 3 мы познакомимся с переменными и типами данных в языке C, а в главе 4 рассмотрим различные операторы, предназначенные для выполнения определен- ных действий с данными. Кроме того, в этой главе мы изучим операторы ветвления и циклы, позволяющие изменить порядок выполнения программы. Глава 5 полностью посвящена работе с числами. Вы узнаете, какие числовые типы данных поддерживает язык C, научитесь применять различные функции, генериро- вать случайные числа и др. Глава 6 познакомит с массивами в языке C. Вы научитесь создавать как одномер- ные массивы, так и многомерные, перебирать элементы массива, сортировать, вы- полнять поиск значений и др. Глава 7 полностью посвящена работе с однобайтовыми символами и C-строками. В этой главе вы также научитесь работать с различными кодировками, настраивать локаль, выполнять форматирование строки и осуществлять поиск внутри строки. А в главе 8 мы рассмотрим работу с широкими символами и L-строками, позво- ляющими использовать двухбайтовые символы из кодировки Unicode. Глава 9 познакомит со способами работы с датой и временем. В главе 10 вы научитесь создавать пользовательские функции, позволяющие при- менять код многократно, а также объединять их в модули. В главе 11 мы рассмотрим способы поиска ошибок в программе и научимся отла- живать программу в редакторе Eclipse. Главы 12–14 научат работать с файлами и каталогами, читать и записывать файлы в различных форматах. Глава 15 познакомит с многопоточными приложениями, позволяющими значи- тельно повысить производительность программы за счет параллельного выполне- ния задач несколькими потоками управления. В главе 16 рассматриваются способы создания статических и динамических биб- лиотек. Статические библиотеки становятся частью программы при компиляции, а динамические библиотеки подгружаются при запуске программы или при ее выполнении. И наконец, в главе 17 мы рассмотрим возможность запуска функции при заверше- нии работы приложения, научимся выполнять системные команды и получать зна- чения переменных окружения, изучим директивы препроцессора, а также создадим значок для EXE-файла. Все листинги из этой книги вы найдете в файле Listings.doc, электронный архив с которым можно загрузить с FTP-сервера издательства "БХВ-Петербург" по ссыл- ке ftp://ftp.bhv.ru/9785977541169.zip или со страницы книги на сайте www.bhv.ru (см. приложение). Желаю приятного изучения и надеюсь, что книга поможет вам реализовать как самые простые, так и самые сложные приложения.
ГЛАВА 1 Установка программ под Windows Вначале необходимо сделать два замечания.  Во-первых, имя пользователя компьютера должно состоять только из латинских букв. Никаких русских букв и пробелов, т. к . многие программы сохраняют раз- личные настройки и временные файлы в каталоге C:\Users\<Имя пользователя>. Если имя пользователя содержит русские буквы, то они могут быть искажены до неузнаваемости из-за неправильного преобразования кодировок, и программа не сможет сохранить настройки. Помните, что в разных кодировках русские буквы могут иметь разный код. Разработчики программ в основном работают с англий- ским языком и ничего не знают о проблемах с кодировками, т. к. во всех одно- байтовых кодировках и в кодировке UTF-8 коды латинских букв одинаковы. Так что, если хотите без проблем заниматься программированием, то от использова- ния русских букв в имени пользователя лучше отказаться.  Во-вторых, имена каталогов и файлов в пути не должны содержать русских букв и пробелов. Допустимы только латинские буквы, цифры, тире, подчеркивание и некоторые другие символы. С русскими буквами та же проблема, что описана в предыдущем пункте. При наличии пробелов в пути обычно требуется допол- нительно заключать путь в кавычки. Если этого не сделать, то путь будет обре- зан до первого встретившегося пробела. Такая проблема будет возникать при сборке и компиляции программ из командной строки. Соблюдение этих двух простых правил позволит избежать множества проблем в дальнейшем при сборке и компиляции программ сторонних разработчиков. 1.1. Создание структуры каталогов Перед установкой программ создадим следующую структуру каталогов: book cpp eclipse projects lib
12 Глава 1 Каталоги book и cpp лучше разместить в корне какого-либо диска. В моем случае это будет диск C:, следовательно, пути к содержимому каталогов — C:\book и C:\cpp. Можно создать каталоги в любом другом месте, но в пути не должно быть русских букв и пробелов — только латинские буквы, цифры, тире и подчеркивание. Ос- тальных символов лучше избегать, если не хотите проблем с компиляцией и запус- ком программ. В каталоге C:\book мы станем размещать наши тестовые программы и тренироваться при изучении работы с файлами и каталогами. Некоторые функции при неправиль- ном действии без проблем могут удалить все дерево каталогов, поэтому для экспе- риментов мы воспользуемся отдельным каталогом, а не каталогом C:\cpp, в котором у нас будет находиться все сокровенное, — обидно, если по случайности мы удалим все установленные программы и проекты. Внутри каталога C:\cpp у нас созданы три вложенных каталога:  eclipse — в этот каталог мы установим редактор Eclipse;  projects — в этом каталоге мы станем сохранять проекты из редактора Eclipse;  lib — путь к этому каталогу мы добавим в системную переменную PATH и будем размещать в ней различные библиотеки, которые потребуются для наших про- грамм. 1.2. Добавление пути в переменную PATH Когда мы в командной строке вводим название программы без предварительного указания пути к ней, то вначале поиск программы выполняется в текущем рабочем каталоге (обычно это каталог, из которого запускается программа), а затем в путях, указанных в системной переменной PATH. Аналогично выполняется поиск библио- тек динамической компоновки при запуске программы с помощью двойного щелч- ка на значке файла, но системные каталоги имеют более высокий приоритет, чем каталоги, указанные в переменной PATH. Пути в системной переменной PATH про- сматриваются слева направо до первого нахождения искомого объекта. Так что, если в путях расположено несколько объектов с одинаковыми именами, то мы по- лучим только первый найденный объект. Поэтому, если вдруг запустилась другая программа, следует либо удалить путь, ведущий к другой программе, либо пере- местить новый путь в самое начало системной переменной PATH. Давайте добавим путь к каталогу C:\cpp\lib в переменную PATH. Для того чтобы из- менить системную переменную в Windows, переходим в Параметры | Панель управления | Система и безопасность | Система | Дополнительные параметры системы. В результате откроется окно Свойства системы (рис. 1.1). На вкладке Дополнительно нажимаем кнопку Переменные среды. В открывшемся окне (рис. 1.2) в списке Системные переменные выделяем строку с переменной Path и нажимаем кнопку Изменить. В следующем окне (рис. 1 .3) изменяем значение в поле Значение переменной — для этого переходим в конец существующей стро- ки, ставим точку с запятой, а затем вводим путь к каталогу C:\cpp\lib: <Текущее значение>;C:\cpp\lib Сохраняем изменения.
Установка программ под Windows 13 Рис. 1.1 . Окно Свойства системы Рис. 1.2 . Окно Переменные среды
14 Глава 1 Рис. 1.3 . Окно Изменение системной переменной Добавлять пути в переменную PATH мы будем несколько раз, поэтому способ изме- нения значения этой системной переменной нужно знать наизусть. ВНИМАНИЕ! Случайно не удалите существующее значение переменной PATH, иначе другие прило- жения перестанут запускаться. 1.3. Работа с командной строкой При изучении материала мы часто будем прибегать к помощи приложения Командная строка. Вполне возможно, что вы никогда не пользовались командной строкой и не знаете, как запустить это приложение. Давайте рассмотрим некоторые способы его запуска в Windows:  через поиск находим приложение Командная строка;  нажимаем комбинацию клавиш <Windows>+<R>. В открывшемся окне вводим cmd и нажимаем кнопку OK;  находим файл cmd.exe в каталоге C:\Windows\System32;  в Проводнике щелкаем правой кнопкой мыши на свободном месте списка фай- лов, удерживая при этом нажатой клавишу <Shift>, и из контекстного меню вы- бираем пункт Открыть окно команд;  в Проводнике в адресной строке вводим cmd и нажимаем клавишу <Enter>. В некоторых случаях для выполнения различных команд могут потребоваться пра- ва администратора. Для того чтобы запустить командную строку с правами адми- нистратора, через поиск находим приложение Командная строка, щелкаем на значке правой кнопкой мыши и затем выбираем пункт Запуск от имени администратора. Запомните способы запуска командной строки. В дальнейшем мы просто будем говорить "запустите командную строку" без уточнения, как это сделать. 1.4. Установка MinGW и MSYS Язык C является компилируемым языком. Для преобразования текстового файла с программой в исполняемый EXE-файл потребуется установить на компьютер специальную программу — компилятор. Для компиляции примеров из книги мы
Установка программ под Windows 15 воспользуемся бесплатной программой gcc.exe, входящей в состав популярной биб- лиотеки MinGW. Для загрузки библиотеки переходим на сайт http://www.mingw.org/ и нажимаем кнопку Download Installer или переходим по ссылке http://www.mingw.org/ download/installer. Скачиваем программу установки и запускаем файл mingw-get- setup.exe. Обратите внимание: для установки библиотеки потребуется активное подключение к Интернету. После запуска программы установки отобразится окно, показанное на рис. 1.4. Нажимаем кнопку Install. На следующем шаге (рис. 1 .5) указываем каталог C:\MinGW и нажимаем кнопку Continue. После скачивания необ- ходимых файлов отобразится окно, показанное на рис. 1.6. Нажимаем кнопку Continue. В следующем окне (рис. 1.7) нужно выбрать требуемые компоненты. Ус- танавливаем флажки mingw32-base-bin, mingw32-gcc-g++-bin и msys-base-bin. За- тем в меню Installation выбираем пункт Apply Changes и в открывшемся окне (рис. 1.8) нажимаем кнопку Apply. После установки на последнем шаге (рис. 1 .9) нажимаем кнопку Close. Рис. 1.4. Установка MinGW. Шаг 1 В результате библиотека MinGW будет установлена в каталог C:\MinGW. Программу gcc.exe, предназначенную для компиляции программ, написанных на языке C, мож- но найти в каталоге C:\MinGW\bin, а заголовочные файлы — в каталоге C:\MinGW\include. Помимо библиотеки MinGW в каталог C:\MinGW\msys была установлена библиотека MSYS, которую редакторы используют для сборки проектов.
16 Глава 1 Рис. 1.5. Установка MinGW. Шаг 2 Рис. 1.6. Установка MinGW. Шаг 3
Установка программ под Windows 17 Рис. 1.7. Установка MinGW. Шаг 4 Рис. 1.8. Установка MinGW. Шаг 5 Пути к каталогам C:\MinGW\bin и C:\MinGW\msys\1.0\bin можно добавить в системную переменную PATH. Однако мы этого делать не станем, т. к . установим несколько версий компилятора. Вместо изменения переменной PATH на постоянной основе мы будем выполнять изменение в командной строке только для текущего сеанса. Про- демонстрируем это на примере, а заодно проверим работоспособность компилято- ра. Запускаем командную строку и выполняем следующие команды:
18 Глава 1 Рис. 1.9. Установка MinGW. Шаг 6 C:\Users\Unicross>cd C:\ C:\>set Path=C:\MinGW\bin;C:\MinGW\msys\1.0\bin;%Path% C:\>gcc --version gcc (MinGW.org GCC-8.2 .0-3) 8.2.0 Copyright (C) 2018 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. C:\>g++ --version g++ (MinGW.org GCC-8.2 .0-3) 8.2.0 Copyright (C) 2018 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Первая команда (cd C:\) делает текущим корневой каталог диска C:. Вторая коман- да (set Path=C:\MinGW\bin;C:\MinGW\msys\1.0\bin;%Path%) изменяет значение сис- темной переменной PATH для текущего сеанса. Пути к каталогам C:\MinGW\bin и C:\MinGW\msys\1.0\bin мы добавили в самое начало переменной PATH. Третья команда (gcc --version) выводит версию программы gcc.exe. Эту программу мы будем использовать для компиляции программ, написанных на языке C. Четвертая команда (g++ --version) выводит версию программы g++.exe. Эту программу можно использовать для компиляции программ, написанных на языке C++. Фрагменты
Установка программ под Windows 19 перед командами означают приглашение для ввода команд. Текст после команд является результатом выполнения этих команд. Вместо отдельных команд можно написать скрипт, который выполняет сразу не- сколько команд и отображает результат их работы в отдельном окне. Запускаться такой скрипт будет с помощью двойного щелчка левой кнопкой мыши на значке файла. Для создания файла потребуется текстовый редактор, позволяющий кор- ректно работать с различными кодировками. Советую установить на компьютер редактор Notepad++. Скачать редактор можно абсолютно бесплатно со страницы https://notepad-plus-plus.org/. Из двух вариантов (архив и инсталлятор) советую выбрать именно инсталлятор, т. к. при установке можно будет указать язык интер- фейса программы. Установка Notepad++ предельно проста и в комментариях не нуждается. Запускаем Notepad++ и создаем новый документ. Консоль в Windows по умолча- нию работает с кодировкой windows-866, поэтому мы должны и файл сохранить в этой кодировке, иначе русские буквы будут искажены. В меню Кодировки выби- раем пункт Кодировки | Кириллица | ОЕМ 866. Вводим текст скрипта (лис- тинг 1.1) и сохраняем под названием script.bat в каталоге C:\book. Запускаем скрипт с помощью двойного щелчка левой кнопкой мыши на значке файла script.bat. Резуль- тат выполнения показан на рис. 1.10. Для закрытия окна консоли достаточно на- жать любую клавишу. Листинг 1.1 . Содержимое файла script.bat @echo off title Заголовок окна cd C:\ echo Текущий каталог: %CD% @echo. set Path=C:\MinGW\bin;C:\MinGW\msys\1.0\bin;%Path% rem Вывод версии gcc.exe echo gcc --version @echo. gcc --version rem Вывод версии g++.exe echo g++ --version @echo. g++ --version pause Рассмотрим инструкции из этого скрипта:  title — позволяет вывести текст в заголовок окна консоли;  echo — выводит текст в окно консоли;  @echo. — выводит пустую строку в окно консоли;  %CD% — переменная, содержащая путь к текущему рабочему каталогу;
20 Глава 1 Рис. 1.10. Результат выполнения программы из листинга 1.1  %Path% — переменная, содержащая значение системной переменной PATH;  rem — вставляет комментарий, поясняющий фрагмент кода;  pause — ожидает ввода любого символа от пользователя. Если эту инструкцию убрать из скрипта, то программа выполнится, и окно консоли сразу закроется, не дав нам возможности увидеть результат. 1.5. Установка MinGW-W64 Компилятор gcc.exe, установленный в предыдущем разделе в каталог C:\MinGW\bin, позволяет создавать 32-разрядные приложения. Такие приложения будут запус- каться и в 32-битной операционной системе, и в 64-битной. Для того чтобы иметь возможность создавать приложения для 64-битной системы, нужно дополнительно установить библиотеку MinGW-W64. Для загрузки библиотеки MinGW-W64 переходим на страницу https:// sourceforge.net/projects/mingw-w64/ и скачиваем файл mingw-w64-install.exe. После запуска программы установки отобразится окно, показанное на рис. 1.11 . Нажима- ем кнопку Next. В следующем окне (рис. 1.12) из списка Version выбираем самую новую версию (в моем случае 8.1.0), из списка Architecture — пункт x86_64, из списка Threads — пункт win32, а из списка Exception — пункт seh. Нажимаем кнопку Next. На следующем шаге (рис. 1 .13) задаем путь C:\MinGW64 и нажимаем кнопку Next. Начнется установка библиотеки. В следующем окне (рис. 1.14) нажи- маем кнопку Next, а на последнем шаге (рис. 1 .15) — кнопку Finish. В состав MinGW-W64 не входит MSYS. Программа из каталога C:\MinGW\msys на- строена на библиотеку MinGW, расположенную в каталоге C:\MinGW, поэтому мы не сможем ее использовать совместно с MinGW-W64. Однако мы можем скопиро- вать каталог C:\MinGW\msys со всем содержимым и вставить его в каталог
Установка программ под Windows 21 Рис. 1.11. Установка MinGW-W64. Шаг 1 Рис. 1.12. Установка MinGW-W64. Шаг 2
22 Глава 1 Рис. 1.13. Установка MinGW-W64. Шаг 3 Рис. 1.14. Установка MinGW-W64. Шаг 4
Установка программ под Windows 23 Рис. 1.15. Установка MinGW-W64. Шаг 5 C:\MinGW64\mingw64\msys, а затем указать в конфигурации путь к MinGW-W64. Так мы и сделаем. В результате путь до файла make.exe должен быть таким: C:\MinGW64\mingw64\msys\1.0\bin\make.exe. Далее с помощью программы Notepad++ открываем файл C:\MinGW64\mingw64\msys\1.0\etc\fstab и находим строку: C:/MinGW /mingw Изменяем ее следующим образом и сохраняем файл: C:/MinGW64/mingw64 /mingw Проверим правильность установки MinGW-W64. Открываем командную строку и выполняем следующие команды: C:\Users\Unicross>cd C:\ C:\>set Path=C:\MinGW64\mingw64\bin;%Path% C:\>gcc --version gcc (x86_64-win32-seh-rev0, Built by MinGW-W64 project) 8.1 .0 Copyright (C) 2018 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. C:\>g++ --version g++ (x86_64-win32-seh-rev0, Built by MinGW-W64 project) 8.1 .0 Copyright (C) 2018 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
24 Глава 1 Теперь установим компилятор для создания 32-битных приложений. Запускаем файл mingw-w64-install.exe еще раз. На втором шаге (см. рис. 1.12) из списка Version выбираем самую новую версию (в моем случае 8.1.0), из списка Architecture — пункт i686, из списка Threads — пункт win32, а из списка Exception — пункт dwarf. На третьем шаге (см. рис. 1 .13) задаем путь C:\MinGW32. После установки ко- пируем каталог msys со всем содержимым из каталога C:\MinGW в каталог C:\MinGW32\mingw32. Далее с помощью программы Notepad++ открываем файл C:\MinGW32\mingw32\msys\1.0\etc\fstab и находим строку: C:/MinGW /mingw Изменяем ее следующим образом и сохраняем файл: C:/MinGW32/mingw32 /mingw Проверим правильность установки. Открываем командную строку и выполняем следующие команды: C:\Users\Unicross>cd C:\ C:\>set Path=C:\MinGW32\mingw32\bin;%Path% C:\>gcc --version gcc (i686-win32-dwarf-rev0, Built by MinGW-W64 project) 8.1 .0 Copyright (C) 2018 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. C:\>g++ --version g++ (i686-win32-dwarf-rev0, Built by MinGW-W64 project) 8.1 .0 Copyright (C) 2018 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 1.6. Установка MSYS2 и MinGW-W64 Существует еще один способ установки компилятора. Предварительно нам нужно установить библиотеку MSYS2. Переходим на сайт http://www.msys2.org/, скачи- ваем файл msys2-x86_64-20180531.exe и запускаем его. В открывшемся окне (рис. 1.16) нажимаем кнопку Далее. В следующем окне (рис. 1.17) указываем ката- лог C:\msys64 и нажимаем кнопку Далее. На следующем шаге (рис. 1 .18) нажимаем кнопку Далее. После установки нажимаем кнопку Далее (рис. 1.19), а затем кнопку Завершить (рис. 1.20). В итоге библиотека MSYS2 будет установлена в каталог C:\msys64. В этом каталоге расположены скрипты для запуска: msys2.exe, mingw32.exe и mingw64.exe.
Установка программ под Windows 25 Рис. 1.16. Установка MSYS2. Шаг 1 Рис. 1.17. Установка MSYS2. Шаг 2 Рис. 1.18. Установка MSYS2. Шаг 3
26 Глава 1 Рис. 1.19. Установка MSYS2. Шаг 4 Рис. 1.20. Установка MSYS2. Шаг 5 Файл msys2.exe запускает командную строку, в которой мы можем установить раз- личные библиотеки, выполнив специальные команды. Если на последнем шаге при установке вы не сбросили флажок, то это приложение запустится автоматически. Если флажок сбросили, то запускаем программу msys2.exe с помощью двойного щелчка левой кнопкой мыши на значке файла. Сначала обновим программу, вы- полнив команду (рис. 1.21): pacman -Syu На запрос подтверждения установки вводим букву Y и нажимаем клавишу <Enter>. После завершения установки закрываем окно, а затем запускаем программу msys2.exe снова. Вводим следующую команду: pacman -Su На запрос подтверждения установки вводим букву Y и нажимаем клавишу <Enter>.
Установка программ под Windows 27 Рис. 1.21. Программа msys2.exe Теперь можно установить библиотеку MinGW-W64. Для этого в окне вводим сле- дующую команду: pacman -S mingw-w64-x86_64-toolchain Для установки всех компонентов нажимаем клавишу <Enter>, а затем на запрос подтверждения установки вводим букву Y и нажимаем клавишу <Enter>. Библиоте- ка MinGW-W64 будет установлена в каталог C:\msys64\mingw64. Давайте проверим установку, выполнив в командной строке следующие команды: C:\Users\Unicross>cd C:\ C:\>set Path=C:\msys64\mingw64\bin;%Path% C:\>gcc --version gcc (Rev1, Built by MSYS2 project) 8.2 .1 20181214 Copyright (C) 2018 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. C:\>g++ --version g++ (Rev1, Built by MSYS2 project) 8.2 .1 20181214 Copyright (C) 2018 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
28 Глава 1 В командной строке мы настраивали переменную окружения PATH. Если запустить программу mingw64.exe, то переменные окружения дополнительно настраивать не нужно, достаточно сразу выполнить команду (рис. 1.22): Unicross@ASUS MINGW64 ~ $ gcc --version gcc.exe (Rev1, Built by MSYS2 project) 8.2 .1 20181214 Copyright (C) 2018 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Рис. 1.22 . Программа mingw64.exe Удобство использования библиотеки MSYS2 заключается в том, что помимо уста- новки какой-либо библиотеки, причем всего лишь одной командой, дополнительно устанавливаются все зависимости. Давайте установим еще несколько компонентов, которые могут пригодиться: pacman -S make pacman -S mingw-w64-x86_64-cmake pacman -S mingw-w64-x86_64-gtk3 pacman -S mingw-w64-x86_64-glade Проверим установку библиотеки GTK+ (она предназначена для разработки окон- ных приложений на языке C). Запускаем программу C:\msys64\mingw64.exe и выпол- няем следующую команду: pkg-config --libs gtk+-3.0 Результат: - LC:/msys64/mingw64/lib -lgtk-3 -lgdk-3 -lgdi32 -limm32 -lshell32 -lole32 - Wl,-luuid -lwinmm -ldwmapi -lsetupapi -lcfgmgr32 -lz -lpangowin32-1.0 - lpangocairo-1 .0 - lpango-1 .0 - latk-1 .0 -lcairo-gobject -lcairo - lgdk_pixbuf-2 .0 - lgio-2.0 -lgobject-2 .0 - lglib-2.0 - lintl Все установленные библиотеки скомпилированы под 64-битные операционные системы. Для установки 32-битных версий библиотек нужно в команде заменить
Установка программ под Windows 29 фрагмент x86_64 фрагментом i686. Пример команды для установки 32-битного компилятора: pacman -S mingw-w64-i686-gcc Компилятор будет установлен в каталог C:\msys64\mingw32. Для выполнения команд можно воспользоваться программой C:\msys64\mingw32.exe. Например, полу- чим версию компилятора: $ gcc --version gcc.exe (Rev1, Built by MSYS2 project) 7.4 .0 Copyright (C) 2017 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Обратите внимание: мы установили только компилятор. Для того чтобы установить все компоненты, нужно воспользоваться командой: pacman -S mingw-w64-i686-toolchain Мы будем учиться создавать приложения на языке C под 64-битные операционные системы, поэтому можно все компоненты для 32-битного компилятора дополни- тельно не устанавливать, тем более что мы уже установили более новую версию компилятора в разд. 1.5. 1.7. Установка и настройка редактора Eclipse В этой книге для создания и компиляции программ на языке C мы воспользуемся редактором Eclipse. Редактор удобен в использовании, он позволяет автоматически закончить слово, подчеркнуть код с ошибкой, подсветить код программы, вывести список всех функций, отладить программу, а также скомпилировать все файлы проекта всего лишь нажатием одной кнопки без необходимости использования командной строки. Для загрузки редактора Eclipse переходим на страницу: https://www.eclipse.org/ downloads/packages/ и скачиваем архив с программой из раздела Eclipse IDE for C/C++ Developers. В моем случае архив называется eclipse-cpp-2018-12 -R -win32- x86_64.zip. Распаковываем скачанный архив и копируем каталог eclipse с файлами редактора в каталог C:\cpp. Редактор не нуждается в установке, поэтому просто пе- реходим в каталог C:\cpp\eclipse и запускаем файл eclipse.exe. Однако не торопитесь прямо сейчас запускать редактор. Редактор Eclipse написан на языке Java, поэтому предварительно нужно установить на компьютер либо Java Runtime Environment (JRE), либо Java Development Kit (JDK). JRE используется для запуска приложений, а JDK — как для запуска, так и для разработки приложений на языке Java. Для того чтобы узнать, какая версия Java требуется для работы редактора Eclipse, открываем конфигурационный файл C:\cpp\eclipse\eclipse.ini и смотрим значение опции requiredJavaVersion. В моем слу- чае опция имеет такое значение: - Dosgi.requiredJavaVersion=1.8
30 Глава 1 Значение 1.8 указывает на то, что нужна Java 8. Устанавливаем на компьютер тре- буемую версию Java и задаем путь к ней с помощью опции -vm. Самый простой способ — создать ярлык для eclipse.exe, отобразить окно Свойства и в поле Объект указать такое значение (замените фрагмент <Путь к JRE> реальным значением): C:\cpp\eclipse\eclipse.exe -vm "<Путь к JRE>\bin\server\jvm.dll" Можно использовать и более новую версию, например Java 10, но не выше, т. к. в версии 11 некоторые пакеты были удалены. Пример указания пути к Java 10: C:\cpp\eclipse\eclipse.exe - vm "C:\Program Files\Java\jre-10\bin\server\jvm.dll" ПРИМЕЧАНИЕ Все способы указания значения подробно описаны на странице http://wiki.eclipse.org/Eclipse.ini. При запуске редактор попросит указать каталог с рабочим пространством (рис. 1.23). Указываем C:\cpp\projects и нажимаем кнопку Launch. Рис. 1.23. Окно Eclipse IDE Launcher Прежде чем пользоваться редактором, нужно изменить некоторые настройки по умолчанию. Начнем с кодировки файлов, в которых мы будем вводить текст про- граммы. Для того чтобы указать кодировку по умолчанию для всех файлов, в меню Window выбираем пункт Preferences. В открывшемся окне переходим в раздел General | Workspace (рис. 1.24). В группе Text file encoding устанавливаем флажок Other и в текстовое поле вводим Cp1251. Сохраняем изменения. Таким образом, мы будем пользоваться кодировкой windows-1251, которая по умолчанию используется в русской версии Windows. Если необходимо изменить кодировку уже открытого файла, в меню Edit выбираем пункт Set Encoding. Если же потребуется указать другую кодировку для всех фай- лов проекта, в меню Project выбираем пункт Properties. В открывшемся окне
Установка программ под Windows 31 Рис. 1.24. Указание кодировки файлов по умолчанию Рис. 1.25 . Указание кодировки файлов для проекта
32 Глава 1 (рис. 1.25) слева в списке выбираем пункт Resource, а затем справа в группе Text file encoding указываем нужную кодировку. По умолчанию редактор вместо пробелов вставляет символы табуляции. Нас это не устраивает. Давайте изменим настройку форматирования кода. Для этого в меню Window выбираем пункт Preferences. В открывшемся окне переходим в раздел C/C++ | Code Style | Formatter (рис. 1.26). Нажимаем кнопку New. В открывшемся окне (рис. 1.27) в поле Profile name вводим название стиля, например MyStyle, а из списка выбираем пункт K&R [built-in]. Нажимаем кнопку OK. Откроется окно (рис. 1.28), в котором можно изменить настройки нашего стиля. На вкладке Indentation из списка Tab policy выбираем пункт Spaces only, а в поля Indentation size и Tab size вводим число 3. Сохраняем все изменения. Рис. 1.26. Окно Preferences, раздел Formatter Если необходимо изменить размер шрифта, то в меню Window выбираем пункт Preferences. В открывшемся окне переходим в раздел General | Appearance | Colors and Fonts (рис. 1.29). Из списка выбираем пункт C/C++ | Editor | C/C++ Editor Text Font и нажимаем кнопку Edit.
Установка программ под Windows 33 Рис. 1.27. Окно New Code Formatter Profile Рис. 1.28. Изменение настроек форматирования
34 Глава 1 Рис. 1.29. Окно Preferences, раздел Colors and Fonts 1.8. Создание проектов в редакторе Eclipse Давайте в редакторе Eclipse создадим два проекта. Первый проект (с названием Test32c) мы будем использовать для создания 32-битных программ (компилятор из каталога C:\MinGW32\mingw32\bin), а второй проект (с названием Test64c) — для соз- дания 64-битных программ (компилятор из каталога C:\msys64\mingw64\bin). Для создания проекта в меню File выбираем пункт New | Project. В открывшемся окне (рис. 1.30) в списке выбираем пункт C/C++ | C Project и нажимаем кнопку Next. На следующем шаге (рис. 1.31) в поле Project name вводим Test32c, в списке слева выбираем пункт Executable | Hello World ANSI C Project, а в списке спра- ва — пункт MinGW GCC. Если в списке справа отсутствует пункт MinGW GCC, то редактор не смог автоматически обнаружить библиотеку. В этом случае следует сбросить флажок Show project types and toolchains, и пункт станет доступен. По- сле нажатия кнопки Next отобразится окно (рис. 1 .32), в котором можно ввести имя автора, информацию об авторских правах, текст, который будет выведен на кон- соль, и каталог, в который будут сохраняться файлы. Нажимаем кнопку Next. В следующем окне (рис. 1.33) нажимаем кнопку Finish для создания проекта. В результате в каталоге C:\cpp\projects будет создан каталог Test32c, содержащий файлы проекта. В каталоге C:\cpp\projects\Test32c\src можно найти файл
Установка программ под Windows 35 Рис. 1.30. Создание проекта. Шаг 1 Рис. 1.31. Создание проекта. Шаг 2
36 Глава 1 Рис. 1.32. Создание проекта. Шаг 3 Рис. 1.33. Создание проекта. Шаг 4 Test32c.c, в который редактор уже вставил тестовую программу на языке C, выво- дящую надпись на консоль. Открыть этот файл можно с помощью любого тексто- вого редактора, например с помощью Notepad++. Однако изменять его лучше в ре- дакторе Eclipse, ведь его содержимое доступно на вкладке. Помимо файла Test32c.c редактор создал вспомогательные каталоги и файлы, названия которых начинаются с точки. В этих каталогах и файлах редактор сохраняет различные настройки. Не изменяйте и не удаляйте эти файлы.
Установка программ под Windows 37 Давайте отобразим свойства проекта и рассмотрим настройки компилятора. Для этого делаем текущей вкладку с содержимым файла Test32c.c и в меню Project вы­ бираем пункт Properties. В открывшемся окне(рис. 1 .34) в списке слева выбираем пункт С/С++ Build I Environment. На отобразившейся вкладке в списке прописаны следующие основные переменные окружения: □ МINGW_НОМЕ-задает путь к библиотеке MinGW; □ мsYs_НОМЕ -задает путь к библиотеке MSYS; □ РАТН - в начало текущего значения переменной РАТН добавляется путь к биб­ лиотекам($ {MINGW_НОМЕ} \Ьin;$ {MSYS_HOМE} \Ьin; ). Ф Properties tor Тest32c type �.�t_�r t� -· - ·- · ·-· ·-· · . JEnvironment - □ ;; Resource f-- -------- - -- --- --- ---- ---- -- Builders • С/С•+ Build Confщ uration: ! [ AII configurotions ] " ! jМonoge Conf,gurotions. .j Build VariaЫes Environment Logging Sd:tin95 Toof Chatn Editor > С/С-+-+ General linux Т ools Pгth Project Natures Project References Run/Debug Sвtings ;> Т ask Repository T•sk Tags t- V!lidation Wiki1<Xt Environment v.!riables to set 1 VariaЫe Va - lue ;!CWD • __ (;:_\cpP\proj,cts\Тt!Sl>Zc\Del>uq "· MINGW_НOME C:\MinGW32\mingw32 МSУS_НОМЕ C:\MinGWЗ2\mingw32\msys\1 .О РАТН S(MINGW_HOME}\Ьin;S(MSYS_HOME}\Ыn;S(MSYS_.,, PWD C:\cpp\projects\Тest32c\0.,bug Origin 8\JILD,SVSТEM) USER: CONFIG USER: CONFIG BUILD .SYSTEM BUILD SYSТEM @Append variaЫ� to native environment String list МоМ: Cc.njunФon +Мoo]f:t О Replace natWe Novironment with speciftIO one Рис. 1 .34. Переменные окружения в настройках проекта �.:J [seю... j L��::_J 0-ele:te � Если внимательно посмотреть на значения переменных окружения MINGW ноМЕ и мsys_НОМЕ, то можно заметить, что редактор Eclipse отдал предпочтение компилято­ ру из каталога C:\msys64\mingw64\Ьin, а нам нужно, чтобы файлы проекта компилиро­ вались компилятором и-з каталога C:\МinGW32\mingw32\Ьin. Давайте изменим значе­ ния переменных окружения следующим образом (предварительно из списка Configuration выбираем пункт AII configurations, а лишь затем изменяем значения переменных окружения): □ MINGW_НОМЕ -указываем nуть C:\MinGW32\mingw32; □ мsУS_НОМЕ-указываем путь C:\МinGW32\mingw32\msys\1.0 . По умолчанию для кодирования символов в L-строках MinGW использует коди­ ровку UTF-8, а файлы нашего проекта сохраняются в кодировке windows-1251. Если мы попробуем указать русские буквы при инициализации L-строки (L"Строка"), то получим ошибку. Для того чтобы избежать ошибок, нужно с помощью флага
38 Глава 1 -f input-charset указать компилятору кодировку исходного файла, а с помощью флага -fexec-charset - кодировку С-строк. Для этого в свойствах проекта из спи­ ска слева выбираем пункт С/С++ Build I Settings. На отобразившейся вкладке из списка Configuration выбираем пункт AII configurations, а затем на вкладке Tool Settings из списка выбираем пункт GCC С Compiler I Miscellaneous (рис. 1 .35). В поле Other flags через пробел к имеющемуся значению добавляем следующие инструкции: - finput-charset=cpl251 -fexec-charset=cpl251 Содержимое поля Other flags после изменения должно быть таким: -с - fmessage-length=O -finput-charset=cpl251 -fexec-charset=cpl251 о type filter trn :, Re:source Builders Properties for Тest32c Settings Configuration: ( AII config��ations] - □ 4 С/С++ Build Build VariaЫes Environment lo99ing S�in9s Тool Ch11in Editor :> С/С•• Gen�al Linux Тool.s Path Project Natures Project Rб�ences Run/Debug �ings Тask: Repository TaskTllgs 41 � GCC Asse:mЫer l . � General 4 �} GCC С Compiler @: Oialect -· i Other flags -< . -fm�sage-length::0 -finput-ch4'r�t=: _ pl251 -f�!c-charset=� _ pl2SI [[]Vo,bose i•v) ;, Validation WikiT� t!� Preprocessor � lncludes @ Optimiu1tion @ Debugging � Warnings MiкeHi!lneous .i � MinGW С linker � General � Libraries @ Misce : llaneous @ Shared Library Sdtings :OSup-port ANSI programs {-ansi) !□Position lndependent Code (-fPIC) Рис. 1.35. Указание кодировки исходного файла и кодировки С-строк Canc� Далее на вкладке Tool Settings из списка выбираем пункт GCC С Compiler 1 Warnings. Устанавливаем флажок -Wconversion (рис. 1.36). Проверяем также установку флажка -Wall. Сохраняем настройки проекта, нажимая кнопку Apply and Close. Для того чтобы преобразовать текстовый файл Test32c.c с программой в исполняе­ мый ЕХЕ-файл, делаем текущей вкладку с содержимым файла Test32c.c и в меню Project выбираем пункт Build Project. В окне Console отобразятся инст­ рукции компиляции и результат ее выполнения. Если компиляция прошла успешно, то никаких сообщений об ошибках в этом окне не будет: 12:22:53 **** Incremental Build of configuration Debug for project Test32c **** Info: Internal Builder is used for build
Установка программ под Windows gcc -00 -gЗ -Wall -Wconversion -с - frnessage-length=0 - finput-charset=cp1251 -fexec-chars&t=cp1251 -о "src\\Test32c.o" " .. \\src\\Test32c.c" gcc -о Test32c.exe "src\\Test32c.o" 12:22:53 Build Finished (took 344rns) 39 Если флаги -Wall, -Wconversion, -finput-charset или -fexec-charset отсутствуют в команде компиляции, то вы неправильно настроили свойства проекта. ф Properties for Тest32c - □ i type filter text _ ____ ___ _____J i Settlngs [, Resource ----------------------------- Builders • С/С++ Build Build VariaЬl6 Environment Lo9ging Settings Тool Chain Editor t> CJC++ General Linux Тools Path Project Notures Project References Run/Debug Settin9s � Тosk Repository Task Ta9s 1, Validation \'/ikiText (J) " -------------, ------� ,,,. ' Conf19urot1on: i [AJIconfogurltions ] " , ; М....gе Confogurot,ons..., 1 � тool s.tt,ngs · в-C�nta,ner Stettings 1 ,;,,. Build Steps P�\!Гв�,ldдrtii�Т �ы Вinщ p....,IS I о н�I 1 r�- • � GCC AssemЫer @Gen,ral • � GCC С Compiler � Dialect � Preprocessor @lncludб @ Optimiution @ Debugg�ng �-�'!'.!'!�.9'.1 @ Miscellan�us • � MinGW С Linker @General @llbrari6 � Miscellaneous О Check syntax only (-fsyntax-only) О Pedantic (-pedantic) О PШntic wamings as errors (-pIOantk-errors) Оlnhibit all warnings (-w) �Allwarnings (-Wall) О Extra warnings (-Wextra) [] Warnings as errors (-Werror) �lmplicit conversion wamings (-Wconvt:rsion) < ������+.i<�� ����1 Рис. 1.36. Настройка вывода предупреждающих сообщений Ш В результате компиляции в каталоге C:\cpp\projects\Тest32c бьm создан каталог Debug. Внутри этого каталога находится файл Test32c.exe, который можно запустить на выполнение с помощью двойного щелчка мыши на значке файла. Если попробовать сделать это сейчас, то окно откроется, а затем сразу закроется. Как сделать, чтобы окно сразу не закрывалось, мы рассмотрим немного позже. Сейчас же попробуем запустить программу в редакторе Eclipse. Для запуска делаем текущей вкладку с содержимым файла Test32c.c и в меню Run выбираем пункт Run. В открывшемся окне (рис. 1.37) выбираем пункт Local С/С++ Application и нажимаем кнопку ОК. Результат выполнения программы отобразится в окне Console. Как видите, компиляция и запуск выполняются в редакторе выбором пункта в ме­ ню. Для более быстрого доступа к этим пунктам можно воспользоваться кнопками на панели инструментов. Для того чтобы выполнить компиляцию, нажимаем кноп­ ку с изображением молотка (рис. 1.38), а чтобы запустить программу на выполне-
40 Глава 1 ние - кнопку с зеленым кругом и белым треугольником внутри него. Причем нажатие кнопки запуска автоматически приводит к компиляции, если были произ­ ведены изменения в тексте программы. Согласитесь, очень просто и удобно. RunAs - □ �elect awayto run 'Tl!S1:32c': r:[JС/С++ Container Application [E:)�ocal С!С++ Application: 1 Oescription Runs а locol С/С++ application �---�г--� �__ о_к_�l 1....... Cancel . .J Рис. 1 .37. Окно Run As 1 @·[ C \Blillit;-• O·g�•Q.· J Build 'Debug' for project 'Т est32c' � 43 Рис. 1 .38 . Кнопки на панели инструментов, предназначенные для компиляции и запуска программы По аналогии с проектом Test32c создаем еще один проект с названием Test64c. Мы будем использовать этот проект для создания 64-битных приложений. Делаем те­ кущей вкладку с содержимым файла Test64c.c и в меню Project выбираем пункт Properties. В открывшемся окне (рис. 1.39) в списке слева выбираем пункт С/С++ Build I Environment. На отобразившейся вкладке из списка Configuration выбира­ ем пункт AII configurations, а затем проверяем значения следующих переменных j_typ�_filte:r text _ (,. Resource Builders � С/(.,,+ Build Build VвriaЫes Environment Logging Settings Тool Chain Editor r> С/(..,+ General linux Т ools Path Project Notures Project References Ru n/Debug Sett.ings Тask Repos�ory Task Tags :> Validation WikiText Properties for Тest64c Environment Configuration: . ( AII con . ��urations ]_ ___ -------·-·-···- ···---·-···- Enviro nment variaЫes to set VariaЫe Value CWD С:\с pp\projects\Тest64c\Debug MINGW_HOМE C:\msys64\mingw64 MSVS_HOME C:\msys64 РАТН S{MINGW_HOME)\Ьin;S{MSYS_HOME}\Ьin;S(MSYS_.. IPWD . S'\9' . P.\P. . �_oiects\Т..t64<:\D,bua ---··: 1 - □ ·····-····" · j Manage Configurationi ... , Origin BUILD SVSTEM BUILD SYSTEM BUILD SYSТEM BUILD SYSТEM BUILD SVSТEM 1 Add.. . ' Sel,ct, . . Edit... ('ielete @Append vllriables to natiw: environml!nt String li.st Mode: (onj�,n('t!.Q.!1 + Mod,fy С> Rtpla:ce native �nvironment with specified one ··•·••·•· 1• •. .. · ······ Rostor: Qof•�-�s, ' " _ t;p..P.� -- - - Рис. 1.39. Настройки проекта Test64c
Установка программ под Windows 41 окружения (в моем случае редактор Eclipse выбрал этот компилятор самостоя­ тельно): □ МINGW_НОМЕ - C:\msys64\mingw64; □ MSYS_HOМE- C:\msys64. Далее в свойствах проекта из списка слева выбираем пункт С/С++ Build I Settings. На отобразившейся вкладке из списка Configuration выбираем пункт AII configurations, а затем на вкладке Tool Settings из списка выбираем пункт GCC С Compiler I Miscellaneous (см. рис. 1 .35). В поле Other Пags через пробел к имею­ щемуся значению добавляем следующие инструкции: -finput-charset=cpl251 -fexec-charset=cpl251 Содержимое поля Other 0ags после изменения должно быть таким: -с -fmessage-length=O -finput-charset=cpl251 -fexec-charset=cpl251 Затем на вкладке Tool Settings из списка выбираем пункт GCC С Compiler I Warn­ ings. Устанавливаем флажок -Wconversion. Проверяем также установку флажка -Wall. Сохраняем настройки проекта, нажимая кнопку Apply and Close. Давайте сравним два проекта в окне Project Explorer (рис. 1.40). Во-первых, обра­ тите внимание на пункт Includes. Пути поиска заголовочных файлов отличаются и соответствуют выбранным компиляторам. Во-вторых, после имени ЕХЕ-файла в проекте Test32c указано значение [x86/le], а в проекте тest64c - [amd64/leJ. Это означает, что проект тest64c успешно настроен на создание 64-битных программ, и мы можем начинать изучение языка С. ф - □ !�P;;;j::tExplo,;;� �_., . '�. -�- EJ%1�.. ��- □-, j •'3Т�2с l • r,,� Binaries • ii;Ji! lncludes � @ C:/MinGW32/mingw32/iб86-w64 ·mingwЗ2/include >@ C,/MinGW32/mingw32/liЬ/gccfi686-w64 -mingw32/8,1 ,0/include 1 :, @ C:/MinGW32/mingw32/liЬ/gccfo686-w64-mingw32/8,1,0/include-fixed j �@ src ' • в Debug �(а,src � � Тest32e,exe • [x86/le] •1'3 Testб4c �f/. Binaries • � lncludes :, @ �/msys64/mingw64/include ,, @ C:/msysб4/mingwб4/lib/g<e/x86_64-w64 -mingw32/8.2 .1/include >@ C:/msysб4/mingwб4/liЬ/gcc/x86_64-w64 -mingw32/8.2 .1/includtcfixed � @ C,/msysб4/mingwб4/x86_64 -w64 -mingwЗ2/include ;,�src 11 •(о�bug �(osrc 1 �,r,,.__т �est64c- - . ex_e _ - _ t ��m-d64/_1e_ 1 _ I _____________ ), Рис. 1.40. Окно Project Explorer
42 Глава 1 Итак, мы установили и настроили программы, позволяющие создавать приложения на двух языках: С и С++. Язык С мы будем изучать в этой книге, начиная со сле­ дующей главы. Язык С++ в этой книге мы изучать не станем, но временами будем о нем вспоминать, т. к . он поддерживает все возможности языка С. Рекомендую дополнительно установить бесплатную версию Visua\ Studio. После установки компилятор из Visua\ Studio можно использовать в редакторе Eclipse, но нужно будет установить плагин С/С++ Visual С++ Support. Для этого в редакторе Eclipse в меню Help выбираем пункт Install New Software (для установки плагина потребуется активное подключение к Интернету). В результате откроется окно, по­ казанное на рис. 1.41. Из списка Work with выбираем пункт 2018-12. После загруз­ ки списка плагинов устанавливаем флажок напротив пункта С/С++ Visual С++ Support. Нажимаем кнопку Next. В следующем окне будут показаны только вы­ бранные плагины. Нажимаем кнопку Next. На последнем шаге нужно принять лицензионное соглашение и нажать кнопку Finish для начала установки. После установки следует перезапустить редактор Eclipse. Теперь при создании проекта станет доступным пункт Microsoft Visual С++ (рис. 1 .42). lnstalt AvailaЫe Software Check the items that you wish to install. :Work with: 2018-12 - http://download.eclipse.org(releases/2018-12 " Name Version ; 4 � GQO Programming Languages �'at С/С++ V"ISШI С++ Suppor1 9.6 .0.201811241055 1 item selected Details Support for building project using the Visual С++ compiler. �dd... 1 ___J. � Show only the l•test versions of availaЫe software � yroup item, Ьу category � J:iide items that are already installed What is already 1nstalled? О Show only software applicaЫe to target environment � rontact all update sites during install to find required software blext > f.ini�h - □ Manage... ,Select AII Q.eselectдll Cancel Рис. 1.41. Окно lnstall: выбор плагина С/С++ Visual С++ Support -'
Установка программ под Windows С Project С Project Projю name must Ье sp�ified � Use _gefault location i. о,: зt,c,r,: C:\cpp\proje<:ts Projю ty� Тoolchains: � 12;, GNU Autotools Cross GCC � 12:;, ExecutaЫe L__ lмк:roюft Visual С-++! Empty Project MinGWGCC • Hello World ANSI С Project � G:;, Shared Library v (с Static Library � (с Makefile projю - □ Brow,;e.. � Show project tурб and toolchains only if th� are supported оп the platform !<Васki L___ - ________; Cancel Рис. 1 .42. Создание проекта с поддержкой Microsoft Visual С++ 43
ГЛАВА 2 Первые шаги Прежде чем мы начнем рассматривать синтаксис языка, необходимо сделать два замечания. Во-первых, не забывайте, что книги по программированию нужно не только читать, но и выполнять все приведенные в них примеры, а также экспери­ ментировать, что-либо в этих примерах изменяя. Поэтому, если вы удобно устрои­ лись на диване и настроились просто читать, у вас практически нет шансов изучить язык! Во-вторых, помните, что прочитать книгу один раз недостаточно - ее вы должны выучить наизусть! Это основы языка! Сколько на это уйдет времени, зави­ сит от ваших способностей и желания. Как минимум вы должны знать структуру книги. Чем больше вы будете делать самостоятельно, тем большему научитесь. Обычно rrocлe первого прочтения многое непонятно, после второго прочтения некоторые вещи становятся понятнее, после третьего - еще лучше, ну а после N-ro прочте­ ния - не понимаешь, как могло быть что-то непонятно после первого прочтения. Повторение - мать учения. Наберитесь терпения, и вперед! Итак, приступим к изучению языка С. 2.1. Первая программа В процессе изучения основ языка С мы будем создавать консольные приложения. Консольное приложение - это программа, отображающая текстовую информацию и позволяющая вводить символы с клавиатуры. Консольное приложение позволит не отвлекаться на изучение среды разработки, а полностью сосредоточить внима­ ние на изучении синтаксиса языка. При изучении языков программирования принято начинать с программы, выводя­ щей надпись "Hello, world!" в окно консоли. Не будем нарушать традицию и про­ демонстрируем, как это выглядит на языке С (листинг 2.1 ).
Первые шаги #include <stdio.h> int main(void) [ printf("Hello, world!"); return О; 45 Рассмотрим структуру программы из листинга 2.1. Вся программа состоит из четы­ рех инструкций: □ включение системного заголовочного файла stdio.h с помощью директивы пре- процессора #include; □ описание функции main(); □ вывод приветствия с помощью функции printf(); □ возврат значения с помощью инструкции return. В первой строке программы с помощью директивы #include включается файл stdio.h, в котором объявлена функция printf( J, предназначенная для форматирован­ ного вывода данных в окно консоли. Так как название файла указано внутри угло­ вых скобок, его поиск будет выполнен в путях поиска заголовочных файлов. Со­ держимое файла stdio.h на одной из стадий компиляции целиком вставляется вместо инструкции с директивой #include. Заголовочный файл stdio.h входит в состав библиотеки MinGW и расп�лагается в каталогах C:\МinGW32\mingw32\i686-w64-mingw32\include и C:\msys64\mingw64\x86_64-w64- mingw32\include. Заголовочный файл является обычным текстовым файлом, поэтому его содержимое можно посмотреть в любом текстовом редакторе. Посмотреть со­ держимое файла можно также на отдельной вкладке редактора Eclipse. Для этого · в нутри вкладки с программой щелкаем правой кнопкой мыши на названии файла stdio.h и из контекстного меню выбираем пункт Open Declaration или вставляем текстовый курсор на название файла и нажимаем клавишу <FЗ>. Далее создается функция main( J, внутри блока которой расположены все остальные инструкции. Именно функция с названием main( J будет автоматически вызываться при запуске программы. Определение функции является составной инструкцией, поэтому после описания параметров указывается открывающая фигурная скобка. Закрывающая фигурная скобка является признаком конца блока: int main(void) Фигурные скобки обрамляют блок, который ограничивает область видимости иден­ тификаторов. Все идентификаторы, описанные внутри блока, видны только внутри этого блока.
46 Глава 2 Перед названием функции main () указывается тип возвращаемого значения. Клю­ чевое слово int означает, что функция возвращает целое число. После названия функции внутри круглых скобок указывается ключевое слово void, которое означа­ ет, что функция не принимает параметров. Что такое функция? Функция - это фрагмент кода внутри фигурных скобок, кото­ рый может быть вызван из какого-либо места программы сколько угодно раз. При этом функция может возвращать какое-либо значение в место вызова или вообще ничего не возвращать, а просто выполнять какую-либо операцию. Вывод строки "Hello, world!" осуществляется с помощью функции printf(). Текст, выводимый в консоль, указывается в кавычках внутри круглых скобок, рас­ положенных после названия функции. Объявление функции printf () расположено внутри файла stdio.h, поэтому в первой строке программы мы включаем этот файл с помощью директивы #include. Если заголовочный файл не включить, то функция будет недоступна. После всех инструкций указывается точка с запятой. Исключением являются со­ ставные инструкции (в нашем примере после закрывающей фигурной скобки блока функции main () точка с запятой не указывается) и директивы препроцессора (в на­ шем примере нет точки с запятой после инструкции с директивой #include). Ука­ зать точку с запятой после инструкции - это то же самое, что поставить точку в конце предложения. Возвращаемое функцией main () значение указывается после ключевого слова return в самом конце блока перед закрывающей фигурной скобкой. Число о в дан­ ном случае означает нормальное завершение программы. Если указано другое чис­ ло, то это свидетельствует о некорректном завершении программы. Согласно стан­ дарту, внутри функции main () ключевое слово return можно не указывать. В этом случае компилятор должен самостоятельно вставить инструкцию, возвращающую значение о. При запуске программы будет автоматически вызвана функция main () и все инст­ рукции внутри фигурных скобок будут выполняться сверху вниз друг за другом. Как только поток выполнения дойдет до закрывающий фигурной скобки или до инструкции return, программа завершит работу. Скомпилируем программу и запустим ее на исполнение. Вначале попробуем это сделать из командной строки без помощи редактора. Запускаем Notepad++ и создаем новый документ. В меню Кодировки выбираем пункт Кодировки I Кириллица I Windows-1251. Вводим текст из листинга 2.1 и сохраняем под названием helloworld.c в каталоге С:\Ьооk. Теперь необходимо скомпилировать программу. Для этого открываем командную строку и делаем текущим каталог С:\Ьооk: C:\Users\Unicross>cd C:\book Затем настраиваем переменную окружения Рдтн: C:\book>set Path=C:\msys64\mingw64\bin;%Path%
Первые шаги 47 Теперь все готово для компиляции. Выполняем следующую команду (текст коман­ ды указывается на одной строке без символа переноса): C:\book>gcc -Wall -Wconversion -03 - finput-charset=cpl251 - fexec-charset=cpl251 -о helloworld.exe helloworld.c Первое слово (gcc} вызывает компилятор gcc.exe . Флаг -Wall указывает выводить все предупреждающие сообщения, возникающие во время компиляции программы, флаг -Wconversion задает вывод предупреждений при возможной потере данных, а флаг -03 определяет уровень оптимизации. С помощью флага -finput-charset указывается кодировка файла с программой, а с помощью флага -fexec-cl ) arset - кодировка С-строк. Название создаваемого в результате компиляции файла (helloworld.exe) задается после флага -о . Далее указывается название исходного тек­ стового файла с программой на языке С (helloworld.c}. Если в процессе компиляции возникнут какие-либо ошибки, то они будут выведе­ ны. Эти ошибки следует исправить, а затем запустить компиляцию повторно. Если компиляция прошла успешно, то никаких сообщений не появится, а отобразится приглашение для ввода следующей команды: C:\book>gcc -Wall -Wconversion -03 - finput-charset=cpl251 - fexec-charset=cp1251 -о helloworld.exe helloworld.c С:\book> После успешной компиляции в каталоге С:\Ьооk будет создан файл helloworld.exe, который можно запустить из командной строки следующим образом: C:\Ьook>helloworld Hello, world! Вторая строка в этом коде- это результат выполнения программы. 2.2. Создание пустого проекта в редакторе Eclipse В разд. 1.8 мы создали два проекта в редакторе Eclipse: Test32c (для создания 32-битных программ) и Test64c (для создания 64-битных программ). При этом мы пользовались мастером, который автоматически создал файл и вставил в него код. Давайте попробуем создать пустой проект и самостоятельно добавить в него файлы. Для создания пустого проекта в меню File выбираем пункт New I Project. В от­ крывшемся окне (см. рис. 1 .30) в списке выбираем пункт С/С++ 1 С Project и на­ жимаем кнопку Next. На следующем шаге (рис. 2 .1) в поле Project name вводим HelloWorld, в списке слева выбираем пункт ExecutaЫe I Empty Project, а в списке справа - пункт MinGW GCC. Для создания проекта нажимаем кнопку Finisb. Проект отобразится в окне Project Explorer.
48 С Project Create С project of selected type Erojoct name: 1 t:!•lloWorld � Use !!efault \ocation �о ,,,;, п C:\cpp\projects\He\ioNorld С Project Project type: Тoolchains: В GNU Autotools Cross GCC • в ExecutaЫe Microsoft Visual С++ • Empty Project 1 MinGWGCC • Hello World ANSI С Project � Shared Library о в Static Library � "3, Makefile project - □ � Showproject types and toolchains only if they are supported on the platform �_f. _ in_is _ h__, jl Рис. 2 .1. Соэдание пустого проекта Глава 2 Далее создадим каталог для файлов с исходным кодом. Для этого в окне Project Explorer щелкаем правой кнопкой мыши на названии проекта и из контекстного меню выбираем пункт New I Source Folder (рис. 2 .2). В открывшемся окне (рис. 2.3) в поле Folder name вводим src и нажимаем кнопку Finisb. �; Project Explorer ;;:; � @)1 l!L� --------- ---�----1 • ["3 Project... •�Test Go lnto о · [1 File н,t� ,�1; Open in NewWindow ,3 File from Template ,(9s! Showin Local Тermina\ ► d Fo\der ,С:;-q •\дTes�!!в Сору '� � Paste ,@!11Х Delete :'>СВs! · Remove: from Conteлt ;,в-[ Source Mcve.,. Renam� .. i::, lmport... cl Export... Ct rl+C (?j C\ass C.trl+ '✓ ( Ш Header File Delete Source File Ctrl+Alt·• Sl,:ft+Down [o:: L Source Fold�- - � -· -- -�-- -·-···­ (Ij (/(++ Project F2 � Conver t to а С/С•+ Project (Adds С/С•+ Nature) ГJ Example... Гj Other... Рис. 2.2 . Контекстное меню проекта Ctrl+N
Первые шаги New Source Folder Source folder ф Exclusion patterns of 1 source folder(s) updated to solve nesting. Project!!am,: j HelloWorld • · -··- -�- -- _-_ - --_-_ · -·· · __J ;__ .B ro �e ... __ J Fol\!бnamo;j�m!�-------- --- --�' ! Browse. ... , � !lpdate exclusion filters in other source folders to solve nesting. (J) Рис. 2.3. Соэдание нового каталога 2.3. Добавление в проект файла с программой 49 Теперь добавим в созданный каталог файл для ввода текста программы. Дnя этого в окне Project Explorer щелкаем правой кнопкой мыши на названии каталога src и из контекстного меню выбираем пункт New I Source File (см. рис. 2 .2). В открыв­ шемся окне (рис. 2 .4) в поле Source folder должно быть уже вставлено значение HelloWorld/src. В поле Source file вводим HelloWorld.c (обратите внимание: файлы с исходным кодом на языке С имеют расширение с), а из списка Template выбира­ ем пункт <None>. Нажимаем кнопку Finisb. В результате файл будет создан в каталоге src и открыт на отдельной вкладке. Вво­ дим сюда код из листинга 2.1 и сохраняем файл. Дnя этого в меню File выбираем пункт Save или нажимаем кнопку Save на панели инструментов. Теперь проверим кодировку файла. Увидеть кодировку файлов проекта можно в свойствах проекта (см. рис. 1.25), а также выбрав в меню Edit пункт Set Encoding (рис. 2 .5). Кодировка должна быть указана Ср1251 (windows-1251). Source File Сгеэtе а n� sourc� file. Source folger. [ HelloV✓orld/src New Sошсе File - □ с: :.=...J ·г.. ···- · · · · ··· · ·.. · · • •"··· Iemplate: : <No _ n•_>___ _ __ ,-,,_ · ____ __ ___ ..,'. Configure.. . (J) Рис. 2.4 . Соэдание файла с программой
50 Set Encoding (!)[(?�fault (inherited from�o�!�.i��ё�P.1}�)} 0 Qther. Ср1251 дррlу 1 � 1 __ о_к_� Cancel Рис. 2 .5. Проверка кодировки файла Глава 2 2.4. Добавление в проект заголовочного файла Помимо файлов с исходным кодом (имеют расширение с) в проекте могут быть за­ головочные файлы (имеют расширение h). В заголовочных файлах указываются прототипы функций и различные объявления. Для создания заголовочного файла в окне Project Explorer щелкаем правой кнопкой мыши на названии каталога src и из контекстного меню выбираем пункт New I Header File (см. рис. 2 .2). В от­ крывшемся окне (рис. 2 .6) в поле Source folder должно быть уже вставлено значе­ ние HelloWorld/src. В поле Header file вводим HelloWorld.h (обратите внимание: заголовочные файлы имеют расширение h), а из списка Template выбираем пункт Default С header template. Нажимаем кнопку Finish. Header File Create а new header file. Source fol!!eг. HelloWorld/src Header fil� [1 _ #�$1ШЙ8� New Header File Iemplate: Default С header template - □ 8.rowse... .., Configure. . . f.inish j Г Cancel Рис. 2 .6. Соэдание заголовочного файла В результате файл будет создан в каталоге src и открыт на отдельной вкладке. При­ чем внутри файла будет вставлен код, приведенный в листинге 2.2 . 1. Листинг 2.2. Содержимое файла- HelloWorld.h #ifndef HELLOWORLD Н #define HELLOWORLD Н #endif /* HELLOWORLD н */
Первые шаги 51 Текст между символами /* и * / является комментарием. Инструкции, начинаю­ щиеся с символа #, - это директивы препроцессора. В нашем примере их три: □ #ifndef - проверяет отсутствие константы с именем HELLOWORLD_н_; □ #define- создает константу с именем HELLOWORLD_H _; □ #endif - обозначает конец блока проверки отсутствия константы. Зачем нужны эти директивы препроцессора? Заголовочный файл мы подключаем к файлу с исходным кодом с помощью директивы #incluc t e: linclude "HelloWorld.h" Встретив в исходном коде директиву #inclucte, компилятор вставляет все содержи­ мое заголовочного файла на место директивы. Если мы вставим две одинаковые директивы #include, то содержимое заголовочного файла будет вставлено дважды. Так как объявить один идентификатор (например, глобальную переменную) дваж­ ды нельзя, компилятор выведет сообщение об ошибке. Для того чтобы этого избе­ жать, прототипы функций и прочие объявления вкладываются в блок, ограничен­ ный директивами #ifndef и #endif. В директиве #ifndef указывается константа, совпадающая с названием заголовочного файла. Все буквы в имени константы заглавные, а точка заменена символом подчеркивания. Если константа не сущест­ вует (при первом включении заголовочного файла так и будет), то с помощью директивы #define эта константа создается и содержимое блока вставляется в ис­ ходный код. При повторном включении заголовочного файла константа уже суще­ ствует, поэтому содержимое блока будет проигнорировано. Таким образом, заголо­ вочный файл дважды вставлен не будет, а значит, и ошибки не возникнет. Вместо этих директив в самом начале заголовочного файла можно указать дирек­ тиву препроцессора #pragrna со значением once, которая таюке препятствует по­ вторному включению файла (в старых компиляторах директива может не поддер­ живаться): #pragrna once // Объявление функций и др. Название заголовочного файла в директиве #include может бь�ть указано внутри угловых скобок: #include <stdio.h> или внутри кав1.1чек: #include "HelloWorld.h" В первом случае заголовочный файл ищется в путях поиска заголовочных файлов. При этом текущиi1 рабочий каталог не просматривается. Добавить каталог в пути поиска заголовочных файлов позволяет флаг - r в команде компиляции. Обычно с помощью угловых скобок включаются заrоловочньlе файлы стандартной библио­ теки или библиотеки стороннего разработчика. Во втором случае мы имеем дело с заголовочным файлом, который вначале ищется в текущем рабочем каталоге (или относительно него), а затем в путях поиска заго-
52 Глава 2 ловочных файлов, как будто название указано внутри угловых скобок. Таким спо­ собом обычно включаются заголовочные файлы проекта. Можно указать просто название заголовочного файла: #include "HelloWorld.h" абсолютный путь к нему: #include "C:\\cpp\\projects\\HelloWorld\\src\\HelloWorld.h" или относительный путь к нему: #include "./HelloWorld.h" Если указано только название заголовочного файла и этот файл не входит с состав проекта (или название указано внутри угловых скобок), нужно дополнительно ука­ зать место поиска заголовочных файлов. Давайте попробуем подключить файл jni.h (находится в каталоге <Путь к JDK>\include): #include "C:\\Prograrn Files\\Java\\jdk-10\\include\\jni.h" Даже если мы укажем абсолютный путь к этому файлу, все равно получим сообще­ ние об ошибке. Дело в том, что внутри файла jni.h подключается файл jni_md.h, кото­ рый расположен в каталоге <Путь к JDK>\include\win32. Компилятору type f1tter te.,rt Resourc.e Builders • C/C- t-• Build Build VariaЫes Environment Logging Se.ttings Tool Chain Editor ., С/С•,. General linux Tools. Path Project Natures Project References Run/Debug Settings Тask Repository Task Tags Validation WikiText Prop€rties for HelloWorld - □ Settings Configuration; ; [ AII configurations] ' ' "; 1 Manage C onfi _ gurat�ns ... 1 � Tool Settings. () Container SettingsJ � Build St�ps � 1 �; Бuild Art�act.J lffi Bina,y ParsersJ О E�J ► i , �· GCC дшmЫ,r � General , !!') GCC С, Compiler @ Dialect @ Preproces.,;or @ lncludes @ Optimitation � Debugging @Ylarnings �� Miscelli!!neous , ф MinGW С Linker � Gene:ral �librari5 � Miscellaneous �) Shared Libmy Setting, , lnclude paths (-1) i'Jii:J�,>1 -�, С,\ т Ies\Jav•\ dk-, (}\mclud• • " C:\Program Files\Java\jdk-10\include\win32" 1 r· lnclude files (�-include) ! Apply and Close ! Рис. 2 .7 . Указание пути к Эа,оловочным файлам
Первые шаги 53 о местоположении этого файла ничего не известно. Давайте в директиве #include оставим только название файла, а путь попробуем указать другим способом: #include <jni.h> Огкрываем свойства проекта (в меню Project выбираем пункт Properties) и из спи­ ска слева выбираем пункт С/С++ Build I Settings. На отобразившейся вкладке (рис. 2.7) из списка Configuration выбираем пункт AII configurations, а затем на вкладке Tool Settings из списка выбираем пункт GCC С Compiler I Includes. В раз­ деле Include patbs (-1) нажимаем кнопку Add. В открывшемся окне (рис. 2 .8) вво­ дим путь к каталогу <Путь к JDK>\include и нажимаем кнопку ОК. Путь будет добав­ лен в список. Аналогичным образом добавляем путь к каталогу <Путь к JDK>\ include\win32. Сохраняем изменения, нажав кнопку Apply and Close. Путь к заголовочным файлам в командной строке добавляется с помощью флага - r: gcc -Wall -03 "-IC:\\Prograrn Files\\Java\\jdk-10\\include" "-IC:\\Prograrn Files\\Java\\jdk-10\\include\\win32" -о helloworld.exe helloworld.c В нашем случае путь содержит пробел, поэтому весь путь указывается внутри ка­ вычек. Если пробелов нет, то кавычки можно не указывать. Add directory path Directory: ! C:\Prog,om FilesVova\jdk-1(1\includ� .____ о_ к -�1 ! C.nce 1 ·1 Worltspaco... i i filesyst�m... j Рис. 2.8 . Добавление пути к заголовочному файлу 2.5. Компиляция и запуск программы в редакторе Eclipse Итак, файл HelloWorld.c с кодом из листинга 2.1 добавлен в проект, теперь мож­ lЮ скомпилировать его и запустить в редакторе Eclipse. Предварительно сохра­ ИJ1ем файл, выбрав в меню File пункт Save или нажав комбинацию клавиш <Ctrl>+<S>. Скомпилировать программу можно несколькими способами: □ в меню Project выбираем пункт Build Project; □ нажимаем кнопку с изображением молотка на панели инструментов (см. рис. 1 .38). В окне Console (рис. 2.9) отобразятся инструкции компиляции и результат ее вы­ оо..1нения. Если компиляция прошла успешно, то никаких сообщений об ошибках • этом окне не будет:
54 CDT Global Build Console •••••••••• ••• ••••О<•••••• •Н•••• •••••• • ••"''''' ••••• 19�51:39 .,,.,,.,,., Increrr;ental Build of configuraticn Debug for pr-oject Helloblorld "'"'** Info: Internal Builder is used fог build - □ gcc "-IC: \\Program Files\ \Java\\jdk-10\\include" "-IC: \\Progr am Files\\Java\\jdk-10\ \include\\winЗ2" gcc -о HelloWorld.exe "src\\HelloWorld.o" 19:51:39 Build finished. 0 errors, 0 warnings. (tock 281ms) < Рис. 2 .9 . Результат компиляции в окне Console 19:51:39 **** Incremental Build of configuration Debug for project HelloWorld **** Info: Internal Builder is used for build gcc "-IC:\\Program Files\\Java\\jdk-10\\include" "-IC:\\Program Files\\Java\\jdk-10\\include\\win32" -00 -gЗ - Wall -с - fmessage-length=0 -о "src\\HelloWorld.o" " .. \\src\\HelloWorld.c" gcc -о HelloWorld.exe "src\\HelloWorld.o" 19:51:39 Build Finished. О errors, О warnings. (took 28lms) Глава 2 EJ .., Компиляция в редакторе Eclipse выполняется в два прохода. При первом проходе создается объектный файл HelloWorld.o, а на втором проходе на его основе создается ЕХЕ-файл. По умолчанию для проекта задается режим компиляции Debug (отладка). В этом режиме дополнительно сохраняется информация для отладчика, и ЕХЕ-файл будет создан без оптимизаций. Когда программа уже написана и отлажена, нужно вы­ брать режим Release. Дпя этого в меню Project выбираем пункт Build Configurations 1 Set Active I Release (рис. 2.1О). Кроме того, на панели инструментов рядом с кноп­ кой с изображением молотка расположена кнопка со стрелкой, направленной вниз. Если щелкнуть мышью на этой кнопке, то отобразится меню (рис. 2.11), в котором также можно выбрать режим компиляции. Ctrl+B � BuildAII �, Hello __ _ _ ________ _, ., , ! ===========г----- - ---, J l ___ Build Conf�gur a _ t i_on_.s____ ►_ 0 ,� _ S_et_A _ ct_i v_ • __ __ ►1 1 Debug h> Build Project Manage. , iв 2��� - _-�-J Build Working Set 1, Clean, . . , wo г.;·1 ...:::_, BuildAutomatically Build Тargets С/С++ lndex Properties Build Ьу Working Set SetActive Ьу Working Set Mariage Wc,rk1ng Se1s, .. Рис. 2.10. Выбор режима компиляции в меню
Первые шаги .с ' . "") incl 1 Debug ' lReJease Wortd.c � 1 Рис. 2.11. Выбор режима компиляции на панели инструментов 55 При создании пустого проекта в настройках режима Release не указан режим опти­ мизации и не отключен вывод отладочной информации. Давайте это исправим. Оr ­ крываем свойства проекта (в меню Project выбираем пункт Properties) и из списка слева выбираем пункт С/С++ Build I Settings. На отобразившейся вкладке из спи­ ска Configuration выбираем пункт Release, а затем на вкладке Tool Settings выби­ раем пункт GCC С Compiler I Optimization. Из раскрывающегося списка Optimization Level (рис. 2.12) выбираем пункт Optimize most (-03). Затем выбира­ ем пункт GCC С Compiler I Debugging и из раскрывающегося списка Debug Level выбираем пункт None. Далее укажем кодировку файла и кодировку С-строк. Дriя этого в свойствах проек­ та из списка слева выбираем пункт С/С++ Build I Settings. На отобразившейся вкладке из списка Configuration выбираем пункт АН configurations, а затем на вкладке Tool Settings из списка выбираем пункт GCC С Compiler I Miscellaneous (см. рис. 1 .35). В поле Other flags через пробел к имеющемуся значению добавляем следующие инструкции: -finput-charset=cpl251 -fexec-charset=cp1251 Содержимое поля Other flags после изменения должно быть таким: -с -fmessage-length=O -finput-charset=cp1251 -fexec-charset=cp1251 Затем на вкладке Tool Settings из списка выбираем пункт GCC С Compiler 1 Warnings. Устанавливаем флажок -Wconversion. Проверяем также установку флажка -Wall. Сохраняем настройки проекта, нажимая кнопку Apply and Close. Settings Configuration: !R;�;[Active] • � GCC AssemЫer @ General • ® GCC С Compiler @ Dialect @Preprocessor @lncludes @ Optimization @1)ebu99in9 @Warnings @ Miscellane<>u . s Optimization Leve\ Optim�emost (-03) , . . . None(-00) Other opt1m,zat1on flags Optimize (-OI) Ootimizemore (·02) •• • LOptimizefor size (·Os) Рис. 2.12. Выбор режима оптимизации "i I М..nage Configuration�:�i с
56 Глава 2 После изменения настроек при компиляции в режиме Release в окне Console ото­ бразятся следующие инструкции: 20:06:27 **** Incremental Build of configuration Release for project HelloWorld **** Info: Internal Builder is used for build gcc "-IC:\\Program Files\\Java\\jdk-10\\include" "-IC:\\Program Files\\Java\\jdk-10\\include\\win32" -03 - Wall - Wconversion -с -fmessage-length=0 -finput-charset=cp1251 - fexec-charset=cp1251 -о "src\\HelloWorld.o" " .. \\src\\HelloWorld.c" gcc -о HelloWorld.exe "src\\HelloWorld.o" 20:06:27 Build Finished. О errors, О warnings. (took 344ms) В результате компиляции в разных режимах были созданы два ЕХЕ-файла: C:\cpp\projects\HelloWorld\Debug\HelloWorld.exe C:\cpp\projects\HelloWorld\Release\HelloWorld.exe Первый файл содержит отладочную информацию и имеет размер 383 Кбайт (78,4 Кбайт, если использовать компилятор из каталога C:\MinGW64\mingw64\ Ьin). Второй файл не содержит отладочной информации и имеет размер 357 Кбайт (52,7 Кбайт, если использовать компилятор из каталога C:\MinGW64\mingw64\ Ьin). При компиляции второго файла была дополнительно выполнена оптимизация. Именно этот файл следует отдавать заказчикам, хотя запустить на исполнение из командной строки можно оба файла: C:\book>C:\cpp\projects\HelloWorld\Debug\HelloWorld.exe Hello, world! C:\book>C:\cpp\projects\HelloWorld\Release\HeJloWorld.exe Hello, world! Запустить программу в редакторе Eclipse можно несколькими способами: □ в меню Run выбираем пункт Run; □ в меню Run выбираем пункт Run As, а затем профиль запуска; □ нажимаем комбинацию клавиш <Ctrl>+<F11>; □ на панели инструментов нажимаем кнопку с зеленым кругом и белым треуголь- ником внутри него (см. рис. 2 .11). Если программа была изменена, то автоматически запустится процесс компиляции, а затем результат отобразится в окне Console. Согласитесь, очень просто и удобно, когда компиляция и запуск выполняются нажатием всего одной кнопки. Для изменения настроек конфигурации запуска в меню Run выбираем пункт Run Configurations. В итоге отобразится окно (рис. 2.13), в котором можно указать спе­ циальные настройки для уже созданных конфигураций или создать новую конфи­ гурацию запуска.
Первые шаги Run Configurations Ctute, mмi.ge, 1111d Nn configur.tions ··- IC $ll�Xi�1Р. j type filter text @') Build Dcx:ker lmage ,,, � С{С++ Application ,� НelloWodd.-1 (!J Тest32c.exe t!:)Test64c.exe [rJ С/С++ Container Launcher [:[] С/С++ RemoteApplication Cij С/С•+ Unit О Docker Compos• !!J Launch Group ► Launch Group (Deprecated) Launch over Serial .t:jame: J HelloWond.exe •-н«м•• -·-- --· ifl Mai�', (>с),Arguments). Environment) [!J �ommon{ frojю: HelloWond ____]i С/С++ Application: i Debug/HelloWorld.exe 1 Y:ariюles• •. 1 1S-ch Project... i \ Build [,t required) lmor• launching Build Configuration: 1Dюug 0 ЕnаЫе auto build О DjQЬle auto build i,�.. Browse... \,/j _ _....: f1 Run Docker lmage @ Use workspace sdtings �onfigur� \Vorl:s11ace Settings.. . Filt� matched 13 of 14 items R�yert Appl 1· !!,un Рис. 2 .13. Окно Run Conflgurations 2.6. Структура программы 57 ··- · ! ' А! ;I1 1il 1� (1,S; 1 "i Что ж, программная среда установлена, и редактор настроен. Теперь можно при­ ступать к изучению языка С. Как вы уже знаете, программа состоит из инструкций, расположенных в текстовом файле. Структура обычной программы выглядит так: <Подюuочение заголовочных файлов> <Объявление глобальных переменных> <Объявление функuий и др.> int main(void) { <Инструкции> return О; <Определения функций и др.> В самом начале программы подключаются заголовочные файлы, в которых содер­ жатся объявления идентификаторов без их реализации. Например, чтобы иметь возможность вывести данные в окно консоли, необходимо подключить файл stdio.h, в котором содержится объявление функции printf ( J. Подключение осуществляется с помощью директивы #include: #include <stdio.h>
58 Глава 2 С помощью директивы #include можно подключать как стандартные заголовочные файлы, так и пользовательские файлы. После подключения файлов производится объявление глобальных переменных: Переменные предназначены для хранения значений определенного типа. Глобш�ь­ ные переменные видны во всей программе, включая функции. Если объявить пере­ менную внуrри функции, то область видимости переменной будет ограничена рам­ ками функции и в других частях программы использовать переменную нельзя. Такие переменные называются локш�ьными. Объявить целочисленную переменную можно так: int х; В этом примере мы объявили переменную с названием х и указали, что она может хранить данные, имеющие тип int. Ключевое слово int означает, что переменная предназначена для хранения целого числа. Если в команде компиляции указан флаг -wconversion, то попытка присвоить пере­ менной значение, имеющее другой тип, приведет к предупреждению. Исключением являются ситуации, в которых компилятор может произвести преобразование типов данных без потери точности. Например, целое число без проблем преобразу­ ется в вещественное число, однако попытка преобразовать вещественное число в целое приведет к выводу предупреждающего сообщения. Пример сообщения: warning: conversion to 'int' alters 'douЬle' constant value Хотя компилятор предупреждает о потери данных, программа все равно будет скомпилирована. Поэтому обращайте внимание на сообщения, которые выводятся в окне ProЫems, и на различные подчеркивания кода в редакторе Eclipse. Иначе программа будет работать, но результат выполнения окажется некорректным. Если в команде компиляции не указан флаг -wconversion, то предупреждающих со­ общений о потери данных вообще не будет. Советую установить этот флаг в на­ стройках проекта для режима Debug. Открываем свойства проекта (в меню Project выбираем пункт Properties) и из списка слева выбираем пункт С/С++ Build 1 Settings. На отобразившейся вкладке из списка Configuration выбираем пункт Debug, а затем на вкладке Tool Settings выбираем пункт GCC С Compiler 1 Warnings. Устанавливаем флажок -Wconversion. Проверяем также установку флажка -Wall. Сохраняем изменения. Обратите внимание на то, что объявление переменной заканчивается точкой с запя­ той. Большинство инструкций в языке С должно заканчиваться точкой с запятой. Если точку с запятой не указать, то компилятор сообщит о синтаксической ошибке. Пример сообщения: error: expected '=', ',', ';', 'asm' or ' attribute 'before 'х' Для того чтобы увидеть строку, о которой сообщает компилятор, сделайте двойной щелчок мышью на сообщении в окне Console. В результате строка с ошибкой в программе станет активной. В нашем случае активной будет строка, расположен­ ная сразу после объявления переменной. Обратите также внимание: на вкладке ре-
Первые шаги 59 дактор пометил строку красным кругом с белым крестиком внутри. Если навести указать мыши на эrу метку, то отобразится текст ошибки. При объявлении можно сразу присвоить начальное значение переменной. Присваи- . . ванне значения переменной при объявлении называется инициш,uзацией перемен­ ной. Для того чтобы произвести инициализацию переменной, после ее названия указывается оператор =, а затем значение. Пример: intх=10; Если глобальной переменной не присвоено значение при объявлении, то она будет иметь значение о. Если локальной переменной не присвоено значение, то перемен­ ная будет содержать произвольное значение. Как говорят в таком случае: перемен­ ная содержит "мусор". Обратите внимание на то, что перед оператором = и после него вставлены пробелы. Количество пробелов может быть произвольным, или пробелов может не быть во­ все. Кроме того, допускается вместо пробелов использовать символ перевода стро­ ки или символ табуляции. Например, эти инструкции вполне допустимы: int х=21; int у= int z 56; 85; Тем не менее следует придерживаться единообразия в коде и обрамлять операторы одним пробелом. Следует учитывать, что это _не строгие правила, а лишь рекомен­ дации по оформлению кода. Как видно из предыдущего примера, инструкция может быть расположена на не­ скольких строках. Концом инструкции является точка с запятой, а не конец с�роки. Например, на одной строке может быть несколько инструкций: intх=21;intу=85;intz=56; Из этого правила есть исключения. Например, после директив препроцессора точка . с запятой не указывается. В этом случае концом инструкции является конец строки. Директиву препроцессора можно узнать по символу # перед названием директивы. Типичным примером директивы препроцессора является директива #include, кото­ рая используется для включения заголовочных файлов: #include <stdio.h> После объявления глобальных переменных могут располагаться объявления функ­ ций. Такие объявления называются прототипами. Схема прототипа функции вы­ глядит следующим образом: <Тип возвращаемого значения> <Название функции>( [<Тип> [<Параметр 1>] [, ... , <Тип> [<Параметр N>]]]); Например, прототип функции, которая складывает два целых числа и возвращает их сумму, выглядит так: int sum(int х, int у);
60 Глава 2 После объявления функции необходимо описать ее реализацию, которая называет­ ся определением функции. Определение функции обычно располагается после оп­ ределения функции main() . Обратите внимание на то, что объявлять прототип функции main() не нужно, т. к. определение этой функции обычно расположено перед определением других функций. Пример определения функции sum(): int sum(int х, int у) { return х + у; Как видно из примера, первая строка в определении функции sum() совпадает с объявлением функции. Следует заметить, что в объявлении функции можно не указывать названия параметров. Достаточно указать информацию о типе данных. Таким образом, объявление функции можно записать так: int sum(int, int); После объявления функции ставится точка с запятой. В определении функции внутри фигурных скобок должна быть описана реализация функции. Так как в объ­ явлении указано, что функция возвращает целочисленное значение, следовательно, после описания реализации необходимо вернуть значение. Возвращаемое значение указывается после ключевого слова return. Если функция не возвращает никакого значения, то перед названием функции вместо типа данных указывается ключевое слово void. Пример объявления функции, которая не возвращает значения: void print(int); Пример определения функции print(): void print(int х) { printf("%d", х); Вся реализация функции должна быть расположена внутри фигурных скобок. От­ крывающая скобка может находиться на одной строке с определением функции, как в предыдущем примере, или в начале следующей строки. Пример: void print(int х) { printf("%d", х); Многие программисты считают такой стиль наиболее приемлемым, т. к. откры­ вающая и закрывающая скобки расположены друг под другом. На мой же взгляд образуется лишняя пустая строка. Так как размеры экрана ограничены, при нали­ чии пустой строки на экран помещается меньше кода и приходится чаще пользо­ ваться полосой прокрутки. Если размещать инструкции с равным отступом, то блок кода выделяется визуально и следить за положением фигурных скобок просто из­ лишне. Тем более, что редактор Eclipse позволяет подсветить парные скобки. Для того чтобы найти пару фигурных скобок, следует поместить указатель ввода после скобки. В результате скобки будут подсвечены. Какой стиль использовать, зависит от личного предпочтения программиста или от правил оформления кода, принятых
Первые шаги 61 в определенной фирме. Главное, чтобы стиль оформления внутри одной программы был одинаковым. Перед инструкциями внутри фигурных скобок следует размещать одинаковый от­ ступ. В качестве отступа можно использовать пробелы или символ табуляции. При использовании пробелов размер отступа равняется трем или четырем пробелам для блока первого уровня. Для вложенных блоков количество пробелов умножают на уровень вложенности. Если для блока первого уровня вложенности использовалось три пробела, то для блока второго уровня вложенности должно использоваться шесть пробелов, для третьего уровня - девять пробелов и т. д. В одной программе не следует использовать и пробелы, и табуляцию в качестве отступа. Необходимо выбрать что-то одно и пользоваться этим во всей программе. Закрывающая· фигурная скобка обычно размещается в конце блока на отдельной строке. После скобки точка с запятой не указывается. Однако наличие точки с запя­ той ошибкой не является, т. к . инструкция может не содержать выражений вообще. Например, такая ·инструкция вполне допустима, хотя она ничего не делает: Самой главной функцией в программе является функция main(). Именно функция с названием rnain ( ) будет автоматически вызываться при запуске программы. Функция имеет три прототипа: int rnain(void); int rnain(int argc, char *argv[]); int rnain(int argc, char *argv[], char **penv); Значение void внутри круглых скобок означает, что функция не принимает пара­ метры. Этим прототипом мы пользовались в листинге 2.1. Второй прототип применяется для получения значений, указанных при запуске программы из командной строки (см. листинг 2.1 О). Количество значений доступно через параметр argc, а сами значения через параметр argv. Параметр penv в третьем прототипе позволяет получить значения переменных окружения (см. листинг 17.3). Перед названием функции указывается тип возвращаемого значения. Ключевое слово int означает, что функция возвращает целое число. Возвращаемое значение указывается после ключевого слова return в самом конце функции rnain() . Число о означает нормальное завершение программы. Если указано другое число, то это свидетельствует о некорректном завершении программы. Согласно стандарту, внутри функции rnain() ключевое слово return можно не указывать. В этом случае компилятор должен самостоятельно вставить инструкцию, возвращающую значе­ ние о. Возвращаемое значение передается операционной системе и может исполь­ зоваться для определения корректности завершения программы. Вместо безликого значения о можно воспользоваться макроопределением EXIT_succEss, а для индикации некорректного завершения программы - макрооп­ ределением EXIT_FAILURE. Предварительно необходимо включить заголовочный файл stdlib.h. Определения макросов:
62 #include <stdlib.h> #define EXIT SUCCESS О #define EXIT FAILURE 1 Пример: return EXIT SUCCESS; Глава 2 В листинге 2.3 приведен пример программы, структуру которой мы рассмотрели в этом разделе. Не расстраивайтесь, если что-то показалось непонятным. Все это мы будем изучать более подробно в следующих главах книги. На этом этапе доста­ точно лишь представлять структуру программы и самое главное - уметь ее ском­ пилировать и запустить на выполнение. 1 Листинr 2.3. Пример проrраммы // Включение заголовочных файлов #include <stdio.h> #include <stdlib.h> // Объявление глобальных переменных intх=21; int у= 85; // Объявление функций и др. int surn(int, int); void print(int); // Главная функция (точка входа в программу) int main(vo . id) { int z; // Объявление локальной переменной z = surn(x, у); // Вызов функции surn() print(z); // Вызов функции print() return EXIT_SUCCESS; // Определения функций int surn(int х, int у) { return х + у; void print(int х) printf("%d", х); Объявление функций можно вынести в отдельный заголовочный файл и включить его с помощью директивы #include. В разд. 2.4 мы создали заголовочный файл HelloWorld.h . Давайте им воспользуемся. Вначале добавим в программу инструк­ цию: #incl1,1de "HelloWorld.h" после инструкции: #include <stdlib.h>
Первые шаги 63 Так как мы указали название заголовочного файла HelloWorld.h внутри кавычек, компилятор вначале будет искать его внутри проекта, а если не найдет, то в путях поиска заголовочных файлов. Удаляем эти инструкции из программы и сохраняем файл: // Объявление функций и др. int sum(int, int); void print(int); Затем вставляем в файл HelloWorld.h код из листинга 2.4. #ifndef HELLOWORLD Н #define HELLOWORLD Н // Объявление функций и др. int sum(int, int); void print(int); #endif /* HELLOWORLD Н */ Директивы препроцессора #ifndef, #define и #endif препятствуют повторному включению заголовочного файла. Если этих директив не будет, то повторное объ­ явление идентификаторов (например, глобальных переменных) вызовет ошибку при компиляции. Вместо этих директив можно указать в самом начале файла директиву препроцессора #pragrna со значением once: #pragrna once // Объявление функций и.др . int sum(int, int); void print(int); 2.7. Комментарии в программе Комментарии предназначены для вставки пояснений в текст программы, и компи­ лятор полностью их игнорирует. Внутри комментария может располагаться любой текст, включая инструкции, которые выполнять не следует. Помните, комментарии нужны программисту, а не компилятору. Вставка комментариев в код позволит. через некоторое время быстро вспомнить предназначение фрагмента кода. В языке С присутствуют два типа комментариев: однострочный и многострочный. Однострочный комментарий начинается с символов // и заканчивается в конце строки. ВставлятJ', однострочный комментарий можно как в начале строки, так и после инструкции. Если символы// разместить перед инструкцией, то она не будет выполнена. Если· символы // расположены внутри кавычек, то они не являются признаком начала комментария. Примеры однострочных комментариев:
64. // Это комментарий printf("Hello, world!\n"); // Это комментарий // printf("Hello, world!"); // Инструкция вьmолнена не будет printf("// Это НЕ комментарий!!!"); Глава 2 Многострочный комментарий начинается с символов / * и заканчивается символа­ ми * /. Комментарий может быть расположен как на одной строке, так и на несколь­ ких. Кроме того, многострочный комментарий можно размещать внуrри выраже­ ния, хотя это и нежелательно. Следует иметь в виду, что многострочные коммента­ рии не могут быть вложенными, поэтому при комментировании больших блоков следует проверять, что в них не встречается закрывающая комментарий комбина­ ция символов * /. Тем не менее однострочный комментарий может быть располо­ жен внутри многострочного комментария. Примеры многострочных комментариев: /* Многострочный комментарий */ printf("Hello, world!\n"); /* Это комментарий */ /* printf("Hello, world!"); // Инструкция вьпюлнена не будет */ int х; х = 10 /* Комментарий*/+ 50 /* внутри выражения */; printf("/* Это НЕ комментарий!!! */"); НА ЗАМЕТКУ Старые компиляторы подцерживали только многострочные комментарии. Редактор Eclipse позволяет быстро добавить символы комментариев. Для того что­ бы вставить однострочный комментарий в начале строки, нужно сделать текущей строку с инструкцией и нажать комбинацию клавиш <Ctrl>+</>. Если предвари­ тельно выделить сразу несколько строк, то перед всеми выделенными строками будет вставлен однострочный комментарий. Если все выделенные строки были за­ комментированы ранее, то комбинация клавиш <Ctrl>+</> удалит все одностроч­ ные комментарии. Для вставки многострочного комментария необходимо выделить строки и нажать комбинацию клавиш <Shift>+<Ctrl>+</>. Для удаления много­ строчного комментария предназначена комбинация клавиш <Shift>+<Ctrl>+<\>. У начинающих программистов может возникнуть вопрос: зачем может потребо­ ваться комментировать инструкции? Проблема заключается в том, что часто в ло­ гике работы программы возникают проблемы. Именно по вине программиста. На­ пример, программа выдает результат, который является неверным. Для того чтобы найти ошибку в алгоритме работы программы, приходится отключать часть кода с помощью комментариев, вставлять инструкции вывода промежуточных результа­ тов и анализировать их. Как говорится: разделяй и властвуй. Таким "дедовским" способом мы обычно ищем ошибки в коде. А "дедовским" мы назвали способ по­ тому, что сейчас все редакторы предоставляют методы отладки, которые позволяют выполнять код построчно и сразу видеть промежуточные результаты. Раньше тако-­ го не было. Хотя способ и устарел, но все равно им часто пользуются.
первые шаги 2.8. Вывод данных 65 Д11я вывода одиночного символа в языке С применяется функция putchar( J. Прото­ mп функции: tinclude <stdio.h> :nt putchar(int ch); Пример вывода символа: putchar( 'w' ) ; // w putchar(ll9); // w Вывести строку позволяет функЦЮ1 puts( J • Прототип функции: ,include <stdio.h> :nt puts(const char *str); Функция выводит строку str и вставляет символ перевода строки. Пример: puts("Stringl"); puts("String2"); Результат выполнения: Stringl String2 Д11я форматированного вывода используется функция printf( J • Можно также вос­ пользоваться функцией _printf 1 (), которая позволяет дополнительно задать локаль. Прототипы функций: iinclude <stdio.h> int printf(const char *forrnat, . .. ); int _printf_l(const char *forrnat, locale_t locale, . . .); В параметре forrnat указывается строка специального формата.Внутри этой строки можно указать обычные символы и спецификаторы формата, начинающиеся с сим­ вола %. Вместо спецификаторов формата подставляются значения, указанные в ка­ честве параметров. Количество спецификаторов должно совпадать с количеством переданных параметров. В качестве значения функция возвращает количество вы­ веденных символов. Пример вывода строки и числа: printf("String\n"); printf("Count %d\n", 10); printf("%s %d\n", "Count", 10); Результат выполнения: String Count 10 Count 10 В первом примере строка формата не содержит спецификаторов и выводится как есть. Во втором примере внутри строки формата используется спецификатор %d,
бб Глава 2 предназначенный для вывода целого числа. Вместо этого спецификатора подстав­ ляется число 10, переданное во втором параметре. В третьем примере строка со­ держит сразу два спецификатора %s и %d. Спецификатор %s предназначен для выво­ да строки, а спецификатор %d - для вывода целого числа. Вместо спецификатора %s будет подставлена строка count, а вместо спецификатора %d-число 10. Обрати­ те внимание на то, что тип данных переданных значений должен совпадать с типом спецификатора. Если в качестве значения для спецификатора %s указать число, то это приведет к ошибке времени исполнения. На этапе компиляции будет выведено лишь предупреждающее сообщение: warning: format '%s' expects argument of type 'char *' but argument 2 has type 'int' [-Wformat=] Параметр locale в функции _printf_1 () позволяет задать локаль. Настройки лакали для разных стран различаются. Например, в одной стране принято десятичный раз­ делитель вещественных чисел выводить в виде точки, в другой- в виде запятqй. В функции printf() используются настройки лакали по умолчанию. Пример: // #include <locale.h> locale_t locale = _create_locale(LC_NUMERIC, "dutch"); printf("%.2f\n", 2.5); // 2.50 _printf_l("%.2f\n", locale, 2.5); // 2,50 setlocale(LC_NUМERIC, "dutch"); printf("%.2f\n", 2.5); free_locale(locale); // 2,50 Спецификаторы имеют следующий синтаксис: %[<Флаги>] [<Ширина>] [.<Точность>] [<Размер>]<Тиn> В параметре <Тип> могут быть указаны следующие символы: □ с-символ: printf("%c", 'w'); //w printf("%c", 119); //w □ s-строка: printf("%s", "String"); // String □ d или i-десятичное целое число со знаком: printf("%d %i", 10, 30); printf("%d %i", -10, -30); //1030 // -10 -30 □ и-десятичное целое число без знака: printf("%u", 10); // 10 □ о-восьмеричное число без знака: printf("%o %о", 10, 077); //1277 printf("%#o %#о", 10, 077); // 012 077
Первые шаги □ х - шестнадцатеричное число без знака в нижнем регистре: printf("%х %х", 10, Oxff); // а ff printf("%#x %#х", 10, Oxff); // Оха Oxff □ х - шестнадцатеричное число без знака в верхнем регистре: printf("%X %Х", 10, Oxff); // А FF printf("%#X %#Х", 10, Oxff); // ОХА OXFF □ f - вещественное число в десятичном представлении: printf("%f %f", 18.65781452, 12.5); // 18.657815 12.500000 printf("%f", -18.65781452); // -18.657815 printf("%#.Of %.Of", 100.0, 100.0); // 100. 100 67 □ е - вещественное число в экспоненциальной форме (буква "е" в нижнем ре­ гистре): printf("%e", 18657.81452); // 1.865781е+ОО4 printf("%e", 0.000081452); // 8.145200е-005 □ Е - вещественное число в экспоненциальной форме (буква "е" в верхнем ре­ �::истре): printf("%E", 18657.81452); // 1.865781Е+004 □ g-эквивалентно f или е (выбирается более короткая запись числа): printf("%g %g %g", 0.086578, 0.000086578, 1.865Е-005); // 0.086578 8.6578е-005 1.865е-005 □ G - эквивалентно f или Е (выбирается более короткая запись числа): printf("%G %G %G", 0.086578, О.000086578, 1.865Е-005); // 0.086578 8.6578Е-005 1.865Е-005 □ р - вывод адреса переменной: intх=10; printf("%p", &х); // Значение в проекте Test32c: 0028FF2C // Значение в проекте Test64c: 000000000023FE4C □ % - символ процента ( %): printf("10%%"); // 10% Параметр <Ширина> задает минимальную ширину поля. Если строка меньше шири­ ны поля, то она дополняется пробелами. Если строка не помещается в указанную ширину, то значение игнорируется и строка выводится полностью: printf("'%3s"', "string"); // 'str;i.ng' printf("'%10s"', "string"); // ' string' Задать минимальную ширину можно не только для строк, но и для других типов: printf(" '%10d' ", 25); // ' 25' printf("' %10f' ", 12.5); // ' 12.500000'
68 Глава 2 Параметр <Точность> задает количество знаков после точки для вещественных чисел. Перед этим параметром обязательно должна стоять точка. Пример: printf("'%10.5f'", 3.14159265359); // ' 3.14159' printf("'%.3f'", 3.14159265359); // '3.142' Если параметр <Точность> используется применительно к целому числу, то он зада­ ет минимальное количество цифр. Если число содержит меньшее количество цифр, то вначале числа добавляются нули. Пример: printf("'%7d"', 100); //' 100' printf("'%.7d'", 100); // '0000100' printf('"%.7d"', 123456789); // '123456789' Если параметр <Точность> используется применительно к строке, то он задает мак­ симальное количество символов. Символы, которые не помещаются, будут отбро­ шены. Пример: printf("'%5.7s'", "Hello, world!"); // 'Hello, ' printf("'%15.20s'", "Hello, world!"); // ' Hello, world!' Вместо минимальной ширины и точности можно указать с . имвол *. В этом случае значения передаются через параметры функции printf() в порядке указания сим­ волов в строке формата. Примеры: printf("'%*.*f'", 10, 5, 3.14159265359); // ' 3.14159' printf("'%.*f'", 3, 3.14159265359); // '3.142' printf("'%*s'", 10, "string"); 11' string' В первом примере вместо первого символа* подставляется число 10, указанное во втором параметре, а вместо второго символа * подставляется число 5, указанное в третьем параметре. Во втором примере вместо символа * подставляется число 3, которое задает количество цифр после точки. В третьем примере символ* заменя­ ется числом 10, которое задает минимальную ширину поля. В параметре <Флаги> могут быть указаны следующие символы: □ # - для восьмеричных значений добавляет в начало символ о, для шестнадцате­ ричных значений добавляет комбинацию символов Ох (если используется тип х) или ох (если используется тип х), для вещественных чисел указывает всегда вы­ водить дробную точку, даже если задано значение о в параметре <Точность>: printf("%#o %#о"' 10, 077); 11 012 077 printf("%#x %#х", 10, Oxff); 11 Оха Oxff printf("%#Х %#Х", 10, Oxff); 11 ОХА OXFF printf("%#.Of %.Of", 100.0, 100.0); 11 100. 100 □ о - задает наличие ведущих нулей для числового значения: printf("'%7d'", 100); // ' 100' printf("'%07d'", 100); // '0000100' □ - - задает выравнивание по левой границе области. По умолчанию использует­ ся выравнивание по правой границе. Пример: printf("'%5d' '%-5d'", 3, 3); 11 ' 3' '3
Первые шаги 69 □ пробел - вставляет пробел перед положительным числом. Перед отрицатель­ ным числом будет стоять минус. Пример: printf('"% d' '% d'", -3, 3); // '-3' ' 3' □ + - задает обязательный вывод знака как для отрицательных, так и для положи­ тельных чисел. Пример: printf('"%+d' '%+d"', -3, 3); // '-3 ' ' +3 ' В параметре <Размер> могут быть указаны следующие буквы: □ h - для вывода значения переменной, имеющей тип short int. Пример: short int х= 32767; printf("%hd", х); // 32767 unsigned short int у= 65535; printf("%hu", у); // 65535 При передаче в функцию значение типа short автоматически расширяется до типа int, поэтому букву h можно вообще не указывать: short х = 32767; printf("%d", х); // 32767 unsigned short у= 65535; printf("%u", у); // 65535 □ 1 (буква "эль") - для вывода значения переменной, имеющей тип long int. Пример: long int х= 21474836471; printf("%ld", х); // 2147483647 Модификатор 1 можно использовать совместно с типами с и s, для вывода двух­ байтового символа и строки, состоящей из двухбайтовых символов, соответст­ венно. Пример: wchar_t str[J = 1"string"; printf("%ls", str); // string □ I64 - для вывода значения переменной, имеющей тип long long int. Использу­ ется в Windows. Пример: long long х = 922337203685477580711; printf("%I64d\n", х); // 9223372036854775807 unsigned long long у= 18446744073709551615U11; printf("%I64u\n", у); // 18446744073709551615 □ 11 - для вывода значения переменной, имеющей тип long long. При использо­ вании функции printf( J в Windows при компиляции в MinGW выводятся пре­ дупреждения. В Windows можно либо указать I64 вместо 11, либо использовать функцию_mingw_printf( J вместо printf( J. Пример: long long х = 922337203685477580711; _mingw_printf("%lld", х); // 9223372036854775807
70 Глава 2 □ L - для вывода значения переменной, имеющей тип long douЬle. Для того что­ бы значение в MinGW в Windows выводилось правильно, нужно использовать функцию _mingw_printf() вместо printf (). Пример: long douЬle х = 8e+245L; _ mingw_printf("%Le\n", х); // 8.000000е+245 Если выводить несколько значений подряд с помощью функции printf(), то они все отобразятся на одной строке: printf("stringl"); printf("string2"); // stringlstring2 Для того чтобы значения выводились на отдельных строках, нужно воспользовать­ ся комбинацией символов \n, обозначающей перевод строки: printf("stringl\n"); printf("string2"); /* stringl string2 */ С помощью стандартного вывода можно создать индикатор выполнения процесса в окне консоли. Для того чтобы реализовать такой индикатор, нужно вспомнить, что символ перевода строки в Windows состоит из двух символов- \r (перевод каретки) и \n (перевод строки). Таким образом, используя только символ перевода каретки \r, можно перемещаться в начало строки и перезаписывать ранее выведен­ ную информацию. Пример индикатора процесса показан в листинге 2.5. Листинг 2.5. Индикатор выполнения процесса #include <stdio.h> #include <windows.h> int main(void) { printf(" .. . 0%%"); for(inti=5;i<101;i+=5){ Sleep(l000); // Имитация процесса printf("\r ... %d%%", i); fflush(stdout); printf("\n"); return О; Открываем командную строку (в редакторе Eclipse эффекта не будет видно), ком­ пилируем программу и запускаем: C:\book>gcc -Wall -Wconversion -03 -о helloworld.exe helloworld.c C:\book>helloworld.exe
Первые шаги 71 Эта программа немного сложнее, чем простое приветствие из листинга 2.1. Здесь присуrствует имитация процесса с помощью функции Sleep() (при этом программа "засыпает" на указанное количество миллисекунд). Прототип функции: linclude <windows.h> VOID Sleep(DWORD dwMilliseconds); Тип данных owoRo объявлен так: typedef unsigned long DWORD; Кроме того, в программе использован цикл for, который позволяет изменить поря­ док обработки инструкций. Обычно программа выполняется сверху вниз и слева направо. Инструкция за инструкцией. Цикл for меняет эту последовательность вы­ полнения. Инструкции, расположенные внуrри блока, выполняются несколько раз. Количество повторений зависит от выражений внутри круглых скобок. Этих выра­ жений три, и разделены они точками с запятой. Первое выражение объявляет цело­ численную переменную i и присваивает ей значение 5. Второе выражение является условием продолжения повторений. Пока значение переменной i меньше значения 101, инструкции внутри блока будуr повторяться. Это услов»е проверяется на каж­ дой итерации цикла. Третье выражение на каждой итерации цикла прибавляет зна­ чение 5 к текущему значению переменной i. Так как данные перед выводом мoryr помещаться в буфер, вполне возможно по­ требуется сбросить буфер явным образом. Сделать это можно с помощью функции fflush(). Прототип функции: #include <stdio.h> int fflush(FILE *stream) В качестве параметра указывается стандартный поток вывода stdout: fflush(stdout); 2.9. Ввод данных Выводить данные на консоль мы научились, теперь рассмотрим функции, предна­ значенные для получения данных от пользователя. Все зти функции объявлены в заголовочном файле stdio.h . 2.9.1. Ввод одного символа Для ввода одного символа предназначена функция getchar() . Прототип функции: #include <stdio.h> int getchar(void); В качестве значения функция возвращает код введенного символа. Для того чтобы символ был считан, необходимо после ввода символа нажать клавишу <Enter>. Если было введено несколько символов, то будет считан первый символ, а осталь-
72 Глава 2 ные останутся в буфере. Ключевое слово void внутри круглых скобок означает, что функция не принимает никаких параметров. Пример получения символа: int ch; printf("ch = "); fflush(stdout); ch = getchar(); printf("%d\n", ch); printf("%c\n", (char) ch); // // // // // 0БРАТИТЕ ВНИМАНИЕ Вывод подсказки Сброс буфера Получение символа � Вывод кода Вывод символа Символ, который мы получим, будет в кодировке консоли. По умолчанию кодировка в консоли windows-866, но пользователь может сменить кодировку с помощью коман­ ды chcp <Кодировка>. Учитывайте, что в программе строки у нас в кодировке windows-1251, а не в кодировке windows-866 . 2.9.2. Функция scanf() Для получения и автоматического преобразования данных в конкретный тип (на­ пример, в целое число) предназначена функция scanf() . При вводе строки функция не производит никакой проверки длины строки, что может привести к переполне­ нию буфера. Обязательно указывайте ширину при использовании спецификатора %s (например, %255s). Прототип функции: #include <stdio.h> int scanf(const char *format, . . . ); В первом параметре указывается строка специального формата, внутри которой задаются спецификаторы, аналогичные применяемым в функции printf(), а также некоторые дополнительные спецификаторы. В последующих параметрах переда­ ются адреса переменных. Функция возвращает количество произведенных при­ сваиваний. Пример получения целого числа: // #include <locale.h> setlocale(LC_ALL, "Russian_Russia.1251"); // Настройка лакали intх=О; printf("Введите число: "); fflush(stdout); fflush(stdin); // Сброс буфера вывода // Очистка буфера ввода int status = scanf("%d", &х); // Символ & обязателен!!! if (status == 1) { printf("Bы ввели: %d\n", х); else { рuts("Ошибка при вводе числа"); printf("status = %d\n", status); Обратите внимание на то, что перед названием переменной х указан символ & • В этом случае передается адрес переменной х, а не ее значение. При вводе строки
Первые шаги 73 символ & не указывается, т. к. название переменной без квадратных скобок является ссылкой на первый элемент массива. Пример ввода строки: // #include <stdlib.h> system("chcp 1251"); // #include <locale.h> // Смена кодировки консоли setlocale(LC_ALL, "Russian_Russia.1251"); // Настройка локали char str[256] = ""; printf("Bвeдитe строку: "); fflush(stdout); fflush(stdin); // Сброс буфера вывода // Очистка буфера ввода int status = scanf("%255s", str); // Символ & не указывается!!! if (status == 1) { printf("Вы ввели: %s\n", str); else { puts("Owибкa при вводе строки"); Обратите внимание: чтобы получить строку в кодировке windows-1251, мы вначале указываем нужную кодировку с помощью функции system(). Если кодировку не указать, то мы получим строку в кодировке консоли (по умолчанию в консоли ис­ пользуется кодировка windows-866). Далее мы настраиваем локаль, чтобы не было проблем при работе с русскими буквами. Не забудьте подключить заголовочные файлы stdlib.h и locale.h, в которых содержатся объявления функций system () и setlocale() Считывание символов производится до первого символа-разделителя, например пробела, табуляции или символа перевода строки. Поэтому после ввода строки "Hello, world!" переменная str будет содержать лишь фрагмент "Hello, ", а не всю строку. В качестве примера произведем суммирование двух целых чисел, введенных поль­ зователем (листинг 2.6). #include <stdio.h> #include <locale.h> int main(void) { setlocale(LC_ALL, "Russian_Russia.1251"); intх=О,у=О; printf("Bвeдитe первое число: "); fflush(stdout); // Сброс буфера вывода if (scanf("%d", &х) != 1) { puts("Bы ввели не число"); return 1; // Выходим из функции main()
74 Глава 2 printf("Bвeдитe второе число: "); fflush(stdout); // Сброс буфера вывода fflush(stdin); // Очистка буфера ввода if (scanf("%d", &у) != 1) { puts("Bы ввели не число"); return 1; // Выходим из функции main() printf("Cyммa равна: %d\n", х + у); return О; В первых двух строках производится подключение заголовочных файлов stdio.h и locale.h . Далее внутри функции main() с помощью функции setlocale() настраива­ ется локаль, а затем объявляются две локальные переменные: х и у. Ключевое слово int в начале строки означает, что объявляются целочисленные переменные. При объявлении переменным сразу присваивается начальное значение (о) с помощью оператора =. Если значение не присвоить, то переменная будет иметь произвольное значение, так называемый "мусор". Как видно из примера, на одной строке можно объявить сразу несколько переменных, разделив их запятыми. В следующей строке с помощью функции printf() выводится подсказка для поль­ зователя ("Введите первое число: "). Благодаря этой подсказке пользователь будет знать, что от него требуется. Далее идет вызов функции fflush(), которая сбрасы­ вает данные из буфера потока вывода stdout в консоль. Если буфер вывода прину­ дительно не сбросить, пользователь может не увидеть подсказку вообще. После ввода числа и нажатия клавиши <Enter> значение будет присвоено перемен­ ной х. Ввод значения производится с помощью функции scanf() . В первом пара­ метре с помощью спецификатора %d мы указываем, что хотим получить целое чис­ ло. Во втором параметре передаем адрес переменной х в памяти компьютера, ука­ зав перед ее именем символ&. Благодаря этому введенное значение будет записано в переменную. Пользователь вместо числа может ввести все что угодно, например строку, не со­ держащую число. В этом случае мы не получим число, а значит, и складывать будет нечего. Поэтому после получения числа с помощью оператора if мы прове­ ряем значение, возвращаемое функцией scanf() . Функция возвращает количество успешных операций присваивания. Если количество присваиваний не равно одно­ му, то с помощью функции puts() выводим сообщение об ошибке и завершаем вы­ полнение функции main(), вызвав оператор return. В этом примере для проверки условия используется оператор ветвления if. После названия оператора внутри круглых скобок указывается проверяемое выражение. Если выражение возвращает логическое значение Истина, то будут выполнены ин­ струкции внутри фигурных скобок. Если выражение возвращает значение ложь, то инструкции внутри фигурных скобок игнорируются и управление передается инст­ рукции, расположенной сразу после закрывающей фигурной скобки. Проверка зна-
первые шаги 75 чения, возвращаемого функцией scanf(), осуществляется с помощью оператора ! = (не равно). Если первое число успешно получено, то выводим подсказку пользователю для ввода второго числа и сбрасываем буфер в окно консоли. Далее, перед получением_ второго числа, с помощью функции fflush() очищаем буфер потока ввода stdin. Если не очистить буфер ввода перед повторным получением числа, то это число может быть получено из предыдущего ввода из буфера. Например, при запросе первого числа было введено значение "47 з". Функция scanf() получит число 47, а второе число оставит в буфере, и оно будет досrупно для следующей операции ввода. Если мы сейчас запустим код из листинга 2.6, то буфер ввода будет очищен: Введите первое число: 47 3 Введите второе число: 5 Сумма равна: 52 Если закомментировать строку (инструкция работает только в Windows и то не во всех версиях): fflush(stdin); // Очистка буфера ввода то второе число будет получено сразу из буфера, не дожидаясь ввода пользователя, и результат будет другим: Введите первое число: 47 3 Введите второе число: Сумма равна: 50 Далее производится получение второго числа, которое сохраняется в переменной у. Затем с помощью оператора if мы проверяем значение, возвращаемое функцией scanf(). Если количество присваиваний не равно одному, то с помощью функции puts() выводим сообщение об ошибке и завершаем выполнение функции main(), вызвав оператор return. И, наконец, производим сложение двух чисел и выводим результат с помощью функции printf(). В листинге 2.6 при первой попытке ввода букв вместо числа выводится сообщение об ошибке, и программа завершается, не давая пользователю возможности испра­ вить свою ошибку. Предоставим пользователю три попытки ввода, прежде чем досрочно завершим программу (листинг 2.7). #include <stdio.h> #include <locale.h> int main(void) { setlocale(LC_ALL, "Russian_Russia.1251"); intх=О,у=О,flag=О,count=1; ·do { printf("Введите первое число: ");
76 fflush(stdout); fflush(stdin); if (scanf("%d", &х) != 1) { puts("Bы ввели не число"); // Сбрасываем флаг ошибки и очищаем буфер ввода if (feof(stdin) 11 ferror(stdin)) clearerr(stdin); intch=О; while ((ch = getchar()) != '\n' && ch != EOF); ++count; if(count>3){ puts("Bы сделали три ошибки"); return 1; else flag = 1; while ( ! flag); flag = О; count = 1; do printf("Bвeдитe второе число: "); fflush(stdout); fflush(stdin); if (scanf("%d", &у) != 1) { puts("Bы ввели не число"); // Сбрасываем флаг ошибки и очищаем буфер ввода if (feof(stdin) 11 ferror(stdin)) clearerr(stdin); intch=О; while ((ch = getchar()) != '\n' && ch != EOF); ++count; if(count>3){ puts("Bы сделали три ошибки"); return 1; else flag = 1; while ( ! flag); printf("Cyммa равна: %d\n", х + у); return О; Глава 2 В этом примере внутри функции main( J объявляются четыре целочисленные ло­ кальные переменные: х, у, flag и count. В переменной flag мы будем сохранять те­ кущий статус обработки ошибок, а в переменной count - количество допущенных ошибок подряд. При возникновении ошибки ее необходимо обработать, а затем вывести повторный запрос на ввод числа. Вполне возможно эту процедуру нужно будет выполнить
Первые шаги 77 несколько раз. Причем количество повторов заранее неизвестно. Для выполнения одних и тех же инструкций несколько раз предназначены циклы. В нашем примере используется цикл do ...while. Инструкции внутри фигурных скобок будут выпол­ няться до тех пор, пока логическое выражение после ключевого слова while является истинным. Проверка условия производится после выполнения инструкций внуrри фигурных скобок, поэтому инструкции выполняются минимум один раз. Обратите внимание на логическое выражение ! flag. Восклицательный знак, расположенный перед переменной, инвертирует значение. Например, если переменная содержит значение Истина (любое число, отличное от нуля), то выражение вернет значение ложь. Так как проверка значения на истинность производится по умолчанию, выра­ жение может не содержать операторов сравнения. Внутри цикла выводим подсказку пользователю, сбрасываем буфер вывода и очи­ щаем буфер ввода. Если буфер ввода не очистить, то значение будет автоматически передано следующей операции ввода, что породит повторную ошибку. Затем полу­ чаем значение и сохраняем его в переменной. Далее проверяем успешность полу­ чения значения. Если пользователь не ввел число, то выводим сообщение вы ввели не число. Количество попыток мы отслеживаем с помощью переменной count. На начальном этапе она имеет значение 1. Если пользователь не ввел число, то увели­ чиваем значение на 1 с помощью инструкции: ++count; Если пользователь допустил три ошибки подряд (переменная count будет содер­ жать значение больше з), то выводим сообщение вы сделали три ошибки и выходим из функции main(), вызвав оператор return. Если ошибки нет, выполняются инструкции, расположенные после ключевого сло­ ва else. В нашем случае переменной flag присваивается значение 1. Это значение является условием выхода из цикла. Далее таким же способом получаем второе число. Так как в предыдущем цикле значения переменных flag и count были изменены, перед циклом производим вос­ становление первоначальных значений. Иначе выход из цикла произойдет даже в случае ошибки. Если второе число успешно получено, производим вывод суммы чисел. 2.9.3. Ввод строки Для ввода строки предназначена функция gets (), однако применять ее в программе не следует, т. к. функция не производит никакой проверки длины строки, что может привести к переполнению буфера. Прототип функции gets() : #include <stdio.h> char *gets(char *buffer); Лучше получать строку посимвольно с помощью функции getchar( ) или восполь­ зоваться функцией fgets(). Прототип функции fgets() : #include <stdio.h> char *fgets(char *buf, int maxCount, FILE *strearn);
78 Глава 2 В качестве параметра stream указывается стандартный поток ввода st ' din. Считыва­ ние производится до первого символа перевода строки или до конца потока либо пока не будет прочитано rnaxcount-1 символов. Содержимое строки buf включает символ перевода строки. Исключением является последняя строка. Если она не за­ вершается символом перевода строки, то символ добавлен не будет. Если произош­ ла ошибка или достигнут конец потока, то функция возвращает нулевой указатель. Пример получения строки приведен в листинге 2.8. Листинг 2.8. Получение �троки с помощью функции fgets О #include <stdio.h> #include <string.h> #include <locale.h> #include <stdlib.h> int rnain(void) { systern("chcp 1251") ; // Смена кодировки консоли setlocale(LC_ALL, "Russian_Russia.1251"); char buf[256] = "", *р = NULL; printf("Введите строку: "); fflush(stdout); fflush(stdin); р = fgets(buf, 256, stdin); if(р){ // Удаляем символ перевода строки size t len = strlen(buf); if (len != О && buf[len - 1] == '\n') buf[len - 1] = '\0'; // Выводим результат printf("Bы ввели: %s\n", buf); else рuts("Возникла ошибка"); return О; В первой строке включается заголовочный файл stdio.h, в котором объявлены функ­ ции, предназначенные для ввода и вывода данных, во второй строке - файл string.h, в котором объявлена функция strlen(), возвращающая длину строки, в третьей строке - файл locale.h, в котором объявлена функция setlocale(), а в четвертой строке - файл stdlib.h, в котором объявлена функция systern(). Внутри функции rnain() сначала изменяется кодировка консоли и настраивается локаль. Строка, которую мы получим по умолчанию, будет в кодировке консоли (например, в кодировке windows-866 или какой-либо другой). Для того чтобы по­ лучить строку в кодировке windows-1251, мы в начале программы передаем коман­ ду смены кодировки chcp 1251 функции systern() .
Первые шаги 79 Далее объявляется символьный массив buf, состоящий из 256 символов. Строки в языке С представляют собой последовательность (массив) символов, последним элементом которого является нулевой символ (•\О•). Обратите внимание на то, что нулевой символ (нулевой байт) не имеет никакого отношения к символу 'о'. Коды этих символов разные. В первой строке объявляется также указатель р на тип char и ему присваивается значение NULL. То, что переменная р является указателем, говорит символ* перед ее именем при объявлении. Значением указателя является адрес данных в памяти компьютера. Указатель, которому присвоено значение NULL, называется нулевым указателем. Такой указатель ни на что не указывает, пока ему не будет присвоен адрес. В следующей строке выводится подсказка пользователю, а последующие инструк­ ции сбрасывают буфер вывода и очищают буфер ввода. Далее мы получаем строку из стандартного потока ввода stdin. Поток указывается в третьем параметре функ­ ции fgets(). В первом параметре передается адрес символьного массива buf, а во втором - его размер. Если в процессе выполнения функции fgets() возникнет ошибка, то функция вернет нулевой указатель. В этом случае мы выводим сообще­ ние возникла ошибка. Если ошибка не возникла, то удаляем символ перевода строки, который был добавлен в символьный массив. Для . этого с помощью функции strlen( J получаем длину строки и сохраняем ее в переменной len. Затем проверяем значение последнего символа в строке. Если это символ \n, то заменяем его нуле­ вым символом. В противном случае ничего не заменяем. После замены выводим полученную строку в окно консоли. Обратите внимание на два момента. Во-первых, размер символьного массива и длина строки - это разные вещи. Размер массива - это общее количество симво­ лов, которое может хранить массив. Длина строки - это количество символов внутри символьного массива до первого нулевого символа. В следующем примере массив содержит 256 символов, а длина строки внутри него составляет всего три символа плюс нулевой символ: char buf[256J = "аЬс"; printf("%s\n", buf); printf("%d\n", (int)sizeof(buf)); printf("%d\n", (int)strlen(buf)); // аЬс // 256 //3 Во-вторых, нумерация элементов массива начинается с о, а не с 1. Поэтому послед­ ний символ имеет индекс на единицу меньше длины строки. Для доступа к отдель­ ному символу нужно указать индекс внутри квадратных скобок: char buf[256] = "аЬс"; printf("%c\n", buf[OJ); printf("%c\n", buf[l]); printf("%c\n", buf[2]); //а //ь //с printf("%c\n", buf[strlen(buf) - 1]); // с
80 Глава 2 2.10. Интерактивный ввод символов Функция getchar() позволяет получить символ только после нажатия клавиши <Enter>. Если необходимо получить символ сразу после нажатия клавиши на кла­ виатуре, то можно воспользоваться функциями _getche() и _getch(). Прототипы функций: #include <conio.h> int _getche(void); int _getch(void); Функция _getche() возвращает код символа и выводит его на экран. При нажатии клавиши функция _getch() возвращает код символа, но сам символ на экран не вы­ водится. Это обстоятельство позволяет использовать функцию _getch() для полу­ чения конфиденциальных данных (например, пароля). Следует учитывать, что код символа возвращается и при нажатии некоторых служебных клавиш, например <Ноте>, <End> и др. В качестве примера использования функции _getch() создадим программу для вво­ да пароля (листинг 2.9). Пароль может содержать до 16 символов (цифры от о до<:­ и латинские буквы от а до z в любом регистре). При нажатии клавиши вместо сим­ вола будем выводить звездочку. Если символ не входит в допустимый набор, выве­ дем сообщение об ошибке и завершим программу. После нажатия клавиши <Enter> выведем на экран полученный пароль. Листинг 2.9. Ввод пароля без отображения вводимых символов #include <stdio.h> #include <conio.h> #include <locale.h> int rnain(void) { setlocale(LC_ALL, "Russian_Russia.1251"); char passwd[l7] ""; intflag=О,i О,ch=О; printf("Bвeдитe пароль: "); fflush(stdout); do( ch = _getch(); if(i>1511ch== '\r' 11ch flag = 1; passwd[i] = '\0'; else if (ch>47 11(ch>64 11(ch>96 && && && passwd[i] = (char)ch; printf("%s", "*") ; ch<58) ch<91) ch < 123)) '\n') ( //ЦифрыотОдо9 //БуквыотАдоZ //Буквыотадоz
первые шаги fflush(stdout); ++i; else { // Если недопустимый символ, то выходим puts("\n Вы ввели недопустимый символ"); return О; while ( ! flag); printf("\n Вы ввели: %s\n", passwd); return О; ОБРАТИТЕ ВНИМАНИЕ 81 Код из листинга 2.9 будет работать только в консоли. Запуск в редакторе Eclipse при­ ведет к бесконечной работе программы. В начале программы производим подключение файлов stdio.h, conio.h и locale.h . Файл stdio.h необходим для использования функций printf(), fflush() и puts(), а файл conio.h - для функции _getch( > . Далее внуrри функции main() объявляем четыре переменные. В символьный массив passwd, содержащий 17 элементов ( 16 символов плюс '\О'), будут записываться введенные символы. В переменную ch будем запи­ сывать код нажатой клавиши. Переменная flag предназначена для хранения статуса ввода (если содержит значение истина (любое число, отличное от о), то ввод пароля \закончен), а целочисленная переменная i предназначена для хранения текущего индекса внуrри массива passwd. Обратите внимание на то, что первый элемент мас­ сива имеет индекс о, а не 1. После объявления переменных выводим подсказку пользователю и сбрасываем бу­ фер. Затем внутри цикла do ... while получаем текущий символ с помощью функции _getch () и сохраняем его код в переменной ch. В следующей строке проверяем вы­ ход индекса за границы массива (i > 15) и нажатие клавиши <Enter>. При нажатии клавиши <Enter> передается последовательность символов \r\n (перевод каретки плюс перевод строки), однако функция _getch() может получить только один сим­ вол, поэтому проверяется наличие или символа \r, или символа \n. Между собой логические выражения разделяются с помощью оператора 1 1 (логическое ипи). Для того чтобы все выражение вернуло истину, достаточно, чтобы хотя бы одно из трех логических выражений было истинным. Если выражение i > 15 является ложным, то проверяется выражение ch == • \ r •, в противном случае все выражение считает­ ся истинным и дальнейшая проверка не осуществляется. Если выражение ch == '\r' является ложным, то проверяется выражение ch == '\n', в противном случае все выражение считается истинным и дальнейшая проверка не осуществля­ ется. Если выражение ch == • \n • является ложным, то все выражение считается ложным, в противном случае все выражение считается истинным. Если выражение является истинным, то ввод пароля закончен. Для того чтобы вый­ ти из цикла, присваиваем переменной flag значение 1. Кроме того, вставляем нуле-
82 Глава 2 вой символ в конец массива. Он будет обозначать конец строки с введенным паро­ лем. После этих инструкций управление передается в конец цикла и производится проверка условия ! flag. Так как условие вернет Ложь ( !Истина), происходит выход из цикла. После выхода из цикла производим вывод введенного пароля на консоль. Если ввод пароля не закончен, то производим проверку вхождения символа в до­ пустимый диапазон значений (цифры от о до 9 и латинские буквы от а до z в любом регистре). Выполнить проверку допустимости символа необходимо, т. к. функция _getch () возвращает также коды некоторых служебных клавиш, например <Ноте>, <End> и др. Если условие не соблюдается, то выводим сообщение об ошибке и воз­ вращаем значение о, тем самым завершая выполнение программы. Проверяемое условие содержит более сложное выражение, нежели в предыдущем условии. Выражение разбито на несколько мелких выражений с помощью круглых скобок. Внутри первых круглых скобок проверяется соответствие символа диапа­ зону чисел от о до 9. Цифра о соответствует коду 48, а цифра 9- коду 57. Коды ос­ тальных цифр находятся в диапазоне между этими кодами. Дnя того чтобы не пе­ речислять в выражении коды всех цифр, используют проверку двух условий, разде­ ленных оператором && (логическое и). Выражение ch > 47 && ch < 58 следует читать так: если переменная ch содержит символ, имеющий код больше 47 и мень­ ше 58, то вернуть значение истина, в противном случае - значение ложь. Если ус­ ловие внутри первых скобок является истинным, то все выражение является истин­ ным и дальнейшая проверка не производится. Если условие внутри первых круглых скобок является ложным, то проверяется условие внутри вторых круглых скобок. Условие внутри третьих круглых скобок проверяется, только если условие внутри вторых круглых скобок является ложным. Вместо кодов символов можно указать сами символы внутри апострофов. В этом случае условие будет выглядеть так: else if (ch>= 'О'&&ch<= '9') //ЦифрыотОдо9 11(ch>= 'А'&&ch<= 'Z') //БуквыотАДОZ 11(ch>='а'&&ch<= 'z'))//Буквыотадоz Если символ входит в допустимый диапазон, то сохраняем символ в массиве, выво­ дим звездочку в окно консоли, сбрасываем буфер, а затем увеличиваем значение в переменной i на единицу, тем самым перемещая указатель текущей позиции на следующий элемент массива. Выражение ++i соответствует выражению i = i + 1. После этих инструкций управление передается в конец цикла и производится про­ верка условия ! flag. Так как условие вернет Истина (!о), инструкции внутри цикла будут выполнены еще раз. 2.11. Получение данных из командной строки Передать данные можно в командной строке после названия файла. Дnя получения этих данных в программе используется следующий формат функции щain ():
первые шаги int main(int argc, char *argv[)) { // Инструкции return О; 83 Через первый параметр (argc) доступно количество аргументов, переданных в ко­ мандной строке. Следует учитывать, что первым аргументом является название ис­ полняемого файла, поэтому значение параметра. argc не может быть меньше едини­ цы. Через второй параметр (argv) доступны все аргументы в виде строки (тип char * ). Квадратные скобки после названия второго параметра означают, что дос­ тупен массив строк. Рассмотрим получение данных из командной строки на приме­ ре (листинг 2.1 О). tinclude <stdio.h> int main(int argc, char *argv(J) 1 print:f("argc = %d\n", argc); for(inti=О;i<argc;++i) printf("%s\n", argv[i]); return О; Сохраняем программу в файл C:\Ьook\test.c. Запускаем командную строку и компи­ лируем программу: C:\Users\Unicross>cd C:\book C:\Ьook>set Path=C:\msys64\mingw64\bin;%Path% C:\Ьook>gcc -Wall -Wconversion -03 -о test.exe test.c Для запуска программы вводим команду: C:\Ьook>test.exe -paraml -param2 В этой команде мы передаем программе test.exe некоторые данные (-paraml -param2). Результат выполнения программы будет выглядеть так: argc=З test.exe - paraml - param2 Первый элемент массива (argv[0J) не всегда будет содержать только название исполняемого файла. Если в командной строке запуск производится следующим образом: C:\book>C:\book\test.exe -paraml -param2
84 то элемент будет содержать не только название файла, но и путь к нему: argc=3 C:\book\test.exe - paraml - pararn2 Глава 2 Если нужно передать значение, которое содержит пробел, то это значение следует указывать внутри кавычек: C:\book>test.exe х + у "х + у" argc=5 test.exe х + у х+у В первом случае мы получили каждый символ по отдельности, а во втором - все символы вместе, указав значение внутри кавычек. 2.12. Предотвращение закрытия окна консоли До сих пор мы запускали программу либо в командной строке, либо в редакторе Eclipse. Однако программу можно запустить и двойным щелчком мыши на значке файла с программой. Если мы попробуем это сделать сейчас с программой test.exe из предыдущего раздела, то окно консоли откроется, а затем сразу закроется. Для того чтобы окно не закрывалось, необходимо вставить инструкцию, ожидающую нажатие клавиши. Сделать это можно несколькими способами. Первый способ заключается в использовании функции _getch() (листинг 2.11). Возвращаемое функцией значение можно проигнорировать. Прототип.Функции: #include <conio.h> int _getch(void); Листинг 2.11. Использование функции _getch() · #include <stdio.h> #include <conio.h> #include <locale.h> int main(void) [ setlocale(LC_ALL, "Russian_Russi a.1251"); printf("Hello, world!\n"); printf("Дnя закрьrrия окна нажмите любую клавишу .. . "); fflush(stdout); // Сброс буфера вывода fflush(stdin); _ getch(); return О; // Очистка буфера ввода
Первые шаги 85 Второй способ заключается в использовании функции system() (листинг 2.12). Эга функция позволяет переда�ь команду операционной системе. Для вывода строки Для продолжения нажмите любую клавишу и ожидания нажатия клавиши предназначе­ на команда pause. Прототип функции: #include <stdlib .h> int system - (const char *command); #include <stdio .h> #include <stdlib.h> int main(void) { printf("Hello, world!\n"); fflush(stdout); system("pause"); return О; // Сброс буфера вывода Два предыдущих способа требовали подключения дополнительных файлов. Кроме того, функция _getch() может не поддерживаться компилятором, а функция system() выполняет много лишних операций. Вместо этих способов лучше исполь­ зовать функцию getchar(). Она позволяет получить введенный символ. Ввод осу­ ществляется после нажатия клавиши <Enter>. Возвращаемое функцией значение можно проигнорировать. Прототип функции: #include <stdio.h> int getchar(void); При использовании функции getchar() следует учитывать один нюанс. Если в про­ грамме производился ввод данных, то в буфере могут остаться символы. В этом случае первый символ автоматически будет передан функции getchar(), и окно консоли сразу закроется. Поэтому после ввода данных необходимо дополнительно очистить буфер потока ввода с помощью функции fflush(). Пример использования функции getchar() приведен в листинге 2.13. #include <stdio.h> #include <locale.h> int main(void) { setlocale(LC_ALL, "Russian_Russia.1251"); printf("Hello, world!\n"); printf("Для закрытия окна нажмите <Enter> "); fflush(stdout); // Сброс буфера вывода
86 fflush(stdin); getchar(); return О; Глава 2 // Очистка буфера ввода Если нужно предотвратить закрытие окна только на этапе отладки программы, то можно создать вспомогательный файл с расширением bat и внутри него запускать программу. После инструкции запуска программы вставляем команду pause, кото­ рая выведет строку Для продолжения нажмите любую клавишу и будет ожидать нажа­ тия клавиши. Пример: @echo off title Запуск программы test.exe echo Результат: @echo . test.exe -pararnl -pararn2 @echo. @echo. pause Как видно из примера, после названия программы мы можем передать параметры. Если программа test.exe расположена в другом каталоге, то перед именем програм­ мы следует указать путь к ней. 2.13. Настройка отображения русских букв в консоли Если мы сохраним файл с программой в кодировке windows-125 l, то результат сле­ дующей инструкции: printf("Пpивeт, мир!"); в окне консоли будет выглядеть так: d:Ёштх€, ьшЁ! Причина искажения русских букв заключается в том, что по умолчанию в окне консоли используется кодировка windows-866, а в программе мы ввели текст в ко­ дировке windows-125 l. Коды русских букв в этих кодировках различаются, поэто­ му происходит искажение. При использовании командной строки пользователь может сменить кодировку вручную, выполнив команду: chcp 1251 Однако этого недостаточно. Кроме смены кодировки необходимо изменить назва­ ние шрифта, т. к. по умолчанию используются точечные шрифты, которые не под­ держивают кодировку windows-125 l. Для нормального отображения русских букв следует в свойствах окна выбрать шрифт Lucida Console (рис. 2 .14). Все эти дейст­ вия вы можете произвести на своем компьютере, однако пользователи не знают, в какой кодировке выводятся данные в вашей программе.
Первые шаги 111 Свойства: "Командная арока" !!,!риq,т □Жирный) j-if Consolas iTочечные wриФты ; fазмер rи-- Размер знаков: Ширина 6,иксели): 7 Высота t,,кcero,): 12 ок 11о- Рис. 2.14. Указание шрифта Lucida Console в свойствах окна консоли Изменить кодировку из программы мы можем с помощью функции system() : system("chcp 1251"); 87 А вот изменить шрифт из программы проблематично. Поэтому никакой гарантии, что пользователь увидит русские буквы без искажений, нет. Консоль в Windows по умолчанию работает с кодировкой windows-866, поэтому мы можем и файл с программой сохранить в этой кодировке. При использовании редактора Notepad++ вначале создаем новый документ, а затем в меню :Кодировки выбираем пункт :Кодировки 1 :Кириллица I ОЕМ 866. Вводим текст программы и сохраняем файл. В редакторе Eclipse кодировка задается в свойствах проекта. Вроде все отлично, т. к. мы сохраняем файл в кодировке консоли по умолчанию: C:\book>helloworld.exe Привет, мир! Однако пользователь может сменить кодировку в консоли, и мы опять получим проблему с русскими буквами: C:\Ьook>chcp 1251 Текущая кодовая страница: 1251 C:\book>helloworld.exe Uа�у1в, -,�а! Кроме того, при сохранении файла с программой в кодировке _windows-866 мы по­ лучим множество проблем при работе с файлами и каталогами.
88 Глава 2 Преобразование кодировки в Windows производится автоматически после настрой­ ки локали (локальных настроек компьютера). Настроить локаль позволяет функция setlocale() . Прототип функции: #include <locale.h> char *setlocale(int category, const char *locale); В первом параметре указывается категория в виде числа от о до 5. Вместо чисел можно использовать макроопределения LC-ALL, LC-COLLATE, LC-СТУРЕ , LC-MONETARY, 1с_NUМERIC и LC_ТIМЕ . Во втором параметре задается название локали, после ко­ торого через точку указывается кодировка файла с программой (например, Russian_Russia.1251 ИЛИ Russian_Russia.866). Пример настройки локали и вывода русских букв приведен в листинге 2.14. 1 Листинг 2.14. Настройка nокали #include <stdio.h> #include <locale.h> int rnain(void) { setlocale(LC_ALL, "Russian_Russia.1251"); printf("Пpивeт, мир!\n"); return О; После компиляции и запуска программы русские буквы будут правильно отобра­ жаться вне зависимости от текущей кодировки консоли: C:\book>chcp Текущая кодовая страница: 866 C:\book>helloworld.exe Привет, мир! C:\book>chcp 1251 Текущая кодовая страница: 1251 C:\book>helloworld.exe Привет, мир! Однако программа может быть запущена на компьютере, в котором кодировка кон­ соли не позволяет отобразить русские буквы. Единственный способ полностью ре­ шить проблему с кодировками - выводить сообщения на английском языке. Коды латинских букв во всех однобайтовых кодировках одинаковые, поэтому при ис­ пользовании английского языка проблем не будет. Однако тут существует другая сложность. Пользователь может не знать английского языка. Как видите, не все так просто с кодировками при использовании консольных приложений.
Гlервыешаги 2.14. Преждевременное завершение выполнения программы 89 В некоторых случаях может возникнуть условие, при котором дальнейшее выпол­ нение программы лишено смысла, например, отсутствует свободная щ,мять при использовании динамической памяти. В этом случае имеет смысл вывести сообще­ ние об ошибке и прервать выполнение программы досрочно. Для этого предназна­ чена: функция exit(). Прототип функции: tinclude <stdlib.h> �oid exit(int code); В качестве параметра функция принимает число, которое является статусом завер­ шения. Число о означает нормальное завершение программы, а любое другое чис­ :ю - некорректное завершение. Эrи числа передаются операционной системе. Вместо чисел можно использовать макроопределения EXIT_succEss (нормальное завершение) и EXIT_FAILURE (аварийное завершение). Определения макросов: tinclude <stdlib.h> tdefine EXIT SUCCESS О tdefine EXIT FAILURE 1 Пример: exit(EXIT_FAILURE); // Аналогично exit(l); Помимо функции exit() для аварийного завершения программы предназначена функция abort(). В этом случае завершение программы осуществляется операци­ онной системой с выводом диалогового окна. Прототип функции: tinclude <stdlib.h> void aЬort(void); В качестве примера получим число от пользователя и выведем результат. При этом обработаем ошибку ввода (листинг 2.15). tinclude <stdio.h> linclude <stdlib.h> linclude <locale.h> int rnain(void) 1 setlocale(LC_ALL, "Russian_Russia.1251"); intх=О; printf("Введите число: "); fflush(stdout); if (scanf("%d", &х) != 1) 1 puts("Вы ввели не число");
90 // Завершаем выполнение программы exit(EXIT_FAILURE); // Аналогично exit(l); printf("Вы ввели: %d\n", х); return О; Глава 2 В этом примере вместо функции exit() можно было воспользоваться инструкцией return, т. к. завершение программы выполнялось внутри функции main() . Однако в больших программах основная часть кода расположена вне функции main(), и в этом случае инструкцией return для завершения всей программы уже не обой­ тись.
ГЛАВА 3 Переменные и типы данных Переменные - это участки памяти, используемые программой для хранения дан­ ных. Говоря простым языком: переменная- это коробка, в которую мы можем 'По-то положить и из которой потом вытащить. Поскольку таких коробок может быть много, то каждая коробка подписыв!\ется (каждая переменная имеет уникаль­ ное имя внутри программы). Коробки могут быть разного размера. Например, не­ обходимо хранить яблоко и арбуз. Согласитесь, размеры яблока и арбуза различа­ ются. Для того чтобы поместить арбуз, мы должны взять соответствующего разме­ ра коробку. Таким образом, тип данных при объявлении переменной задает размер коробки, которую нужно подготовить, и определяет, что мы туда будем класть. Кроме того, в одну коробку мы можем положить только один предмет. Если нам нужно положить несколько яблок, то мы уже должны взять ящик (который в языке программирования называется массивом) и складывать туда коробки с яблоками. 3.1. Объявление переменной Прежде чем использовать переменную, ее необходимо предварительно объявить глобально (вне функций) или локально (внутри функции). В большинстве случаев объявление переменной является сразу и ее определением. Глобальные переАtенные видны внутри всех функций в файле, а локальные переменные - только внутри той функции, в которой они объявлены. Для объявления переменной используется сле­ дующий формат: [<Спецификатор>] [ <Модификатор>] <Тип> <Переменнаяl>[=<Значение 1>] [, . .. , <ПеременнаяN>[=<Значение N>]]; Пример объявления целочисленной переменной х: int х; В одной инструкции можно объявить сразу несколько переменных, указав их через запятую после названия типа данных: intх,у,z;
92 Глава З НА ЗАМЕТКУ При использовании старых компиляторов все локальные переменные должны быть объявлены в самом начале функции. 3.2. Именование переменных Каждая переменная должна иметь уникальное имя, состоящее из латинских букв, цифр и знака подчеркивания, причем имя переменной не может начинаться с циф­ ры. При указании имени переменной важно учитывать регистр букв: х и х - раз­ ные переменные: intх=5,Х=10; printf("%d\n", х); // 5 printf("%d\n", Х); // 10 В качестве имени переменной нельзя использовать ключевые слова. Список ключе­ вых слов приведен в табл. 3.1 (в скобках указан стандарт, начиная с которого на­ звание стало ключевым словом). Следует учитывать, что в некоторых компилято­ рах могут быть определены дополнительные ключевые слова. Запоминать все клю­ чевые слова нет необходимости, т. к. в редакторе эти слова подсвечиваются. Любая попытка использования ключевого слова вместо названия переменной приведет к ошибке при компиляции. Помимо ключевых слов следует избегать совпадений со встроенными идентификаторами. □ Правильные имена переменных: х, yl, str_name, strName. □ Неправильные имена переменных: ly, ИмяПеременной. Последнее имя неправиль­ ное, т. к . в нем используются русские буквы. Таблица 3.1. Ключевь,е слова языка С _-� - asm __ _j douЫe 1 int struct _ Alignof (С11) auto else long switch _ Atomic (С11) ---·------ break enurn register typedef _ Bool (С99) -- --·· case extern restrict (С99) union _Complex (С99) char float return uns{gned _Generic (С11) ----·- const for short void - Imaginary (С99) continue goto signed volatile _ Noreturn (С11) -- - default if sizeof while _ Static_assert (С11) -- do inline (С99) static _ Alignas (С11) _Thread_local (С11)
Переменные и типы данных 3.3. Типы данных В языке С доступны следующие элементарные типы данных. 93 □ _вооl - логический тип данных. Может содержать значения истина (соответст­ вует числу 1) или ложь (соответствует числу о). Тип данных _вооl доступен, на­ чиная со стандарта С99. Пример: _Bool is_int = 1; printf("%d\n", is_int); //1 printf("%d\n", !is_int); //О // Выводим размер в байтах pr1.ntf("%d\n", (int) sizeof(_Bool)); // 1 В языке С нет ключевых слов true (истина) и false (ложь). Вместо них использу­ ются числа 1 и о соответственно. Любое число, отличное от нуля, является Исти­ ной, а число, равное нулю, � Ложью. Если вы хотите объявлять логические пере­ менные так же как в языке С++, можно подключить заголовочный файл stdЬool.h, в котором определены макросы bool, true и false: #include <stdЬool.h> #define bool Bool #define true 1 #define false О Пример: bool is int = true; printf("%d\n", is_int); is_int = false; printf("%d\n", is_int); //1 //о □ char- код символа. Занимает 1 байт. Пример: char ch = 'w'; // Выводим символ printf("%c\n", ch); // Выводим код символа printf("%d\n", ch); // Выводим размер в байтах printf("%d\n", (int) sizeof(char)); // #include <lirnits.h> printf("%d\n", CНAR_MIN); printf("%d\n", СНАR_МАХ); printf("%d\n", СНАR BIT); //w . // 119 //1 // -128 // 127 //8 □ int - целое число со знаком. Диапазон значений зависит от компилятора. Минимальный диапазон от -32 768 до 32 767. На практике диапазон от -2 147 483 648 до 2 147 483 647 (занимает 4 байта). Пример: intх=10; // Выводим десятичное значение printf("%d\n", х); printf("%i\n", х); // 10 // 10
94 // Выводим восьмеричное значение printf("%o\n", х); // Выводим шестнадцатеричное значение printf("%x\n", х); printf("%X\n", х); // Выводим размер в байтах printf("%d\n", (int) sizeof(int)); // #include <limits.h> printf("%d\n", INТ_MIN); printf("%d\n", INТ_МАХ); // 12 //а //А //4 // -2147483648 // 2147483647 □ float - вещественное число. Занимает 4 байта. Пример: float х = 10.5432f; // Выводим значение printf("%f\n", х); // 10.543200 printf("%.2f\n", х); // 10.54 // Выводим размер в байтах printf("%d\n", (int) sizeof(float)); // 4 Глава З □ douЫe - вещественное число двойной точности. Занимает 8 байтов. Пример: douЫe х = 10.5432; // Выводим значение printf("%f\n", х); printf("%.2f\n", х); // Выводим размер в байтах // 10.543200 // 10.54 printf("%d\n", (int) sizeof(douЬle)); // 8 □ void - означает отсутствие типа. Используется в основном для того, чтобы ука­ зать, что функция не возвращает никакого значения или не принимает парамет­ ров, а также для передачи в функцию данных произвольного типа. Пример объявления функции, которая не возвращает значения и принимает указатель на данные произвольного типа: void free(void *memory); Перед элементарным типом данных могут быть указаны следующие модификаторы или их комбинация. □ signесt-указывает, что символьный или целочисленный типы могут содержать отрицательные значения. Тип signed char может хранить значения от -128 до 127. Пример: signed char min = -128, max = 127, ch = 'w'; // Выводим символ printf("%c\n", ch); //w // Выводим код printf("%d\n", ch); // 119 printf("%d\n", min); // -128 printf("%d\n", max); // 127
Переменные и типы данных // Выводим размер в байт ах printf("%d\n", (int) sizeof(ch)); // #include <lirnits.h> printf("%d\n", SCНAR_MIN); printf("%d\n", SСНАR_МАХ); //1 // -128 // 127 Тип signed int (или просто signed) соответствует типу int. Пример: signed int min = -2147483647 - 1; signed max = 2147483647; printf("%d\n", min); printf("%i\n", max); // ВЫводим размер в байтах printf("%d\n", (int) sizeof(rnin)); // -2147483648 // 2147483647 //4 95 □ unsigned - указывает, что символьный или целочисленный типы не могут со­ держать отрttцательные значения. Тип unsigned char может содержать значения от о до 255. Пример: unsigned char rnin = О, max = 255; printf("%d\n", rnin); //о printf("%d\n", max); // 255 printf("%hu\n", rnin); //о printf("%hu\n", max); // 255 // Выводим размер в байтах printf("%d\n", (int) sizeof(min)); //1 // #include <limits.h> printf("%d\n", UСНАR_МАХ); // 255 Диапазон значений для типа unsigned int (можно указать просто unsigned) зави­ сит от компилятора. Минимальный диапазон от о до 65 535. На практике диапа­ зон от о до 4 294 967 295. Пример: unsigned int min = О; unsigned max = 4294967295U; printf("%u\n", min); printf("%u\n 11 , max); // Выводим размер в байтах printf("%d\n", (int) sizeof(min)); // #include <limits.h> printf("%u\n", UINT_МAX); //о // 4294967295 //4 // 4294967295 □ short - может быть указан перед целочисленным типом. Занимает 2 байта. Диапазон значений у типов short int (или просто short) и signed short int (или просто signed short)- от -32 768до 32 767. Пример: short int rnin = -32768; short max = 32767; printf("%hd\n", rnin); printf("%hi\n", max); // -32768 // 32767
96 // Выводим размер в байтах printf("%d\n", (int) sizeof(min)); // #include <limits.h> printf("%d\n", SHRT_MIN); printf("%d\n", SHRT_МAX); //2 // -32768 // 32767 Глава 3 Диапазон значений у типа unsigned short int (или просто unsigned short) - от о до 65 535. Пример: unsigned short int min = О; unsigned short max = 65535; printf("%hu\n", min); printf("%hu\n", max); // Выводим размер в байтах printf("%d\n", (int) sizeof(min)); // #include <limits.h> printf("%d\n", USHRT_МAX); //о // 65535 //2 // 65535 □ long - может быть указан перед целочисленным типом и типом douЫe. Диапазон значений у типов long int (или просто long) и signed long int (или просто signed long)- от -2 147 483 648 до 2 147 483 647. Занимает 4 байта. Пример: long int min = -21474836471 - 1; long max = 21474836471; printf("%ld\n", min); printf("%li\n", max); // Выводим размер в байтах printf("%d\n", (int) sizeof(min)); // #include <limits .h> printf("%ld\n", 1ONG_MIN); printf("%ld\n", 1ONG_МAX); // -2147483648 // 2147483647 //4 // -2147483648 // 2147483647 Диапазон значений у типа unsigned long int (или просто unsigned long)- от с до 4 294 967 295. Пример: unsigned long int min = 0U1; unsigned long max = 4294967295U1; printf("%lu\n", min); printf("%lu\n", max); // Выводим размер в байтах printf("%d\n", (int) sizeof(min)); // #include <limits.h> printf("%lu\n", U1ONG_МАХ); //о // 4294967295 //4 // 4294967295 Ключевое слово long перед целым типом может быть указано дважды. Диапа­ зон значений у типов long long int (или просто long long) и signed long long int (или просто signed long long) - от -9 223 372 036 854 775 808 до 9 223 372 036 854 775 807. Пример:
Переменные и типы данных long long int rnin = -922337203685477580711 - 111; long long max = 922337203685477580711; printf("%I64d\n", rnin); // -9223372036854775808 printf("%I64i\n", max); // Быводим размер в байтах printf("%d\n", (int) sizeof(rnin)); // #include <limits.h> printf("%I64d\n", 110NG_MIN); printf("%I64d\n", 1LONG_МAX); printf("%I64d\n", 10NG_LONG_MIN); printf("%I64d\n", 10NG_10NG_МAX); // // // // // // 9223372036854775807 8 - 9223372036854775808 9223372036854775807 - 9223372036854775808 9223372036854775807 97 Диапазон значений у типа unsigned 10 446 744 073 709 551 615. Пример: long long int может быть от о до unsigned long long int rnin = OU11; unsigned long long max = 18446744073709551615U11; printf("%I64u\n", min); //О printf("%I64u\n", max); // Выводим размер в байтах printf("%d\n", (int) sizeof(rnin)); // #include <limits.h> printf("%I64u\n", U110NG_МAX); printf("%I64u\n", U10NG_10NG_МAX); // 18446744073709551615 //8 // 18446744073709551615 // 18446744073709551615 Ключевое слово long можно указать таюке перед типом douЫe. Пример: long douЬle х = 8е+2451; _ mingw_printf("%1e\n", х); printf("%d\n", (int) sizeof(x)); // Значение в проекте Test32c: 12 // Значение в проекте Test64c: 16 // 8.000000е+245 При использовании модификаторов тип int подразумевается по умолчанию, по­ этому тип int можно не указывать. Пример объявления переменных: short х; // Эквивалентно: short int х; :ong у; // Эквивалентно: long int у; signed z; // Эквивалентно: signed int z; -.msigned k; // Эквивалентно: unsigned int k; Если переменная может изменять свое значение извне, то перед модификатором указывается ключевое слово volati . le. Эrо ключевое слово предотвращает проведе­ ние оптимизации программы, при котором предполагается, что значение перемен­ lЮЙ может быть изменено только в программе. 3.4. Целочисленнь1е типы фиксированноrо размера Вместо указания конкретного целочисленного типа можно воспользоваться макро­ определениями _int8, _intl6, _int32 и _int64. Определения макросов:
98 Глава З #define int8 char #define intl6 short #define int32 int #define int64 long long Пример объявления переменных: int8х=10; intl6 у = 20; int32 z = 30; int64 k = 4011; Кроме того, в заголовочном файле stdint.h объявлены знаковые типы фиксиро­ ванной длины int8_t, intl6_t, int32_t и int64_t, а таюке беззнаковые uint8_t, uint16_t, uint32_t и uint64_t (полный список дос,упных типов см. в заголовочном файле): #include <stdint.h> typedef signed char int8_t; typedef unsigned char uint8_t; typedef short intl6_t; typedef unsigned short uintl6 t; typedef int int32 t; typedef unsigned uint32_t; typedef long long int64_t; typedef unsigned long long uint64_t; Заголовочный файл stdint.h содержит таюке макроопределения с допустимыми диа­ пазонами значений для этих типов: #define INT8_MIN (-128) #define INТ8 МАХ 127 #define INТ16 MIN (-32768) #define INT16 МАХ 32767 #define INТ32 MIN (-2147483647 - 1) #define INТ32 МАХ 2147483647 #define INT64 MIN (-922337203685477580711 - 1) #define INT64 МАХ 922337203685477580711 #define UINТ8 МАХ 255 #define UINТ16 МАХ 65535 #define UINТ32 МАХ 0xffffffffU /* 4294967295U */ #define UINT64 МАХ 0xffffffffffffffffU11 /* 18446744073709551615UL1 */ Пример: // #include <stdint.h> или #include <inttypes.h> int8 t min = INT8 MIN; int8_t max = INТ8_МАХ; uint8_t umax = UINТ8_МAX; printf("%d\n", min); // -128
переменные и типы данных printf("%d\n", max); printf("%d\n", umax); // 127 // 255 99 При использовании типов с фиксированной шириной следует учитывать, что в раз­ личных компиляторах объявления типов могут быть разными. Гарантируется толь- 1[0 диапазон значений, но, например, при объявлении int32_t вместо типа int может быть использован тип long. Спецификаторы у этих типов разные. Для того чrобы правильно указать спецификатор, нужно воспользоваться следующими мак­ роопределениями (полный список макроопределений см. в заголовочном файле mtypes.h): tinclude <inttypes.h> tdefine PRid8 "d" tdefine PRidlб "d" ldefine PRid32 "d" tdefine PRid64 "I64d" ldefine PRiu8 "u" tdefine PRiulб "и" ldefine PRiu32 "и" ldefine PRiu64 "I64u" Пример указания спецификатора при выводе значения типа int64_t: // #include <inttypes.h> int64_t х = 9223372036854775807LL; printf("%" PRid64, х); // 9223372036854775807 3.5. Оператор sizeof и тип size_t После объявления переменной под нее выделяется определенная память, размер 1еоторой зависит от используемого типа данных и разрядности операционной сис­ темы. Для того чтобы сделать код машинно-независимым, следует определять раз­ мер типа с помощью оператора sizeof. Оператор имеет два формата: <Размер> = sizeof <Переменная>; <Размер> = sizeof (<Тип данных>); Пример: intх=10; printf("%d\n", (int) sizeof х); //4 printf("%d\n", (int) sizeof(х)); // 4 printf("%d\n", (int) sizeof(int)); // 4 Обратите внимание на то, что тип данных обязательно должен быть указан внутри круглых скобок, в то время как название переменной можно указать как внутри скобок, так без них. Оператор sizeof возвращает значение типа size_t . Объявление этого типа в раз­ личных компиляторах может быть разным. В проекте Test64c объявление выглядит следующим образом:
100 #define _int64 long long typedef unsigned int64 size t; Глава З При выводе значения можно воспользоваться спецификатором %zd, но нужно ис­ пользовать функцию _mingw_printf () вместо функции printf (): size_t size = sizeof{int); _ mingw_printf("%zd\n", size); // 4 3.6. Инициализация переменных При объявлении переменной ей можно сразу присвоить начальное значение, указав его после оператора =. Эта операция называется инициализацией переменной. При­ мер указания значения: intх,у=10,z=30,k; Переменная становится видимой сразу после объявления, поэтому на одной строке с объявлением (после запятой) эту переменную уже можно использовать для ини­ циализации других переменных: intх=5,у=10,z=х+у;//zравно15 Инициализация глобальных (объявленных вне функций) переменных производится только один раз. Локальные (объявленные внутри функций) переменные инициали­ зируются при каждом вызове функции, а статические (сохраняющие свое значе­ ние между вызовами) локальные переменные- один раз при первом вызове функ­ ции. Если при объявлении переменной значение не было присвоено, то: □ глобальные переменные автоматически получают значение о; □ локальным переменным значение не присваивается. Переменная будет содер- жать произвольное значение, так называемый "мусор"; □ статические локальные переменные автоматически получают значение о. Присвоить значение переменной можно уже после объявления, указав его после оператора=. Эта операция называется присваиванием. Пример присваивания: int х; х=10; 3.7. Оператор typedef Оператор typedef позволяет создать псевдоним для существующего типа данных. В дальнейшем псевдоним можно указывать при объявлении переменной. Оператор имеет следующий формат: typedef <Существующий тип> <Псевдоним>; В качестве примера создадим псевдоним для типа long int: typedef long int lint; lintх=5L,у=l0L;
переменные и типы данных 101 После создания псевдонима его имя можно использовать при создании другого псевдонима: �ypedef long int lint; �ypedef lint newint; �ewint х = 5L, у= l0L; Псевдонимы предназначены для создания машинно-независимых программ. При переносе программы на другой компьютер достаточно будет изменить одну строку. Подобный подход часто используется в стандартной библиотеке. Например, прото- 1ИП функции strlen {),,позволяющей получить длину строки, выглядит так: size_t strlen(const char *str); В этом прототипе тип данных size_t, возвращаемый функцией strlen(), является псевдонимом, а не новым типом. Его размер зависит от компилятора. Объявление в MinGW-W64: ktefine _int64 long long �ypedef unsigned int64 size_t; 3.8. Константы Константы - это участки памяти, значения в которых не должны изменяться во время работы программы. В более широком смысле под константой понимают .т1юбое значение, которое нельзя изменить, например 10, 12. 5, •w•, "string". При объявлении константы перед типом данных указывается ключевое слово const: const int МУ CONST = 10; printf("%d\n", МY_CONST); // 10 Обратите внимание на то, что в названии константы принято использовать буквы только в верхнем регистре. Если название константы состоит из нескольких слов, то между словами указывается символ подчеркивания. Эrо позволяет отличить внутри программы констанrу от обычной переменной. После объявления констан­ ты ее можно использовать в выражениях: const int МУ CONST = 10; int у; у= МY_CONST + 20; Присвоить значение константе можно только при объявлении. Любая попытка изменения значения в программе приведет к ошибке при компиляции: error: assignment of read-only variaЫe 'МY_CONST' Создать констанrу можно также с помощью директивы Jldefine. Значение, указан­ ное в этой директиве, подставляется в выражение до компиляции. Название, ука­ занное в директиве #define, принято называть макроопределением или макросом. Директива имеет следующий формат: #define <Название макроса> <Значение>
102 Пример использования директивы #define приведен в листинге 3.1. 1·л ' �ти�� 3.1: Использование ДИреt(ТИВЬf ldёine. ,, #include <stdio.h> #define МУ CONST (-5) int main(void) { int у; у= МY_CONST + 20; printf("%d\n", МY_CONST); // -5 printf("%d\n", у); // 15 return О; Глава 3 Обратите внимание на то, что оператор = не используется, и в конце инструкции точка с запятой не указывается. Если точку с запятой указать, то значение вместе с ней будет вставлено в выражение. Например, если определить макрос так: #define МУ CONST 5; то после подстановки значения инструкция у= МY_CONST + 20; будет выглядеть следующим образом: у=5;+20; Точка с запятой после цифры 5 является концом инструкции, поэтому переменной у будет присвоено значение 5, а не 25. Подобная ситуация приводит к ошибкам, которые трудно найти, т. к. в этом случае инструкция+ 20; не возбуждает ошибку при компиляции. В качестве значения макроса можно указать целое выражение, например: #define МУ CONST 5 + 5 Эrо значение также может привести к недоразумениям. Никакого вычисления вы­ ражения не производится. Все выражение целиком подставляется вместо названия макроса. Если инструкция выглядит так: у= МY_CONST * 20; то после подстановки значения инструкция примет следующий вид: у=5+5*20; Приоритет оператора умножения выше приоритета оператора сложения, поэтому число 5 будет умножено на 20, а затем к результату прибавлено число 5. Таким образом, результат будет 105, а не 200, как это было бы при использовании кон­ станты:
переменные и типы данных con$t int МY_CONST = 5 + 5; int у; у= МY_CONST * 20; // 200 103 Для того чтобы избежать недоразумений, выражение (а таюке отрицательное зна- чение) в директиве #define следует заключать в круглые скобки: ldefine МY_CONST (5 + 5) При подстановке инструкция будет выглядеть так: у=(5+5)*20; Теперь мы получим число 200, а не 105, т. к. скобки меняют приоритет операторов. В качестве значения макроса можно указать строку в кавычках: ldefine ERR "Сообщение об ошибке" При указании длинной строки следует учитывать, что определение макроса должно быть расположено на одной строке. Если нужно разместить значение на нескольких строках, то в конце строки необходимо добавить обратную косую чер'I)'. После ко­ сой черты не должно быть никаких символов, в том числе комментариев: ldefine ERR "Сообщение об ошибке \ на нескольких \ строках" Удалить макрос позволяет директива #undef <Имя макроса>: lundef ERR В языке С существуют встроенные макросы (до и после названия два символа под­ черкивания): □ _FILE_ - им я файла; □ _LINE_ - но мер текущей строки; □ _DАТЕ_ - дата компиляции файла; □ _ТIМЕ_ - время компиляции файла. Помимо этих макросов существует множество других, определенных в различных заголовочных файлах. Эти макросы мы рассмотрим в соответствующих разделах книги. Выведем текущие значения встроенных макросов (листинг 3.2). linclude <stdio.h> int main(void) ( printf("%d\n", printf("%s\n", printf("%s\n", printf("%s\n", return О; LINE ); FILE ); DATE ); ТIМЕ );
104 Примерный результат выполнения: 4 .. \src\Test64c.c Jan 20 2019 14:35:53 3.9. Спецификаторы хранения Глава: Перед модификатором и типом могут быть указаны следующие спецификаторы. □ auto - локальная переменная создается при входе в блок и удаляется при выхо­ де из блока. Так как локальные переменные по умолчанию являются автомати­ ческими, ключевое слово auto в языке С практически не используется. Пример объявления автоматической локальной переменной: autointх=10; □ register - является подсказкой компилятору, что переменная будет использо­ ваться интенсивно. Для ускорения доступа значение такой переменной сохраня­ ется в регистрах процессора. Компилятор может проигнорировать это объявле­ ние и сохранить значение в памяти. Ключевое слово может использоваться при объявлении переменной внутри блока или в параметрах функции. К глобальным переменным не применяется. Пример объявления переменной: register int х = 20; □ extern - сообщает компилятору, что переменная определена в другом месте, например в другом файле. Ключевое слово лишь объявляет переменную, а не определяет ее. Таким образом, память под переменную повторно не выделяется. Если при объявлении производится инициализация переменной, то объявление становится определением переменной. В качестве примера объявим переменную внутри функции main (), а определение переменной разместим после функции: #include <stdio.h> int main(void) { extern int х; // Определение в другом месте printf("%d\n", х); // 10 return О; intх=10; // Определение переменной х □ static - если ключевое слово указано перед локальной переменной, то значе­ ние будет сохраняться между вызовами функции. Инициализация статических локальных переменных производится только при первом вызове функции. При последующих вызовах используется сохраненное ранее значение. Создадим функцию со статической переменной. Внутри функции увеличим зна­ чение переменной на единицу, а затем выведем значение в окно консоли. Далее вызовем эту функцию несколько раз:
Переменные и типы данных #include <stdio.h> void func(void); int main(void) { func(); // 1 func(); // 2 func(); // 3 return О; void func(void) static int � О; ++х; printf("%d\n", х); 105 // Статическая переменная // Увеличиваем значение на 1 При каждом вызове функции func () значение статической переменной х будет увеличиваться на единицу. Если убрать ключевое слово static, то при каждом вызове будет выводиться число 1, т. к. автоматические локальные переменные инициализируются при входе в функцию и уничтожаются при выходе из нее. Если ключевое слово static указано перед глобальной переменной, то ее значе­ ние будет видимо только в пределах файла. 3.10. Области видимости переменных Прежде чем использовать переменную, ее необходимо предварительно объявить. До объявления переменной она не видна в программе. Объявить переменную мож­ но rлобально (вне функций) или локально (внутри функции или блока). □ Глобш,ьные переменные - это переменные, объявленные в программе вне функций. Глобальные переменные видны в любой части программы, включая функции. Инициализация таких переменных производится только один раз. Если при объявлении переменной не было присвоено начальное значение, то производится автоматическая инициализация нулевым значением. □ Локш,ьные переменные - это переменные, которые объявлены внутри функции или блока (области, ограниченной фигурными скобками). Локальные перемен­ ные видны только внутри функции или блока. Инициализация таких перемен­ ных производится при каждом вызове функции или входе в блок. После выхода из функции или блока локальная переменная уничтожается. Если при объявле­ нии переменной не было присвоено начальное значение, то переменная будет содержать произвольное значение, так называемый "мусор". Исключением яв­ ляются статические локш,ьные переменные, которым автоматически присваи­ вается нулевое значение и которые сохраняют значение при выходе из функции. Если имя локальной переменной совпадает с именем глобальной переменной, то все операции внутри функции осуществляются с локальной переменной, а значение глобальной не изменяется.
106 Глава 3 Область видимости глобальных и локальных переменных показана в листинге 3.3. Листинr 3.3. Область видимости переменных #include <stdio.л> iпt х = 10; // Глобальная переменная void func{void); int main{void) { func{); // Переменная х из функции func{) здесь не видна // ВЫвод значения глобальной переменной х printf{"%d\n", х); // Блок 1110 intz=30; printf{"%d\n", z); // 30 } // Переменная z здесь уже не видна!!! for{inti=О;i<10;++i) printf("%d\n", i); } // Переменная i здесь уже не видна!!! return О; void func{void) { // Переменная z здесь не видна int х = 5; // Локальная переменная // ВЫвод значения локальной переменной х, // а не одноименной глобаJ\Ьной printf{"%d\n", х); // 5 Очень важно учитывать, что переменная, объявленная внуrри блока, видна только в пределах блока (внуrри фигурных скобок). Например, присвоим значение пере­ менной в зависимости от некоторого условия: //Неправильно!!! if {х == 10) { // Какое-то условие int у; у5; else int у; у= 25; } // Переменная у здесь не определена!!!
переменные и типы данных 107 В этом примере переменная у видна только внуrри блока. После условного опера­ rора if переменной не существует. Для того чтобы переменная была видна внуrри блока и после выхода из него, необходимо поместить объявление переменной перед блоком: // Правильно int у; if (х = 10) { // Какое-то условие у= 5; else { у= 25; printf("%d\n", у); Если обращение к переменной внуrри функции или блока производится до объяв­ ления одноименной локальной переменной, то до объявления будет использоваться глобальная переменная, а после объявления-локальная переменная (листинг 3.4). Если глобальной переменной с таким названием не существует, то будет ошибка при компиляции. tinclude <stdio.h> int х = 10; // Глобальная переменная int main(void) // Выводится значение глобальной переменной printf("%d\n", х); // 10 intх=5; // Локальная переменная // Выводится значение локальной переменной printf("%d\n", х); // 5 return О; 3.11. Массивы Массив - это нумерованный набор переменных одного типа. Переменная в масси­ ве называетс)1 элементом, а ее позиция в массиве задается индексом. Объем памяти (в байтах), занимаемый массивом, определяется так: <Объем памяти>= sizeof (<Тип>) * <Количество элементов> Объявление массива выглядит следующим образом: <Тип> <Переменная>[<Количество элементов>]; Пример объявления массива из трех элементов, имеющих тип long int: long arr[З];
108 Глава 3 При объявлении элементам массива можно присвоить начальные значения. Для этого после объявления указывается оператор =, а далее значения через запятую внутри фигурных скобок. После закрывающей фигурной скобки обязательно ука­ зывается точка с запятой. Пример инициализации массива: longarr[3] = {10, 20, 30); Количество значений внутри фигурных скобок может быть меньше количества элементов массива. В этом случае значения присваиваются соответствующим эле­ ментам с начала массива. Пример: longarr[3] = {10, 20); for(inti=О;i<3;++i) printf("%ld ", arr[i]); )//1020О В этом примере первому элементу массива присваивается значение 10, второму­ значение 20, а третьему элементу будет присвоено значение о. Если при объявлении массива указываются начальные значения, то количество элементов внутри квадратных скобок можно не указывать. Размер будет соответст­ вовать количеству значений внутри фигурных скобок. Пример: longarr[] = {10, 20, 30); for(inti=О;i<3;++i) printf("%ld ", arr[i]); )//102030 Если при объявлении массива начальные значения не указаны, то: □ элементам глобальных массивов автоматически присваивается значение о; □ элементы локальных массивов будут содержать произвольные значения, так называемый "мусор". Обращение к элементам массива осуществляется с помощью квадратных скобок. в которых указывается индекс элемента. Обратите внимание на то, что нумеращu элементов массива начинается с о, а не с 1, поэтому первый элемент имеет индекс с. С помощью индексов можно присвоить начальные значения элементам массива уже после объявления: longarr[3]; arr[0] 10; // Первый элемент имеет индекс о!!! arr[l] = 20; // Второй элемент arr[2] = 30; // Третий элемент Следует учитывать, что проверка выхода указанного индекса за пределы диапазона на этапе компиляции не производится. Таким образом, можно перезаписать значе­ ние в смежной ячейке памяти и тем самым нарушить работоспособность програм­ мы или даже повредить операционную систему. Помните, что контроль корректн<r сти индекса входит в обязанности программиста. Все элементы массива располагаются в смежных ячейках памяти. Например, если объявлен массив из трех элементов, имеющих тип int (занимает 4 байта), то адреса соседних элементов будут отличаться на 4 байта:
переменные и типы данных int arr(З] = (10, 20, 30}; for(inti=О;i<3;++i) printf("%p\n", &arr[i)); Результат в проекте Test64c: 000000000023FE40 000000000023FE44 00000000002ЗFЕ48 109 После определения массива выделяется необходимый размер памяти, а в перемен­ ной сохраняется адрес первого элемента массива. При указании индекса внутри квадратных скобок производится вычисление адреса соответствующего элемента массива. Зная адрес элемента массива, можно получить значение или перезаписать его. Иными словами, с элементами массива можно производить такие же операции, как и с обычными переменными. Пример: long arr[3) = (10, 20, 30}; longх=О; х=arr(l]+12; arr[2] = х - arr[2]; printf("%ld ", х); // 32 printf("%1d ", arr[2]); // 2 Массивы в языке С могуr быть ,wногомерными. Объявление многомерного массива имеет следующий формат: <Тип> <Переменная>[<Количество элементовl>]...(<Количество элементовN>]; На практике наиболее часто используются двумерные массивы, позволяющие хра­ нкrь значения ячеек таблицы, содержащей определенное количество строк и столбцов. Объявление двумерного массива выглядит так: <Тип> <Переменная>[<Количество строк>] (<Количество столбцов>]; Пример объяWJения двумерного массива, содержащего две строки и четыре столбца: int arr(2] (4]; // Две строки из 4-х элементов каждая Все элементы двумерного массива располагаются в памяти друг за другом. Вначале элементы первой строки, затем второй и т. д . При инициализации двумерного массива элементы указываются внуrри фигурных скобок через запятую. Элементы каждой строки также размещаются внутри фигурных скобок. Пример: int arr[2][41 = 1 ); (1, 2, 3, 4}, {5,6,7,8} for(inti=О,j =О;i<2;++i) for(j=О;j<4;++j){ printf("%3d ", arr(i] (j)); printf("\n");
110 Глава 3 Для того чтобы сделать процесс инициализации наглядным, мы расположили эле­ мекrы на отдельных строках.. Количество элементов на строке совпадает с количе­ ством столбцов в массиве. Результат выполнения: 1234 5б78 Если при объявлении производится инициализация, то количество строк можно не указывать, оно будет определено автоматически: int arr[] [4] = { ); {1, 2, 3, 4), {5,б,7,8) Получить или задать значение элемента можно, указав два индекса (не забывайте, что нумерация начинается с нуля): int arr[2] [4] = { ); (1, 2, 3, 4), (5,6,7,8) printf("%d\n", arr[0J[0J); // 1 printf("%d\n", arr[l] [О]);// 5 printf("%d\n", arr[l] [3]);// 8 3.12. Строки Строка является массивом символов, последний элемент которого содержит нуле­ вой символ (' \О'). Обратите внимание на то, что нулевой символ (нулевой байт) не имеет никакого отношения к символу 'о'. Коды этих символов разные. Такие строки часто называют С-строками. Объявляется С-строка так же, как и массив элементов типа char: char str[7]; При инициализации можно перечислить символы внутри фигурных скобок: char str[7] = { 's', 't', 'r', 'i', 'n', 'g', '\0'); или указать строку внутри двойных кавычек: char str[7] = "String"; При использовании двойных кавычек следует учитывать, что длина строки на один символ больше, т. к. в конец будет автоматически вставлен нулевой символ. Если это не предусмотреть и объявить массив из шести элементов вместо семи, то будет ошибка при работе со строкой в дальнейшем. Если размер массива при объявлении не указать, то он будет определен автомати­ чески в соответствии с длиной строки: char str[] = "String";
Гlврвменныв и типы данных 111 Обратите внимание на то, что присваивать строку в двойных кавычках можно только при инициализации. Попытка присвоить строку позже приведет к ошибке: char str[7]; str = "String"; // Ошибка! ! ! Внуrри строки в двойных кавычках можно указывать специальные символы (на­ пример, \n, \r и др.). Если внуrри строки встречается кавычка, то ее необходимо экранировать с помощью обратного слэша: char str[] = "Группа \"Кино\"\n"; Объявить массив строк можно следующим образом: char str[] (20) = {"Stringl", "String2", "StringЗ"}; printf("%s\n", str[0]}; // Stringl printf("%s\n", str[l]}; // String2 printf("%s\n' . ', str[2]); // StringЗ 3.13. Указатели Указатель - это переменная, которая предназначена для хранения адреса. В язы­ ке С указатели часто используются в следующих случаях: □ для управления динамической памятью; □ чтобы иметь возможность изменить значение переменной внуrри функции; □ для эффективной работы с массивами и др. Объявление _указателя имеет следующий формат: <Тип> *<Переменная>; Пример объявления указателя на тип int: int *р = NULL; printf("%p\n", р); // Вывод адреса // Значение в проекте Test32c: 00000000 // Значение в проекте Test64c: 0000000000000000 printf("%d\n", (int) sizeof(p)); // Значение в проекте Test32c: 4 // Значение в проекте Test64c: 8 // Вывод размера Очень часто символ * указывается после типа, а не перед именем переменной: int* р; С точки зрения компилятора эти два объявления ничем не различаются. Однако следует учитывать, что при объявлении нескольких переменных в одной инструк­ ции символ * относится к переменной, перед которой он указан, а не к типу дан­ ных. Например, следующая инструкция объявляет указатель и переменную, а не два указателя: int* р, х; // Переменная х указателем не является!!!
112 Глава 3 Поэтому более логично указывать символ * перед именем переменной: int *р, х; Для того чтобы указателю присвоить адрес переменной, необходимо при присваи­ вании значения перед названием переменной добавить оператор &. Типы данных переменной и указателя должны совпадать. Это нужно, чтобы при адресной ариф­ метике был известен размер данных. Пример присвоения адреса: int*р=NULL,х=10; р=&х; printf("%p\n", р); // Значение в проекте Test64c: 000000000023FE44 printf("%p\n", &х); // Значение в проекте Test64c: 000000000023FE44 Для того чтобы получить или изменить значение, расположенное по адресу, на ко­ торый ссылается указатель, необходимо выполнить операцию разыменования ука­ зателя. Для этого перед названием переменной указывается оператор*. Пример: int*р=NULL,х=10; р=&х; printf("%d\n", *р); // 10 *р = *р+20; printf("%d\n", *р); // 30 Основные операции с указателями приведены в листинге 3.5 . j Листинг 3.5. Указатеnи #include <stdio.h> intх=10; int *р = NULL; // Нулевой указатель int main(void) // Присваивание указателю адреса переменной х р=&х; // Вывод адреса printf("%p\n", р); // Например: 0000000000403010 // Вывод значения printf("%d\n", *р); // 10 return О; В этом примере при объявлении указателя ему присваивается значение NULL. Указа­ тель, которому присвоено значение NULL или о, называется нулевым указателем. Определение макроса NULL выглядит так: #include <stdio.h> #define NULL ((void *) О)
Переменные и типы данных 113 В данном случае можно было и не присваивать указателю значение NULL, т. к . гло­ бальные и статические локальные указатели автоматически получают значение о. Однако указатели, юоторые объявлены в локальной области видимости, будут иметь произвольное значение. Если попытаться записать какое-либо значение через такой указатель, то можно повредить операционную систему. Поэтому, согласно соглашению, указатели, которые ни на что не указывают, должны иметь значение NULL. Значение одного указателя можно присвоить другому указателю. При этом важно учитывать, что типы указателей должны совпадать. Пример: int*pl=NULL, *р2=NULL,х =10; pl=&х; р2=pl; printf("%d\n", *р2 = 40; printf("%d\n", printf("%d\n", *р2); *pl); х); // Копирование адреса переменной х // 10 // Изменение значения в переменной х // 40 // 40 В этом примере мы просто скопировали адрес переменной х из одного указателя в другой. Помимо копирования адреса можно создать указатель на указатель. Для этого при объявлении перед названием переменной указываются два оператора *: int **р = NULL; Для того чтобы получить адрес указателя, используют оператор &, а ДЛЯ получения значения переменной, на которую ссылается указатель, применяют два оператора '*. Пример: int*pl=NULL, **р2=NULL,х =10; pl=&х; р2 = &pl; // Указатель на указатель printf("%d\n", **р2); // 10 **р2 = 40; // Изменение значения в переменной х printf("%d\n", *pl); // 40 printf("%d\n", х); // 40 При каждом вложении добавляется дополнительная звездочка: int *pl = NULL, **р2 = NULL, ***рЗ = NULL, х = 10; pl &х; р2 = &pl; рЗ = &р2; printf("%d\n", ***рЗ = 40; printf("%d\n", printf("%d\n", printf("%d\n", ***рЗ); **р2); *pl); х); // 10 // Изменение значения в переменной х // 40 // 40 // 40 Подобный синтаксис трудно понять и очень просто сделать ошибку, поэтому обычно ограничиваются использованием указателя на указатель.
114 Глава.3 При инициализации указателя ему можно присвоить не только числовое значение, но и строку. Пример: const char *str = "String"; printf("%s", str); // String Указатели можно сохранять в массиве. При объявлении массива указ_ателей ис­ пользуется следующий синтаксис: <Тип> *<Название массива>[<Количество элементов>]; Пример использования массива указателей: int *р[3]; // Массив указателей из трех элементов intх=10,у=20,z=30; р[О) &х; p[l] = &у; р[2) = &z; printf("%d\n", printf("%d\n", printf("%d\n", *р[О]); *p[l] 1; *р[2]); // 10 // 20 // 30 Объявление массива указателей на строки и вывод значений выглядит так: const char *str[] = {"Stringl", "String2", "String3"}; printf("%s\n", str[0]); // Stringl printf("%s\n", str[l)); // String2 printf("%s\n", str[2]); // String3 Указатели очень часто используются для обращения к элементам массива, т. к . ад­ ресная арифметика выполняется эффективнее, чем доступ по индексу. В качестве примера создадим массив из трех элементов, а затем выведем значения (лис­ тинг 3.6). 1 Листмнr з:&. Перебор элементов массива. #include <stdio.h> #define ARR SIZE 3 int main(void) { int *р = NULL, arr[ARR_SIZE] = {10, 20, 30}; // Устанавливаем указатель на первый элемент массива р = arr; // Оператор & не указывается!!! for(inti=О;i<ARR_SIZE;++i) printf("%d\n", *р); ++р; // Перемещаем указатель на следующий элемент р = arr; // Восстанавливаем положение указателя // Выполняем какие-либо инструкции return О;
Переменные и типы данных 115 Вначале создается констаmа ARR_srzE, в которой сохраняется количество элементов в массиве. Есди массив используется часто, то_ лучше сохранить его размер как констаmу, т. к . количество элементов нужно будет указывать при каждом переборе массива. Если в каждом цикле указывать конкретное число, то при изменении размера массива придется вручную вносить изменения во всех циклах. При объяв­ лении константь1 достаточно будет изменить ее значение один раз при определе­ нии. В первой инструкции внутри функции main() объявляется указатель на тип int и массив. Количество элемеmов массива задается константой ARR_srzE. При объяв­ лении массив и,ttициализируется начальными значениями. После объявления переменных указателю присваивается адрес первого элемеmа массива. Обратите внимание на то, что перед названием массива отсутствует опе­ ратор &, т. к . название переменной содержит адрес первого элемента. Если исполь­ зовать оператор &, то необходимо дополнительно указать индекс внутри квадрат­ ных скобок: р = &arr[OJ; // Эквивалентно: р = arr; Для перебора элементов массива используется цикл for. В первом параметре цикла задается начальное значение (int i = о), во втором - условие (i < ARR_srzE), а в третьем - приращение на единицу (++i) на каждой итерации цикла. Инструкции внутри цикла будут выполняться, пока условие является истинным (значение пере­ менной i меньше количества элементов массива). Внутри цикла выводится значение элемента, на который ссьmается указатель, а за­ тем значение указателя увеличивается на единицу (++р). Обратите внимание на то, что изменяется адрес, а не значение элемента массива. При увеличении значения указателя используются правила адресной арифметики, а не правила обычной арифметики. Увеличение значения указателя на единицу означает, что значение будет увеличено на размер типа. Например, если тип int занимает 4 байта, то при увеличении значения на единицу указатель вместо адреса 0x0012FFЗ0 будет содер­ жать адрес 0x0012FFЗ4. Значение увеличилось на 4, а не на 1. В нашем примере вме­ сто двух инструкций внутри цикла можно использовать одну: printf("%d\n", *р++); Выражение р++ возвращает текущий адрес, а затем увеличивает его на единицу. Символ * позволяет получить доступ к значению элемента по указанному адресу. Последовательность выполнения соответствует следующей расстановке скобок: printf("%d\n", *(р++)); Если скобки расставить так: printf("%d\n", (*р)++); то вначале будет получен доступ к элементу массива и выведено его текущее зна­ чение, а затем будет произведено увеличение значения элемента массива. Переме­ щение указателя на следующий элемент не выполняется.
116 Глава 3 Получить доступ к элементу массива можно несколькими способами. Первый спо­ соб заключается в указании индекса внутри квадратных скобок. Во втором способе используется адресная арифметика совместно с разыменованием указателя. В третьем способе внутри квадратных скобок указывается название массива, а пе­ ред квадратными скобками - индекс элемента. Этот способ может показаться странным. Однако если учесть, что выражение 1[arr] воспринимается компилято­ ром как * (1 + arr), то все встанет на свои места. Таким образом, все эти инструк­ ции являются эквивалентными: int arr[З] = { 10, 20, 30}; printf("%d\n", arr[1]); // 20 printf("%d\n", *(arr + 1)); // 20 printf("%d\n", *(1 + arr)); // 20 printf ("%d\n", 1 [arr]); // 20 С указателем можно выполнять следующие арифметические и логические опера­ ции: □ прибавлять целое число. Число умножается на размер базового типа указателя. а затем результат прибавляется к адресу; □ вычитать целое число. Вывести значения элементов массива в обратном порядке можно так: #define ARR SIZE 3 int *р = NULL, arr[ARR_SIZE] = {10, 20, 30}; // Устанавливаем указатель на последний элемент р = &arr[ARR_SIZE - 1]; for(inti=ARR_SIZE-1;i>=О;--i) printf("%d\n", *р--); □ вычитать один указатель из другого. Это позволяет получить количество эле- ментов бцзового типа между двумя указателями; □ сравнивать указатели между собой. При использовании ключевого слова const применительно к указателям важно учи­ тывать местоположение ключевого слова const. Например, следующие объявления не эквивалентны: const char *р = str; char const *р = str; char * const р = str; const char * const р = str; Первые два объявления являются эквивалентными. В этом случае изменить значе­ ние, на которое ссылается указатель, нельзя, но указателю можно присвоить другой адрес: char strl[] = "String", str2[] = const char *р = strl; р = str2; р[О] = 's'; "New"; // Нс;--:-1ально // Ошибка
Переменные и типы данных 117 При третьем объямении изменить значение. на которое ссылается указатель, мож­ но. но указателю нельзя присвоить другой адрес: char strl[] = "String", str2(] = "New"; char * const р = strl; р = str2; // Ошибка р[О] = 's'; // Нормально Четвертое объямение запрещает изменение значения. на которое ссылается указа­ тель, и присвоение другого адреса: char strl[] = "String", str2(] const char * const р = strl; р = str2; р[О] = 's'; "New"; // Ошибка // Ошибка Указатели часто используются при передаче параметров в функцию. По умолчанию в функцию передается копия значения переменной. Если мы в этом случае изменим значение внутри функции. то это действие не затронет значения внешней перемен­ ной. Для того чтобы иметь возможность изменять значение внешней переменной, параметр функции объямяют как указатель. а при вызове передают адрес перемен­ ной (листинг 3.7). � linclude <stdio.h> void funcl(int х); void func2(int *х); int main(void) i int у= 10; funcl (у); printf("%d\n", у); func2(&y); printf("%d\n", у); return О; void funcl(int х) ( х=х*2; void func2(int *х) *х=*х*2; // Передаем копию значения // 10 (значение не изменилось!!!) // Передаем адрес, а не значение // 20 (значение изменилось!!!) // Значение нигде не сохраняется При использовании функции funcl() передача параметра осуществляется по значе­ нию (применяется по умолчанию). При этом создается копия значения. и все опера­ ции производятся с этой копией. Так как локальные переменные видны только
118 Глава 3 внутри тела функции, после завершения выполнения функции копия удаляется. Любые изменения значения копии не затронут значения оригинала. При использовании функции func2() мы передаем адрес переменной: func2 (&у); Внутри функции адрес присваивается указателю. Используя операцию разымено­ вания указателя, можно изменить значение самой переменной, а не значение копии. Достигается это с помощью следующей инструкции: *х=*х*2; 3.14. Динамическое выделение памяти Как вы уже знаете, при объявлении переменной необходимо указать тип данных, а для массива дополнительно задать точное количество элементов. На основе этой информации при запуске программы автоматически выделяется необходимый объ­ ем памяти. После завершения программы память автоматически освобождается. Иными словами, объем памяти необходимо знать до выполнения программы. Во время выполнения программы создать новую переменную или увеличить размер существующего массива нельзя. Для того чтобы произвести увеличение массива во время· выполнения программы, необходимо выделить достаточный объем динамической памяти, перенести суще­ ствующие элементы, а лишь затем добавить новые элементы. Управление динами­ ческой памятью полностью лежит на плечах программиста, поэтому после завер­ шения работы с памятью необходимо самим возвратить память операционной системе. Если этого не сделать, то участок памяти станет недоступным для даль­ нейшего использования. Подобные ситуации приводят к утечке памяти. 3.14.1. Функции та//осО и freeo Для выделения динамической памяти в языке С предназначена функция malloc(). Прототип функции: #include <stdlib.h> void *malloc(size_t size); Функция malloc() принимает в качестве параметра размер памяти в байтах и воз­ вращает указатель, имеющий тип void *. В языке С указатель типа void * неявно приводится к другому типу, поэтому использовать явное приведение не нужно (в языке С++ нужно обязательно выполнять явное приведение). Если память выде­ лить не удалось, то функция возвращает нулевой указатель. Все элементы будут иметь произвольное значение, так называемый "мусор". Для того чтобы программа была машинно-независимой, следует применять оператор sizeof для вычисления размера памяти, требуемого для определенного типа. Пример выделения памяти для десяти элементов типа int: const unsigned int ARR_SIZE = 10; t · int *р = malloc (ARR_SIZE * sizeof (int));
переменные и типы данных 119 Освободить ранее выделенную динамическую память позволяет функция free(). Прототип функции: tinclude <stdlib.h> void free(void *rnernory); Функция free() принимает в качестве параметра указатель на ранее выделенную память и освобождает ее. Пример использования функций malloc() и free() приве­ ден в листинге 3.8 . tinclude <stdio.h> linclude <stdlib.h> linclude <locale.h> int main(void) { setlocale(LC_ALL, "Russian_Russia.1251"); const unsigned int ARR_SIZE = 10; int *р = malloc(ARR_SIZE * sizeof(int)); if (!р) { // Проверяем на корректность } puts("He удалось ,выделить память"); exit(1); // выходим при ОUП!lбке // Нумеруем элементы массива от 1 до 10 for (int i = О; i < ARR_SIZE; ++i) { // Пользуемся памятью p[i]=i+1; // Выводим значения for (int i = О; i < ARR_SIZE; ++i) { // Пользуемся памятью printf("%d\n", p[i]); free(р); // Освобождаем память р = NULL; // Обнуляем указатель return О; 3.14.2. Функция са//ос() Вместо функции rnalloc() можно воспользоваться функцией calloc(). Прототип функции: linclude <stdlib.h> void *calloc(size_t count, size_t elern_size); В первом параметре функция calloc() принимает количество элементов, а во вто­ ром - размер одного элемента. Если память выделить не удалось, то функция воз­ вращает нулевой указатель. Все элементы будут иметь значение о.
120 Используя функцию calloc(), следующую инструкцию из листинга 3.8: int *р = malloc(ARR_SIZE * sizeof(ipt)); мы можем записать так: int *р = calloc(ARR_SIZE, sizeof(int)); Глава 3 В качестве примера использования функции calloc() создадим двумерный массив (листинг 3.9). Для этого нам нужно создать массив указателей и в каждом элементе массива сохранить адрес строки. Память для каждой строки нужно выдешm, дополнительно. Листинг 3.9. Динамическое выделение памяти под двумерный массив #include <stdio.h> #include <stdlib.h> int main(void) { const unsigned int ROWS = 2; // Количество строк const unsigned int COLUМNS = 4; // Количество столбцов inti=О,j=О; // Создаем массив указателей int **р = calloc(ROWS, sizeof(int*)); if ( ! р) exit(1); // Выходим при ошибке // Добавляем строки for(i=О;i<ROWS;++i)[ } p[i] = calloc(COLUМNS, sizeof(int)); if (!p[i]) exit(l); // Выходим при ошибке // Нумеруем элементы массива intn=1; for(i=О;i<ROWS;++i){ } for(j=0;j<COLUМNS;++j) p[i] [j] = n++; //*(*(р+i)+j)=n++; // Выводим значения for(i=О;i<ROWS;++i){ for(j=О;j<COLUМNS;++j) printf("%3d", p[i][j]); // printf("%3d", *(*(р + i) + j)); printf("\n"); } // Освобождаем память for(inti=О;i<ROWS;++i){ free(p[ ' i]);
Переменные и типы данных free(p); р = NULL; return О; 121 // Обнуляем указатель Обратите внимание: при возвращении памяти вначале освобождается память, вы­ .JеЛенная ранее под строки, а лишь затем освобождается память, выделенная ранее аод массив указателей. Так как мы сохраняем в массиве указателей лишь адрес строки, а не саму строку, �rоличество элементов в строке может быть произвольным. Это обстоятельство по- 180ляет создавать так называемые "зубчатые" двумерные массивы. Строки в памяти могут быть расположены в разных местах, что не позволяет эф­ +ективно получать доступ к элементам двумерного массива. Дrlя того чтобы доступ 1: элементам сделать максимально быстрым, можно представить двумерный массив • виде одномерного массива (листинг 3 .1 О). tinclude <stdio.h> tinclude <stdlib.h> �Ilt main(void) { const unsigned int ROWS = 2; // Количество строк const unsigned int COLUМNS = 4; // Количество столбцов unsignedinti=О,j=О; int *р = calloc(ROWS * COLUМNS, if ( !р) exit(1); // Нумеруем элементы массива intn=l; for(i=О;i<ROWS;++i){ sizeof(int)); // Выходим при ошибке for (j О; j < COLUМNS; ++j) *(р+i*COLUМNS+j)=n++; } // Выводим значения for(i=О;i<ROWS;++i){ for (j=О;j<COLUМNS;++j) printf("%3d", *(р + i * COLUМNS + j)); printf("\n"); free(р); р = NULL; return О; // Освобождаем память // Обнуляем указатель
122 Глава 3 Так как в этом случае все элементы двумерного массива расположены в смежных ячейках, мы можем получить доступ к элементам с помощью указателя и адресной арифметики. Например, пронумеруем все элементы: int *р2 = р; // Сохраняем адрес первого элемента intn=1; unsigned int count = ROWS * COLUМNS; for(i=О;i<count;++i){ *р2 = n++; ++р2; 3.14.3. Функция rea/locO Функция realloc() выполняет перераспределение памяти. Прототип функции: #include <stdlib.h> void *realloc(void *memory, size_t newSize); В первом параметре функция realloc() принимает указатель на ранее выделенную динамическую память, а во втором - новый требуемый размер в байтах. ФункцИJI выделит динамическую память длиной newSize, скопирует в нее элементы из старой области памяти, освободит старую память и вернет указатель на новую область памяти. Новые элементы будут иметь произвольные значения, так называемый "мусор". Если новая длина меньше старой длины, то лишние элементы будут уда­ лены. Если память не может быть выделена, то функция вернет нулевой указатель. при этом старая область памяти не изменяется (в этом случае возможны утечки памяти, если значение присваивается прежнему указателю). Если в первом параметре задать значение NULL, то будет выделена динамическu память и функция вернет указатель на нее. Если второй параметр имеет значение о, то ранее выделенная динамическая память освобождается и функция вернет нуле­ вой указатель. Пример использования функции realloc() приведен в листинге 3 .11 . 1 Листинг 3.11 . Функция realloc() #include <stdio.h> #include <stdlib.h> int main(void) { unsigned int arr_size = 5; int *р = malloc(arr_size * sizeof(int)); if ( !р) exit(1); // Выходим при ошибке // Нумеруем элементы массива for(inti=О;i<arrsize;++i) p[i]=i+1;
n.ременные и типы данных // Увеличиваем количество элементов arr_size += 2; р = realloc(p, arr_size * sizeof(int)); // Здесь возможна утечка памяти, если realloc() вернет NULL if ( !р) exit(1); // Выходим при ошибке р[5] = 55; р[6] = 66; // Выводим значения for(inti=О;i<arr_size;++i){ printf("%d ", p[i]); //123455566 free(p); // Освобождаем память р = NULL; // Обнуляем указатель return О; 3.15. Структуры 123 Сmруктура - это совокупность переменных (называемых членами, элементами 8ЛИ полями), объединенных под одним именем. Объявление струюуры выглядит аrедующим образом: struct [<Название структуры>] { <Тип данных> <Название поля 1>; <Тип данных> <Название поля N>; [<Объявления переменных через запятую>]; Допустимо не задавать название струюуры, если после закрывающей фигурной аобки указано объявление переменной. Точка с запятой в конце объявления струк­ tуры является обязательной. Объявление структуры только описывает новый тип данных, а не определяет пере­ менную, поэтому память под нее не выделяется. Для того чтобы объявить перемен­ ную, ее название указывается после закрывающей фигурной скобки при объяв­ хнии структуры или отдельно с помощью названия струкrуры в качестве типа l18ННЫХ: struct <Название структуры> <Названия переменных через запятую>; Пример одновременного объявления струкrуры и переменной: struct Point int х; int у; pointl; Пример отдельного объявления переменной: struct Point point2;
124 Глава 3 Одновременно с объявлением переменной можно выполнить инициализацию полей структуры, указав значения внутри фигурных скобок: struct Point point3 = (10, 20); Можно также в списке инициализации указать точку и название поля структуры, а после оператора = - присваиваемое значение: struct Point point4= {.х = 10, .у= 20}; После объявления переменной выделяется необходимый размер памяти. Для полу­ чения размера структуры внутри программы следует использовать оператор sizeof: <Размер> sizeof <Переменная>; <Размер>= sizeof (struct <Название структуры>); Пример: printf("%d\n", (int) sizeof pointl); //8 printf("%d\n", (int) sizeof(struct Point)); // 8 Присвоить или получить значение поля можно с помощью точечной нотации: <Переменная>.<Название поля>= <Значение>; <Значение>= <Переменная>.<Название поля>; Пример: pointl.x = 10; pointl.y = 20; printf("%d\n", pointl.x); // 10 printf("%d\n", pointl.y); // 20 Одну структуру можно присвоить другой структуре с помощью оператора =. В этом случае копируются значения всех полей структуры. Пример: point2 = pointl; printf("%d\n", point2.x); // 10 printf("%d\n", point2.y); // 20 После оператора = и при передаче структуры в качестве параметра в функцию допускается указание составного литерш,а. Литерал структуры состоит из круглых скобок, внутри которых указываются ключевое слово struct и название структуры. и фигурных скобок, внутри которых через запятую перечисляются значения: point4 = (struct Point) (30, 40}; printf("%d\n", point4.x); // 30 printf("%d\n", point4.y); // 40 Структуры можно вкладывать. При обращении к полю вложенной структуры дополнительно указывается название структуры родителя. В качестве примера объявим структуру Point (точка), а затем используем ее ДJJЯ описания координат прямоугольника (листинг 3.12). Структуру, описывающую прямоугольник, объ­ явим без названия.
llервменные и типы данных tinclude <stdio.h> nruct Point { // Объявление именованно� структуры int х; int у; J; 51:ruct // Объявление структуры без названия struct Point top_left; struct Point bottom_right; rect, rect2 = [ (10, 20}, (30, 40} }; int main(void) { rect.top_left.x = О; rect.top_left.y = О; rect.bottom_right.x = 100; rect.bottom_right.y = 100; printf("%d\n", rect.top_left.x); printf("%d\n", rect.top_left.y); printf("%d\n", rect.Ьottom_right.x); printf("%d\n", rect.bottom_right.y); printf("%d\n", rect2.top_left.x); printf("%d\n", rect2.top_left.y); //о //о // 100 // 100 // 10 // 20 printf("%d\n", rect2.bottom_right.x); // 30 printf("%d\n", rect2.bottom_right.y); // 40 printf("%d\n", (int) sizeof rect); // 16 return О; Стандарт С11 позволяет создавать анонимные вложенные структуры: struct MyStruct [ int а; struct { // Анонимная вложенная структура int Ь; int с; }; int d; Инициализация и доступ к полям осуществляется следующим образом: struct MyStruct my_struct = (1, (2, 3}, 41; printf("%d\n", my_struct.a); // 1 printf("%d\n", my_struct.Ь); // 2 print:f("%d\n", my_struct.c); // 3 printf("%d\n", my_struct.d); // 4 125
126 Глава 3 Адрес струкrуры можно сохранить в указателе. Объявление указателя на структуру производится так же, как и на любой другой тип данных. Для получения адреса струкrуры используется оператор &, а для доступа к полю струкrуры вместо точки применяется оператор ->. Пример использования указателя на структуру приведен в листинге 3.13. Листинг 3.13. Исnользо�ание указателя на структуру #include <stdio.h> struct Point int х; int у; pointl; int main(void) struct Point *р р->х = 10; р->у = 20; printf("%d\n", printf("%d\n", printf("%d\n", printf("%d\n", return О; // Объявление структуры и переменной &pointl; // Объявление указателя р->х); // 10 р->у); // 20 (*р) .х); // 10 (*р) .у); // 20 3.16. Битовые поля Язык С поддерживает битовые поля, которые предоставляют доступ к отдельным битам, позволяя тем самым хранить в одной переменной несколько значений, за­ нимающих указанное количество битов. Один бит может содержать только числа : или 1. Следует учитывать, что минимальный размер битового поля будет соответ­ ствовать типу int. Объявление битового поля имеет следующий формат: struct [<Название битового поля>] ( <Тип данных> [<Название поля 1>]:<Дпина в битах>; <Тип данных> [<Название поля N>]:<Дпина в битах>; [<Объявления переменных через запятую>]; Битовые поля объявляются только с типом int. В одной структуре можно исполь­ зовать одновременно битовые поля и обычные поля. Обратите внимание на то, чn1 название битового поля можно не указывать. Кроме того, если длина поля соста. ляет 1 бит, то дополнительно следует указать ключевое слово unsigned. Пример объявления битового поля и переменной: struct Status { unsigned int flagl:1;
р.,,е,,,енные и типы данных шisigned int flag2:l; шisigned int flagЗ:1; 1 status = (О, 1, 1}; � к полю осуществляется так же, как и к полю структуры: printf("%d\n", status.flagl); //О �intf("%d\n", status.flag2); //1 �intf("%d\n", status. flagЗ); //1 �tus.flagl = 1; pE'intf("%d\n", status. flagl); //1 Jl['intf("%d\n", (int) sizeof(struct Status)); // 4 3.17. Объединения 127 Обьединение - это область памяти, используемая для хранения данных разных 'IIIПOB. В один момент времени в этой области могут храниться данные только од- 8Х'О типа. Размер объединения будет соответствовать размеру более сложного типа 8ННЫХ. Например, если внутри объединения определены переменные, имеющие '111ПЬ1 int, float и douЬle, то размер объединения будет соответствовать размеру uma douЬle. Объявление объединения имеет следующий формат: union [<Название объединения>] ( <Тип данных> <Название члена 1>; <Тип данных> <Название члена N>; [<Объявления переменных через запятую>]; Точка с запятой в конце объявления обязательна. Объявление только описывает вовый тип данных, а не определяет переменную, поэтому память пQд нее не выде­ uется. Для того чтобы объявить переменную, ее название уюnывается после :JIUСРЫВающей фигурной скобки при объявлении объединения или отдельно с по­ мощью названия объединения в качестве типа данных: шiion <Название объединения> <Названия переменных через запятую>; После объявления переменной компилятор выделяет необходимый размер памяти. Пример объявления объединения и переменной: шiion rnyUnion int х; float у; douЬle z; unionl; Пример отдельного объявления переменной: union rnyUnion union2; Одновременно с объявлением переменной можно выполнить инициализацию пер­ вого члена объединения, указав значение внутри фигурных скобок: union rnyUnion unionЗ = (10);
128 Глава 3 Для инициализации произвольного члена объединения нужно после точки указать его название, а после оператора = - его значение: union myUnion union4 = {.z = 2.5}; Присвоить или получить значение можно с помощью точечной нотации: <Переменная>.<Название члена> = <Значение>; <Значение> = <Переменная>.<Название члена>; Пример: Шlionl.x = 10; printf("%d\n", unionl.x); // 10 При использовании указателя на объединение доступ осуществляется так: <Указатель> -> <Название члена> = <Значение>; <Значение> = <Указатель> -> <Название члена>; Пример использования объединений приведен в листинге 3 .14 . 1 Листинг 3.14 . Объединения. #include <stdio.h> union myUnion int х; float у; douЫe z; unionl; int main(void} unionl.x = 10; printf("%d\n", unionl.x); unionl.z = 2.5; printf("%.2f\n", unionl.z); // Объявление переменной Шlion myUnion union2; Шlion2.x = 5; printf("%d\n", union2.x); // Объявление указателя на объединение union myUnion *р = &union2; printf("%d\n", р->х); р->х = 20; printf("%d\n", р->х); printf("%d\n", union2.x); // Определение размера объединения printf("%d\n", (int) sizeof(union myUnion)); printf("%d\n", (int) sizeof(unionl)); return О; . // 10 11 2.50 //5 //5 11 20 11 20 //8 //8 ]
переменные и типы данных Стандарт С 11 позволяет создавать анонимные вложенные объединения: s�ruct MyStruct { int а; union { // АнонИl\.1Ное вложенное объединение int Ь; douЫe с; 1; int d; Инициализация и доступ к полям осуществляется следующим образом: s�ruct MyStruct my_struct = {1, [.с= 2.5}, 4}; ;::-intf("%d\n", my_struct.a}; // 1 ;::-intf("%.2f\n", my_struct.c}; // 2.50 ;:rintf("%d\n", my_struct.d}; // 4 3.18. Перечисления 129 Перечисление - это совокупность целочисленных констант, описывающих все .:t0пустимые значения переменной. Если переменной присвоить значение, не совпа­ �щее с перечисленными константами, то компилятор выведет сообщение об ошибке. Объявление перечисления имеет следующий формат: enum [<Название перечисления>] { <Список констант через запятую> , [<Объявления переменных через запятую>]; Точка с запятой в конце объявления является обязательной. Для объявления пере­ менной ее название указывается после закрывающей фигурной скобки при объяв­ _ ;rении перечисления или отдельно с помощью названия перечисления в качестве nша данных: -enum <Название перечисления> <Названия переменных через запятую>; Пример одновременного объявления перечисления и переменной: enum Color [ RED, BLUE, GREEN, BLACK i colorl; Пример отдельного объявления переменной: enum Color color2; Константам RED, BLUE, GREEN и BLACK автоматически присваиваются целочисленные значения, начиная с нуля. Значение каждой последующей константы будет на еди­ ницу больше предыдущей. Нумерация производится слева направо. Таким образом, константа RED будет иметь значение о, ВШЕ - 1, GREEN - 2, а BLACK - з .
130 Глава З При объявлении перечисления константе можно присвоить другое значение. В этом случае последующая константа будет иметь значение на единицу больше того, дру­ гого значения. Пример: enurn Color { RED = 3, BLUE, GREEN 7, BLACK ) colorl; В этом примере константа RED будет иметь значение 3, а не о, BLUE- 4, GREEN- 7, а BLACK - в. Присвоить значение переменной можно так: colorl = BLACK; Допустимо не задавать одновременно название перечисления и объявление пере­ менной. В этом случае переменные, указанные внутри фигурных скобок, исполь­ зуются как константы. Пример: enurn { RED, BLUE, GREEN, BLACK ); printf("%d\n", RED); // О Пример использования перечисления приведен в листинге 3 .15. 1 Листинг 3.15. Перечисления #include <stdio.h> enurn Color { RED, BLUE, GREEN 5, BLACK ); int main(void) enurn Color color; color = BLACK; printf("%d\n", color); if (color == BLACK) printf("color == BLACK\n"); else { printf("color != BLACK\n"); return О; // Объявление переменной //6 // Проверка значения // Выведет: color == BLACK 3.19. Приведение типов 1 Как вы уже знаете, при объявлении переменной необходимо указать определенный тип данных. Далее над переменной можно производить операции, предназначенные для этого типа данных. Если в выражении используются переменные, имеющие
Гlеременные и типы данных 131 разный тип данных, то тип результата выражения будет соответствовать наиболее сложному типу. Например, если производится сложение переменной, имеющей тип int, с переменной, имеющей тип douЫe, то целое число будет автоматически пре­ образовано в вещественное. Результатом этого выражения будет значение типа douЫe. Например, после компиляции следующего фрагмента кода будет выведено преду­ преждающее сообщение (при условии, что в команде на компиляцию указан флаг - Wconversion): shortyl=1,у2=2; yl=yl+у2; // warning: conversion from 'int' to 'short int' may change value Для того чтобы результат выполнения этого выражения не вызывал сомнений ком­ пилятора, необходимо выполнить операцию явного приведения типов. Формат опе­ рации: (<Тип результата>}<Выражение или переменная> Пример приведения типов: shortyl=1,у2=2; yl = (short}(yl + у2}; // ок В этом случае компилятор считает, что мы знаем, что делаем, и осведомлены о возможном несоответствии значения указанному типу данных. Если значение выражения будет выходить за диапазон значений типа, то произойдет усечение результата, но никакое сообщение об ошибке не появится. Давайте рассмотрим это на примере: charcl=127,с2=О; cl (char}(cl + с2}; // ок. 127 cl 127, с2= 10; cl (char}(cl + с2}; // Усечение. - 119 Результатом первого выражения будет число 127, которое входит в диапазон значе­ ний типа char, а вот результат второго выражения- число 137 - в диапазон не входит, и происходит усечение. В итоге мы имеем число -119 . Согласитесь, мы по­ лучили совсем не то, что хотели. Поэтому приведением типов нужно пользоваться осторожно, хотя в некоторых случаях такое усечение очень даже полезно. Напри­ мер, вещественное число можно преобразовать в целое. В этом случае дробная часть просто отбрасывается: float х= 1.2f; douЫe у= 2.5; printf("%.2f\n", х}; printf("%d\n", (int}x}; printf("%.2f\n", у}; printf("%d\n", (int}y}; ./ / 1.20 //1 // 2.50 //2
132 Глава 3 Рассмотрим пример, который демонстрирует частый способ применения приведе­ ния типов. В языке С деление целых чисел всегда возвращает целое число. Дробнw� часть при этом просто отбрасывается. Для того чтобы деление целых чисел воз­ вращало вещественное число, необходимо преобразовать одно из целых чисе.1 в вещественное (второе число преобразу�тся автоматически): intх=10,у=З; printf("%d\n", х / у); //3 printf("%.2f\n", (douЫe) (х / у)); // 3.00 printf("%.2f\n", (douЫe)x / у); // 3.33
ГЛАВА 4 Операторы и циклы Операторы позволяют выполнить определенные действия с данными. Например, операторы присваивания служат для сохранения данных в переменной, математи­ ческие операторы предназначены для арифметических вычислений, а условные операторы позволяют в зависимости от значения логического выражения выпол­ нить отдельный участок программы или, наоборот, не выполнять его. 4.1. Математические операторы Производить арифметические вычисления позволяют следующие операторы: □ + - сложение: printf("%d\n", 10 + 15); // 25 □ - - выч итание: printf("%d\n", 35 - 15); // 20 □ - - унарный минус: intх=10; printf("%d\n", -х); // -10 □ * - умножение: printf("%d\n", 25 * 2); // 50 □ ! - деление. Если производится деление целых чисел, то остаток отбрасывается и возвращается целое число. Деление вещественных чисел производится клас­ сическим способом. Если в выражении участвуют вещественное и целое числа, то целое число автоматически преобразуется в вещественное. Пример: printf ( "%d\n", 10/ 3); //3 printf("%.3f\n", (douЫe)(10/ 3)); // 3.000 printf("%.3f\n", 10.0/ 3.0); // 3.333 printf("%.3f\n", 10.0/ 3); // 3.333 printf("%.3f\n", (douЫe)l0/ 3); // 3.333
134 Глава 4 Целочисленное деление на о приведет к неопределенности. Деление веществен­ ного числа на о приведет к значению плюс или минус INFINITY (бесконечность). а деление вещественного числа о. о на о. о - к неопределенности или значению NAN (нет числа): printf("%f\n", 10.0/ 0.0); // 1.#INFOO printf("%f\n", -10.0/ О.О); // -1.#INFOO printf("%f\n", О.О/ О.О); // -1.#INDOO // #include <rnath.h> printf("%f\n", INFINITY); /! 1.#INFOO printf("%f\n' . ', -INFINITY); // -1.#INFOO printf("%f\n", NAN); // 1.#QNANO printf("%f\n", NAN + 1); // 1.#QNANO printf("%f\n", О * INFINITY); // 1.#QNANO printf("%f\n", INFINITY/ INFINITY); // 1.#QNANO printf("%f\n", -INFINITY + INFINITY); // 1.#QNANO □ % - остаток от деления. Обратите внимание на то, что этот оператор применять к вещественным числам. Пример: printf("%d\n", 10 % 2); //о(10-10/2*2) printf("%d\n", 10 % 3); //1(10-10/ 3*3) printf("%d\n", 10 % 4); //2(10-10/4*4) printf("%d\n", 10 % б); //4(10-10/б*6) □ ++ - оператор инкремента. Увеличивает значение переменной на 1: intх=10; ++х; printf("%d\n", х); // Эквивалентно х = х + 1; // 11 □ -- - о ператор декремента. Уменьшает значение переменной на 1: intх=10; - -х; printf("%d\n", х); // Эквивалентно х х-1; //9 нельз. Операторы инкремента и декремента могут использоваться в постфиксной или префиксной формах: х++; х--; // Постфиксная форма ++х; -- х; // Префиксная форма При постфиксной форме (х++) возвращается значение переменной перед операцией. а при префиксной форме (++х) - вначале производится операция и только потом возвращается значение. Продемонстрируем это на примере (листинг 4.1 ). Листинг 4.1. Постфиксная и префиксная форма #include <stdio.h> #include <locale.h>
Операторы и циклы int main(void) { setlocale(LC_ALL, "Russian_Russia.1251"); intх=О,у=О; х=5; у=х++;//у=5,х=6 рuts("Постфиксная форма (у= х++;) :"); printf("y = %d\n", у); printf("x = %d\n", х); х=5; у=++х;//у=6,х=6 рuts("Префиксная форма (у= ++х;) :"); printf("y = %d\n", у); printf("x = %d\n", х); return О; В итоге получим следующий результат: Постфиксная форма (у= х++;): у=5 х=6 Префиксная форма (у= ++х;): у=6 х=6 135 Если операторы инкремента и декремента используются в сложных выражениях, то понять, каким будет результат выполнения выражения, становится сложно. Напри­ мер, каким будет значение переменной у после выполнения этих инструкций? intх=5,у=О; у= ++х + ++х; Дгlя того чтобы облегчить жизнь себе и всем другим программистам, которые будут разбираться в программе, операторы инкремента и декремента лучше ис­ пользовать отдельно от других операторов. 4.2. Побитовые операторы Побитовые операторы предназначены для манипуляции отдельными битами. Эти операторы нельзя применять к вещественным числам, логическим значениям и другим более сложным типам. Язык С поддерживает следующие побитовые опе­ раторы: □ - - двоичная инверсия. Значение каждого бита заменяется противоположным: □ unsigned char х = 100; //01100100 х = (unsigned char) ~х; //10011011 & � двоичное и: unsigned char х = 100; // 01100100 unsigned char у = 75; // 01001011 unsignedcharz=х&у; // 01000000
136 Глава 4 □ 1 - двоичное ипи: unsigned char х = 100; !/ 01100100 unsigned char у= 75; // 01001011 unsignedcharz=х1у; // 01101111 □ л - двоичное исключающее ипи: unsigned char х= 100; // 01100100 unsigned char у= 250; // 11111010 unsignedcharz=хлу; // 10011110 □ « - сдвиг влево - сдвигает двоичное представление числа влево на один или более разрядов и заполняет разряды справа нулями: unsigned char х= 100; // 01100100 х = (unsigned char)(х << 1); // 11001000 х = (unsigned char)(х << 1); // 10010000 х = (unsigned char)(х << 2); // 01000000 □ » - сдвиг вправо - сдвигает двоичное представление числа вправо на один или более разрядов и заполняет разряды слева нулями, если число положитель- ное: unsigned char х = 100; // 01100100 х= (unsigned char)(х >> 1); // 00110010 х= (unsigned char)(х >> 1); // 00011001 х= (unsigned char) (х >> 2); // 00000110 Если число отрицательное, то разряды слева заполняются единицами: char х= -127; // 10000001 х= (char)(х >> 1); // 11000000 х = (char)(х >> 2); // 11110000 х= (char)(х << 1); // 11100000 х = (char)(х >> 1); /! 11110000 Наиболее часто двоичное представление числа используется для хранения различ­ ных флагов (о-флаг сброшен, 1- флаг установлен). Примеры установки, снятм. и проверки установки флага приведены в листинге 4.2. Для того чтобы иметь воз­ можность видеть результат в окне консоли, дополнительно напишем функцию, п� зволяющую преобразовать число типа unsigned char в строку в двоичном формате. 1 Листинг 4.2. Работа с фnаrами #include <stdio.h> #include <locale.h> char *ucharToBinaryString(unsigned char х, char *str); int main(void) { setlocale(LC_ALL, "Russian_Russia.1251");
Операторы и циклы char str [ 91· = '"'; const unsigned char FLAGl = 1, FLAG2 = 2, FLAGЗ FLAG5 = 16, FLAG6 = 32, FLAG7 unsigned char х =·о; // Все флаги сброшены printf("%s\n", ucharToBinaryString(x, str)); unsigned char у=0xFF; // Все флаги установлены printf("%s\n", ucharToBinaryString(y, str)); // Устанавливаем флаги FLAGl и FLAG7 х=хIFLAGlIFLAG7; printf("%s\n", ucharТoBinaryString(x, str)); // Устанавливаем флаги FLAG4 и FLAG5 х=хIFLAG41FLAG5; printf("%s\n", ucharToBinaryString(x, str)); // Снимаем флаги FLAG4 и FLAG5 х=х лFIAG4лFLAG5; printf("%s\n", ucharТoBinaryString(x, str)); // Проверка установки флага FLAGl if((х&FLAGl)!=О)( puts("FLAGl установлен"); printf("%s\n", ucharТoBinaryString(FLAGl, str)); printf("%s\n", ucharТoBinaryString(FLAG2, str)); printf("%s\n", ucharToBinaryString(FLAGЗ, str)); printf("%s\n", ucharToBinaryString(FLAG4, str)); printf("%s\n", ucharТoBinaryString(FLAG5, str)); printf("%s\n", ucharToBinaryString(FLAG6, str)); printf("%s\n", ucharТoBinaryString(FLAGJ, str)); printf("%s\n", ucharТoBinaryString(FLAGB, str)); return О; 4,FLAG4=8, 64, FLAGB = 128; // 00000000 // 11111111 // 01000001 // 01011001 // 01000001 // 00000001 // 00000010 // 00000100 // 00001000 // 00010000 // 00100000 // 01000000 // 10000000 char *ucharТoBinaryString(unsigned char х, char *str) { intk=7; while (k >= О) if((х&1)!=О) str[k] '1'; else str[k] = 'О'; // Проверка статуса последнего бита х = (unsigned char)(х >> 1); // Сдвиг на один разряд вправо - - k; str[B] '\0'; return str; 137
138 Глава 4 4.3. Операторы присваивания Операторы присваивания предназначены для сохранения значения в переменной. Перечислим операторы присваивания, доступные в языке С. □ = - присваивает переменной значение. Обратите внимание на то, что хотя опе­ ратор похож на математический знак равенства, смысл у него в языке С совер­ шенно другой. Справа от оператора присваивания может располагаться пере­ менная, литерал или сложное выражение. Слева от оператора присваивания должна располагаться переменная, а не литерал или выражение. Пример при­ сваивания значения: int х; х=10; х=12*10+45/5; 12+45=45+5;//Такнельзя!!! В одной инструкции можно присвоить значение сразу нескольким переменным: intх,у,z; х=у=z=2; □ += - увеличивает значение переменной на указанную величину: х+=10; // Эквивалентно х = х + 10; □ -= - уменьшает значение переменной на указанную величину: х-= 10; // Эквивалентно х = х - 10; □ *= - умножает значение переменной на указанную величину: х*=10+5;//Эквивалентнох=х * (10+5); □ /= - делит значение переменной на указанную величину: х!=2; // Эквивалентно х = х / 2; □ %= - делит значение переменной на указанную величину и возвращает остаток: х%=2; // Эквивалентно х = х % 2; □ &=, 1 =, л=, «= и »= - побитовые операторы с присваиванием. 4.4. Оператор запятая Оператор запятая позволяет разместить сразу несколько выражений внутри одной инструкции. Например, в предыдущих примерах мы использовали этот оператор для объявления нескольких переменных в одной инструкции: int х, у; Результат вычисления последнего выражения можно присвоить какой-либо пере­ менной. При этом выражения должны быть расположены внутри круглых скобок, т. к . приоритет оператора "запятая" меньше приоритета оператора присваивания. Пример:
Операторы и циклы :ntх=О,у=о,z= ][= (у=10, z=20, p::::intf("%d\n", х); // printf("%d\n", у); // printf("%d\n", z); // 139 О; у+z); 30 10 20 В этом примере после объявления и инициализации переменных переменной у присваивается значение 10, затем переменной z присваивается значение 20, далее вычисляется выражение у + z и его результат присваивается переменной х. 4.5. Операторы сравнения Операторы сравнения используются в логических выражениях. Перечислим опера­ юры сравнения, доступные в языке С: □ ==-равно; □ != - неравно; □ <-меньше; □ >-больше; □ <= - меньше или равно; □ >= - больше или равно. Логические выражения возвращают только два значения: истина (соответствует числу 1) или ло_жь (соответствует числу о). Пример вывода значения логического выражения: printf("%d\n", 10 10); // 1 (Истина) printf("%d\n", 10 5); // о (Ложь) printf("%d\n", 10 != 5); // 1 (Истина) printf("%d\n", 10 < 5); // о (Ложь) printf("%d\n", 10 > 5); // 1 (Истина) printf("%d\n", 10 <= 5); // о (Ложь) printf("%d\n", 10 >= 5); // 1 (Истина) Логическое выражение может не содержать операторов сравнения вообще. В этом случае число о автоматически преобразуется в ложное значение, а любое ненулевое значение (в том числе и отрицательное)- в истинное значение: printf("%d\n", (_8001)10); // 1 (Истина) printf("%d\n", (_8001)-10); // 1 (Истина) printf("%d\n", (_8001)12.5); // 1 (Истина) printf("%d\n", (_8001)0.1); // 1 (Истина) printf("%d\n", (_8ool)"str"); // 1 (Истина) printf("%d\n", (_8001)""); // 1 (Истина) printf("%d\n", (_8001)0.0); // о (Ложь) printf("%d\n", (_8001)О); // о (Ложь) printf("%d\n", (_8001)NULL); // о (Ложь)
140 // #include <math.h> printf("%d\n", (_Bool)INFINITY); // 1 (Истина) printf("%d\n", (_Bool)NAN); // 1 (Истина) Глава 4 В этом примере мы воспользовались приведением к типу _Bool. Внутри логическо­ го выражения приведение типов выполнять не нужно, т. к . оно производится авто­ матически. Следует учитывать, что оператор проверки на равенство содержит два символа =. Указание одного символа = является логической ошибкой, т. к. этот оператор ис­ пользуется для присваивания значения переменной, а не для проверки условия. Ис­ пользовать оператор присваивания внутри логического выражения допускается. поэтому компилятор не выведет сообщение об ошибке, однако программа может выполняться некорректно. Подобную ошибку часто допускают начинающие про­ граммисты. Например, в следующем примере вместо проверки равенства числу 1: производится операция присваивания: intх =10; if(Х=11){ puts("x == 11"); Выражение внутри круглых скобок присвоит переменной х значение 11, а затем будет произведено сравнение. Любое число, не равное о, трактуется как логическое значение Истина, поэтому в окне консоли отобразится строках == 11. Значение логического выражения можно инвертировать с помощью оператора ! . В этом случае, если логическое выражение возвращает ложь, то !Ложь вернет значе­ ние Истина. Пример: printf("%d\n", (10 printf("%d\n", ! (10 5)); // О (Ложь) 5)); // 1 (Истина) Если оператор ! указать дважды, то любое значение преобразуется в логическое: printf("%d\n", !!10); printf("%d\n", !!О); printf ("%d\n", ! !NULL); // 1 (Истина) // О (Ложь) // О (Ложь) Несколько логических выражений можно объединl-!ть в одно большое с помощью следующих операторов. □ && - логическое и. Логическое выражение вернет истина только в случае, если оба подвыражения вернут истина. Пример: printf("%d\n", (10 10) && (5 != 3)); // 1 (Истина) printf("%d\n", (10 == 10) && (5 ==3)); // О (Ложь) □ 1 1 - логическое или. Логическое выражение вернет истина, если хотя бы одно из подвыражений вернет Истина. Следует учитывать, что если подвыражение вер­ нет значение истина, то следующие подвыражения вычисляться не будут. Например, в логическом выражении п () 1 1 f2 () 1 1 fЗ () функция f2 () будет
Операторы и циклы 141 вызвана, только если функция п () вернет ложь, а функция fЗ () будет вызвана, только если функции f1 () и f2 () вернут ложь. Пример использования оператора: printf("%d\n", (10 == 10) 11 (5 != З)); // 1 (Истина) printf("%d\n", (10 == 10) 11 (5 == З)); // 1 (Истина) Результаты выполнения операторов && и 1 1 показаны в табл. 4 .1. Таблица 4.1 . Операторы && и 1 1 ' а ь а&&Ь а 11Ь ИСТИНА ИСТИНА ИСТИНА ИСТИНА ИСТИНА ложь ложь ИСТИНА ложь ИСТИНА ложь ИСТИНА ложь ложь ложь ложь 4.6. Приоритет выполнения операторов Все операторы выполняются в порядке приоритета (табл. 4.2). Вначале вычисляет­ ся выражение, в котором оператор имеет наивысший приоритет, а затем выражение с меньшим приоритетом. Например, выражение с оператором умножения будет выполнено раньше выражения с оператором сложения, т. к. приоритет оператора умножения выше. Если приоритет операторов одинаковый, то используется поря­ док вычисления, определенный для конкретного оператора. Операторы присваива­ ния и унарные операторы выполняются справа налево. Математические, побитовые и операторы сравнения, а также оператор запятая выполняются слева направо. Из­ менить последовательность вычисления выражения можно с помощью круглых скобок. Пример: intх=О; х = 5 + 10 * з / 2; // Умножение-> деление-> сложение printf("%d\n", х); // 20 х = (5 + 10) * з / 2; // Сложение->умножение-> деление printf("%d\n", Х); // 22 Таблица 4.2. Приоритеты операторов Приоритет Операторы 1 (высший) () [] . - > ++ - - (постфиксная форма) 2 ++ -- (префиксная форма)~ ! * (разыменование) & (взятие адреса) - (унарный минус) (приведение типа) sizeof з */% 4 + - (сложение и вычитание) 5 << >>
142 Глава 4 Таблица 4.2 (окончание) Приоритет Операторы 6 <><=>= 7 == != 8 & (побитовое И) 9 л 10 1 11 && 12 11 13 ?: 14 =*=/=%=+=-=>>=<<=& = л =1= 15 (низший) , (запятая) 4.7. Оператор ветвления if Оператор ветвления if позволяет в зависимости от значения логического выраже­ ния выполнить отдельный блок программы или, наоборот, не выполнять его. Опе­ ратор имеет следующий формат: if (<Логическое выражение>) { <Блок, вьmолняемый если условие истинно> [else { <Блок, вьmолняемый если условие ложно> }] Если логическое выражение возвращает значение истина, то выполняются инструк­ ции, расположенные внутри фигурных скобок, сразу после оператора if. Если леr гическое выражение возвращает значение ложь, то выполняются инструкции после ключевого слова else. Блок else является необязательным. Допускается не указы­ вать фигурные скобки, если блоки состоят из одной инструкции. В качестве приме­ ра проверим число, введенное пользователем, на четность и выведем соответст­ вующее сообщение (листинг 4.3). 1 Листинг 4.3. Проверка числа н� четность #include <stdio.h> #include <locale .h> int main(void) { setlocale(LC_ALL, "Russian Russia.1251"); intх=О;
Операторы и циклы printf("Bвeдитe целое число: "); fflush(stdout); int status = scanf("%d", &х); if (status != 1) { puts("Вы ввели не число"); return О; else { if(х%2== О) printf("%d - четное число", х); else printf("%d - нечетное число", х); printf("\n"); return О; 143 Как видно из примера, один оператор ветвления можно вложить в другой. Первый оператор if проверяет отсутствие ошибки при вводе числа. Если ошибки при вводе числа нет, то выполняется блок else. В этом блоке находится второй оператор ветвления, который проверяет число на четность. В зависимости от условия х%2== о выводится соответствующее сообщение. Если число делится на 2 без остатка, то оператор % вернет значение о, в противном случае - число 1. Обратите внимание на то, что оператор ветвления не содержит фигурных скобок: if(Х%2==0) printf("%d - четное число", х); else printf("%d - нечетное число", х); printf("\n"); В этом случае считается, что внутри блока содержится только одна инструкция, поэтому последняя инструкция к блоку else не относится. Она будет выполнена в любом случае, вне зависимости от условия. Для того чтобы это сделать наглядным, перед инструкциями, расположенными внутри блока, добавлено одинаковое коли­ чество пробелов. Если записать это следующим образом, то ничего не изменится: if(х%2==О) printf("%d - четное число", х); else printf("%d - нечетное число", х); printf("\n"); Однако в дальнейшем разбираться в таком коде будет неудобно. Поэтому перед инструкциями внутри блока всегда следует размещать одинаковый отступ. В каче­ стве отступа можно использовать пробелы или символ табуляции. При использова­ нии пробелов размер отступа равняется трем или четырем пробелам для блока пер-
144 Глава 4 вого уровня. Для вложенных блоков количество пробелов умножают на уровень вложенности: если для блока первого уровня вложенности использовались три пробела, то для блока второго уровня вложенности должны использоваться шесть пробелов, для третьего уровня - девять пробелов и т. д. В одной программе не следует применять и пробелы, и табуляцию в качестве отступа. Необходимо вы­ брать что-то одно и пользоваться этим во всей программе. При отсутствии пробелов или фигурных скобок чтение программы может вызывать затруднения. Даже если знаешь приоритет выполнения операторов, всегда закра­ дывается сомнение, и чтение программы останавливается. Например, к какому опе­ ратору if принадлежит блок else в этом примере? if(х>=О)if(х==О)у=О;elseу=1; Задумались? А зачем задумываться об этом, если можно сразу расставить фигур­ ные скобки? Ведь тогда никаких сомнений вообще не будет: if(х>=О){if(х== О)у=О;elseу=1;1 А если сделать так, то чтение и понимание программы станет мгновенным: if(х>=О){ if(х==О)у=О; else у= 1; Если блок состоит из нескольких инструкций, следует указать фигурные скобки. Существует несколько стилей размещения скобок в операторе i f: // Стиль 1 if (<Логическое выражение>) // Инструкции else { // Инструкции 1 // Стиль 2 if (<Логическое выражение>) // Инструкции else { // Инструкции // Стиль 3 if (<Логическое выражение>) // Инструкции else // Инструкции
Операторы и циклы 145 Многие программисты считают стиль 3 наиболее приемлемым, т. к. открывающая и wкрывающая скобки расположены друг под другом. На мой же взгляд образуются пшние пустые строки. Так как размеры экрана ограничены, при наличии пустой строки на экран помещается меньше кода, и приходится чаще пользоваться поло­ сой прокрутки. Если размещать инструкции с равным отступом, то блок кода выде­ .uется визуально, и следить за положением фигурных скобок просто излишне. Тем более что редактор кода позволяет подсветить парные скобки. Какой стиль исполь­ �вать, зависит от личного предпочтения программиста или правил оформления кода, принятых в определенной компании. Главное, чтобы стиль оформления внут­ ри одной программы бьm одинаковым. В этой книге мы будем пользоваться сти­ хм 1. В листинге 4.3 при вложении операторов мы воспользовались следующей схемой: if (<Условие 1>) // Инструкции else { if (<Условие 2>) // Инструкции else { // Инструкции Для того чтобы проверить несколько условий, эту схему можно изменить так: if (<Условие 1>) // <Блок 1> else if (<Условие 2>) { // <Блок 2> // • . . Фрагмент опущен ... else if (<Условие N>) { // <Блок N> else { // <Блок else> Если <Условие 1> истинно, то выполняется <Блок 1>, а и все остальные условия пропускаются. Если <Условие 1> ложно, то проверяется <Условие 2>. Если <Условие 2> истинно, то выполняется <Блок 2>, а все остальные условия пропускаются. Если <Условие 2> ложно, то точно так же проверяются остальные условия. Если все ус­ ловия ложны, то выполняется <Блок else>. В качестве примера определим, какое число от о до 2 ввел пользователь (листинг 4.4).
146 Листинг 4.4 . Проверка нескольких условий #include <stdio.h> #include <locale.h> int rnain(void) { setlocale(LC_ALL, "Russian_Russia.1251"); intх=О; printf("Bвeдитe число от О до 2: "); fflush (stdout); int status = scanf("%d", &х); if (status != 1) { puts("Bы ввели не число"); return О; else 1 if(х== О) puts("Bы ввели число О"); elseif(х==1) puts("Bы ввели число 1"); elseif(х==2) puts("Bы ввели число 2"); else { puts("Bы ввели другое число"); printf("x = %d\n", х); return О; 4.8. Оператор ?: Глава 4 Для проверки условия вместо оператора if можно использовать оператор ? : , кото­ рый имеет следующий формат: <Переменная> = <Логическое выражение>? <Выражение если Истина> <Выражение если Ложь>; Если логическое выражение возвращает значение истина, то выполняется выраже­ ние, расположенное после вопросительного знака. Если логическое выражение воз­ вращает значение ложь, то выполняется выражение, расположенное после двоето­ чия. Результат выполнения выражений становится результатом выполнения опера­ тора. Пример проверки числа на четность и вывода результата: intх=10; printf("%d%s", х, (х%2== О)?" - четноечисло" " - нечетное число");
Операторы и циклы 147 Обратите внимание на то, что в качестве операндов указываются именно выраже­ ния, а не инструкции. Кроме того, выражения обязательно должны возвращать ка­ кое-либо значение, причем одинакового типа. Так как оператор возвращает значе­ ние, его можно использовать внутри выражений: int х, у; х=О; у=30+10/(!х?1 х); //30+10/1 printf("%d\n", у); // 40 х= 2; у=30+10/(!х?1:х); //30+10/2 printf("%d\n", у); // 35 В качестве операнда можно указать функцию, которая возвращает значение: int funcl(int х) { printf("%d%s\n", х, " - четное число"); return О; int func2(int х) { printf("%d%s\n", х, " - нечетное число"); return О; //... Фрагмент опущен ... intх=10; (х % 2 == О) ? funcl(x) : func2(x); Как видно из примера, значение, возвращаемое оператором, можно проигнориро­ вать. 4.9. Оператор выбора switch Оператор выбора switch имеет следующий формат: switch (<Выражение>) { case <Константа 1>: <Инструкции> break; [ ... case <Константа N>: <Инструкции> break;] default: <Инструкции>] Вместо условия оператор swi tch принимает выражение. В зависимости от значения выражения выполняется один из блоков case, в котором указано это значение. Зна­ чением выражения должно быть целое число или символ. Если ни одно из значений не описано в блоках case, тq выполняется блок default (если он указан). Обратите
148 Гпава4 внимание на то, что значения в блоках case не могут иметь одинаковые константы.. Пример использования оператора switch приведен в листинге 4.5 . Листинг 4.5. Использование оператора switch #include <stdio.h> #include <locale.h> int rnain(void) { setlocale(LC_ALL, "Russian Russia.1251"); intos=О; printf("Kaкoй операционной системой вы nользуетесь?\n\ 1 - Windows XP\n\ 2 - Windows 8\n\ 3 - Windows 10\n\ 4 - Другая\n\n\ Введите число, соответствующее ответу: "); fflush(stdout); int ·status = scanf("%d", &os); if (status != 1) { puts("Bы ввели не число"); return О; else { switch (os) { case 1: puts("Bы выбрали - Windows ХР"); break; case 2: puts("Вы выбрали - Windows 8"); break; case 3: puts("Bы выбрали - Windows 10"); break; case 4: puts("Bы выбрали - Другая"); break; default: puts("Mы не смогли определить систему"); return О; Как видно из примера, в конце каждого блока case указан оператор break. Этот оператор позволяет досрочно выйти из оператора выбора switch. Если не указать
Операторы и циклы 149 оператор break, то будет выполняться следующий блок c·ase вне зависимости от указанного значения. В некоторых случаях это может быть полезным. Например, можно выполнить одни и те же инструкции при разных значениях, разместив инст­ рукции в конце диапазона значений. Пример: �har ch = 'Ь'; switch (ch) { case 'а': case 'Ь': case 'с': puts("a, Ь или с"); break; case 'd': рuts("Только d"); В операторе case можно указать одно из значений перечисления. Пример проверки выбранного значения перечисления приведен в листинге 4.6. �нr 4.�. Испоnьзо•ние оператора swi.tф с перечисnениями linclude <stdio.h> enurn Color { RED, BLUE, GREEN, BLACK }; int main(void) { enurn Color color GREEN; switch (color) { case RED: puts("RED"); break; case BLUE: puts("BLUE"); break; case GREEN: puts("GREEN"); break; case BLACK: puts("BLACK"); return О; 4.1 О. Цикл for Операторы циклов позволяют выполнить одни и те же инструкции многократно. Предположим, нужно вывести все числа от 1 до 100 по одному на строке. Обычным способом пришлось бы писать 100 строк кода:
150 printf("%d\n", 1); printf("%d\n", 2); // ... printf("%d\n", 100); С помощью циклов то же действие можно выполнить одной строкой кода: for (int i = 1; i <= 100; ++i) printf("%d\n", i); Глава4 Цикл for используется для выполнения инструкций определенное число раз. Цик..1 имеет следующий формат: for (<Начальное значение>; <Условие>; <Приращение>) { <Инструкции> Параметры имеют следующие значения: □ <Начальное значение> - присваивает переменной-счетчику начальное значение; □ <Условие> - содержит логическое выражение. Пока логическое выражение воз- вращает значение Истина, выполняются инструкции внутри цикла; □ <Приращение> - задает изменение переменной-счетчика на каждой итерации. Последовательность работы цикла for: 1. Переменной-счетчику присваивается начальное значение. 2. Проверяется условие: если оно истинно, выполняются инструкции внутри цик- ла, а в противном случае выполнение цикла завершается. 3. Переменная-счетчик изменяется на величину, указанную в <Приращение>. 4. Переход к пункту 2. Переменная-счетчик может быть объявлена как вне цикла for, так и в параметре <Начальное значение>. Если переменная объявлена в параметре, то она будет видна только внутри цикла. Кроме того, допускается объявить переменную вне цикла и сразу присвоить ей начальное значение. В этом случае параметр <Начальное значе­ ние> можно оставить пустым. Пример: int i; // Объявление вне цикла for(i=1;i<=20;++i) printf("%d\n", i); ) // Переменная i видна вне цикла printf("%d\n", i); // 21 // Объявление внутри цикла for(intj=1;j<=20;++j) printf("%d\n", j); 1 // Переменная j НЕ видна вне цикла int k = 1; // Инициализация вне цикла for(;k<=20;++k) printf("%d\n", k);
Операторы и циклы НА ЗАМЕТКУ 151 При использовании старых ком пиляторов все локальные переменные должны быть объявлены в самом начале функции. Цикл выполняется до тех пор, пока <Условие> не вернет ложь. Если это не произой­ дет, то цикл будет бесконечным. Логическое выражение, указанное в параметре <Условие>, вычисляется на каждой итерации. Поэтому если внутри логического вы­ ражения производятся какие-либо вычисления и значение не изменяется внутри цикла, то вычисление следует вынести в параметр <Начальное значение>. В этом случае вычисление указывается после присваивания значения переменной­ счетчику через запятую. Пример: for(inti=1,j =10+30;i<=j;++i){ printf("%d\n", i); Выражение, указанное в параметре <Приращение>, может не только увеличивать зна­ чение переменной-счетчика, но и уменьшать его. Кроме того, значение может из­ меняться на любую величину. Пример: // Выводим числа от 100 до 1 for(inti=100;i>О;--i){ printf("%d\n", i); // Выводим четные числа от 1 до 100 for(intj=2;j<=100;j+=2)[ printf("%d\n", j); Если переменная-счетчик изменяется внутри цикла, то выражение в параметре <Приращение> можно вообще не указывать: for(inti=1;i<=10;){ printf("%d\n", i); ++i; // Приращение Все параметры цикла for и инструкции внутри цикла являются необязательными. Хотя параметры можно не указывать, точки с запятой обязательно должны быть. Если все параметры не указаны, то цикл окажется бесконечным. Для того чтобы выйти из бесконечного цикла, следует использовать оператор break. Пример: int for i=1; (;;){ if(i<=10) printf("%d\n", ++i; else { break; // <Начальное значение> // Бесконечный: цикл // <Условие> i); // <Приращение> // Выходим из цикла
152 Глава4 4.11. Цикл while Выполнение инструкций в цикле while продолжается до тех пор, пока логическое выражение истинно. Цикл имеет следующий формат: <Начальное значение> while (<Условие>) <Инструкции> <Приращение> Последовательность работы цикла while: 1. Переменной-счетчику присваивается начальное значение. 2. Проверяется условие: если оно истинно, выполняются инструкции внутри цик- ла, иначе выполнение цикла завершается. 3. Переменная-счетчик изменяется на величину, указанную в <Приращение>. 4. Переход к пункту 2. Выведем все числа от 1 до 100, используя цикл while: inti=1; while (i <= 100) { printf("%d\n", i); ++i; ВНИМАНИЕ/ // <Начальное значение> // <Условие> // <Инструкции> // <Приращение> Если <Приращение> не указано, то цикл будет бесконечным. 4.12. Цикл do... while Выполнение инструкций в цикле do . . . while продолжается до тех пор, пока логиче­ ское выражение истинно. В отличие от цикла while условие проверяется не в нача­ ле цикла, а в конце. По этой причине инструкции внутри цикла do .. . while выпо,1- нятся минимум один раз. Цикл имеет следующий формат: <Начальное значение> do <Инструкции> <Приращение> while (<Условие>); Последовательность работы цикла do...while: 1. Переменной-счетчику присваивается начальное значение. 2. Выполняются инструкции внутри цикла. 3. Переменная-счетчик изменяется на величину, указанную в <Приращение>.
Операторы и циклы 153 -4. Проверяется условие: если оно истинно, происходит переход к пункту 2, а если нет - выполнение цикла завершается. Выведем все числа от 1 до 100, используя цикл do...while: .:.nti=1; do{ printf("%d\n", i); ++i; while (i <= 100); ВНИМАНИЕ! // <Начальное значение> // <Инструкции> // <Приращение> // <Условие> Если <Приращение> не указано, то цикл будет бесконечным. 4.13. Оператор continue: переход на следующую итерацию цикла Оператор continue позволяет перейти к следующей итерации цикла до завершения выполнения всех инструкций внуrри цикла. В качестве примера выведем все числа от 1 до 100, кроме чисел от 5 до 10 включительно: for(inti=1;i<=100;++i){ if(i>4&&i<11)continue; printf("%d\n", i); 4.14. Оператор break: прерывание цикла Оператор break позволяет прервать выполнение цикла досрочно. Для примера вы­ ведем все числа от 1 до 100 еще одним способом: inti=1; while (1) { if (i > 100) break; printf("%d\n", i); ++i; Здесь мы в условии указали значение 1. В этом случае инструкции внуrри цикла будут выполняться бесконечно. Однако использование оператора break прерывает его выполнение, как только 100 строк уже напечатано; 0БРАТИТЕ ВНИМАНИЕ Оператор break прерывает выполнение цикла, а не программы, т. е. далее будет вы­ полнена инструкция, следующая сразу за циклом. Бесконечный цикл совместно с оператором ьreak удобно использовать для получе­ ния неопределенного заранее количества данных от пользователя. В качестве при­ мера просуммируем неопределенное количество целых чисел (листинг 4.7).
154 Листин� 4.7. Суммирование неопределенного количества чисел #include <stdio.h> #include <locale.h> int main(void) { setlocale(LC_ALL, "Russian_Russia.i251"); intх=О,surn=О,status=О; рuts("Введите число О для получения результата"); for(;;){ printf("Введите число: "); fflush(stdout); fflush(stdin); status = scanf("%d", &х); if (status != 1) { puts("Bы ввели не число"); // Сбрасываем флаг ОПП1бки и очищаем буфер if (feof(stdin) 11 ferror(stdin)) clearerr(stdin); intch=О; while ((ch = getchar()) != '\n' && ch != EOF); continue; if (!х) break; surn+=х; printf("Cyммa чисел равна: %d\n", surn); return О; 4.15. Оператор goto Глава4 С помощью оператора безусловного перехода goto можно передать управление в любое место программы. Оператор имеет следующий формат: goto <Метка>; Значение в параметре <Метка> должно быть допустимым идентификатором. Место в программе, в которое передается управление, помечается одноименной меткой. после которой указывается двоеточие. В качестве примера имитируем цикл и выве­ дем числа от 1 до 100: inti=1; BLOCK_START: { if (i > 100) goto BLOCK_END; printf("%d\n", i); ++i; goto BLOCK_START; BLOCK_END: ;
Операторы и циклы 155 Как видно из примера, фигурные скобки можно использовать не только примени­ тельно к условным операторам, циклам, функциям, но и как отдельную конструк­ цию. Фрагмент кода, заключенный в фигурные скобки, называется блоком. Пере­ менные, объявленные внутри блока, видны только в пределах блока. СОВЕТ Следует избегать использования оператора goto, т. к. его применение делает про­ грамму слишком запутанной и может привести к неожиданным результатам.
ГЛАВА 5 Числа В языке С практически все элементарные типы данных (_вооl, char, int, float ■ douЫe) являются числовыми. Тип _вооl может содержать только значения l и :. которые соответствуют значениям истина и ложь. Тип char содержит код символа. а не сам символ. Поэтому значения этих типов можно использовать в одном выра­ жении вместе со значениями, имеющими типы int, Поаt и douЫe. Пример: Boolа=1; char ch = 'w'; // 119 printf("%d\n", а+ch + 10); // 130 Для хранения целых чисел предназначен тип int. Диапазон значений зависит от компилятора. Минимальный диапазон- от -32 768 до 32 767. На практике- ди;r пазон от-2 147 483 648 до 2 147 483 647(занимает4байта). С помощью ключевых слов short, long и long long можно указать точный размер типа int. При использовании этих ключевых слов тип int подразумевается по умолчанию, поэтому его можно не указывать. Тип short занимает 2 байта (диап;r зон от -32 768 до 32 767), тип long - 4 байта (диапазон от -2 147 483 Е�= до 2 147 483 647), а тип long long занимает 8 байт (диапазон значений от -9 223 372 036 854 775 808 ДО 9 223 372 036 854 775 807). По умолчанию целочисленные типы являются знаковыми (signed). Знак числа хра­ нится в старшем бите: о соответствует положительному числу, а 1 - отрицатель­ ному. С помощью ключевого слова unsigned можно указать, что число являете• только положительным. Тип unsigned char может содержать числа в диапазоне от : ДО 255, ТИП unsigned short - ОТ О ДО 65 535, ТИП unsigned long- ОТ О ,10 4 294 967 295, а тип unsigned long long- от О до 18 446 744 073 709 551 615. При преобразовании значения из типа signed в тип unsigned следует учитывать, что знаковый бит (если число отрицательное, то бит содержит значение 1) может стап. причиной очень больших чисел, т. к. старший бит у типа unsigned не содержит при­ знака знака: intх=-1; printf ("%u\n", (unsigned int)х); // 4294967295
157 Целочисленное значение задается в десятичной, восьмеричной или шестнадцате­ р,l'IНОй форме. Восьмеричные числа начинаются с нуля и содержат цифры от о до 7. Шестнадцатеричные числа начинаются с комбинации символов Ох (или ох) и могут содержать числа от о до 9 и буквы от А до F (регистр букв не имеет значения). Восьмеричные и шестнадцатеричные значения преобразуются в десятичное значе­ ние. Пример: :.Г..tх,у,z; :it = 119; ;;·десятичное значение у= 0167; // Восьмеричное значение z = OxFF; // Шестнадцатеричное значение ;=rintf("%d\n", х); // printf("%d\n", у); // printf("%d\n", z); // printf("%#o\n", у); // printf("%#X\n", z); // 119 119 255 0167 OXFF После целочисленной константы могут быть указаны следующие суффиксы: □ без суффикса - в зависимости от значения может иметь тип int, long или long long (компилятор выбирает минимальный тип из указанных, позволяющий хра­ нить значение): printf("%d\n", (int) sizeof(l)); //4 printf("%d\n", (int) sizeof(2147483647)); //4 printf("%d\n", (int) sizeof(2147483648)); //8 printf("%d\n", (int) sizeof(9223372036854775807)); // 8 // warning: integer cqnstant is so large that it is unsigned // printf("%d\n", (int) sizeof(9223372036854775808)); □ 1 (или 1)- в зависимости от значения может иметь тип long или long long (компилятор выбирает минимальный тип из указанных, позволяющий хранить значение). Пример указания значения: 101; □ 11 (или 11)- тип long long. Пример указания значения: 1011; □ u (или u)- в зависимости от значения может иметь тип unsigned int, unsigned long или unsigned long long (компилятор выбирает минимальный тип из ука­ занных, позволяющий хранить значение). Пример указания значения: 1ou; □ u1- в зависимости от значения может иметь тип unsigned long или unsigned long long. Пример указания значения: 1ou1; □ u11- тип unsigned long long. Пример указания значения: 1ou _ 11. Пример объявления переменных с указанием суффиксов: inti=10; unsigned int ui = 4294967295U; printf("%d\n", i); printf("%u\n", ui); printf("%d\n", (int) sizeof(lO)); printf("%d\n", (int) sizeof(4294967295U)); // 10 // 4294967295 //4 //4
158 printf("%d\n", (int) sizeof(4294967295)); // 8 long 1 = 21474836471; unsigned long ul = 4294967295U1; printf("%ld\n", 1); // 2147483647 printf("%lu\n", ul); // 4294967295 printf("%d\n", (int) sizeof(21474836471)); // 4 printf("%d\n", (int) sizeof(4294967295U1)); // 4 printf("%d\n", (int) sizeof(42949672951)); // 8 long long 11 = 922337203685477580711; unsigned long long ull = 18446744073709551615U1 1; printf("%I64d\n", 11); // 9223372036854775807 printf("%I64u\n", ull); // 18446744073709551615 Глава 5 Для хранения вещественных чисел предназначены типы float и douЫe. Вещест­ венное число может содержать точку и (или) экспоненту, начинающуюся с буквы Е (регистр не имеет значения): float х, у; douЫe z, k; х = 20.0F; у= 12.le5F; z = .123; k = 47.Е-5; printf("%.5f\n", х); printf("%e\n", у); printf("%.2f\n", z); printf("%g\n", k); // 20.00000 // 1.210000е+006 // 0.12 // 0.00047 По умолчанию вещественные константы имеют тип douЫe. Если необходимо изме­ нить тип на другой, то после числа указываются следующие суффиксы: □ F (или f) - тип float. Пример указания значения: 12.3f; □ 1 (или 1)- тип long douЬle. Значение должно содержать точку и (или) экспо­ ненту, иначе тип будет long int. Пример указания значения: 12.31. Пример указания суффиксов: printf("%d\n", (int) sizeof(20.0F)); printf("%d\n", (int) sizeof(20.0)); printf("%d\n", (int) sizeof(20.01)); //4 //8 // 16 При выполнении операций над вещественными числами следует учитывать огра­ ничения точности вычислений. Например, результат следующей инструкции может показаться странным: printf("%e", 0.3 - 0.1 - 0.1 - 0 .1); // -2.775558е-017 Ожидаемым был бы результат о. о, но, как видно из примера, мы получили совсем другой результат (-2 .775558e-Ol 7). Если в выражении используются числа, имеющие разный тип данных, то тип ре­ зультата выражения будет соответствовать наиболее сложному типу. Например,
Числа 159 если производится сложение переменной, имеющей тип int, с переменной, имею­ щей тип douЬle, то целое число будет автоматически преобразовано в веществен­ ное. Результатом этого выражения будет значение, имеющее тип douЫe. Однако если результат выражения присваивается переменной типа int, то тип douЬle будет преобразован в тип int (при этом компилятор выведет предупреждающее сообще­ ние о возможной потере данных): intх=10,у=О; douЬle z = 12 .5; у=х+z; // warning: conversion from 'douЬle' to 'int' may change value . printf("%d\n", у); // 22 (тип int) printf("%.2f\n", х + z); // 22.50 (тип douЬle) Для того чтобы компилятор не выводил предупреждающего сообщения, следует выполнить приведение типов: у= (int) (х + z); 5.1. Математические константы В заголовочном файле math.h определены следующие макросы, содержащие значе­ ния математических констант: □ м_PI -число пи (л). Определение макроса: #define М PI 3.14159265358979323846 □ м_РI_2 -значение выражения pi/2. Определение макроса: #define М PI 2 1.57079632679489661923 □ м_PI_4 -значение выражения pi/4. Определение макроса: #define М PI 4 О.78539816339744830962 □ м_l _PI -значение выражения 1/pi. Определение макроса: #define М 1 PI О.31830988618379067154 □ м_2_РI-значение выражения 2/pi. Определение макроса: #define М 2 PI О.63661977236758134308 □ м_Е - значение константы е. Определение макроса: #define М Е 2.7182818284590452354 □ м_LOG2E - значение выражения log2(е J • Определение макроса: #define М LOG2E 1.4426950408889634074 □ м_LОGlОЕ-значение выражения logl0 (е). Определение макроса: #define М LOGl0E О.43429448190325182765 □ м_LN2-значение выражения ln 12). Определение макроса: #define М LN2 О.69314718055994530942
160 □ м_LNl0 - значение выражения ln (10). Определение макроса: #define М LNl0 2.30258509299404568402 □ M_2_SQRTPI - значение выражения 2/sqrt(pi). Определение макроса: #define M_2_SQRTPI 1.12837916709551257390 □ M_SQRT2 - значение выражения sqrt(2). Определение макроса: #define M_SQRT2 1.41421356237309504880 □ м_sQRT1_2 - значение выражения 1/sqrt (2). Определение макроса: #define M_SQRT1_2 0.70710678118654752440 Глава 5 Прежде чем использовать константы, необходимо перед подключением файла math.h определить макрос с названием _usE_МАТН_DEFINES. В Visual С без этого макроса константы недоступны. В MinGW определять макрос не нужно. Пример вывода значения констант приведен в листинге 5. 1 . Листинг 5.1. Вывод значений математических констант #include <stdio.h> #define USE МАТН DEFINES - - - #include <rnath.h> int main(void) { printf("%.5f\n", М PI); // 3.14159 printf("%.5f\n", М Е); // 2.71828 return О; 5.2. Основные функции для работы с числами Перечислим основные функции для работы с числами. □ abs () - возвращает абсолютное значение. Прототипы функции: #include <stdlib.h> int abs(int х); long labs(long х); lung long llabs(long long х); int64 abs64( int64 х); #include <rnath.h> int abs(int х) ; long labs(long х); float fabsf(float х); douЫe fabs(douЫe х); long douЫe fabsl(long douЫe х);
Пример: printf("%d\n", abs(-1)); //1 □ irnaxabs( ) - возвращает абсолютное значение. Прототип функции: #include <inttypes.h> intrnax_t irnaxabs(intrnax_t j); typedef long long intrnax t; Пример: printf("%I64d\n", irnaxabs(-5)); // 5 CJ pow() - возводит число х в степень у. Прототипы функции: #include <rnath.h> float powf(float х, float у); douЬle pow(douЬle х, douЬle у); long douЬle powl(long douЬle х, long douЬle у); Примеры: printf("%.lf\n", pow(l0.0, 2)); // 100.О printf("%.lf\n", роw(З.О, 3.0)); // 27.0 □ sqrt() - квадратный корень. Прототипы функции: #include <rnath.h> float sqrtf(float х); douЬle sqrt(douЬle х); long douЬle sqrtl(long douЬle х); Примеры использования функции: printf("%.lf\n", sqrt(l00.0)); printf("%.lf\n", sqrt(25.0)); // 10.О // 5.0 CJ ехр 1) - экспонента. Прототипы функции: #include <rnath.h> float expf(float х); douЬle exp(douЬle х); long douЬle expl(long douЬle х); □ log 1) - натуральный логарифм. Прототипы функции: #include <rnath.h> float logf(float х); douЬle log(douЬle х); long douЬle logl(long douЬle х); □ logl0 1) - десятичный логарифм. Прототипы функции: #include <rnath.h> float logl0f(float х); douЬle logl0(douЬle х); long douЬle logl0l(long douЬle х); 161
162 □ frnod() - остаток от деления. Прототипы функции: #include <rnath.h> float frnodf(float х, float у); douЫe frnod(douЫe х, douЫe у); long douЫe frnodl(long douЬle х, long douЬle у); Примеры использования функции: printf("%.lf\n", frnod(lOO.O, 9.0)); printf("%.lf\n", frnod(lOO.O, 10.0)); // 1.0 // о.о Глава 5 □ rnodf() - разделяет вещественное числох на целую и дробную части. В качестве значения функция возвращает дробную часть. Целая часть сохраняется в пере­ менной, адрес которой передан во втором параметре. Прототипы функции: #include <rnath.h> float rnodff(float х, float *ру); douЬle rnodf(douЬle х, douЫe *ру); long douЬle rnodfl(long douЫe х, long douЬle *ру); Пример использования функции: douЬle х = О.О; printf("%.lf\n", rnodf(12.5, &х)); printf("%.lf\n", х); // 0.5 // 12.0 □ div() - возвращает структуру из двух полей: quot (результат целочисленного делениях / у), rern (остаток от делениях % у). Прототипы функции: #include <stdlib.h> div_t div(int х, int у); ldiv_t ldiv(long х, long у); lldiv_t lldiv(long long х, long long у); Объявления структур: typedef struct div t int quot; int rern; div t; typedef struct ldiv t long quot; long rern; ldiv t; typedef struct long long quot; long long rern; l ' ldiv t; Пример использования функции: div_t d; d = div(lЗ, 2);
Числа 163 printf("%d\n", d.quot); · // 6 == 13 /2 printf("%d\n", d.rern); //1==13 %2 □ imaxdiv( J - возвращает структуру из двух полей: quot (результат целочислен­ ного деления х / у), rern (остаток от деления х % у). Прототип функции: #include <inttypes.h> imaxdiv_t imaxdiv(intmax_t х, intmax_t у); typedef long long· intmax_t; Объявление структуры imaxdiv_t: typedef struct { intmax_t quot; intmax_t rem; imaxdiv t; Пример: imaxdiv t d = imaxdiv(13, 2); printf("%I64d\n", d.quot); //6 printf("%I64d\n", d.rern); //1 □ _max( J - максимальное значение: #include <stdlib.h> 13 /2 13 %2 #define _max(a,b) (( (а) > (Ь)) ? (а) (Ь)) Пример: printf("%d\n", _max(l0, 3)); //10 □ fmax( J - максимальное значение. Прототипы функции: #include <math.h> float fmaxf(float а, float Ь); douЬle fmax(douЬle а, douЬle Ь); long douЬle fmaxl(long douЬle а, long douЬle Ь); Пример: printf("%.0f\n", fmax(l0, 3)); //10 □ _min ( J - минимальное значение: #include <stdlib.h> #define _min(а,Ь) ( ((а) < (Ь)) ? (а) (Ь)) Пример: printf("%d\n", _min(l0, 3)); //3 □ fmin( J - минимальное значение. Прототипы функции: #include <math.h> float fminf(float а, float Ь); dot./Ьle fmin(douЫe а, douЬle Ь); long douЬle fminl(long douЬle а, long douЬle Ь);
164 Глава5 Пример: printf("%.0f\n", fmin(l0, 3)); // 3 5.3. Округление чисел Для округления чисел предназначены следующие функции. □ ceil() - возвращает значение, округленное до ближайшего большего значенИI. Прототипы функции: #include <math.h> float ceilf(float х); douЬle ceil(douЬle х); long douЫe ceill(long douЬle х); Примеры использования функции: printf("%.lf\n", ceil(l.49)); printf("%.lf\n", ceil(l.5)); printf("%.lf\n", ceil(l.51)); // 2.0 // 2.0 // 2.0 □ floor() - значение, округленное до ближайшего меньшего значения. Протот. пы функции: #include <math.h> float floorf(float х); douЬle floor(douЬle х); long douЬle floorl(l�ng douЬle х); Примеры использования функции: printf("%.lf\n", floor(l.49)); printf("%.lf\n", floor(l.5)); printf("%.lf\n", floor(l.51)); // 1.0 // 1.0 // 1.0 □ round() - возвращает число, округленное до ближайшего меньшего целого д.8 чисел с дробной частью меньше о. 5, или значение, округленное до ближайшеn большего целого для чисел с дробной частью больше или равной о. 5. Протот. пы функции: #include <math.h> float roundf(float х); douЫe round(douЫe х); long douЬle roundl(long douЬle х); Примеры использования функции: printf("%.lf\n", round(1.49)); // 1.О printf("%.lf\n", round(l.5)); // 2.0 printf("%.lf\n", round(l.51)); // 2.0
Числа 165 5.4. Тригонометрические функции В языке С доступны следующие тригонометрические функции. □ sin(), cos(), tan () - стандартные тригонометрические функции (синус, коси­ нус, тангенс). Угол задается в радианах. Прототипы функций: #include <math.h> float sinf(float х); douЫe sin(douЬle х); long douЬle sinl(long douЬle х); float cosf(float х); douЬle cos(dQuЫe х); long douЬle cosl(long douЬle х); float tanf(float х); douЬle tan(douЬle х); long douЬle tanl(long douЬle х); Пример: douЬle degrees = 90.0; // Перевод градусов в радианы douЬle'radians = degrees * (M_PI. / 180.0); // Перевод радианов в градусы printf("%.lf\n", radians * (180.0 / M_PI)); // 90.0 printf("%.lf\n", sin(radians)); // 1.0 □ asin(), acos( J, atan() - обратные тригонометрические функции (арксинус, арк­ косинус, аркuнrенс). Значение возвращается в радианах. Прототипы функций: #include <math.h> float asinf(float х); douЬle asin(douЬle х); long douЬle asinl(long douЬle х); float acosf(float х); douЬle acos(douЬle х); long douЬle acosl(long douЬle х); float atanf(float х); douЬle atan(douЬle х); long douЬle atanl(long douЬle х); 5.5. Преобразование строки в число Дпя преобразования строки в число используются следующие функции. □ atoi() и _аtoi_1() - преобразуют С-строку в число, имеющее тип int. Прото­ типы функций: #include <stdlib.h> int atoi(const char *str); int _atoi_l(const char *str, locale t locale);
166 Глввв5 Считывание символов производится, пока они соответствуют цифрам. Иным. словами, в строке могут содержаться не только цифры. Пробельные симво.лw в начале строки игнорируются. Функция _аtoi _1() позволяет дополнительNo задать локаль. Примеры преобразования: printf("%d\n", atoi("25")); // 25 printf("%d\n", atoi("2.5")); //2 printf("%d\n", atoi("5str")); //5 printf("%d\n", atoi("5sl0")); //5 printf("%d\n", atoi(" \t\n 25")); // 25 printf("%d\n", atoi("-25")); // -25 printf("%d\n", atoi("str")); //о Если значение выходит за диапазон значений типа int, то в Visual С возвращает• ся максимальное или минимальное значение типа int и переменной errno при­ сваивается значение ERANGE: printf("%d\n", atoi("2147483649")); // 2147483647 printf("%d\n", errno); // 34 // #include <math.h> или #include <errno.h> if (errno == ERANGE) { puts("Bыxoд за диапазон значений"); В MinGW будет усечение значения: printf("%d\n", atoi("2147483649")); // -2147483647 printf("%d\n", errno); //О □ atol(1 и _atol_1(1 - преобразуют С-строку в число, имеющее тип long. Про� типы функций: #include <stdlib.h> long atol(const char *str); long _atol_l(const char *str, _locale_t locale); Функция _atol_l(1 позволяет дополнительно задать локаль. Примеры преобра­ зования: printf("%ld\n", atol("25")); // 25 printf("%ld\n", atol(" \n -25")); // -25 printf("%ld\n", atol("2.5")); //2 printf("%ld\n", atol("5str")); //5 printf("%ld\n", atol("5sl0")); //5 printf("%ld\n", atol("str")); //о Если значение выходит за диапазон значений типа long, то в Visual С возвраща­ ется максимальное или минимальное значение типа long и переменной errno присваивается значение ERANGE: printf("%ld\n", atol("2147483649")); // 2147483647 printf("%d\n", errno); // 34
// #include <math.h> или #include <errno.h> if (errno == ERANGE) { рuts("Быход за диапазон значений"); В MinGW будет усечение значения: printf("%ld\n", atol("2147483649")); // -2147483647 printf("%d\n", errno); //О 167 [J atoll() - преобразует С-строку в число, имеющее тип long long. Прототип функции: iinclude <stdlib.h> long long atoll(const char *str); Пример преобразования: printf("%I64d\n", atoll("9223372036854775807")); // 9223372036854775807 Если значение выходит за диапазон значений типа long long, то возвращается максимальное или минимальное значение типа long long и переменной errno присваивается значение ERANGE: printf("%I64d\n", atoll("9223372036854775809"J); // 9223372036854775807 printf("%d\n", errno); // 34 // #include <math.h> или #include <errno.h> if (errno == ERANGE) { puts( "ВЫход за диапазон значений"); CJ _atoi64() - преобразует С-строку в число, имеющее тип функции: #include <stdlib .h> int64 _atoi64(const char *str); Пример преобразования: printf("%I64d\n", _atoi64("9223372036854775807")); // 9223372036854775807 int64. Прототип Если значение выходит за диапазон значений типа _int64, то возвращается максимальное или минимальное значение типа _int64 и переменной errno при­ сваиваетсJ1 значение ERANGE: printf("%I64d\n", atoi64("9223372036854775809")); // 9223372036854775807 printf("%d\n", errno); // 34 // #include <math.h> или #include <errno .h> if (errno == ERANGE) { рuts("Быход за диапазон значений");
168 Глава 5 □ strtol() и strtoul() - преобразуют С-строку в число, имеющее типы long и unsigned long соответственно. Функции _strtol_l() и _strtoul_1() позволяют дополнительно задать локаль. Прототипы функций: #include <stdlib.h> long strtol(const char *str, char **endPtr, int radix); long _strtol_l(const char *str, char **endPtr, int radix, _ locale_t locale); unsigned long strtoul(const char *str, char **endPtr, int radix); unsigned long _strtoul_l(const char *str, char **endPtr, int radix, _locale_t locale); Пробельные символы в начале строки игнорируются. Считывание символов за­ канчивается на символе, не являющемся записью числа. Указатель на этот сим­ вол доступен через параметр endPtr, если он не равен NULL. В параметре radix можно указать систему счисления (число от 2 до 36). Если в параметре radix задано число о, то система счисления определяется автоматически. Пример пре­ образования: char *р = NULL; printf("%ld\n", strtol("25", NULL, О)); // 25 printf("%ld\n", strtol("025", NULL, О)); // 21 printf("%ld\n", strtol("Ox25", NULL, О)); // 37 printf("%ld\n", strtol("lll", NULL, 2)); // 7 printf("%ld\n", strtol("025", NULL, 8)); // 21 printf("%ld\n", strtol("Ox25", NULL, 16)); // 37 printf("%ld\n", strtol("5s10", &р, О)); //5 printf("%s\n", р); // slO Если в строке нет числа, то возвращается число о. Если число выходит за диапа­ зон значений для типа, то значение будет соответствовать минимальноМ) (LONG MIN или о) или максимальному (LONG МАХ или ULONG МАХ) значению дл1 - - - типа, а переменной errno присваивается значение ERANGE. Пример: printf("%ld\n", strtol("str", NULL, О)); //О printf("%ld\n", strtol("2147483649", NULL, О)); // 2147483647 (соответствует LONG_МAX) printf("%d\n", errno); // 34 // #include <rnath.h> или #include <errno.h> if (errno == ERANGE) { puts("Bыxoд за диапазон значений"); □ strtoll() и strtoull() - преобразуют С-строку в число, имеющее типы lor.::; long и unsigned long long соответственно. Прототипы функций: #include <stdlib.h> long long strtoll(const char *str, char **endPtr, int radix); unsigned long long strtoull(const char *str, char **endPtr, int radix);
169 Пробельные символы в начале строки игнорируются. Считывание символов за­ · канчивается на символе, не являющемся записью числа. Указатель на этот сим­ вол доступен через параметр endPtr, если он не равен NULL. В параметре radix можно указать систему счисления (число от 2 до 36). Если в параметре radix задано число о, то система счисления определяется автоматически. Пример пре­ образования: char *р = NULL; printf("%I64d\n", strtoll("25", NULL, О)); // 25 printf("%I64d\n", strtoll("025", NULL, О)); // 21 printf("%I64d\n", strtoll("Ox25", NULL, О)); // 37 printf("%I64d\n", strtoll("lll", NULL, 2)); // 7 printf("%I64d\n", strtoll("025", NULL, 8)); // 21 printf("%I64d\n", strtoll("Ox25", NULL, 16)); // 37 printf("%I64d\n", strtoll("5s10", &р, О)); //5 printf("%s\n", р); // s10 Если в строке нет числа, то возвращается о. Если число выходит за диапазон значений для типа, то значение будет соответствовать минимальному (�LONG_МIN или о) или максимальному (LLONG_МAX или ULLONG_МAX) значению для типа, а пе­ ременной errno присваивается значение ERANGE. Пример: printf("%I64d\n", strtoll("str", NULL, О)); // О printf("%I64d\n", strtoll("9223372036854775809", NULL, О)); // 9223372036854775807 (соответствует LLONG_МAX). printf("%d\n", errno); // 34 // #include <math.h> или #include <errno.h> if (errno == ERANGE) { рuts("ВЫХод за диапазон значений"); □ strtoi64() и _strtoui64() - преобразуют С-строку в число, имеющее типы _iпt64 и unsigned _int64 соответственно. Прототипы функций: #include <stdlib.h> int64 _strtoi64(const char *str, char **endPtr, int radix); unsigned int64 _strtoui64(const char *str, char **endPtr, int radix); Пробельные символы в начале строки игнорируются. Считывание символов за­ канчивается на символе не являющемся записью числа. Указатель на этот сим­ вол доступен через параметр endPtr, если он не равен NULL. В параметре radix можно указать систему счисления (число от 2 до 36). Если в параметре radix задано число о, то система счисления определяется автоматически. Пример пре­ образования: char *р = NULL; printf("%I64d\n", strtoi64("25", NULL, О)); // 25 printf("%I64u\n", strtoui64("25;', NULL, 0)); // 25 - printf("%I64d\n", strtoi64("025", NULL, О)); // 21 - printf("%I64d\n", strtoi64("0x25", NULL, О)); // 37 -
170 Глава5 printf("%I64d\n", - strtoi64("111", NULL, 2)); // 7 printf("%I64d\n", - strtoi64("025", NULL, 8)); // 21 printf("%I64d\n", strtoi64("0x25", NULL, 16)); // 37 - printf("%I64d\n", - strtoi64("5sl0", &р, О)); //5 printf("%s\n", р); // sl0 Если в строке нет числа, то возвращается число о. Если число выходит за диапа­ зон значений для типа, то значение будет соответствовать минимальному или максимальному значению для типа, а переменной errno присваивается значение ERANGE. Пример: printf("%I64d\n", _strtoi64("str", NULL, О)); //О printf("%I64d\n", _strtoi64("9223372036854775809", NULL, О)); // 9223372036854775807 printf("%d\n", errno); // 34 // #include <math.h> иm1 #include <errno .h> if (errno == ERANGE) { puts("Bыxoд за диапазон значений"); □ strtoimax() и strtoumax() - преобразуют С-строку в число, имеющее типы intmax_t и uintmax_t соответственно. Прототипы функций: #include <inttypes.h> intmax_t strtoimax(const char *str, char **endPtr, int radix); typedef long long intmax_t; uintmax_t strtoumax(const char *str, char **endPtr, int radix); typedef unsigned long long uintmax_t; Пробельные символы в начале строки игнорируются. Считывание символов за­ канчивается на символе, не являющемся записью числа. Указатель на этот сим­ вол доступен через параметр endPtr, если он не равен NULL. В параметре radix можно указать систему счисления (число от 2 до 36). Если в параметре radix задано число о, то система счисления определяется автоматически. Пример пре­ образования: char *р = NULL; printf("%I64u\n", strtoumax("25", NULL, О)); // 25 printf("%I64d\n", strtoimax("25", NULL, 0)); // 25 printf("%I64d\n", strtoimax("025", NULL, О)); // 21 printf("%I64d\n", strtoimax("0x25", NULL, О)); // 37 printf("%I64d\n", strtoimax("lll", NULL, 2)); // 7 printf("%I64d\n", strtoimax("025", NULL, 8)); // 21 printf("%I64d\n", strtoimax("0x25", NULL, 16)); // 37 printf("%I64d\n", strtoimax("5sl0", &р, О)); //5 printf("%s\n", р); // sl0 Если в строке нет числа, то возвращается о. Если число выходит за диапазон значений для типа, то значение будет соответствовать минимальному {INТМAX_MIN или о) или максимальному (INТМАХ_МАХ или UINTМAX_МAX) значению для типа, а переменной errno присваивается значение ERANGE. Пример:
// #include <stdint.h> printf("%I64d\n", INТМAX_MIN); printf("%I64d\n", INТМАХ_МАХ); // -9223372036854775808 // 9223372036854775807 printf("%I64u\n", UINТМАХ_МАХ); // 18446744073709551615 printf("%I64d\n", strtoirnax("str", NULL, О)); // О printf("%I64d\n", strtoirnax("9223372036854775809", NULL, О)); // 9223372036854775807 (соответствует INТМАХ_МАХ) printf("%d\n", errno); // 34 // #include <rnath.h> или #include <errno.h> if (errno == ERANGE) ( рuts("Выход за диапазон значений"); 171 а atof() - преобразует С-строку в вещественное число, имеющее тип douЬle. Функция _atof_l() позволяет дополнительно задать локаль (от локали за:висит десятичный разделитель вещественных чисел). Прототипы функций: #include <stdlib.h> /* или #include douЬle atof(const char *str); douЬle _atof_l(const char *string, Примеры преобразования: printf("%. 2f\n", atof("25")); printf("%.2f\n", atof("2.5")); printf("%.2f\n", atof("5.lstr")); printf("%.2f\n", atof("5s10")); printf("%.2f\n", atof("str")); // #include <locale.h> <rnath.h> locale t // // // // // */ locale); 25.00 2.50 5.10 5.00 0.00 locale t locale _ cr eate_locale(LC_NUМERIC, "dutch"); printf("%.2f\n", atof("2,5")); // 2.00 printf("%.2f\n", atof 1("2,5", locale)); // 2.50 _ free_locale(locale); □ strtod( J - преобразует С-строку в вещественное число, имеющее тип douЬle. Функция _strtod_1( J позволяет дополнительно задать локаль. Прототипы функ­ ций: #include <stdlib.h> douЬle strtod(const char *str, char **endPtr); douЬle _strtod_l(const char *str, char **endPtr, _locale_t locale); Пробельные символы в начале строки игнорируются.. Считывание символов заканчивается на символе, не являющемся записью вещественного числа. Указа­ тель на этот символ дос,упен через параметр endPtr, если он не равен NULL. Пример преобразования: char *р = NULL; printf("%.2f\n", strtod(" \t\n 25", NULL)); // 25.00 printf("%.2f\n", strtod("2.5", NULL)); // 2.50
172 printf("%.2f\n", strtod("5.lstr", NULL)); printf("%e\n", strtod("14.5e5s10", &р)); printf("%s", р); // 5.10 // 1.450000е+ОО6 // sl0 Глава5 Обратите внимание: от настроек локали зависит десятичный разделитель веще-­ ственных чисел: setlocale(LC_ALL, "Russian_Russia.1251"); printf("%.2f\n", strtod("2.5", NULL)); printf("%.2f\n", strtod("2,5", NULL)); // 2,00 // 2,50 Если в строке нет числа, то возвращается о. Если число выходит за диапазон значений для типа, то возвращается значение -HUGE_VAL или HUGE_VAL, а переме� ной errno присваивается значение ERANGE; □ strtof 11 - преобразует С-строку в вещественное число, имеющее тип floa:.. Прототип функции: #include <stdlib.h> float strtof(const char *str, char **endPtr); Пробельные символы в начале строки игнорируются. Считывание символов за­ канчивается на символе, не являющемся записью вещественного числа. Указа­ тель на этот символ доступен через параметр endPtr, если он не равен NUL:.. Пример преобразования (обратите внимание: от настроек локали зависит дес1- тичный разделитель вещественных чисел): setlocale (LC _ALL, "С"); char *р = NULL; printf("%.2f\n", strtof(" \t\n 25", NULL)); // 25.00 printf("%.2f\n", strtof("2.5", NULL)); // 2.50 printf("%.2f\n", strtof("5.lstr", NULL)); printf("%e\n", strtof("14.5e5s10", &р)); printf("%s\n", р); setlocale(LC_ALL, "Russian_Russia.1251"); printf("%.2f\n", strtof("2.5", NULL)); printf("%.2f\n", strtof("2,5", NULL)); // 5.10 // 1.450000е+ОО6 // sl0 // 2,00 // 2,50 Если в строке нет числа, то возвращается о. Если число выходит за диапазон значений для типа, то возвращается значение -HUGE_VALF или HUGE_VALF, а пере­ менной errno присваивается значение ERANGE; □ strtold(1 - преобразует С-строку в вещественное число, имеющее тип lon;­ douЫe. Прототип функции: #include <stdlib.h> long douЬle strtold(const char *str, char **endPtr); Пробельные символы в начале строки игнорируются. Считывание символов заканчивается на символе, не являющемся записью вещественного числа. Указа­ тель на этот символ доступен через параметр endPtr, если он не равен NULL. Пример преобразования:
Числа char *р = NULL; _ mingw_printf("%.2Lf\n", strtold(" \t\n 25", NULL)); // 25.00 _rningw_printf("%.2Lf\n", strtold("2.5", NULL)); // 2.50 _mingw_printf("%.2Lf\n", strtold("5.lstr", NULL)); // 5.10 _ rningw_printf("%Le\n", strtold("14.5e5s10", &р)); // 1.450000е+006 printf("%s", р); // s10 173 Если в строке нет числа, то возвращается о. Если число выходит за диапазон значений для типа, то возвращается значение -HUGE_VALL или НUGE_VALL, а пере­ менной errno присваивается значение ERANGE. Все рассмотренные в этом разделе функции позволяют преобразовать всю С-строку • число. Если необходимо преобразовать отдельный символ (представляющий чис­ .ю) в соответствующее целое число от о до 9, то можно воспользоваться следую­ щим кодом: char ch = '8'; intn=ch-'О'; printf("%d\n", n); // Эквивалентно int n = 56 - 48; //8 В переменной типа char хранится код символа, а не сам символ. Символы от 'о' до '9' в кодировке ASCII имеют коды от 48 до 57 с . оответственно. Следовательно, что­ бы получить целое число от о до 9, достаточно вычесть из текущего символа код символа 'о'. В качестве еще одного примера сохраним все цифры, встречающиеся в С-строке, в целочисленном массиве (листинг 5.2). linclude <stdio.h> ldefine ARR SIZE 10 int main(void) { char str[) = "аЬс0123456789"; int arr(ARR_SIZE) = {О}, index � О; for (char *р = str; •р; ++р) { if(*р>='0'&&•р<= '9') arr[index] = *р - '0'; ++index; if (index >= ARR_SIZE) break; // Перебираем строку for (int i = О; i < ARR_SIZE; ++i) { // Выводим значения printf("%d\n", arr[i)); return О; Для преобразования строки в число можно таюке воспользоваться .Функциями sscanf() И _snscanf(). Прототипы функций:
174 Глава5 #include <stdio.h> int sscanf(const char *str, const char *forrnat, ...); int _snscanf(const char *str, size_t rnaxCount, const char *forrnat, ... ); В параметре str указывается С-строка, содержащая число, в параметре mахсоunt­ максимальное количество просматриваемых символов в строке, а в параметре forrnat - строка специального формата, внутри которой задаются спецификаторы.. аналогичные применяемым в функции printf() (см. разд. 2 .8), а также некоторые дополнительные спецификаторы. В последующих параметрах передаются адреса переменных. Функции возвращают количество произведенных присваиваний.. Пример получения двух целых чисел: // #include <locale.h> setlocale(LC_ALL, "Russian_Russia.1251"); char str(] = "20 30 str"; intn=О,k=О; int count = sscanf(str, "%d%d", &n, &k); // int count = _snscanf(str, strlen(str), "%d%d", &n, &k); if (count == 2) { printf("n = %d\n", n); // n = 20 printf("k = %d\n", k); // k = 30 else puts("He удалось преобразовать"); Функция sscanf() не производит никакой проверки длины строки, что может при­ вести к ошибке. Обязательно указывайте ширину при использовании спецификато­ ра %s (например, %255s). Кроме того, следует учитывать, что функция не проверяет возможность выхода значения за диапазон значений для типа, что может привести к неправильному результату. Функция никак об этом вас не предупредит, поэтому лучше использовать функции, рассмотренные в начале этого раздела. 5.6. Преобразование числа в строку Преобразовать целое число в С-строку позволяют следующие функции: □ _itoa() и _itoa_s() - преобразуют число типа int в С-строку. Прототипы функций: #include <stdlib.h> char *_itoa(int val, char *dest, int radix); errno_t _ itoa_s(int val, char *dest, size_t size, int radix); В параметре val указывается число, в параметре dest - указатель на символь­ ный массив, в который будет записан результат, в параметре size - максималь­ ный размер символьного массива dest, а в параметре radix - система счисления (2, в, 10 или 16). Пример использования функции _itoa_s(): char str[l00] = {О}; intх=255;
itoa_s(x, str, 100, 10); printf("%s\n", str); // 255 Пример вывода двоичного, восьмеричного и шестнадцатеричного значений: char str[l00J = {О}; // Двоичное значение printf("%s\n", _itoa(l00, str, 2)); // 1100100 // Восьмеричное значе ни е printf("%s\n", itoa(l0, str, 8)); // 12 // Шестнадцатеричное значение printf("%s\n", _itoa(255, str, 16)); // ff 175 □ _ltoa() и _ltoa_s() - преобразуют число типа long в С-строку. Прототипы функций: #include <stdlib.h> char *_ltoa(long val, char *dest, int radix); errno_t _ltoa_s(long val, char *dest, size t size, int radix); Пример: char str[l00J {01; long х = 255; _ ltoa_s(x, str, 100, 10); printf("%s\n", str); printf("%s\n", _ltoa(x, str, 16)); // 255 // ff □ _ul toa () и _ul toa_ s () - преобразуют число типа unsigned long в С-строку. Прототипы функций: #include <stdlib.h> char * ultoa(unsigned long val, char *dest, int radix); errno_t _ ultoa_s(unsigned long val, char *dest, size t size, int radix); Пример: char str[l00J = {О}; unsigned long х = 255; _ ultoa_s(x, str, 100, 10); printf("%s\n", str); // 255 printf("%s\n", _ultoa(x, str, 16)); // ff □ _i64toa() и _i64toa_s() - преобразуют число типа _int64 в С-строку. Прото­ типы функций: #include <stdlib.h> char *_i64toa( int64 val, char *dest, int radix); errno_t _i64toa_s(_int64 val, char *dest, size t size, int radix); Пример: char str[l00] = {О); _int64 х = 255; i64toa_s(x, str, 100, 10);
176 Глава 5 printf("%s\n", str); // 255 printf("%s\n", i64toa(x, str, 16)); // ff □ _ui64toa() и _ui64toa_s() - преобразуют число типа unsigned _int64 в С-сТр<r ку. Прототипы функций: #include <stdlib.h> char *_ui64toa(unsigned int64 val, char *dest, int radix); errno t ui64toa_s(unsigned int radix); Пример: char str[l00] = {О}; unsigned int64 х = 255; _ ui64toa_s(x, str, 100, 10); int64 val, char *dest, size t size, printf("%s\n", str); // 255 printf("%s\n", _ui64toa(x, str, 16)); // ff Преобразовать значения элементарных типов в С-строку можно с помощью функ­ ции sprintf( 1. Существует также функция _sprintf_l(), которая позволяет допол­ нительно задать локаль. Прототипы функций: #include <stdio.h> int sprintf(char *buf, const char *foпnat, ... ); int _sprintf_l(char *buf, const char *forrnat, locale_t locale, ...); В параметре forrnat указывается строка специального формата. Внутри этой строки можно указать обычные символы и спецификаторы формата, начинающиеся с сим­ вола %. Спецификаторы формата совпадают со спецификаторами, используемыми в функции printf() (см. разд. 2 .8). Вместо спецификаторов формата подставляются значения, указанные в качестве параметров. Количество спецификаторов должно совпадать с количеством переданных параметров. Результат записывается в буфер. адрес которого передается в первом параметре (ьuf). В качестве значения функция возвращает количество символов, записанных в символьный массив. Пример пре­ образования целого числа в С-строку: char buf[50] = '"'; intх=100,count=О; count = sprintf(buf, "%d", х); printf("%s\n", buf); printf("%d\n", count); // 100 //3 Функция sprintf( 1 не производит никакой проверки размера буфера, поэтому воз­ можно переполнение буфера. Вместо функции sprintf( 1 следует использовать функцию sprintf_s() или_sprintf_s_l(). Прототипы функций: #include <stdio.h> int sprintf_s(char *buf, size_t sizelnBytes, const char *forrnat, ...); int _sprintf_s_l(char *buf, size_t sizelnBytes, const char *forrnat, locale t locale, ... );
177 Параметры buf и foпnat аналогичны параметрам функции sprintf(). В параметре sizeinBytes указывается размер буфера. В качестве значения функции возвращают 81DJ1Нчество символов, записанных в символьный массив. Пример преобразования кщественноrо числа в С-строку: ct.a.r buf[50] = ""; int count = О; a:;uЬle pi = 3.14159265359; count = sprintf_s(buf, 50, printf("%s\n", buf); printf("%d\n", count); "%.2f", pi); // 3.14 //4 5.7. Генерация псевдослучайных чисел Для генерации псевдослучайных чисел используются следующие функции. CJ rand().- генерирует псевдослучайное число от о до RAND_МАХ . Прототип функ­ ции и определение макроса RAND_МАХ: linclude <stdlib.h> int rand(void); ldefine RAND МАХ 0x7fff Примеры: printf("%d\n", rand()); // 41 printf("%d\n", rand()); // 18467 printf("%d\n", RAND_МAX); // 32767 Дпя того чтобы получить случайное число от о до определенного значения, а не до RAND_МАХ, следует использовать оператор % для получения остатка от деления. Пример получения числа от о до 1 о включительно: printf("%d\n", rand() % 11); Дпя того чтобы получить случайное число от rmin до определенного значения пnах (исключая это значение), например от 5 до 10 включительно, можно вос­ пользоваться следующим кодом: intrmin=5,пnах=11; printf("%d\n", rrnin + rand() % (пnах - rrnin)); □ srand() - настраивает генератор случайных чисел на новую последователь­ ность. В качестве параметра обычно используется значение, возвращаемое функцией time() с нулевым указателем в качестве параметра. Функция time() возвращает количество секунд, прошедшее с 1 января 1970 r. Прототипы функ­ ций: linclude <stdlib.h> void srand(unsigned int seed); #include <time.h> time t time(time_t *time);
178 Пример: srand( (unsigned int)time(NULL) ); printf("%d\n", rand()); printf("%d\n", rand()); printf("%d\n", rand()); Глвва5 Если функция srand() вызвана с одним и тем же параметром, то будет генерИJ»­ ваться одна и та же последовательность псевдослучайных чисел: srand(l00); printf("%d\n", rand()); // 365 srand(l00); printf("%d\n", rand()); // 365 В качестве примера создадим генератор паролей произвольной длины (лис­ тинг 5.3). Для этого добавляем в массив arr все разрешенные символы, а далее в цикле получаем случайный элемент из массива. Затем записываем символ, кото­ рый содержит элемент массива, в итоговый символьный массив, адрес первого элемента которого передан в качестве первого параметра. В конец символьного массива вставляем нулевой символ. Следует учитывать, что символьный массив должен быть минимум на один символ больше, чем количество символов в пароле. / Листинг 5.3. Генератор паролей #include <stdio.h> #include <stdlib.h> #include <time.h> void passw_generator(char *pstr, int count_char); int main(void) { char str[B0] = 11 11. , srand( (unsigned int)time(NULL) ); passw_generator(str, В); printf("%s\n", str); // Выведет passw_generator(str, В); printf("%s\n", str); // 9tpgmUts passw_generator(str, 10); примерно printf("%s\n", str); // MYGeIXXx2g return О; JМhFAE29 void passw_generator(char *pstr, int count_char) { const short SIZE = 60; char arr[60] = {'a','b','c','d','e','f','g','h','i','j', 'k', '1', 'm','n','р ','q','r','s','t','u','v','w','х', 'у','z', 'А', 'В', 'С', 'D', 'Е', 'F', 'G', 'Н', 'I', 'J', 'К', 'L', 'М', 'N', 'Р', 'Q', 'R', 'S', 'Т', 'U', 'V', 'W', 'X','Y','Z','l','2','3','4','5','6','7','8','9','0'}; JJ
Числа for(inti=О;i<countchar;++i){ *pstr = arr[rand() % SIZE]; ++pstr; *pstr = '\0'; 5.8. Бесконечность и значение NAN 179 Целочисленное деление на о приведет к неопределенности, а вот с вещественными числами все обстоит несколько иначе. Деление вещественного числа на о приведет к значению плюс или минус INFINITY (бесконечность), а деление вещественного числа о. о на о. о - к неопределенности или значению NAN (нет числа): printf("%f\n", 10.0/ О.О); printf("%f\n", -10.0/ О.О); printf("%f\n", О.О/ О.О); // 1. #INFOO // -1.#INFOO /1 -1. #INDOO В заголовочном файле math.h для этих значений существуют константы INFINITY и NAN: // #include <math.h> printf("%f\n", INFINITY); 11 1.#INFOO printf("%f\n", -INFINITY); 11 -1.#INFOO printf("%f\n", NAN); 11 1.#QNANO printf("%f\n", NAN + 1); 11 1.#QNANO printf("%f\n", О * INFINITY); 11 1.#QNANO printf("%f\n", INFINITY/ INFINITY); 11 1.#QNANO printf("%f\n", -INFINITY + INFINITY); 11 1.#QNANO Для проверки соответствия этим значениям нельзя использовать логические опера­ торы. Например, значение NAN не равно даже самому себе: printf("%d\n", NAN == NAN); 11о Для того чтобы проверить значения, нужно воспользоваться следующими функ­ циями: □ _finite(J и _finitef(J - возвращают ненулевое число, если значение не равно плюс или минус бесконечность или значению NAN, и о - в противном случае. Прототипы функций: #include <math.h> int _finitef(float х); // Только в версии хб4 (MinGW-W64) int finite(douЫe х); Примеры: printf("%d\n", finite(l0.5)); //1 printf("%d\n", finite(NAN)); 11о printf("%d\n", finite(INFINITY)); 11о
180 Глава 5 □ _isnan <), _isnanf <) и макрос isnan() - возвращают ненулевое число, если зна­ чение равно NAN, и о - в противном случае. Прототипы функций: #include <rnath.h> int _isnanf(float х); // Только в версии хб4 (MinGW-W64) int _isnan(douЫe х); Примеры: printf("%d\n", isnan(l0.5)); //о printf("%d\n", isnan(INFINITY)); //о printf("%d\n", isnan(NAN)); /!1 printf("%d\n", isnan(l0.5)); //о printf("%d\n", isnan(INFINITY)); //о printf("%d\n", isnan(NAN)); //1 □ isinf <) - возвращает ненулевое число, если значение равно плюс или минус бесконечность, и о - в противном случае. Прототип функции: #include <rnath.h> #define isinf(x) (fpclassify(x) == FP_INFINITE) Примеры: printf("%d\n", isinf(l0.5)); //о printf("%d\n", isinf(NAN)); //о printf("%d\n", isinf(INFINITY)); //1 printf("%d\n", isinf(-INFINITY)); !/1
ГЛАВА 6 Массивы Массив - это нумерованный набор переменных одного типа. Переменная в масси­ ве называется элементом, а ее позиция в массиве задается индексом. Все элементы массива располагаются в смежных ячейках памяти. Например, если объявлен массив из трех элементов, имеющих тип int (занимает 4 байта), то адреса соседних элементов будут отличаться на 4 байта: int arr(3] = (10, 20, 30}; for(inti=О;i<3;++i) printf("%p\n", &arr[i]); Результат в проекте тest64c: 000000000023FE40 000000000023FE44 000000000023FE48 6.1. Объявление и инициализация массива Объявление массива выглядит следующим образом: <Тип> <Переме нная>[<Количество элементов>]; Пример объявления массива из трех элементов, имеющих тип long int: long arr[3]; При объявлении элементам массива можно присвоить начальные значения. Для этого после объявления указывается оператор =, а далее значения через запятую внутри фигурных скобок. После закрывающей фигурной скобки обязательно ука­ зывается точка с запятой. Пример инициализации массива: long arr[3] = (10, 20, 301; Количество значений внутри фигурных скобок может быть меньше количества элементов массива. В этом случае значения присваиваются соответствующим эле­ ментам с начала массива. Пример:
182 long arr[3] = {10, 20}; for(inti=О;i<3;++i) printf("%ld ", arr[i]); }//1020О Главаl В этом примере первому элементу массива присваивается значение 10, второму­ значение 20, а третьему элементу будет присвоено значение о. Присвоить всем элементам массива значение о можно так: long arr[3] = {О}; for(inti=О;i<3;++i) printf("%ld ", arr[i]); }//ооо Стандарт С99 добавил возможность инициализации произвольных элементов мас­ сива. Для этого при инициализации массива внутри квадратных скобок указываета индекс элемента, а затем после оператора = - его значение. Можно присвоить зна­ чения нескольким элементам, указав индексы и значения через запятую. Элемен11�1. которым не было присвоено значение при инициализации, автоматически получа1 значение о. В этом примере первому элементу массива присваивается значение 5, второму- значение о, а третьему элементу будет присвоено значение 11: long arr[3] = { [О] = 5, [2] 11}; for(inti=О;i<3;++i) printf("%ld ", arr[i]); }// 5011 Можно комбинировать инициализацию по позиции и по индексу: long arr[8] = {5, [2] = 11, 15, [6] = 22, 551; for(inti=О;i<8;++i) printf("%ld ", arr[i]); }//5О1115ОО2255 Если при объявлении массива указываются начальные значения, то количество элементов внутри квадратных скобок можно не указывать. Размер будет соответст­ вовать количеству значений внутри фигурных скобок. Пример: long arr[] = {10, 20, 30}; for(inti=О;i<3;++i) printf("%ld ", arr[i]); }//102030 Обратите внимание: при инициализации массива значениями внутри фигур­ ных скобок мы не можем внутри квадратных скобок указать константу, объявлен­ ную с помощью ключевого слова const. Этот код приведет к ошибке при компи­ ляции: const int ARR_SIZE = 3; int arr[ARR_SIZE] = {1, 2, 3}; // error: variaЬle-sized object rnay not Ье initialized
llессивы Однако мы можем использовать директиву препроцессора lldefine: tdefine ARR SIZE 3 i.�t arr[ARR_SIZE] = {1, 2, 3}; // ок for(inti=О;i<ARRSIZE;++i){ printf("%d ", arr[i]); i//123 183 Количество элементов массива можно указать с помощью переменной или кон• аанты, объявленной с помощью ключевого слова const, но в этом случае нельзя 111,1полнять инициализацию при объявлении массива: const int ARR SIZE = 2; int arr[ARR_SIZE]; arr[O] = 1; arr[l] = 2; for(inti=О;i<ARRSIZE;++i){ printf("%d ", arr[i]); i//12 Если при объявлении массива начальные значения не указаны, то: □ элементам глобальных массивов автоматически присваивается значение о; □ элементы локальных массивов будут содержать произвольные значения, так на­ зываемый «мусор». 6.2. Определение количества элементов и размера массива Количество элементов массива задается при объявлении и не может быть изменено позже. Поэтому лучше количество элементов сохранить в каком-либо макросе и в дальнейшем указывать этот макрос, например, при переборе элементов массива. Для того чтобы получить количество элементов массива динамически при выпол­ нении программы, нужно общий размер массива в байтах разделить на размер типа. Получить эти размеры можно с помощью оператора sizeof. Пример динамического определения количества элементов массива: int arr[15] = {О}; printf("%d\n", (int) (sizeof(arr) / sizeof(int))); // 15 Можно также воспользоваться макросом _countof(). Определение макроса: tinclude <stdlib.h> tdefine _countof(_Array) (sizeof(_Array) / sizeof(_Array[OJ)) Пример: int arr[l5] = {О}; printf("%d\n", (int)_count<;>f(arr)); // 15
184 Главаl Если мы сохраним адрес первого элемента массива в указателе и попробуем опре­ делить количество элементов массива через него, то ничего не получится. Резуш,­ татом будет размер указателя: int arr[15] = {О}; int *р = arr; printf("%d\n", (int) (sizeof(arr))); // 60 printf("%d\n", (int) (sizeof(р))); // Размер указателя!!! // Значение в проекте Test64c: 8 Объем памяти (в байтах), занимаемый массивом, определяется так: <Объем памяти> sizeof (<Массив>) <Объем памяти> = sizeof (<Тип>) * <Количество элементов> Пример: int arr[3] = {10, 20, 30}; printf("%d\n", (int) sizeof(arr)); // 12 printf("%d\n", (int) sizeof(int) * 3); // 12 6.3. Получение и изменение значения элемента массива Обращение к элементам массива осуществляется с помощью квадратных скобок, в которых указывается индекс элемента. Обратите внимание на то, что нумерацИI элементов массива начинается с о, а не с 1, поэтому первый элемент имеет индекс с. С помощью индексов можно присвоить начальные значения элементам массива уже после объявления: longarr[3]; arr(0] 10; // Первый элемент имеет индекс О!!! arr[l] = 20; // Второй элемент arr[2] = 30; // Третий элемент Следует учитывать, что проверка выхода указанного индекса за пределы диапазона на этапе компиляции не производится. Таким образом, можно перезаписать значе­ ние в смежной ячейке памяти и тем самым нарушить работоспособность програм­ мы или даже повредить операционную систему. Помните, что контроль корректно­ сти индекса входит в обязанности программиста! После определения массива выделяется необходимый размер памяти, а в перемен­ ной сохраняется адрес первого элемента массива. При указании индекса внутри квадратных скобок производится вычисление адреса соответствующего элемента массива. Зная адрес элемента массива, можно получить значение или перезаписать его. Иными словами, с элементами массива можно производить такие же операции, как и с обычными переменными. Пример: longarr(3] = {10, 20, 30}; longх = О; х=arr[l]+12;
llассивы 185 arr(2] = х - arr[2]; printf("%ld ", х); // 32 printf("%ld ", arr[2]); // 2 6.4. Перебор элементов массива Для перебора элементов массива удобно использовать цикл for. В первом парамет­ ре переменной-счетчику присваивается значение о (элементы массива нумеруются с нуля), условием продолжения является значение переменной-счетчика меньше l[Оличества элементов массива. В третьем параметре указывается приращение на единицу на каждой итерации цикла. Внутри цикла доступ к элементу осуществля­ ется с помощью квадратных скобок, внутри которых указывается переменная­ счетчик. Пронумеруем все элементы массива, li затем выведем все значения в пря­ мом и обратном порядке: const short ARR_SIZE = 20; int arr[ARR_SIZE]; // Нумеруем все элементы массива for(inti=О,j=1;i<ARRSIZE;++i,++j){ arr[i] = j; // Выводим значения в прямом порядке for(inti=О;i<ARRSIZE;++i){ printf("%d\n", arr[i]); puts(" -------- -- -----�--- "); // ВЫБодим значения в обратном порядке for(inti=ARR_SIZE-1;i>=О;--i) printf("%d\n", arr[i]); В этом примере мы объявили количество элементов массива как постоянную вели­ чину (константа ARR_SIZE). Эго очень удобно, т. к . размер массива приходится ука­ зывать при каждом переборе массива. Если количество элементов указывать в виде числа, то при изменении размера массива придется вручную изменять все значения. При использовании константы количество элементов достаточно будет изменить только в одном месте. Цикл for всегда можно заменить циклом while. В качестве примера пронумеруем элементы в обратном порядке, а затем выведем все значения: const short ARR_SIZE = 20; int �rr[ARR_SIZE]; // Нумеруем все элементы inti=О,j =ARR_SIZE; while (i < ARR_SIZE) { arr[i] = j; ++i; - -j;
186 // Выводим значения массива i=О; while (i < ARR_SIZE) { printf("%d\n", arr[i]); ++i; 6.5. Доступ к элементам массива с помощью указателя Глава 5 После определения массива в переменной сохраняется адрес первого элемента. Иными словами, название переменной является указателем, который ссылается на первый элемент массива. Поэтому доступ к элементу массива может осуществлять­ ся как по индексу, указанному внутри квадратных скобок, так и с использованием адресной арифметики. Например, следующие инструкции вывода являются эквива- лентными: int arr[З] = {10, 20, 30); printf("%d\n", arr[l]); // 20 printf("%d\n", *(arr + 1)); // 20 printf("%d\n", * (1 + arr)); // 20 printf("%d\n", 1 [arr]); // 20 Последняя инструкция может показаться странной. Однако если учесть, что выра­ жение 1[arr] воспринимается компилятором как* (1 + arr), то все встанет на свои места. При указании индекса внутри квадратных скобок каждый раз производится вычис­ ление адреса соответствующего элемента массива. Для того чтобы сделать процесс доступа к элементу массива более эффективным, объявляют указатель и присваи­ вают ему адрес первого элемента. Далее для доступа к элементу просто перемеща­ ют указатель на соответствующий элемент. Пример объявления указателя и при­ сваивания ему адреса первого элемента массива: int *р = NULL; int arr[З] = 11, 2, З}; р=arr; printf("%d\n", *р); //1 printf("%d\n", *(р + 1)); // 2 printf("%d\n", *(р + 2)); // 3 Обратите внимание на то, что перед названием массива не указывается оператор &, т. к . название переменной содержит адрес первого элемента. Если использовать оператор &, то необходимо дополнительно указать индекс внутри квадратных скобок: р = &arr(0]; // Эквивалентно: р = arr; Таким же образом можно присвоить указателю адрес произвольного элемента мас­ сива, например третьего:
"8ссивы 187 int *р = NULL; int arr[3] = {1, 2, 3}; р = &arr[2]; // Указатель на третий элемент массива printf("%d\n", *р); // 3 ,Аля того чтобы получить значение элемента, на который ссылается указатель, не- 8бходимо произвести операцию разыменования. Для этого перед названием указа­ 'RЛЯ добавляется оператор *. Пример: printf("%d\n", *р); Указатели часто используются для перебора элементов массива. В качестве приме­ " создадим массив из трех элементов, а затем выведем значения с помощью цикла for: tdefine ARR SIZE 3 int *р = NULL, arr[ARR_SIZE] = {1, 2, 3}; // Устанавливаем указатель на первый элемент массива р = arr; // Оператор & не указывается!!! for(inti=О;i<ARR_SIZE;++i) printf("%d\n", *р); ++р; // Перемещаем указатель на следукхций элемент р = arr; // Восстанавливаем положение указателя В первой строке объявляется константа ARR_srzE, которая описывает количество эnементов в массиве. Если массив используется часто, то лучше сохранить его раз­ мер как констанrу, т. к. количество элементов нужно будет указывать при каждом переборе массива. Если в каждом цикле указывать конкретное число, то при изме­ нении размера массива придется вручную вносить изменения во всех циклах. При объявлении константы достаточно будет изменить ее значение один раз при ини­ анализации. В следующей строке объявляется указатель на тип int и массив. Количество эле­ ментов массива задается константой ARR_srzE. При объявлении массив инициализи­ руется начальными значениями. Обратите внимание: при инициализации массива 311ачениями внутри фигурных скобок мы не можем внутри квадратных скобок ука­ зать констаН'I)', объявленную с помощью ключевого слова const. Этот код приведет к ошибке при компиляции: const int ARR SIZE = 3; int arr[ARR_SIZE] = {1, 2, 3}; // error: variaЬle-sized object may not Ье initialized Поэтому для определения константы мы использовали директиву препроцессора ldefine, а не ключевое слово const. Внутри цикла for выводится значение элемента, на который ссылается указатель, а затем значение указателя увеличивается на единицу (++р; ). Обратите внимание на то, что изменяется адрес, а не значение элемента массива. При увеличении значе­ lПIЯ указателя используются правила адресной арифметики, а не правила обычной
188 Главаf арифметики. Увеличение значения указателя на единицу приведет к прибавлению к адресу размера типа данных. Например, если тип int занимает 4 байта1 то при увеличении значения на единицу указатель вместо адреса Ox0012FF30 будет соде� жать адрес Ox0012FF34. Значение увеличилось на 4, а не на 1. В нашем примере вместо двух инструкций внутри цикла можно использовать одну: printf("%d\n", *р++); Выражение р++ возвращает текущий адрес, а затем увеличивает его на единицу . Символ * позволяет получить доступ к значению элемента по указанному адресу. Последовательность выполнения соответствует следующей расстановке скобок: printf("%d\n", *(р++)); Если скобки расставить так: printf("%d\n", (*р)++); то вначале будет получен доступ к элементу массива и выведено его текущее зна­ чение, а затем произведено увеличение знач�ния элемента массива. Перемещение указателя на следующий элемент не выполняется. Указатель можно использовать в качестве переменной-счетчика в цикле for. В этом случае начальным значением будет адрес первого элемента массива, а условием продолжения - адрес меньше адреса первого элемента плюс количество элемен­ тов. Приращение осуществляется аналогично обычному, только вместо классиче­ ской арифметики применяется адресная арифметика. Пример: #define ARR SIZE 3 int arr[ARR_SIZE] = {1, 2, 3}; for(int*р =arr, *р2=arr+ARRSIZE;р<р2;++р){ printf("%d\n", *р); Вывести все значения массива с помощью цикла while и указателя можно так: #define ARR SIZE 3 int *р = NULL, i = ARR_SIZE, arr[ARR_SIZE] = {1, 2, 3}; р=arr; while (i-- > О) { printf("%d\n", *р++); р=arr; В указателе можно сохранить адрес составного литерш�а, который удобно переда­ вать в качестве параметра в функцию. Составной литерал состоит из круглых ско­ бок, внутри которых указываются тип данных и количество элементов внутри квадратных скобок. После круглых скобок указываются фигурные скобки, внутри которых через запятую перечисляются значения: const long *р = (long [3]) {1, 2, 3}; printf("%ld " *р); //1 , printf("%ld *(р+1)); //2 printf("%ld " *(р+2)); //3 ,
llассивы 189 Iоличество элементов внутри квадратных скобок можно не указывать. Размер мас­ аmа будет соответствовать количеству элементов внутри фигурных скобок: const long *р= (long []) {1, 2, 3}; 6.6. Массивы указателей Указатели можно сохранять в массиве. При объявлении массива указателей ис- 80Льзуется следующий синтаксис: <Тип> *<Название массива>[<Количеётво элементов>]; Пример использования массива указателей: int *р(3]; // Массив указателей из трех элементов intх=10,у=20,z=30; р[О] &х; p[l] = &у; р[2]= &z; printf("%d\n", priпtf("%d\n", printf("%d\n", *р[О]); *p[l]); *р[2]); // 10 // 20 // 30 6.7. Динамические массивы При объявлении массива необходимо задать точное количество элементов. На основе этой информации при запуске программы автоматически выделяется необ­ ходимый объем памяти. Иными словами, размер массива необходимо знать до выполнения программы. Во время выполнения программы увеличить размер суще­ ствующего массива нельзя. Для того чтобы произвести увеличение массива во время выполнения программы, необходимо· выделить достаточный объем динамической памяти с помощью функ­ ции malloc () или calloc (), перенести существующие элементы, а лишь затем доба­ вить новые элементы. Для перераспределения динамической памяти можно вос­ пользоваться функцией realloc (). Управление динамической памятью полностью лежит на плечах программиста, по­ этому после завершения работы с памятью необходимо самим возвратить память операционной системе с помощью функции free (). Если память не возвратить опе­ рационной системе, то участок памяти станет недоступным для дальнейшего ис­ пользования. Подобные ситуации приводят к утечке памяти. Функции malloc (), calloc (), realloc () и free () применительно к массивам мы уже рассматривали в разд. 3 .14, поэтому не будем повторяться. Просто откройте этот раздел и прочитайте еще раз.
190 Глава 5 6.8. Многомерные массивы Массивы в языке С могут быть многомерными. Объявление многомерного массива имеет следующий формат: <Тип> <Переменная>[<Количество элементовl>] ... [<Количество элементовN>]; На практике наиболее часто используются двумерные массивы, позволяющие хра­ нить значения ячеек таблицы, содержащей определенное количество строк и столбцов. Объявление двумерного массива выглядит так: <Тип> <Переменная>[<Количество строк>][<Количество столбцов>]; Пример объявления двумерного массива, содержащего две строки и четыре столбца: iпt arr[2] [4]; // Две строки из 4-х элементов каждая Все элементы двумерного массива располагаются в памяти друг за другом. Вначале элементы первой строки, затем второй и т. д . При инициализации двумерного мас­ сива элементы указываются внутри фигурных скобок через запятую. Элементы каждой строки также размещаются внутри фигурных скобок. Пример: int arr[2] [4] = { }; {1, 2, 3, 4}, {5,6,7,8} for(inti=О,j=О;i<2;++i) for(j=О;j<4;++j){ printf("%3d ", arr[i][j]); printf("\n"); Дпя того чтобы сделать процесс инициализации наглядным, мы расположили эле­ менты на отдельных строках. Количество элементов на строке совпадает с количе­ ством столбцов в массиве. Результат выполнения: 1234 5678 Если при объявлении производится инициализация, то количество строк можно не указывать, оно будет определено автоматически: int arr[] [4] = { (1, 2, 3, 4}, (5,6,7,8} ); Получить или задать значение элемента можно, указав два индекса (не забывайте, что нумерация начинается с нуля): int arr[2][4] = { ); {1, 2, 3, 4}, (5,6,7,8)
llассивы 191 printf("%d\n", arr[O][О]); // 1 printf("%d\n", arr[l][O]); // 5 printf("%d\n", arr[l] [З]); // 8 Для того чтобы вывести все значения массива, необходимо использовать вложен­ ные циклы. В качестве примера пронумеруем все элементы массива, а затем выве­ дем все значения: const int ROWS = 4; // Количество строк const int COLUМNS = 5; // Количество столбцов int i = О, j = О, n = 1, arr[ROWS][COLUМNS]; // Нумеруем все элементы массива for(i=О;i<ROWS;++i){ 1 for(j=О;j<COLUМNS;++j) arr(i](j] = n; ++n; // Выводим значения for(i=О;i<ROWS;++i){ for(j=О;j<COLUМNS;++j) printf("%3d", arr[i] [j]); printf("\n"); Результат: 12345 678910 1112131415 1617181920 Все элементы многомерного массива располагаются в памяти друг за другом. По­ этому для перебора массива можно использовать указатель. В качестве примера выведем все элементы двумерного массива с помощью цикла for и указателя: ldefine ROWS 4 ldefine COLUМNS 5 int arr[ROWS] [COLUМNS] {1,2,з,4,5}, }; (6, 7, 8, 9, 10}, {11, 12, 13, 14, 15}, {16, 17, 18, 19, 20} for (int *р = arr[O], *р2 arr[O]+ROWS*COLUМNS; р < р2; ++р) ( printf("%d\n", *р); Обратите внимание на инициализацию указателя: int *р = arr[O]
192 Глава 1 Мы сохраняем в указателе адрес первого элемента массива. Эта инструкция экви­ валентна такой инструкции: int *р = &arr[0][О] Следующая инструкция является неверной, т. к. arr содержит адрес массива, а не адрес первого элемента двумерного массива: int*р=arr Для того чтобы такая инициализация указателя стала корректной, нужно объявип. указатель следующим образом: int (*р)[COLUМNS] = arr; printf("%d\n", р[О][О]); printf("%d\n", р[З][4]); //1 // 20 Можно также объявить указатель на двумерный массив, в этом случае при инициа­ лизации перед названием двумерного массива нужно указать оператор &: int (*р) [ROWS][COLUМNS] = &arr; printf("%d\n", (*р)[О][О]); // 1 printf("%d\n", (*р)[3][4]); // 20 При динамическом создании двумерного массива вначале создается массив указа­ телей (см. листинг 3.9): int **р = calloc(ROWS, sizeof(int*)); Затем в этот массив добавляются строки: for(i=О;i<ROWS;++i){ p[i] = calloc(COLUМNS, sizeof(int)); Сохранить значение в элементе массива можно так: p[i][j] = n++; или так: *(*(р+i)+j)=n++; Получить значение и вывести его можно так: printf("%3d", p[i][j]); или так: printf("%3d", *(*(р + i) + j)); После окончания работы с динамическим двумерным массивом нужно не забыть освободить память: for(inti=О;i<ROWS;++i){ free(p[i]); free(р); р = NULL; // Обнуляем указатель
Массивы 193 6.9. Поиск минимального и максимального значений Дrlя того чтобы в массиве найти минимальное значение, следует присвоить пере­ менной min значение первого элемента массива, а затем сравнить с остальными 1Лементами. Если значение текущего элемента меньше значения переменной min, то присваиваем значение текущего элемента переменной пiin. Дrlя того чтобы найти максимальное значение, следует присвоить переменной max 111ачение первого элемента массива, а затем сравнить с остальными элементами. Если значение текущего элемента больше значения переменной �х, то присваива­ ем значение текущего элемента переменной max. Пример поиска минимального и максимального значения приведен в листинге 6.1 . linclude <stdio.h> tdefine ARR SIZE 5 int min(int *pArr, int length); int max(int *pArr, int length); void min_max(int *pArr, int length, int *pMin, int *рМах); L�t main(void) { int arr[ARR_SIZE] = {2, 5, 6, 1, 3); printf("min = %d\n", min(arr, ARR_SIZE)); printf("max = %d\n", max(arr, ARR_SIZE)); int_min=О, max=О; min_max(arr, ARR_SIZE, &_min, &_max); printf("min %d\n", _min); printf("max = %d\n", _max); min=О; min_rnax(arr, ARR_SIZE, &_min, NULL); printf("rnin = %d\n", _min); max=О; min_max(arr, ARR_SIZE, NULL, &_max); printf("max = %d\n", _max); return О; int rnin(int *pArr, int length) int rnin = pArr[O]; for(inti=О;i<length;++i) if (pArr[i] < min) min = pArr[i];
194 return rnin; int rnax(int *pArr, int length) int rnax = pArr[O]; for(inti=О;i<length;++i) if (pArr[i] > rnax) rnax = pArr[i]; return rnax; void rn�n_rnax(int *pArr, int length, int *pMin, int *рМах) { if ( ! pArr 11 length < 1) return; int *р = pArr, rnin = pArr[O], rnax = pArr[O]; for(inti=О;i<length;++i,++р){ if(*р<rnin)rnin= *р; if(*р>rnax)rnax=*р; if (pMin) *pMin = rnin; if (рМах) *рМах = rnax; Глава 11 В этом примере мы создали три функции: rnin(), rnax() и rnin_rnax(). Функции в пер­ вом параметре принимают адрес первого элемента массива, а во втором - длиН)· массива. Внутри функции rnin() выполняется поиск минимального значения, а внутри функции rnax() - максимального значения. Найденные значения функции возвращают с помощью оператора return. Как только внутри функции встречаетсt оператор return или поток доходит до конца блока функции, управление передает­ ся в место вызова функции. При этом становится доступно значение, указанное в операторе return. Если функция ничего не возвращает, то при определении перез именем функции указывается ключевое слово void. В этом случае оператор retuг.. использовать не нужно. Хотя его можно и указать, например, чтобы досрочно вый­ ти из функции: if ( !pArr 11 length < 1) return; Если pArr является нулевым указателем или длина массива меньше 1, то просто вы­ ходим из функции. При этом после оператора return значение не указывается. Функция rnin_rnax() выполняет поиск и минимального значения, и максимального значения массива одновременно. Однако из функции с помощью оператора returr. мы можем вернуть только одно значение, поэтому вместо возврата значени.1 в третьем и четвертом параметрах функция принимает адреса переменных, в кото­ рые будут записаны найденные значения. Для того чтобы передать в функцию адрес переменной, перед ее именем указывается оператор&: int_rnin=О, _rnax=О; rnin_rnax(arr, ARR_SIZE, &_rnin, &_rnax); Внутри функции rnin_rnax() мы проверяем наличие адреса переменной, поэтому мо­ жем передать значение NULL вместо адреса, если какое-либо значение нам не нужно. В этом примере мы хотим получить только минимальное значение:
lllllccuвы 195 _ain =О; ain_max(arr, ARR_SIZE, &_min, NULL); Ввуrри функций min() и max() мы используем доступ к элементу массива по индек­ су. указанному внутри квадратных скобок: pArr [ i J. При этом каждый раз положе­ ..е элемента вычисляется относительно начала массива: * (pArr + i). Чем больше '181СНХ обращений, тем менее эффективна программа. Для того чтобы сделать про­ rрамму более быстрой и эффективной, внутри функции min_max( ) на каждой итера- 8111 мы перемещаем указатель, используя адресную арифметику. В этом случае ни­ аких дополнительных вычислений положения элемента внутри массива не произ- 80дится. 1.1 О. Сортировка массива Сортировка массива - это упорядочивание элементов по возрастанию или убыва- 800 значений. Сортировка применяется при выводе значений, а также при подrо­ '108Ке массива к частому поиску значений. Поиск по отсортированному массиву •изводится гораздо быстрее, т. к. не нужно каждый раз просматривать все значе- 811 массива. Д.п упорядочивания элементов массива в учебных целях очень часто применяется метод, называемый пузырьковой сортировкой (листинг 6.2), При этом методе наи­ меньшее значение как бы «всплывает» в начало массива, а наибольшее значение аюгружается» в конец массива. Сортировка выполняется в несколько проходов. При каждом проходе последовательно сравниваются значения двух элементов, ко- 10рые расположены рядом. Если значение первого элемента больше второго, то ]118Чения элементов меняются местами. Для сортировки массива из пяти элементов необходимо максимум четыре прохода и десять сравнений. Если после прохода не было ни одной перестановки, то сортировку можно прервать. В этом случае для сортировки ранее уже отсортированного массива нужен всего один проход. tinclude <stdio.h> tdefine ARR SIZE 5 int main(void) { int arr[ARR_SIZE] {10, 5, 6, 1, 3}; intj=О,tmp=О,k=ARRSIZE -2; _Bool is_swap = О; for(inti=О;i<=k;++i){ is_swap = О; for(j=k;j>=i;--j){ if (arr[j] > arr(j + l]) tmp=arr[j+l]; arr[j + l] = arr(j];
196 ) arr[j] = tmp; is_swap = 1; // Если перестановок не было, то выходим if (!is_swap) break; // Выводим отсортированный массив for (int i = О; i < ARR_SIZE; ++i) printf("%d\n", arr[i]); return О; Глава 6 В качестве еще одного примера произведем сортировку по убыванию (листинг 6.3). Для того чтобы пример был более полезным, изменим направление проходов. 1 Листинr 6.3. Пузыра:.коsая сорп,ровка no убыванию #include <stdio.h> #define ARR SIZE 5 int main(void) { int arr[ARR_SIZE] intj=О,tmp=О; _ Bool is_swap = О; (5, 6, 1, 10, 3); for(inti=ARRSIZE -1;i>=1;--i){ is_swap = О; for(j=О;j<i;++j) if (arr[j] < arr[j + 1]) tmp = arr[j + 1]; arr[j + 1] = arr[j]; arr[j] = tmp; is_swap = 1; if (!is_swap) break; ) // Выводим отсортированный массив for (int i = О; i < ARR_SIZE; ++i) printf("%d\n", arr[i]); return О; Для сортировки массива лучше воспользоваться стандартной функцией qsort(). Прототип функции:
llассивы 197 tinclude <stdlib.h> void qsort(void *base, size_t numOfElements, size t sizeOfElements, tnt (*PtFuncCompare)(const void *, const void *)); В параметре base указывается адрес первого элемента массива, в параметре nшnOfElements - количество элементов массива, а в параметре sizeOfElements - размер каждого элемента в байтах. Адрес пользовательской функции (указывается название функции без круглых скобок и параметров), внутри которой производится сравнение двух элементов, передается в последнем параметре. Прототип пользова­ тельской функции сравнения должен выглядеть так: :�t <Название функции>(соnst void *argl, const void *arg2); При сортировке по возрастанию функция должна возвращать отрицательное значе­ ние, если arg1 меньше arg2, положительное значение, если argl больше arg2, или о, если значения равны. Внутри функции необходимо выполнить приведение указате­ п void * к определенному типу. Пример использования функции qsort() приведен • листинге 6.4. linclude <stdio.h> linclude <stdlib.h> tdefine ARR SIZE 5 int compare(const void *argl, const void *arg2) if (*(int *)argl < *(int *)arg2) return -1; if (*(int *)argl > *(int *)arg2) return 1; return О; :nt main(void) int arr[ARR_SIZE] = {10, 5, б, 1, З}; qsort(arr, ARR_SIZE, sizeof(int), compare); for(inti=О;i<ARR_SIZE;++i){ printf("%d\n", arr[i]); return .О; Пример функции сравнения для сортировки по убыванию: :�t compare(const void *argl, const void *arg2) if (*(int *)argl > *(int *)arg2) return -1; if (*(int *)argl < *(int *)arg2) return 1; return О;
198 Главаl Можно также внутри функции сравнения вычесть одно значение из другого, но нужно учитывать, что при вычитании больших значений знак может поменяться на противоположный, что приведет к неправильной сортировке. Пример функции сравнения для сортировки по возрастанию: int compare(const void *argl, const void *arg2) ( return *(int *)argl - *(int *)arg2; Для того чтобы произвести сортировку по убыванию, достаточно поменять значе-­ ния местами: int co�pare(const void *argl, const void *arg2) { return *(int *)arg2 - *(int *)argl; 6.11. Проверка наличия значения в массиве Если массив не отсортирован, то проверка наличия значения в массиве сводита к перебиранию всех элементов от начала до конца. При наличии первого вхожде-­ ния можно прервать поиск. Пример поиска первого вхождения приведен в листин­ ге 6.5. Листинr 6.5. Поиск nepвoro вхождения элемента #include <stdio.h> #include <locale.h> #define ARR SIZE 5 int main(void) { setlocale(LC_ALL, "Russian_Russia.1251"); int arr[ARR_SIZE] = {10, 5, 6, 1, 6}, index -1; int search_key = 6; for (int i = О; i < ARR_SIZE; ++i) if (arr[i] search_key) index = i; break; if (index != -1) { printf("index = %d\n", index); else рuts("Элемент не найден"); return О; Если значение находится в начале массива, то поиск будет произведен достаточно быстро, но если значение расположено в конце массива, то придется просматривать
llассивы 199 весь массив. Если массив состоит из 100 ООО элементов, то нужно будет сделать 100 ООО сравнений. Поиск по отсортированному массиву производится гораздо быстрее, т. к. не нужно каждый раз просматривать все значения массива. Наиболее часто применяется би­ нарный поиск (листинг 6.6), при котором массив делится пополам и производится сравнение значения элемента, расположенного в середине массива, с искомым зна­ чением. Если искомое значение меньше значения элемента, то пополам делится первая половина массива, а если больше, то пополам делится вторая половина. Да­ хе таким же образом производится деление оставшейся части массива. Поиск за­ анчивается, когда будет найдено первое совпадение или начальный индекс станет больше конечного индекса. Таким образом, на каждой итерации отбрасывается половина оставшихся элементов. Поиск значения, которое расположено в конце массива, состоящего из 100 ООО элементов, будет произведен всего за 17 шагов. Однако если поиск производится один раз, то затраты на сортировку сведут на нет все преимущества бинарного поиска. В этом случае прямой перебор элементов может стать эффективнее. linclude <stdio.h> linclude <stdlib.h> linclude <locale.h> ldefine ARR SIZE 5 int compare(const void *argl, const void *arg2); int search(int key, const int *arr, int count); int main(void) 1 setlocale(LC_ALL, "Russian_Russia.1251"); int arr[ARR_SIZE] = {10, 5, 6, 1, 3}; // Сортируем по возрастанию qsort(arr, ARR_SIZE, sizeof(int), compare); // Производим поиск int index = search(б, arr, ARR_SIZE); if (index != -1) printf("index = %d\n", index); else рuts("Элемент не найден"); return О; // Сортировка по возрастанию int compare(const void *argl, const void *arg2) if (*(int *)argl < *(int *)arg2) return -1; if (*(int *)argl > *(int *)arg2) return 1; return О;
200 // Бинарный поиск в отсортированном массиве int search(int key, const int *arr, int count) if (count < 1 11 !arr) return -1; intstart=О,end=count-1,i=О; while (start <= end) { i=(start+end)/2; if (arr[i] == key) return i; else if (arr[i] < key) start else if (arr[i] > key) end return -1; i+1; i-1; Главаl Для проверки наличия элемента в массиве можно также воспользоваться стандарт­ ной функцией bsearch(). Прототип функции: #include <stdlib.h> void *bsearch(const void *key, const void *base, size_t nurnOfElernents, size_t sizeOfElernents, int (*PtFuncCornpare)(const void *, const void *)); В параметре key передается адрес переменной, в которой хранится искомое значе­ ние, в параметре ьаsе указывается адрес первого элемента массива, в параметре nurnOfElernents - количество элементов массива, а в параметре sizeOfElernents - размер каждого элемента в байтах. Адрес пользовательской функции (указываете� название функции без круглых скобок и параметров), внутри которой производите• сравнение двух элементов, передается в последнем параметре. Прототип пользова­ тельской функции сравнения должен выглядеть так: int <Название функции>(соnst void *argl, const void *arg2); Функция должна возвращать отрицательное значение, если argl меньше arg2, по­ ложительное значение, если argl больше arg2, или о, если значения равны. Внутри функции необходимо выполнить приведение указателя void * к определенному ти­ пу. Если значение не найдено, то возвращается нулевой указатель, в противном случае указатель ссылается на найденный элемент. Пример использования функции bsearch() приведен в листинге 6.7 . Лисntнr 6.7. Проверка наличия 3начения в мас�ве с помощью функции bsearoЬ () #include <stdio.h> #include <stdlib.h> #include <locale.h> #define ARR SIZE 5 int compare(const void *argl, const void *arg2) if (*(int *)argl < *(int *)arg2) return -1;
Массивы if (*(int *)argl > *(int *)arg2) retum 1; return О; int main(void) setlocale(LC_ALL, "Russian_Russia.1251"); int arr[ARR_SIZE] = (10, 5, 6, 1, 3}, search_key = 6; // Сортируем по возрастанию qsort(arr, ARR_SIZE, sizeof(int), compare); // Производим поиск int *р (int *)bsearch(&search_key, arr, ARR_SIZE, sizeof(int), compare); if (р) int index = {int) (р - arr); printf("index = %d\n", index); else рuts("Элемент не найден"); return О; 6.12. Копирование элементов из одного массива в другой 201 Для копирования элемеmов из одного массива в другой используются следующие функции. □ memcpy{) - копирует первые size байтов из массива src в массив dst. В качестве значения функция возвращает указатель на массив dst. Если массив src длиннее массива dst, то произойдет переполнение буфера. Если указатели пересекаются, то поведение функции непредсказуемо. Прототип функции: #include <stri�g.h> void *memcpy(void *dst, const void *src, size t size); Пример использования функции: #define ARRl SIZE 5 #define ARR2 SIZE 3 int arrl[ARRl_SIZE] = (0), arr2[ARR2_SIZE] int*р=О,i=О; // Копируем все элементы массива arr2 р = (int *)memcpy(arrl, arr2, sizeof arr2); for(i=О;i<ARRl_SIZE;++i){ printf("%d ", arrl[i]); }//123ОО printf("\n"); if ( !р) exit(1); (1, 2, 3); // Перемещаем указатель на четвертый элемент массива р+=3;
202 Главаl // Копируем только два первых элемента массива arr2 rnerncpy(p, arr2, 2 * sizeof(int)); for (i = О; i < ARRl_SIZE; ++i) { printf("%d ", arrl[i]); }//12З12 printf("\n"); Вместо функции rnerncpy() лучше использовать функцию rnerncpy_s(). Прототип функции: #include <string.h> errno t rnerncpy_s(void *dst, size t dstSize, const void *src, size_t rnaxCount); Функция rnerncpy_s() копирует первые rnaxcount байтов из массива src в мaccJD dst. В параметре dstsize указывается размер массива dst в байтах. Если копи� ванне прошло успешно, функция возвращает значение о. Пример: #define ARRl SIZE 5 #define ARR2 SIZE З int arrl[ARRl_SIZE] = {О}, arr2[ARR2_SIZE] = {1, 2, З); // Копируем только два первых элемента массива arr2 rnerncpy_s(arrl, sizeof arrl, arr2, 2 * sizeof(int)); for (int i = О; i < ARRl_SIZE; ++i) { printf("%d ", arrl[i]); )//12ООО printf("\n"); □ rnemrnove() - копирует первые size байтов из массива src в массив dst. В качест­ ве значения функция возвращает указатель на массив dst. Если массив src длиннее массива dst, то произойдет переполнение буфера. Основное отличие функции rnemrnove() от rnerncpy() в выполнении корректных действий при пересе­ чении указателей. Прототип функции: #include <string.h> void *rnemrnove(void *dst, const void *src, size t size); Пример: #define ARR SIZE 5 int arr[ARR_SIZE] = (1, 2, З, 4, 5), *р О; р=arr+2; // Указатели пересекаются rnemrnove(p, arr, З * sizeof(int)); for(inti=О;i<ARR_SIZE;++i) printf("%d ", arr[i]); ) // 1 2 1 2 З - копирование произведено корректно printf("\n"); Вместо функции rnemrnove() лучше использовать функцию rnemrnove_s(). Прототип функции:
203 linclude <string.h> errno_t rnemmove_s(void *dst, size_t dstSize, const void *src, size_t maxCount); Функция rneппnove_s() копирует первые maxcount байтов из массива src в массив dst. В параметре dstSize указывается размер массива dst в байтах. Если копиро­ вание прошло успешно. функция возвращает значение о. Пример: ldefine ARR SIZE 5 int arr[ARR_SIZE] = {1, 2, 3, 4, 5), *р = О; р=arr+2; memmove_s(p, sizeof arr, arr, 3 * sizeof(int)); for(inti=О;i<ARR_SIZE;++i){ printf("%d ", arr[i]); 1//12123 printf("\n"); 6.13. Сравнение массивов Д1U1 сравнения массивов предназначена функция rnerncmp (). Она сравнивает первые size байтов массивов bufl и buf2. В качестве значения функция возвращает: CJ отрицательное число - если ьuп меньше buf2; CJ о - если массивы равны; □ положительное число - если bufl больше buf2. Прототип функции: tinclude <string.h> int merncmp(const void *bufl, const void *buf2, size t size); Пример: intarrl[ЗJ={1,2,3),arr2(3]={1,2,3),х=О; х = rnerncmp(arrl, arr2, sizeof arr2); printf("%d\n", х); //о arrl(2] = 2; // arrl[] = {1, 2, 2), arr2[] {1, 2, 31 х = memcmp(arrl, arr2, sizeof arr2); printf("%d\n", Х); // -1 arrl [2] = 4; // arrl[] = {1, 2, 41, arr2[] {1, 2, 3} х = rnemcmp(arrl, arr2, sizeof arr2); printf("%d\n", х); /!1 Функция merncmp < J производит сравнение с учетом регистра символов. Если необхо­ димо произвести сравнение без учета символов, то можно воспользоваться функ­ циями _mernicrnp() и _rnernicrnp_1 () . Для сравнения русских. букв следует настроить локаль. Прототипы функций: linclude <string.h> linclude <locale.h>
204 int _memic:rnp(const void *bufl, const.void *buf2, size_t size); int _memic:rnp_l(const void *bufl, const void *buf2, size t size, locale_t locale); Главаl Предназначение параметров и возвращаемое значение такое же, как у функЦН11 memcmp() . Функция _memicmp_1() позволяет дополнительно задать локаль. Пример использования функций: setlocale(LC_ALL, "Russian_Russia.1251"); // Настройка локали char strl [] = "абв", str2 [] = "АБВ"; intх=О; х = _memic:rnp(strl, str2, sizeof str2); printf("%d\n", х); //О х = memc:rnp(strl, str2, sizeof str2); printf("%d\n", х); //1 locale t locale = _create_locale(LC_ALL, "Russian_Russia.1251"); х = _memic:rnp_l(strl, str2, sizeof str2, locale); printf("%d\n", х); //О free_locale(locale); 6.14. Переворачивание массива В языке С нет функции, позволяющей изменить порядок следования элементов массива, а в некоторых случаях это может пригодиться. Давайте потренируемс1 и напишем функцию reverse(), позволяющую перевернуть массив, состоящий ю целых чисел (листинг 6.8). fЛисти�r 6.8. Переворачивание массива #include <stdio.h> #define ARR SIZE 5 void reverse(int *pArr, int length); void print_array(int *pArr, int length); int main(void) { int arr[ARR_SIZE] = {1, 2, 3, 4, 5}; print_array(arr, ARR_SIZE); // 1 2 3 4 5 reverse(arr, ARR_SIZE); print_array(arr, ARR_SIZE); // 5 reverse(arr, ARR_SIZE); print_array(arr, ARR_SIZE); // 1 return О; void reverse(int *pArr, int length) if (!pArr 1 1 length < 1) return; inttmp=О; 4321 2345 J
Массивы 205 for(inti=О,j=length-1;i<j;++i, --j)( tmp = pArr[i]; pArr[i] = pArr[j]; pArr[j] = tmp; void print_array(int *pArr, int length) if ( !·pдrr 1 1 length < 1) return; for(inti=О;i<length;++i)( printf("%d ", pArr[i]); printf("\n"); Вначале переменной i присваиваем индекс первого элемента, а перем�нной j - индекс последнего элемента. На каждой итерации цикла будем увеличивать значе­ ние переменной i и уменьшать значение переменной j. Цикл будет выполняться, пока i меньше j. На каждой итерации цикла мы просто меняем местами значения двух элементов массива, предварительно сохраняя значение в промежуточной переменной tmp.
ГЛАВА 7 Символы и С-строки В языке С доступны два типа строк: С-строка и L-строка. С-строка является мас­ сивом однобайтовых символов (тип char), последний элемент которого содержит нулевой символ ('\О'). Обратите внимание на то, что нулевой символ (нулевой байт) не имеет никакого отношения к символу 'о'. Коды этих символов разные. L-строка является массивом широких символов (тип wchar_t), последний элемеtп которого содержит нулевой символ. 7.1. Объявление и инициализация отдельного символа Для хранения символа используется тип char. Переменной, имеющей тип char. можно присвоить числовое значение (код символа) или указать символ внутри апо­ строфов. Обратите внимание на то, что использовать кавычки нельзя, т. к . в этом случае вместо одного символа будет два: собственно сам символ плюс нулевой символ. Пример: char chl, ch2; chl = 119; // Буква w ch2 = 'w'; // Буква w // Выводим символ printf("%c\n", chl); printf("%c\n", ch2); // Выводим код символа printf("%d\n", chl); printf("%d\n", ch2); //w //w // 119 // 119 Внутри апострофов можно указать спецuШ1ьные символы - комбинации знаков, соответствующих служебным или непечатаемым символам. Перечислим специаль­ ные символы, доступные в языке С: □ \О - нулевой символ; □ \n - перевод строки; □ \r - возврат каретки;
Символы и С-строки [] \t - горизонтальная табуляция; [] \v - вертикальная табуляция; [] \а - звуковой сигнал; [] \Ь - возврат на один символ; [] \f - перевод формата; [] \' - апостроф; □ \" - кавычка; □ \? - знак вопроса; □ \\ - обратная косая черта; □ \N - восьмеричное значение N; □ \xN - шестнадцатеричное значение N. Пример указания восьмеричного и шестнадцатеричного значений: char chl, ch2; chl = '\167'; // Восьмеричное значение ch2 = '\х77'; // Шестнадцатеричное значение printf("%c\n", chl); printf("%c\n", ch2); //w //w 207 По умолчанию тип char является знаковым и позволяет хранить диапазон значений or -128 до 127. Если перед типом указано ключевое слово unsigned, то диапазон будет от о до 255. Когда переменной присваивается символ внутри апострофов, он автоматически преобразхется в соответствующий целочисленный код. Пример вывода размера и диапазона значений: // Выводим размер в байтах printf("%d\n", (int) sizeof(char)); // 1 // #include <limits.h> printf("%d\n", CНAR_HIN); // -128 printf("%d\n", СНАR_МАХ); // 127 printf("%d\n", UСНАR_МАХ); // 255 printf("%d\n", CНAR_BIT); //8 Тип char занимает в памяти 1 байт (8 бит). Если тип является знаковым, то старший бит содержит признак знака: о соответствует положительному числу, а 1 - отрица­ тельному. Если тип является беззнаковым, то признак знака не используется. Это следует учитывать при преобразовании знакового типа в беззнаковый, т. к. старший бит станет причиной больших значений: char chl = -1; unsigned char ch2 = (unsigned char)chl; printf("%u\n", ch2); // 255 Символы, имеющие код меньше 120 (занимают 7 бит), соответствуют кодиров­ ке ASCII. Коды этих символов одинаковы практически во всех однобайтовых коди­ ровках. В состав кодировки ASCIJ входят цифры, буквы латинского алфавита,
208 Символ dec oct hex \0 о о о \а 7 7 7 \Ь 8 10 8 \t 9 11 9 \n 10 12 а \v 11 13 ь \f 12 14 с \r 13 15 d пробел 32 40 20 ! 334121 " 344222 # 354323 $ 364424 % 374525 & 384626 ' 394727 405028 ) 415129 * 42522а + 43532Ь , 44542с - 45552d 46562е 47572f о 486030 1 496131 2 506232 516333 4 526434 536535 546636 556737 8 567038 9 577139 : 5872За ГлаваJI Таблица 7.1 . Коды основных символов в кодировке ASCI Символ dec oct hex ; 5973зь < 6074Зс = 61753d > 62763е ? 63773f @ 6410040 А 65 101 41 в 66 102 42 с 67 103 43 D 68 104 44 Е 69 105 45 F 70 106 46 G 71 107 47 н 72 110 48 I 73 111 49 J 74 112 4а к 75 113 4Ь L 76 114 4с м 77 115 4d N 78 116 4е о 79 117 4f р 80 120 50 Q 81 121 51 R 82 122 52 s '83 123 53 т 8412454 u 85 125 55 V 8612656 w 87 127 57 х 88 130 58 у 89 131 59 z 90 132 5а [ 91 133 5Ь \ 92 134 5с ·--·-- ] 193 135'5d : Символ dec oct л 94 136 95 137 96 140 а 97 141 ь 98 142 ·- с 99 143 d 100 144 е 101 145 ----- --- -- f g h i j k 1 m n о р q r s t и V w х у z { 1 } - DEL 102 146 103 147 104 150 105 151 106 152 107 153 108 154 109 155 ---f- 110 111 112 113 114 115 116 -- --- -- 117 118 119 120 121 122 - ------ 123 124 156 157 160 161 162 163 164 1------ -- ·- 165 166 167 170 �-- 171 172 --···--- 173 174 --- · --- -- 125 175 126 176 127 177 he• 5е 5f 60 61 62 63 64 65 66 67 68 69 6а 6Ь 6с 6е 6f 70 71 72 73 74 - ··-- 75 76 77 78 --- 79 7а --- 7Ь --- 7с 7d 7е 7f --- ---- · - - - - -- - ·-- ---- --
Символы и С-строки 209 знаки препинания и некоторые служебные символы (например. перенос строки, табуляция и т. д.). Коды основных символов в кодировке ASCII в десятичном (dec), восьмеричном (oct) и шестнадцатеричном (hex) виде приведены в табл. 7 .1. Восьмой бит предназначен для кодирования символов национальных алфавитов. У типа char эти символы имеют отрицательные значения (старший бит содержит признак знака). Таким образом, тип char позволяет закодировать всего 256 сим­ волов. Для кодирования букв русского языка предназначено пять кодировок - windows- 1251 (cpl251), windows-866 (ср866), iso8859-5, koi8-r и mac-cyrillic. Проблема за­ ключается в том, что код одной и той же русской буквы в этих кодировках может быть разным. Из-за этого возникает множество проблем. Например, при выводе русских букв в консоли может отобразиться нечитаемый текст. Причина искажения русских букв заключается в том, что по умолчанию в окне консоли используется 1Содировка ср866. Коды русских букв и некоторых символов в кодировках ср866 и cpl25l в десятичном (unsigned и signect), восьмеричном (oct) и шестнадцатеричном (hex) виде приведены в табл. 7 .2. Обратите внимание на то, что коды букв "ё" и "Ё" выпадают из последовательности кодов. Таблица 7.2. Коды русских букв и некоторых символов в кодировках срВбб и ср1251 'ср866 Символ ,_______ unsigned signed oct hex unsigned А 128 - 128 200 80 192 --- в 129 �127 201 81 193 в 130 - 126 202 82 194 г 131 -125 203 83 195 - --- --- ..... ·-- --· - --- д 132 - 124 204 84 196 ----- Е 133 -123 205 85 197 -- -- -- ----·-- t 240 -16 360 fO 168 ж 134 -122 206 86 198 з 135 -121 207 87 199 -- ---- --1--· - --- - · --- и 136 -120 210 88 200 ·· · ·- 137 -11 9 211 89 201 - ------- - ·· к 138 - 118 212 8а 202 --� - --- · -------- - ·- -· • · ·-·----·· л 139 - 117 213 8Ь 203 -- -- - - м 140 - 116 214 8с 204 --· -- -- н 141 -115 215 8d 205 о 142 -114 216 8е 206 �-- � - - --- -- -- п 143 -113 1217 8f 207 ср1251 signed l- - -т-- ---· -- oct hex -64 -63 -- -62 -61 ·---- -- - --- -60 -- -59 --- --------- - -88 -58 -57 ------ - - - -- -56 --------·- -55 ··· · - · ·· · -·· -54 -· ·- - -- - - -53 ------ - - -52 �-- -· · -- -51 -50 -- · · · · · ---- - - · · -49 300 301 -----·- 302 303 -------- 304- 305 -- - 250 306 307 со -·- -- с1 --···-- -· с2 сЗ - --- -· с4 ·-----. с5 -··-- ·- а8 -- с6 с7 ,_ ____ _ --· --- - 310 с8 �- -- -- -- - , - · - · ·- - - - 311iс9 � ---- 1- -- - �12-+�_ � 31�1-� 314 се �-- - - - -- ·---. 315 cd ·- - --- 316 се -- - . , _., --- !317 i cf !
210 Глава 1 Таблица 7.2 (продолжение) ср866 ср1261 Символ unslgned slgned oct hex unsigned slgned oct hex р 144 -112 220 90 208 -48 320 d0 с 145 - 111 221 91 209 -47 321 d1 т 146 -110 222 92 210 -46 322 d2 у 147 -109 223 93 211 -45 323 dЗ ф 148 -108 224 94 212 -44 324 d4 х 149 - 107 225 95 213 -43 325 d5 ц 150 -106 226 96 214 -42 326 d6 ч 151 -105 227 97 215 -41 327 d7 ш 152 - 104 230 98 216 -40 330 d8 щ 153 - 103 231 99 217 -39 331 d9 ъ 154 -102 232 9а 218 -38 332 da ы 155 -10 1 233 9Ь 219 -37 333 db ь 156 -100 234 9с 220 -36 334 dc э 157 -99 235 9d 221 -35 335 dd ю 158 -98 236 9е 222 -34 336 de я 159 -97 237 9f 223 -33 337 df а 160 -96 240 ао 224 -32 340 еО б 161 -95 241 а1 225 -31 341 е1 в 162 -94 242 а2 226 -30 342 е2 г 163 -93 243 аз 227 -29 343 еЗ д 164 -92 244 а4 228 -28 344 е4 е 165 -91 245 а5 229 -27 345 е5 ё 241 -15 361 f1 184 -72 270 Ь8 ж 166 -90 246 а6 230 -26 346 е6 з 167 -89 247 а7 231 -25 347 е7 и 168 -88 250 а8 232 -24 350 ев й 169 -87 251 а9 233 -23 351 е9 к 170 -86 252 аа 234 -22 352 еа л 171 -85 253 аЬ 235 -21 353 еЬ м 172 -84 254 ас 236 -20 354 ее н 173 -83 255 ad 237 -19 355 ed о 174 -82 256 ае 238 -18 356 ее
Символы и С-строки 211 Таблица 7.2 (продолжение) ср866 ср1251 Символ unslgned slgned oct hex unsigned slgned oct hex п 175 -81 257 af 239 -17 357 ef р 224 -32 340 ео 240 -16 360 ю с 225 -31 341 е1 241 -15 361 f1 т 226 -30 342 е2 242 -14 362 f2 у 227 -29 343 е3 243 -13 363 fЗ ф 228 -28 344 е4 244 -12 364 f4 х 229 -27 345 е5 245 -11 365 f5 ц 230 -26 346 е6 246 -10 366 f6 ч. 231 -25 347 е7 247 -9 367 f7 ш 232 -24 350 ев 248 -8 370 f8 щ 233 -23 351 е9 249 -7 371 f9 ъ 234 -22 352 еа 250 --6 372 fa ы 235 -21 353 еЬ 251 -5 373 fЬ ь 236 -20 354 ее 252 :--4 374 fc э 237 -19 355 ed 253 -3 375 fcl ю 238 -18 356 ее 254 -2 376 fe я 239 -17 357 ef 255 -1 377 ff € 242 -14 362 f2 170 -86 252 аа е: 243 -13 363 f3 186 -70 272 Ьа о 248 -8 370 f8 176 -80 260 ьо No 252 -4 374 fc 185 -71 271 Ь9 ,, - - - - 132 -1 24 204 84 ... - - - - 133 -123 205 85 € - - - - 136 - 120 210 88 �о - - - - 137 - 119 211 89 - - - 145 -111 221 91 - - - - 146 - 110 222 92 \\ - - - - 147 - 109 223 93 ,, - - - - 148 -1 08 224 94 - - - - - 150 -1 06 226 96 - - - - - 151 -105 227 97 - - - - 153 - 103 231 99
212 Глава1 Таблица 7.2 (окончаниеJ ср866 ср1251 Символ unslgned signed oct hex unsigned signed oct hea - - - - 166 -90 246 а6 ' § - - - - 167 -89 247 а7 © - - - - 169 -87 251 а9 « - - - - 171 -85 253 аЬ -, - - - - 172 -84 254 ас ® - - - - ' 174 -82 256 ае ± - - - - 177 -79 261 Ь1 --- )) - - - - 187 -69 273 ьь 7.2. Настройка локали При изменении регистра русских букв может возникнуть проблема. Для того чтобw ее избежать, необходимо правильно настроить локш,ь (совокупность локальных настроек системы). Для настройки локали используется функция setlocale(). П� тотип функции: #include <locale.h> char *setlocale(int category, const char *locale); В первом параметре указывается категория в виде числа от о до 5 или соответст­ вующий числу макрос: □ о - 1с_ALL - устанавливает локаль для всех категорий; □ 1 - 1с_COLLATE - для сравнения строк; □ 2 - 1с_СТУРЕ - для перевода символов в нижний или верхний регистр; □ з - 1с_MONETARY - для отображения денежных единиц; □ 4 - 1с _NUМERIC - для форматирования дробных чисел; □ 5 - 1с_ТIМЕ - для форматирования вывода даты и времени. Во втором параметре задается название локали в виде строки, например: rus., russian или Russian_Russia. В этом случае будет задана кодировка, используемu в системе по умолчанию: char *pLocale = setlocale(LC_ALL, "Russian_Russia"); if (pLocale) { printf("%s\n", pLocale); // Russian Russia.1251 else { puts("He удалось настроить локаль");
Символы и С-строки 213 Для того чтобы использовать другую кодировку, нужно указать ее название после точки, например ".1251" или "Russian _Russia.1251": char *pLocale = setlocale(LC_ALL, "Russian_Russia.1251"); if (pLocale) { printf("%s\n", pLocale); // Russian Russia.1251 else { puts("He удалось настроить локаль"); Вместо названия можно указать пустую строку. В этом случае будет использовать­ ся локаль, настроенная в системе: char *pLocale = setlocale(LC_ALL, ""); if (pLocale) { printf("%s\n", pLocale); // Russian_Russia.1251 В качестве значения функция возвращает указатель на строку с названием локали, соответствующей заданной категории, или нулевой указатель в случае ошибки. Если во втором параметре передан нулевой указатель, то функция возвращает ука­ затель на строку с названием текущей локали: char *pLocale = setlocale(LC_�L, NULL); if (pLocale) { printf("%s\n", pLocale); // С Для настройки локали можно также использовать функцию _wsetlocale(). Прото­ тип функции: linclude <locale.h> wchar_t *_wsetlocale(int category, const wchar_t *locale); Параметры аналогичны одноименным параметрам функции setlocale(), но во вто­ ром параметре указывается L..:строка с названием локали и возвращается указатель на L-строку: wchar_t *pLocale = _wsetlocale(LC_ALL, L"Russian_Russia.1251"); if (pLocale) { wprintf(L"%s\n", pLocale); // Russian_Russia.1251 После настройки локали многие функции будут работать не так, �ак раньше. На­ пример, от настроек локали зависит вывод функции printf(): printf("%.2f\n", 2.5); // 2.50 setlocale(LC_NUMERIC, "dutch"); printf("%.2f\n", 2.5); // 2,50 Некоторые функции позволяют указать локаль в качестве параметра, например, функция _printf _1 (): int _printf_l(const char *format, locale t locale, .. . );
214 Глава 1 Во втором параметре функция принимает структуру _locale_t с настройками лока­ ли. Создать эту структуру позволяет функция_create _locale(). Прототип функции: #include <locale.h> locale_t _c re ate_locale(int category, const char *locale); Все параметры аналогичны одноименным параметрам функции setlocale(). Ее.,. структуру создать не удалось, то функция возвращает нулевой указатель. В Visua\ С вместо функции _create _locale() можно воспользоваться функциеl _wcreate_locale(). Обратите внимание: функция недоступна в MinGW. ПротоТИ8 функции: #include <locale.h> locale_t _ wcreate_locale(int category, const wchar_t *locale); Создать структуру _locale_t с настройками текущей локали позволяет функЦИ1 _get_current_locale(). Прототип функции: #include <locale.h> locale_t _get_current_locale(void); Когда структура больше не нужна, ее следует освободить с помощью функlUОI _ free _locale() . Прототип функции: #include <locale.h> void _free_locale(_locale_t locale); Пример настройки локали и вывода текущего названия локали приведен в листин­ ге 7.1. 1 Ли�тинг 7.1 . Настройка ло�и #include <stdio.h> #include <locale.h> int rnain(void) ( char *pLocale = setlocale(LC ALL, NULL); if (pLocale) { printf("%s\n", pLocale); // С printf("%.2f\n", 3.14); // 3.14 pLocale = setlocale(LC_NUМERIC, "dutch"); if (pLocale) { printf("%s\n", pLocale); // Dutch Netherlands.1252 printf("%.2f\n", 3.14); // 3,14 pLocale = setlocale(LC_ALL, "Russian_Russia"); if (pLocale) { printf("%s\n", pLocale); // Russian Russia.1251
Символы и С-строки pLocale = setlocale(LC_ALL, "Russian_Russia.866"); if (pLocale) { printf("%s\n", pLocale); // Russian Russia.866 locale t locale с=_create_locale(LC_NUМERIC, "С"); _printf_l("%.2f\n", locale_c� 3.14); // 3.14 _free_locale(locale_c); locale t locale de create_locale(LC_NUМERIC, "dutch"); _printf_l("%.2f\n", locale_de, 3.14); // 3,14 free_locale(locale_de); return О; 215 Функция localeconv() позволяет получить информацию о способе форматирования вещественных чисел и денежных сумм для текущей локали. Прототип функции: linclude <locale.h> struct lconv *localeconv(void); Функция возвращает указатель на структуру lconv. Пример вывода символа деся­ тичного разделителя для локали Russian_Russia.1251: setlocale(LC_ALL, "Russian Russia.1251"); struct lconv *р = localeconv(); if(р){ printf("%s\n", p->decimal_point); Объявление структуры lconv выглядит следующим образом: struct lconv { char *decimal_point; char *thousands_sep; char *grouping; char *int_curr _symЬol; // Десятичный разделитель (",") // Разделитель тысяч (" ") // Способ группировки значений // Название валюты ("RUB") char *currency_symЬol; // Символ валюты ("р. ") char *mon decimal_point; // Десятичный разделитель для // денежных сумм(",") char *mon_thousands_sep; // Разделитель тысяч для денежных // сумм(" ") char *mon_grouping; char *positive_sign; char *negative_sign; char int_frac_digits; char frac_digits; char p_cs_precedes; // Способ группировки для денежных сумм // Положительный знак для денежных сумм // Отрицательный знак для денежных сумм("-") // Количество цифр в дробной части для // денежных суммв международном формате (2) // Количество цифр в дробной части для // денежных сумм в национальном формате (2) // 1 - если символ валюты перед значением // О - если символ валюты после значения
216 Главаl }; char p_sep_by_space; // 1 - если символ валюты отделяется пробелом // О - в противном случае // p_cs_precedes и p_sep_by_space применяются // для положительных значений char п_cs_precedes; // 1 - если символ валюты перед значением // О - если символ валюты после значения char п_sep_by_space; // 1 - если символ валюты отделяется пробелом // О - в противном случае // п_cs_precedes и n_sep_by_space применяются // для отрицательных значений char p_sign_posп; // Позиция символа положительного значения char п_sign_posn; // Позиция символа отрицательного значения В Visua\ С доступны также следующие поля структуры lconv: wchar t *_W _decimal_poiпt; wchar t *_W _thousands_sep; wchar t *_W_int_curr_symЬol; wchar t *_W _currency_symЬol; wchar t *_W _rnon _decimal_point; wchar t *_W _rnon_thousands_sep; wchar t *_W_positive_sign; wchar t *_W _negative_sign; 7.3. Изменение регистра символов Для изменения регистра символов предназначены следующие функции. □ toupper() и _toupper_ 1 () - возвращают код символа в верхнем регистре. ЕСЛ11 преобразования регистра не было, то код символа возвращается без изменений. Прототипы функций: #include <ctype.h> int toupper(iпt ch); int _toupper_l(int ch, locale t locale); Пример: // #include <string.h> setlocale(LC ALL, "Russian_Russia.1251"); printf("%c\n", (char)toupper( 'w')); //w printf("%c\n", (char)toupper((unsigned char)'б')); // Б char str[] = "аб вгдеёжзийклмнопрстуфхцчunцьыьэюя"; for (int i = О, len = (int) strlen(str); i < len; ++i) { str[i] = (char)toupper((unsigned char)str[i]); printf("%s\n", str); // АБВГдЕ�ИЙКЛМНОПРСТУФХЦЧ!ШЦЬЬIЬЭЮЯ
Символы и С-строки 217 □ tolower() и _tolower_1() :__ возвращают код символа в нижнем регистре. Если преобразования регистра не было, то код символа возвращается без изменений. Прототипы функций: #include <ctype.h> int tolower(int ch); int _tolower_l(int ch, locale t locale); Пример: // #include <string.h> setlocale(LC_ALL, "Russian_Russia.1251"); printf("%c\n", (char)tolower('w')); //w printf("%c\n", (char)tolower('W')); //w printf("%c\n", (char)tolower((unsigned char)'Б')); // б char str[] = "АБВГДЕЁЖЗИЙЮIМНОПРСТУФХЦЧIШЦЬЫЬЭЮЯ"; for (int i = О, len = (int) strlen(str); i < len; ++i) { str[i] = (char)tolower((unsigned char)str[i]); printf("%s\n", str); // абвгдеёжзийклмнопрстуфхцЧIШЦ'ЬЫЬэюя □ _strupr() - заменяет все буквы в С-строке str соответствующими прописными буквами. Функция возвращает указатель на строку str. Прототип функции: #include <string.h> char *_strupr(char *str); Пример: setlocale(LC_ALL, "Russian_Russia.1251"); char str [] = "абвгдеёжэийклмнопрстуфхцчllШfЬl:,IЬэюя"; _strupr(str); printf("%s\n", str); // АБВГДЕtжзИЙЮIМНОПРСТУФХЦЧ]lПЦЬЫЬЗОЯ Вместо функции _strupr() лучше использовать функцию _strupr_s (). Прототип функции: #include <string.h> errno_t _strupr_s(char *str, size_t strSize); В параметре strsize указывается размер строки. Функция возвращает значе­ ние о, если операция успешно выполнена, или код ошибки. Пример: setlocale(LC_ALL, "Russian_Russia.1251"); char str[40] = "абвгдеёжэийклмнопрстуфхцЧIШЦ'ЬЫЬэюя"; _strupr_s(str, 40); printf("%s\n", str); // АБВГДЕЁЖЗИЙЮIМНОПРСТУФХЦЧ!ШЦЬЫЬЗОЯ □ _strlwr() - заменяет все буквы в С-строке str соответствующими строчными буквами. Функция возвращает ук�атель на строку str. Прототип функции:
218 #include <string.h> char *_strlwr(char *str); Пример: setlocale(LC_ALL, "Russian_Russia.1251"); char str [] = "АБВГдЕЁЖЗИЙКЛМНОПРСТУФХЦЧIIШ.{ЬЬlЬЭЮЯ"; _ strlwr(str); printf("%s\n", str); // абвгдеёжзийклмнопрстуфхцчnnцъыьэюя Глава! Вместо функции _strlwr() лучше использовать функцию _strlwr_s(). ПротоТ8 функции: #include <string.h> errno_t _strlwr_s(char *str, size_t strSize); В параметре strSize указывается размер строки. Функция возвращает зна11& ние о, если операция успешно выполнена, или код ошибки. Пример: setlocale(LC_ALL, "Russian_Russia.1251"); char str[40] = "АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧIШЦЪЫЬЭЮЯ"; _ strlwr_s(str, 40); printf("%s\n", str); // абвгдеёжзийклмнопрстуфхцч11ПЦЪЫЬэюя 7.4. Проверка типа содержимого символа Для проверки типа содержимого символа предназначены следующие функции. □ isdigit( 1 и _isdigit_l( 1 - возвращают ненулевое значение, если символ яв.J�. ется десятичной цифрой, в противном случае- о. Прототипы функций: #include <ctype.h> int isdigit(int ch); int _isdigit_l(int ch, locale_t locale); Для русских букв необходимо настроить локаль или указать ее в парамеl]JС locale. Пример: setlocale(LC_ALL, "Russian_Russia.1251"); printf("%d\n", isdigit( 'w')); printf("%d\n", isdigit('2')); //о //1 printf("%d\n", isdigit((unsigned char)'б')); // О □ isxdigit() и _isxdigit_l(I - возвращают ненулевое значение, если симва.J является шестнадцатеричной цифрой (число от о до 9 или буква от А до :: регистр не имеет значения), в противном случае - о. Прототипы функций: #include <ctype.h> int isxdigit(int ch); int _isxdigit_l(int ch, locale t locale);
Символы и С-строки 219 Для русских букв необходимо настроить локаль или указать ее в параметре locale. Пример: setlocale(LC_ALL, "Russian_Russia.1251"); printf("%d\n", isxdigit('8')); // 128 printf("ld\n", isxdigit('a')); // 128 printf("%d\n", isxdigit('F')); // 128 printf("%d\n", isxdigit('g')); // О □ isalpha() и _isalpha_1() - возвращают ненулевое значение, если символ явля­ ется буквой, в противном случае - о. Прототипы функций: #include <ctype.h> int isalpha(int ch); int _isalpha_l(int ch, _locale_t locale); Для русских букв необходимо настроить локаль или указать ее в параметре locale. Пример: setlocale(LC_ALL, "Russian_Russia.1251"); printf("%d\n", isalpha( 'w')); // 258 printf("%d\n", isalpha('2')); //о printf("%d\n", isalpha((unsigned char)'б')); // 258 printf("%d\n", isalpha((unsigned char) 'Б')); // 257 □ isspace() и _isspace_1() - возвращают ненулевое значение, если символ явля­ ется пробельным символом (пробелом, табуляцией, переводом строки или воз­ вратом каретки), в противном случае - о . Прототипы функций: #include <ctype,h> int isspace(int ch); int isspace_l(int ch, _locale_t locale); Для русских букв необходимо настроить локаль или указать ее в параметре locale. Пример: setlocale(LC_ALL, "Russian_Russia.1251"); printf("%d\n", isspace( 'w')); //о printf("%d\n", isspace('\n')); //8 printf("%d\n", isspace('\t')); //8 printf("%d\n", isspace((unsigned char)'б')); // о □ isalnurn() и _isalnurn_l() - возвращают ненулевое значение, если символ явля­ ется буквой или цифрой, в противном случае - о. Прототипы функций: #include <ctype.h> int isalnurn(int ch); int _isalnurn_l(int ch, _locale_t locale); Для русских букв необходимо настроить локаль или указать ее в параметре locale. Пример: setlocale(LC_ALL, "Russian_Russia.1251"); printf("%d\n", isalnurn ( 'w')); // 258
220 printf("%d\n", isalnurn('8')); printf("%d\n", isalnurn('\t')); //4 //о printf("9,d\n", isalnurn((unsigned char)'б')); // 258 Глава 7 □ islower() и _islower_l() - возвращают ненулевое значение, если символ явля­ ется буквой в нижнем регистре, в противном случае - о . Прототипы функций: #incl11de <ctype.h> int islower(int ch); int islower l(int ch, locale t locale); Для русских букв необходимо настроить локаль или указать ее в параметре locale. Пример: setlocale(LC__ALL, "Russian Russia.1251"); printf("%d\n", islower('w')); //2 printf("%d\n", islower('8')); //о printf("%d\n", islower('\t')); //о printf("%d\n", islower((unsigned char)'б')); // 2 printf("%d\n", islower((unsigned char)'Б')); // о □ isupper( J и _isupper_1( J - возвращают ненулевое значение, если символ явля­ ется буквой в верхнем регистре, в противном случае - о. Прототипы функций: #include <ctype.h> int isupper(int ch); int isupper_l(int ch, locale t locale); Для русских букв необходимо настроить локаль или указать ее в параметре locale. Пример: setlocale(LC_ALL, "Russian_Russia.1251"); printf("%d\n", isupper('W')); //1 printf("%d\n", isupper('8')); //о printf("%d\n", isupper('\t')); //о printf("%d\n", isupper((unsigned char)'б')); // о printf("%d\n", isupper((unsigned char)'Б')); // 1 □ ispunct() и _ispunct_l() - возвращают ненулевое значени�, если символ явля­ ется символом пунктуации, в противном случае - о . Прототипы функций: #include <ctype .h> int ispunct(int ch); int ispunct l(int ch, locale_t locale); Для русских букв необходимо настроить локаль или указать ее в параметре locale. Пример: setlocale(LC_ALL, "Russian_Russia.1251"); printf(",;d\n", ispunct('B')); // о printf("%d\n", ispunct('f')); // о printf("%d\n", ispunct(', ')); // 16 printf("%d\n", ispunct(' . ')); // 16 printf("%d\n", ispunct(' ' ) ) ; // о
Символы и С-строки 221 □ isprint() и _isprint_l() - возвращают ненулевое значение, если символ яв­ ляется печатаемым (включая пробел), в противном случае- о. Прототипы функций: #include <ctype.h> int isprint(int ch); int _isprint_l(int ch, _locale_t locale); Для русских букв необходимо настроить локаль или указать ее в параметре locale. Пример: //4 //о setlocale(LC_ALL, "Russian_Russia.1251"); printf("%d\n", isprint('8')); printf("%d\n", isprint('\x5')); printf("%d\n", isprint(' ')); // 64 char)'б')); // 258 printf("%d\n", isprint((unsigned □ isgraph() и _isgraph _1 () - возвращают ненулев. значение, если символ явля­ ется печатаемым (пробел печатаемым не считается), в противном случае- о. Прототипы функций: #include <ctype.h> int isgraph(int ch); int _isgraph_l(int ch, _locale_t locale); Для русских букв необходимо настроить локаль или указать ее в параметре locale. Пример: setlocale(LC_ALL, "Russian_Russia.1251"); printf("%d\n", isgraph('8')); //4 printf("%d\n", isgraph('\х5' )) ; //о printf("%d\n", isgraph(' ')); //о printf("%d\n", isgraph((unsigned char)'б')); // 258 □ iscntrl() и _iscntrl_l() - возвращают ненулевое значение, если символ явля­ ется непечатаемым, в противном случае- о. Прототипы функций: #include <ctype.h> int iscntrl(int ch); int _iscntrl_l(int ch, locale t locale); - - Для русских букв необходимо настроить локаль или указать ее в параметре locale. Пример: setlocale(LC_ALL, "Russian_Russia.1251"); printf("%d\n", iscntrl('8')); // О printf("%d\n", iscntrl('\x5')); // 32 printf("%d\n", iscntrl(' ')); // О □ isЫank() . - возвращает ненулевое значение, если символ является пробельным (пробел, горизонтальная табуляция и др.), в противном случае- о. Прототип функции:
222 #include <ctype.h> int isЬlank(int ch); Для русских букв необходимо настроить локаль. Пример: setlocale(LC_ALL, "Russian_Russia .1251"); printf("%d\n", isЫank('8')); //О printf("%d\n", isЬlank('\t')); //1 printf("%d\n", isЫank(' ')); //1 Глава1 Обратите внимание на то, что перед русскими буквами указывается операция при, ведения к типу unsigned char. Если это не сделать, то производится попытка прео& разования к типу unsigned int. Поскольку коды русских букв по умолчанию име!ОI отрицательные значения, знаковый бит станет причиной большого значения, кото­ рое выходит за рамки диапазона значений для типа char. Так, для буквы "б" зна� ние будет равно 4294967265. Пример: printf("%u\n", (unsigned intNunsigned char)'б'); //225 printf("%u\n", (unsigned int)'б'); //4294967265 7.5. Объявление и инициализация С-строки С-строка является массивом символов (тип thar), последний элемент которого со, держит нулевой символ ('\ о'). Обратите внимание на то, что нулевой символ (Н)" левой байт) не имеет никакого отношения к символу 'о'.• Коды этих символов рв ные. Объявляется С-строка так же, как и массив элементов типа char: char str[7]; При инициализации С-строки можно перечислить символы внутри фигурных с11,."О­ бок: char str[7] = {'S', ' t' _ , 'r ', 'i', 'n', 'g', '\0'1; printf("%s\n", str); //String или указать строку внутри двойных кавычек: char str[7] = "String"; printf("%s\n", str); //String При использовании двойных кавычек следует учитывать, что длина строки на 0Д111 символ больше, т. к. в конец будет автоматически вставлен нулевой символ. Ec,;w это не предусмотреть и объявить массив из шести элементов вместо семи, то эта приведет к ошибке. Если размер массива при объявлении не указать, то он будет определен автомапt­ чески в соответствии с длиной строки: char str[] = "String"; printf("%d\n", (int) sizeof(str)); //7 printf("%s\n", str); //String
Символы и С-строки 223 Обратите внимание на то, что присваивать строку в двойных кавычках можно только при инициализации. Попытка присвоить строку позже приведет к ошибке: char str[7]; str = "String"; // Ошибка!! ! В этом случае нужно воспользоваться функцией strcpy() или strcpy_s(): char str[7]; strcpy_s(str, 7, "String"); printf("%s\n", str); // String Внутри строки в двойных кавычках можно указывать специальные символы (на­ пример,\n, \r и др.), которые мы уже рассматривали в разд. 7.1 . Если внутри стро­ п встречается кавычка, то ее необходимо экранировать с помощью обратного слэша: setlocale(LC_ALL, "Russian_Russia.1251"); char str[] = "Группа \"I<ино\''\n"; printf("%s", str); // Группа "Кино" Если внутри строки встречается обратный слэш, то его необходимо экранировать. Эrо обязательно следует учитывать при указании пути к файлу: char strl[] = "С:\\temp\\new\\file. txt"; // Правильно char str2[] = "C:\temp\new\file.txt"; //Неправильно!!! printf("%s\n", strl); // C:\temp\new\file.txt printf("%s\n", str2); // Строк� с ошибками!!! Обратите внимание на вторую строку. В этом пути присутствуют сразу три спе­ циальных символа: \t, \n и \f. После преобразования специальных символов путь будет выглядеть следующим образом: С:<Табуляция>еmр<Перевод строки>еw<Перевод фopмaтa>ile.txt Разместить С-строку при инициализации на нескольких строках нельзя. Переход на новую строку вызовет синтаксическую ошибку: char str[] = "stringl string2"; // Ошибка!!! Для того чтобы разместить С-строку на нескольких строках, следует перед симво­ лом перевода строки указать символ\: char str[] = "stringl \ string2 \ stringЗ"; // После символа \ не должно бьrrь никаких символов printf("%s\n", str); // stringl string2 stringЗ Кроме того, можно воспользоваться неявной конкатенацией. Если строки распо­ . ложены подряд внутри одной инструкции, то они объединяются в одну большую строку. Пример: char str[] = "stringl" •string2" "stringЗ"; printf("%s\n", str); // stringlstring2string3
224 Главаl Строку можно присвоить указателю, но в этом случае строка будет доступна тот.­ ко для чте11ия. Попытка изменить какой-либо символ внутри строки приведе1 к ошибке при выполнении: const char *р = "string"; printf("%s\n", р); // string Мы можем объявить несколько указателей и присвоить им одинаковую строку, Ri все они будут ссылаться на один и тот же адрес. Иными словами, строка, указанна1 в программе внутри двойных кавычек, в памяти существует в единственном экзеw­ пляре. Если бы была возможность изменить такую строку, то она изменилась бw во всех указателях. Поэтому строка, присвоенная указателю, доступна только д..w чтения: const char *pl const char *р2 "string"; "string"; const char *рЗ "string"; printf("%p %р �p\n", pl, р2, р3); // 0000000000409030 0000000000409030 0000000000409030 Объявление массива строк выглядит так: char str[] [20] = {"Stringl", "String2", "String3"}; printf("%s\n", str[О]); // Stringl printf("%s\n", str [1]); // String2 printf("%s\n", str[2] 1; // String3 или так: char *str[] = { "Stringl", "String2", "String3"}; printf("�s\n", str[О]); // Stringl printf("�s\n", str [ 1]); // String2 printf("i\s\n", str[2]); // String3 Обратите внимание: при использовании первого способа мы можем измениn. символы в строках, а вот при использовании второго способа попытка измененИJ1 символа приведет к ошибке при выполнении программы. Так что эти способw объявления массива строк не эквивалентны. 7 .6. Доступ к символам внутри С-строки После определения С-строки в переменной сохраняется адрес первого символа. Иными словами, название переменной является указателем, который ссылается на первый символ строки. Поэтому доступ к символу в строке может осуществлятьа как по индексу (нумерация начинается с нуля), указанному внутри квадратных скобок, так и с использованием адресной арифметики. Например, следующие две инструкции вывода являются эквивалентными: char str[] = "строка"; printf("%c\n", str[l]); //т printf("%c\n", *(str + 1}); // т
Символы и С-строки Символ можно не только получить таким обра!ом, но и иэменить: char str[] = "строка"; str[О] = 'с'; // Изменение с помощью индекса •(str + 1) = 'Т'; // Изменение с помощью указателя printf("%s\n", str); // строка 225 Обратите внимание на то, что отдельный символ указывается внутри апострофов, а • внутри кавычек. Если указать символ внутри кавычек, то вместо одного символа iудет два: собственно сам символ плюс нулевой символ. Объ,�вить указатель и присвоить ему адрес строки можно следующим обрезом: char str[] = "строка"; char *р = NULL; р=str; --р= 'С'; ++р; // Перемещаем указатель на второй символ --р= 'Т'; printf("%s\n", str); // строка ,Обратите внимание на то, что перед названием строки не указывается оператор &, т. к . название переменной содержит адрес первого символа. Если ИС{lользовать еператор ,, то необходимо дополнительно указать индекс внутри квадратных ско­ бок: р = &str[O]; // Эквивалентно: р = str; .Jlpи инициализации указателя ему можно присвоить строку. Такие строки нельзя 18111СНЯТЬ, поэтому обычно перед типом указывают ключевое слово const. Пример: const char *str = "String"; printf("%s\n", str); // String 7.7. Определение длинь1 строки Получить длину строки можно с помощью функции strlen (). Прототип функции: linclude <string.h> �ize_t strlen(const char *str); 41'ункци• strlen() возвращает количество символов без учета нулевого символа. Тип size_t объявлен как беззнаковое целое: typedef unsigned _int64 size_t; ldefine �int64 long long Пример: char str[] = "строка"; int len = (int)strlen(str); printf("%d\n", len); //6 printf("%d\n", (int) sizeof(str)); // 7
226 char *р = str; printf("%d\n", (int) sizeof(p)); // Значение в проекте Test64c: 8 Глава 7 Обратите внимание: длина строки и размер символьного массива - это разные вещи. Длина строки - это количество символов с начала массива до первого ну.,е­ вого символа. Размер сшwвольного массива - это общее количество символов, дос­ тупное под строку. Мы можем получить размер символьного массива с помощЫС' оператора sizeof, указав имя переменной, но если присвоить ее адрес указателю. тt' мы получим размер указателя, а не символьного массива. 7.8. Перебор символов С-строки Для перебора символов удобно использовать цикл for. В первом параметре пе� менной-счетчику присваивается значение о (символы в строке нумеруются с ну.,я L условием продолжения является значение переменной-счетчика меньще количества символов в строке. В третьем параметре цикла for указывается приращение на е.1� ницу на каждой итерации цикла. Выведем все символы строки в прямом и обрат­ ном порядке: char str[] = "String"; int len = (int)strlen(str); // Выводим символы в прямом порядке for(inti=О;i<len;++i) printf("%c\n", str[i]); puts("------------------- "); // Выводим символы в обратном порядке for(inti=len-1;i>=О;--i){ printf("%c\n", str[i]); В этом примере количество символов сохраняется в переменной len вне цик.u Если функцию strlen () указать внутри условия, то вычисление количества симв,._� лов будет выполняться на каждой итерации цикла. Поэтому количество симво.,ое лучще получать вне цикла или присваивать значение переменной в первом парr метре цикла for. Пример: char str[] = "String"; for (int i = О, len = (int)strlen(str); i < len; ++i) { printf("%c\n", strfi]); Перебор символов С-строки с помощью указателя и цикла for выполняется так: char str[] = "String"; for (char *р = str; *р; ++р) { printf("%c\n", *р);
Символы и С-строки 227 В этом примере начальным значением является адрес первого символа. Условием продолжения цикла служит значение, на которое ссылается указатель. Любой сим­ вол трактуется как Истина, кроме нулевого символа, который имеет код о. Так как С-строка завершается нулевым символом, то этот символ вернет значение ложь в цикл завершится. В третьем параметре цикла for указывается приращение указа­ теля на единицу на каждой итерации цикла. В этом случае используются правила адресной арифметики. Вместо цикла for всегда можно использовать цикл while: char str[] = "String"; char *р = str; while (*р) { printf("%c\n", *р++); р = str; // Восстанавливаем положение указателя Вначале объявляются строка и указатель, которому присваивается адрес первого символа. Цикл while выполняется до тех пор, пока значение, на которое ссьтается указатель, не равно нулевому символу. Внутри цикла while выводится символ, на l[()ТОрый ссьшается указатель, а затем указатель перемещается на следующий сим- 80Л (*р++). Выражение р++ возвращает текущий адрес, а затем увеличивает его. Символ * позволяет получить доступ к символу по указанному адресу. Последова­ тельность выполнения соответствует следующей расстановке скобок: *(р++ J или JIВYMинструкциям:*рир=р +1. 7.9. Основные функции для работы с С-строками Перечислим основные функции для работы с С-строками. □ strlen о - возвращает количество символов в С-строке без учета нулевого символа. Прототип функции: #include <string.h> size_t strlen(const char *str); Тип size_t объявлен как беззнаковое целое число: typedef unsigned int64 size t; #define int64 long l6ng - Пример: char str[7] = "строка"; int len = (int)strlen(str); printf("%d\n", len); //6,ане7 Определить общий размер массива можно с помощью оператора sizeof. Опера­ тор возвращает размер в байтах. Так как тип char занимает 1 байт, следователь­ но, размер в байтах будет равен размеру массива. Пример:
228 Глава 7 char str[20] = "строка"; printf("%d\n", (int) sizeof(str)); // 20 □ strnlen() - возвращает количество символов в С-строке без учета нулевоГС' символа. Прототип функции: #include <string .h> size t strnlen(const char *str, size_t maxCount); Во втором параметре указывается размер символьного массива. Если нулевоi символ не встретился в числе maxcount символов, то возвращается значение maxCount. Пример: char str[7] = "строка"; int len = (int)strnlen(str, 7); printf("%d\n", len); // 6. □ strcpy( J - копирует символы из С-строки source в С-строку dest и вставляет нулевой символ. В качестве значения функция возвращает указатель на стро�..� dest. Если строка source длиннее строки dest, то произойдет переполнение буфера. Прототип функции: #include <string.h> char *strcpy(char *dest, const char *source); Пример использования функции: char str[7]; strcpy(str, "String"); printf("%s\n", str); // String Вместо функции strcpy( J лучше использовать функцию strcpy_s( J. Протопm функции: #include <string.h> errno_t strcpy_s(char *dest, rsize_t sizeinBytes, const char *source); Функция strcpy_s() копирует символы из строки source в строку dest и вставл.­ ет нулевой символ. В параметре sizeinBytes указывается максимальное количе­ ство элементов массива dest. Пример: #define SIZE 7 char str[SIZE]; strcpy_s(str, SIZE, "String"); printf("%s\n", str); // String □ strncpy() - копирует первые count символов из С-строки source в С-стро� dest. В качестве значения функция возвращает указатель на строку dest. Ее.,■ строка source длиннее строки dest, то произойдет переполнение буфера. Про� тип функции: #include <string .h> char *strncpy(char *dest, const char *source, size t count);
Символы и �траки 229 Если количество символов в строке source меньше числа count, то строка dest · б удет дополнена нулевыми символами, а если больше или равно, то копируются только count символов, при этом нулевой символ автоматически не вставляется. Пример копирования шести символов: char str[7]; strncpy(str, "String", 6); str[б] = '\О'; // Нулевой символ автоматически не вставляется printf("%s\n", str); // String Вместо функции strncpy() лучше использовать функцию strncpy_s(). Прототип функции: *include <string.h> errno_t strncpy_s(char *dest, size_t sizeinBytes, const char *source, size_t maxCount); Функция strncpy_s() копирует первые maxCount символов из строки source в строку dest и вставляет нулевой символ. В параметре sizeinВytes указывается максимальное количество элементов массива dest. Пример: tdefine SIZE 7 char str[SIZE); strncpy_s(str, SIZE, "String", 5); prlntf("%s\n", str); // Strin С] strcat() - копирует символы нз С-строки source в конец С-строки dest и вставляет нулевой символ. В качестве значения функция возвращает указатель на строку dest. Если строка dest имеет недостаточный размер, то произойдет переполнение буфера. Прототип функции: *include <string.h> char *strcat(char *dest, const char *source); Пример использования функции: char str[20] = "ст"; strcat(str, "ро"); strcat(str, "ка"); printf("%s\n", str); // строка Обратите внимание на то, что перед копированием в строке dest обязательно должен быть нулевой символ. Локальные переменные автоматически не ини­ циализируются, поэтому этот код приведет к ошибке: char str[20]; // Локальная переменная strcat(str, "строка . "); // Ошибка, нет нулевого сиывола! Для того чтобы избавиться от ошибки, нужно произвести инициализацию стро­ ки нулями следующим образом: char str[20J = {О}; // инициализация нулями strcat(str, "строка"); // Все нормально printf("%s\n", str); // строка
230 Глава 7 Вместо функции strcat() лучше использовать функцию strcat_s(). Протопи; функции: #include <string.h> errno_t strcat_s(char *dest, rsize t sizeinBytes, const char *source); Функция strcat_s() копирует символы из строки source в конец строки deo: В параметре sizeinBytes указывается максимальное количество элементов мас­ сива dest. Пример: #define SIZE 20 char str[SIZE] = "ст"; strcat_s(str, SIZE, "ро"); strcat_s(str, SIZE, "ка"); printf("%s\n", str); // строка □ strncat() - копирует первые count символов из С-строки source в конец строки dest и вставляет нулевой символ. В качестве значения функция возвращает указатель на строку dest. Обратите внимание на то, что перед копированием в строке dest обязательно должен быть нулевой символ. Если строка dest имеет недостаточный размер, то произойдет переполнение буфера. Протопtп функции #include <string.h> char *strncat(char *dest, const char *source, size t count); Пример использования функции: char str[20] = "ст"; strncat(str, "ром", 2); strncat(str, "какао", 2); printf("%s\n", str); // строка Вместо функции strncat() лучше использовать функцию strncat_s(). Прототип функции: #include <string.h> errno t strncat_s(char *dest, size_t sizeinBytes, const char *source, size_t maxCount); Функция strncat_s() копирует первые maxCount символов из строки source в ко­ нец строки dest. В параметре sizeinBytes указывается максимальное количестве элементов массива dest. Пример: #define SIZE 20 char str[SIZE] = "ст"; strncat_s(str, SIZE, "ром", 2); strncat_s(str, SIZE, "какао", 2); printf("%s\n", str); // строка □ _strdup() - создает копию строки в динамической памяти и возвращает указа­ тель на нее. Прототип функции: #include <string.h> char *_strdup(const char *str);
Символы и С-строки 231 Память выделяется с помощью функции malloc(), поэтому после использования строки следует освободить память с помощью функции free(). Пример: // #include <stdlib.h> setlocale(LC_ALL, "Russian_Russia.1251"); char str[] = "строка", *р = NULL; р = _strdup(str); if(р){ printf("%s\n", р); // строка free (р); □ strtok() - разделяет С-строку str на подстроки, используя в качестве раздели­ телей символы из С-строки deli.rn. При первом вызове указываются оба парамет­ ра. В качестве значения возвращается указатель на первую подстроку или нуле­ вой указатель, если символы-разделители не найдены в С-строке str. Для того чтобы получить последующие подстроки, необходимо каждый раз вызывать функцию strtok(), указывая в первом параметре нулевой указатель, а во втором параметре те же самые символы-разделители. Обратите внимание: функция из­ меняет исходную строку str, вставляя вместо символов-разделителей нулевой символ. Прототип функции: #include <string.h> char *strtok(char *str, const char *delim); Пример использования функции: char str[] = "a,b.c=d", *р = NULL; р = strtok(str, ",. ="); while (р) { printf("%s-", р); р = strtok(NULL, ",. ="); // Результат: a-b-c -d ­ printf("\n"); // Функция изменяет исходную строку! ! ! for (int i = О, len = (int)sizeof(str); i < len; ++i) { printf("%d ", str[i]); //97О98О99О100О printf("\n"); Вместо функции strtok() лучше использовать функцию strtok_ s(). Прототип функции: #include <string.h> char *strtok_s(char *str, const char *delirn, char **context); Первые два параметра аналогичны параметрам функции strtok(). В параметре context передается адрес указателя. Обратите внимание: функция изменяет ис­ ходную строку str, вставляя вместо символов-разделителей нулевой символ. Пример:
232 Глава 7 char str[] = "а Ь c,d", *р = NULL, *context NULL; р = strtok_s(str, " while (р) { printf("%s-", р); 11 ,, р = strtok_s(NULL, " // Результат: a-b -c-d - printf("\n"); &context); " , , &context); // Функция изменяет исходную строку!!! for (int i = О, len = (int)sizeof(str); i < len; ++i) { printf("%d ", str[i]); )//97О98О99О100О printf("\n"); Практически все функции без суффикса _s, которые мы рассмотрели в этом разде­ ле, в Visual С выводят предупреждающее сообщение warning С4996 , т. к. по умо.1- чанию функции не производят никакой проверки корректности данных. В это-. случае возможно переполнение буфера. Забота о корректности данных полностью лежит на плечах программиста. Если вы уверены в своих действиях, то можно по­ давить вывод предупреждающих сообщений. Сделать это можно двумя способами: □ определить макрос с названием _CRT_SECURE_No_WARNINGS в самом начале про­ граммы, перед подключением заголовочных файлов. Пример: #define CRT SECURE NO WARNINGS - - - - #include <stdio.h> #include <string.h> □ вставить прагму warning. Пример: #pragma warning( disaЬle : 4996) 0&РАТИТЕ ВНИМАНИЕ После директив #define и #pragrna точка с запятой не указывается. 7.1 О. Поиск и замена в С-строке Для поиска и замены в С-строке предназначены следующие функции. □ strchr() - ищет в С-строке str первое вхождение символа ch. В качестве зна­ чения функция возвращает указатель на первый найденный символ в С-строке str или нулевой указатель, если символ не найден. Прототип функции: #include <string.h> char *strchr(const char *str, int ch); Пример использования функции: setlocale(LC_ALL, "Russian_Russia.1251"); char str[] = "строка строка", *р = NULL; р = strchr(str, 'к');
Символы и С-строки if(р)( printf("Индeкc: %d\n", (int)(р - str)); 1 // Индекс: 4 233 □ strrchr <) - ищет в С-строке str последнее вхождение символа ch. В качестве значения функция возвращает указатель на символ или нулевой указатель, если символ не найден. Прототип функции: #include <string.h> char *strrchr(const char *str, int ch); Пример: setlocale(LC_ALL, "Russian_Russia.1251"); char str [] = "строка строка", *р = NULL; р = strrchr(str, 'к'); if(р)( printf("Индекс: %d\n", (int)(р - str)); 1 // Индекс: 11 □ mernchr( J - ищет в строке str первое. вхождение символа ch. Максимально про­ сматривается rnaxcount символов. В качестве значения функция возвращает ука­ затель на первый найденный символ в строке str или нулевой указатель, если символ не найден. Прототип функции: #include <string.h> void *memchr(const void *str, int ch, size_t rnaxCount); Пример: char str[J = "строка строка", *р = NULL; р = rnernchr(str, 'к', strlen(str)); if(р)( printf("Индeкc: %d\n", (int)(р - str)); } // Индекс: 4 □ strpbrk() - ищет в С-строке str первое вхождение одного из символов (нуле­ воti символ не учитывается), входящих в С-строку control. В качестве значения функция возвращает указатель на первый найденный символ или нулевой указа­ тель, если символы не найдены. Прототип функции: #include <string.h> char *strpbrk(const char *str, const char *control); Пример: setlocale(LC_ALL, "Russian_Russia.1251"); char str [] = "строка строка", *р = NULL; р = strpbrk(str, " кр"); if(р)( printf("Индекс: %d\n", (int)(р - . str)); 1 // Индекс: 2 (индекс первой буквы "р")
234 Глава 7 □ strcspn() - возвращает индекс первого символа в С-строке str, который совпа­ дает с одним из символов, входящих в С-строку control. Прототип функции: #include <string.h> size_t strcspn(const char *str, const char *control); Пример: setlocale(LC_ALL, "Russian_Russia.1251"); char str[] = "строка строка"; int index = (int)strcspn(str, "кр"); printf("Индeкc: %d\n", index); // Индекс: 2 (индекс первой буквы "р") index = (int)strcspn(str, "ф"); printf ("Индекс: %d\n", index); // Индекс: 13 (длина строки, если ничего не найдено) □ strspn() - возвращает индекс первого символа в С-строке str, который не сов­ падает ни с одним из символов, входящих в С-строку control. Прототип функ­ ции: #include <string.h> size_t strspn(const char *str, const char *control); Пример: setlocale(LC_ALL, "Russian_Russia.1251"); char str[] = "строка строка"; int index = (int)strspn(str, "окстр"); printf("Индекс: %d\n", index); // Индекс: 5 ("а" не входит в "окстр") □ strstr() - ищет в С-строке str первое вхождение целого фрагмента из С-стро­ ки subStr. В качестве значения функция возвращает указатель на первое вхож­ дение фрагмента в строку или нулевой указатель - в противном случае. Прото­ тип функции: #include <string.h> char *strstr(const char *str, const char *suЬStr); Пример: setlocale(LC_ALL, "Russian_Russia.1251"); char str[] = "строка строка", *р = NULL; р = strstr(str, "ока"); if(р){ printf("Индекс: %d\n", (int)(р - str)); 1 // Индекс: 3 □ strset() - заменяет все символы в С-строке str указанным символом с:-.. Функция возвращает указатель на строку str. Прототип функции: #include <string.h> char * strset(char *str, int ch);
Символы и С-строки Пример: setlocale(LC_ALL, "Russian Russia .1251"); char str[] = "строка"; _ strset(str, '*'); printf("'%s'\n", str); // '******' 235 Вместо функции _strset() лучше использовать функцию _strset_s(). Прототип функции: #include <string.h> errno_t _ strset_s(char *str, size_t strSize, int ch); В параметре strSize указывается размер строки. Функция возвращает значе­ ние о, если операция успешно выполнена, или код ошибки. Пример: setlocale(LC_ALL, "Russian_Russia.1251"); char str[l0] = "строка"; _ strset_s(str, 10, '*'); printf("'%s'\n", str); // '******' □ _strnset() - заменяет первые count символов в С-строке str указанным симво­ лом ch. Функция возвращает указатель на строку str. Прототип функции: #include <string.h> char *_strnset(char *str, int ch, size t count); Пример: setlocale(LC_ALL, "Russian_Russia.1251"); char str[] = "строка"; _ strnset(str, · '*', 5); printf("'%s'\n", str); // '*****а' Вместо функции _strnset() лучше использовать функцию _strnset_s(). Прото­ тип функции: #include <string.h> errno_t _strnset_s(char *str, size_t strSize, int ch, size_t count); в параметре strSize указывается размер строки. Функция возвращает значе­ ние о, если операция успешно выполнена, или код ошибки. Пример: setlocale(LC_ALL, "Russian_Russia.1251"); char str[l0] = "строка"; _strnset_s(str, 10, '*', 4); printf('"%s'\n", str); // '****ка' □ memset() - заменяет первые count элементов массива dest символом ch. В ка­ честве значения функция возвращает указатель на массив dest. Прототип функ­ ции: #include <string.h> void *memset(void *dest, int ch, size t count);
236 Пример: char str[] = "String"; memset(str, '*', 4); printf("'%s'\n", str); // ' ****ng' Глава 1 □ _strrev() - меняет порядок следования символов внутри С-строки на противо­ положный, как бы переворачивает строку. Функция возвращает указатель на строку str. Прототип функции: #include <string.h> char *_strrev(char *str); Пример: setlocale(LC_ALL, "Russian_Russia.1251"); char str[] = "строка"; _strrev(str); printf("%s\n", str); // акортс □ _strupr() - заменяет все символы в С-строке str соответствующими пропис­ ными буквами. Функция возвращает указатель на строку str. Прототип функ­ ции: #include <string.h> char *_strupr(char *str); Пример: setlocale(LC_ALL, "Russian_Russia.1251"); char str[] = "абвгдеёжзийклмнопрстуфхцчшщьыьэюя"; _ strupr(str); printf("%s\n", str); // АБВГдЕ�ИЙЮ1МНОПРСТУФХЦЩШЦЬЫЬЭЮЯ Вместо функции _strupr() лучше использовать функцию _strupr_s(). Прототип функции: #include <string.h> errno_t _ strupr_s(char *str, size_t strSize); В параметре strSize указывается размер строки. Функция возвращает значе­ ние о, если операция успешно выполнена, или код ошибки. Пример: setlocale(LC_ALL, "Russian_Russia.1251"); char str[40] = "абвгдеёжзийклмнопрстуфхцчI1ПЦЪЫьэюя"; _strupr_s(str, 40); printf("%s\n", str); // АБВГДЕ�ИЙКJIМНОПРСТУФХЦЩШЦЬЬIЬЭЮЯ □ _strlwr() - заменяет все символы в С-строке str соответствующими строчны­ ми буквами. Функция возвращает указатель на строку str. Прототип функции: #include <string.h> char *_strlwr(char *str);
Символы и С-строки Пример: setlocale(LC_ALL, "Russian_Russia.1251"); char str [] = "АБВГдЕ�ИЙКЛМНОПРСГУФХЦЧIШЦЬЬIЬЭЮЯ"; _ strlwr(str); printf("%s\n", str); // абвгдеёжзийклмнопрстуфхцЧIШЦ'WЬэюя 237 Вместо функции _strlwr() лучше использовать функцию _strlwr_s(). Прототип функции: #include <string.h> errno_t _strlwr_s(char *str, size_t strSize); В параметре strSize указывается размер строки. Функция возвращает значе­ ние о, если операция успешно выполнена, или код ошибки. Пример: setlocale(LC_ALL, "Russian_Russia.1251"); char str[40] = "АБВГдЕООИЙКЛМНОПРСГУФХWШПЦЫlliЭЮЯ"; _ strlwr_s(str, 40); printf(J'%s\n", str); // абвгдеёжзийклмнопрстуфхцчwщъыьэюя 7.11. Сравнение С-строк Для сравнения С-строк предназначены следующие функции. □ stranp( > - сравнивает С-строку strl с С-строкой str2 без учета настроек лока­ ли и возвращает одно из значений: • отрицательное число - если strl меньше str2; • о - если строки равны; • положительное число - если strl больше str2. Сравнение производится с учетом регистра символов. Прототип функции: #include <string.h> int strcmp(const char *strl, const char *str2); Пример: char strl [] = "абв", str2 [] = "абв", strЗ[] "АБВ"; printf("%d\n", stranp(strl, str2)); // О printf("%d\n", stranp(strl, strЗ)); // 1 strl[2] = 'б'; // strl [] = "абб", str2 (] "абв"; printf("%d\n", stranp(strl, str2)); // -1 str1[2] = 'г'; // strl[] = "абг", str2() = "абв"; printf("%d\n", stranp(strl, str2)); // 1 □ strnanp() - сравнивает count первых символов в С-строках strl и str2. Если нулевой байт встретится раньше, то значение параметра count игнорируется. Функция возвращает одно из значений:
238 • отрицательное число - если strl меньше str2; • о - если строки равны; • положительное число - если strl больше str2. Сравнение производится с учетом регистра символов. Прототип функции: #include <string.h> int strncrnp(const char *strl, const char *str2, size t count); Пример: char strl[] = "абв", str2[] = "абг"; printf("%d\n", strncrnp(strl, str2, 2)); // О printf("%d\n", strncrnp(strl, str2, 3)); // -1 Глава" □ strcoll() - функция аналогична функции strcrnp(), но сравнение производит� с учетом значения 1с_COLLATE в текущей локали. Например, буква "ё" в диапазон между буквами "е" и "ж" не попадает, т. к. буква "ё" в кодировке windows-1251 имеет код -72, буква "е" - -27, а буква "ж" - -2 6 . Если сравнение производите� с помощью функции strcrnp(), то буква "е" будет больше буквы "ё" ( -27 > - - � L При использовании функции strcoll() с настройкой локали Russian_Russia. 1.::: буква "ё" попадет в диапазон между буквами "е" и "ж", т. к . используются.,� кальные настройки по алфавиту. Если локаль не настроена, то эта функция эк­ вивалентна функции strcrnp(). Сравнение производится с учетом регистра си�­ волов. Можно также воспользоваться функцией _strcoll_1 (), которая позволяет задать локаль в третьем параметре. Прототипы функций: #include <string.h> int strcoll(const char *strl, const char *str2); int _strcoll_l(const char *strl, const char *str2, locale t locale); Пример: char strl [] = "е", str2 [] "ё"; printf("%d\n", strl [О]); printf("%d\n", str2 [О]); printf("%d\n", strcrnp(strl, str2)); printf("%d\n", strcoll(strl, str2)); // Без настройки локали: е (код -27) // Настройка локали // -27 // -72 //1 //1 больше setlocale(LC_ALL, "Russian_Russia.1251"); printf("%d\n", strcmp(strl, str2)); // 1 printf("%d\n", strcoll(strl, str2)); // -1 // После настройки локали: е меньше ё (е) (ё) ё (КОД -72) □ _strncoll() и _strncoll_1 () - сравнивают maxCount первых символов в С-стро­ ках strl и str2. Сравнение производится с учетом регистра символов. Функции аналогичны функции strncmp(), но сравнение вьшолняется с учетом значения 1с_со11дтЕ в текущей локали. Прототипы функций:
Символы и �троки #include <string.h> int _strncoll(const char *strl, const char *str2, size t maxCount); int _strncoll_l(const char *strl, const char *str2, Пример: size t maxCount, locale t locale); setlocale(LC_ALL, "Russian_Russia.1251"); char strl[] = "абв", str2[] = "абг"; printf("%d\n", _strncoll(strl, str2, 2)); // О printf("%d\n", _strncoll(strl, str2, 3)); // -1 239 □ strxfrm() - преобразует С-строку source в строку специального формата и за­ писывает ее в dest. В конец вставляется нулевой символ. Записывается не более JЩ1xCount символов. Если количество символов maxcount меньше необходимого количества символов после преобразования, то содержимое dest не определено. В качестве значения функция возвращает число необходимых символов. Для того чтобы просто получить количество необходимых символов (без учета нуле­ вого символа), в параметре maxcount указывается число о, а в параметре dest передается нулевой указатель. Можно также воспользоваться функцией _ strxfrm_1(), которая дополнительно позволяет задать локаль в последнем па­ раметре. Прототипы функций: #include <string.h> size t strxfrm(char *dest, const char *source, size t maxCount); size t _strxfrm_l(char *dest, const char *source, size_t maxCount, _locale_t locale); Функция strcoll <), прежде чем произвести сравнение, неявно выполняет преоб­ разование переданных строк в строки специального формата. Функция strxfrm() позволяет произвести такое преобразование явным образом. Строка преобразу­ ется с учетом значения LC_coLLATE в текущей локали. Если локаль не настроена, то просто выполняется копирование. В дальнейшем строки специального фор­ мата можно сравнивать с помощью функции strcmp 1) • Результат сравнения будет соответствовать результату сравнения с помощью функции strcoll <). Функцию strxfrm{) следует использовать, если сравнение строк производится многократно. Пример: setlocale(LC_ALL, "Russian_Russia.1251"); intх=О; char strl[] = "е", str2[] = "ё"; char bufl[l0] = {О), buf2[10] = {О}; х = (int)strxfrm(NULL, strl, О); printf("%d\n", х); strxfrm(bufl, strl, 10); strxfrm{buf2, str2, 10); printf{"%d\n", strcmp(strl, str2)); printf("%d\n", strcoll(strl, str2)); printf("%d\n", strcmp(bufl, buf2)); //6(нуженбуфер6+1) // 1 ("е" больше "ё") // -1 ("е" меньше "ё") // -1 {"е" меньше "е")
240 Глава 7 □ stricmp() и _stricrnp_1() - сравнивают С-строки без учета регистра символ0&­ Функции учитывают настройки локали для категории 1с_СТУРЕ. Для русскю. букв необходимо настроить локаль. Прототипы функций: #include <string.h> int _stricrnp(const char *strl, const char *str2); int stricrnp_l(const char *strl, const char *str2, locale t locale); Пример: setlocale(LC_ALL, "Russian_Russia.1251"); char strl [] = "абв", str2 [] = "АБВ"; printf("%d\n", _stricrnp(strl, str2)); // О □ _strnicrnp() и _strnicrnp_1() - сравнивают rnaxcount первых символов в С-с� ках strl и str2 без учета регистра символов. Функции учитывают настройКII локали для категории 1с_СТУРЕ. Для русских букв необходимо настроить лока.11,,.. Прототипы функций: #include <string.h> int strnicrnp(const char *strl, const char *str2, size t rnaxCount); int _strnicrnp_l(const char *strl, const char *str2, size t rnaxCount, locale t locale); Пример: setlocale(LC_ALL, "Russian_Russia.1251"); char strl[] = "абве", str2[] = "АБВЖ"; printf("%d\n", strnicmp(strl, str2, З)); // О printf("%d\n", _strnicmp(strl, str2, 4)); // -1 □ _stricoll() и _stricoll_l() - сравнивают С-строки без учета регистра сим� лов. Функции учитывают настройки локали для категорий 1с_СТУРЕ и 1с_COLIA� Для русских букв необходимо настроить локаль. Прототипы функций: #include <string.h> int _stricoll(const char *strl, const char *str2); int _stricoll_l(const char *strl, const char *str2, locale t locale); Пример: char strl[] = "абв", str2[] = "АБВ"; printf("%d\n", stricoll(strl, str2)); // 32 locale t locale = create_locale(LC_ALL, "Russian_Russia.1251"); printf("%d\n", _stricoll_l(strl, str2, locale)); // О free_locale(locale); setlocale(LC_ALL, "Russian_Russia.1251"); printf("%d\n", _stricoll(strl, str2)); //о □ _strnicoll() и _strnicoll_1 () - сравнивают rnaxcount первых символов в С-� ках strl и str2 без учета регистра символов. Функции учитывают настройки локали для категорий 1с_СТУРЕ и 1с_COLIATE. Для русских букв необходимо настроить локаль. Прототипы функций:
Символы и С-строки #include <string.h> int _strnicoll(const char *strl, const char *str2, size_t ma):CC . 01.шt); int _strnicoll_l(const char *strl, const char *str2, Пример: size_t maxCount, locale t locale); setlocale(LC_ALL, "Russian_Russia.1251"); char strl[] = "абвг", str2[] = "АБВД"; printf("%d\n", _strnicoll(strl, str2, З)); // О printf("%d\n", _strnicoll(strl, str2, 4)); // -1 241 Для сравнения строк можно также использовать функцию memcmp(). Функция шemcmp () сравнивает первые size байтов массивов bufl и buf2. В качестве значения функция возвращает: □ отрицательное число - если bufl меньше buf2; □ о - если массивы равны; □ положительное число - если bufl больше buf2. Прототип функции: linclude <string.h> int memcmp(const void *bufl, const void *buf2, size t size); Пример: char strl[] = "аЬс", str2[] = "аЬс"; int х = memcmp(strl, str2, sizeof str2); printf("%d\n", х); str1[2] = 'Ь'; х = memcmp(strl, str2, sizeof str2); printf("%d\n", х); str1[2] = 'd'; х = memcmp(strl, str2, sizeof str2); printf("%d\n", х); //о // -1 //1 Функция memcmp() производит сравнение с учетом регистра символов. Если необхо­ димо произвести сравнение без учета символов, то можно воспользоваться функ­ циями _memicmp() и _memicmp_1 (). Для сравнения русских букв следует настроить локаль. Прототипы функций: linclude <string.h> int _memicmp(const void *bufl, const void *buf2, size_t size); int _memicmp_l(const void *bufl, const void *buf2, size t size, _locale_t locale); Предназначение параметров и возвращаемое значение такое же, как у функции memcmp(). Функция _�emicmp_1 () позволяет дополнительно задать локаль. Пример использован�я функций:
242 setlocale(LC_ALL, "Russian_Russia.1251"); char strl [] = "абв", str2 [] = "АБВ"; int х = _rnernicrnp(strl, str2, sizeof str2); printf("%d\n", х); //О х = rnerncrnp(strl, str2, sizeof str2); printf("%d\n", х); //1 locale t locale = _create_locale(LC_ALL, "Russian_Russia.1251"); х = _rnernicrnp_l(strl, str2, sizeof str2, locale); printf("%d\n", х); free locale(locale); !/о 7.12. Форматирование С-строк Глава 7 Выполнить форматирование С-строки, а таюке преобразовать значения элементар­ ных типов в С-строку можно с помощью функции sprintf(). Существует также функция _sprintf_l(), которая позволяет дополнительно задать локаль. Прототипы функций: #include <stdio.h> int sprintf(char *buf, const char *forrnat, . ..); int sprintf_l(char *buf, const char *forrnat, locale_t locale, . ..); В параметре forrnat указывается строка специального формата. Внутри этой строки можно указать обычные символы и спецификаторы формата, начинающиеся с си\1- вола %. Спецификаторы формата совпадают со спецификаторами, используемыми в функции printf() (см. разд. 2.8). Вместо спецификаторов формата подставляютсJ1 значения, указанные в качестве параметров. Количество спецификаторов должно совпадать с количеством переданных параметров. Результат записывается в буфер. адрес которого передается в первом параметре (buf). В качестве значения функцИJ1 возвращает количество символов, записанных в символьный массив. Пример: setlocale(LC_ALL, "Russian_Russia.1251"); char buf[50] = ""; intх=100,count=О; count = sprintf(buf, "х printf("%s\n", buf); printf("%d\n", count); %d11 , х); //х=100 //7 Функция sprintf() не производит никакой проверки размера буфера, поэтому воз­ можно переполнение буфера. Вместо функции sprintf() следует использовать функцию sprintf_s() или_sprintf_s _ l(). Прототипы функций: #include <stdio.h> int sprintf_s(char *buf, size_t sizeinBytes, const char iforrnat, ... ); int _sprintf_s_l(char *buf, size_t sizeinBytes, const char *forrnat, locale t locale, ...);
Символы и С-строки 243 Параметры buf и format аналогичны параметрам функции sprintf(). В параметре sizeinBytes указывается размер буфера. В качестве значения функции возвращают количество символов, записанных в символьный массив. Пример: setlocale(LC_ALL, "Russian_Russia.1251"); char buf[SO] = ""; int count = О; douЬle pi = З.14159265359; count = sprintf_s(buf, 50, "pi %.2f", pi); printf("%s\n", buf); //pi=3,14 printf("%d\n", count); //9
ГЛАВА 8 Широкие символы и L-строки Помимо обычttых символов, имеющих тип char, язык С поддерживает ишрт.:w си.нволы, имеющие тип wchar_ t. Строки, состоящие из широких символов, \IЫ будем называть L-строкаш1. Прежде чем мы начнем рассматривать широкие символы и L-строки, попробуйте за11устить этот код и сравните результат: // #include <stdio.h> // #include <string.h> wchar_t str[] = L"строка string"; for (int i = О, len = (int)wcslen(str); i < len; ++i) ( printf("%u ", str[i]); printf ( "\n"); // 1089 1090 1088 1086 1082 1072 32 115 116 114 105 110 103 Если получили точно такие же числа, то можно начать изучение. Если же чис.,а отличаются, то следует проверить кодировку файла с программой - она должна быть windo\vs-1251. Хотя на самом деле кодировка файлов по умолчанию в МinG\\ должна быть UTF-8, но в этом случае вы не сможете работать с обычными строка­ ми, т. к . русские буквы в кодировке UTF-8 кодируются двумя байтами, а не одни�. Для того чтобы избежать проблем с русскими буквами, для файлов мы используе"' кодировку \Vindows-1251, а не UTF-8. Если при компиляции полу•1или эту ошибку: err·or: converting to execution character set: Illegal byte sequence то следует явным образом указать кодировку файла с программой и кодиров"-: символов в С-строке. Для этого в MinGW предназначены следующие флаги: □ -finput-charset - задает кодировку файла с программой; □ -fexec-charset - определяет кодировку символов в С-строке; □ -fwide-exec-charset - задает кодировку •:имволов в L-строке.
Широкие символы и L-строки Команда компиляции в командной строке должна выглядеть так: C:\book>gcc -Wall -Wconversion -03 -finput-charset=cpl251 -fexec-charset=cpl251 -о test.exe test.c C:\Ьoox>test.exe 1089 1090 1088 1086 1082 1072 32 115 116 114 105 110 103 Если получили следующую ошибку: ccl.exe: error: no iconvimplementation, cannot convert frorn ср1251 to DТF-8 245 то нужно либо сменить компилятор, т. к. он собран без поддержки библиотеки iconv, либо использовать для файлов кодировку UTF-8 и не указывать флаги - finput-charset И -fexec-charset при КОМПИЛЯЦИИ. Для того чтобы задать флаги в Eclipse, в свойствах проекта из списка слева выбира­ ем пункт С/С++ Build I Settings. На отобразившейся вкладке из списка Configuration выбираем пункт AII configurations, а затем на вкладке Tool Settings из списка выбираем пункт GCC С Compiler I Miscellaneous (см. рис. 1 .35). В поле Otber Oags через пробел к имеющемуся значению добавляем следующие инструк­ ции: - finput-charset=cp1251 -fexec-charset=cp1251 Содержимое поля Other Oags после изменения должно быть таким: -с - frnessage--ength=0 -finput-charset=cp1251 -fexec-charset=cp1251 Эти флаги должны быть в окне Console при компиляции в Eclipse: 07:22:28 **** Incremental Build of configuration Debug for project Test64c **** Info: Internal Builder is used for build qcc �оо -g3 -Wall -Wconversion -с -frnessage-length=0 -finput-charset=cp1251 -fexec-charset=cp1251 -о "src\\Test64c.o" " .. \\src\\Test64c.c" gcc -о Test64c.exe "src\\Test64c.o" 07:22:29 Build Finished. О errors, О warnings. (took 266rns) 8.1. Объявление и инициализация широкого символа Широкие символы объявляются с помощью типа wchar_t: typedef unsigned short wchar_t; Диапазон допустимых значений содержится в макросах wcНAR_МIN и wcНAR_МАХ: // #include <stdint.h> или #include <wchar.h> printf("%u\n", WCНAR_MIN); //О printf("%u\n", WСНАR_МАХ); // 65535
246 Глава В Обратите внимание: не следует рассчитывать, что при использовании другого ко� пилятора тип wchar _t будет объявлен точно так же. Для определения размера типа wchar_t всегда используйте оператор sizeof: printf ("%d\n", (int) sizeof(wchar_t)); // 2 Кроме того, существует тип wint_t: typedef unsigned short wint_t; Диапазон допустимых значений содержится в макросах WINT_MIN и WINT_МАХ: printf("td\n", (int) sizeof(wint_t)); // 2 // #include <stdint.h> printf("%u\n", WINT_MIN); printf("%u\n", WINT_МAX); //о // 65535 Переменной, имеющей тип wchar_t, можно присвоить числовое значение (код СИ\1- вола) или указать символ внутри апострофов, перед которыми добавлена буква :... Внутри апострофов можно указать специальные символы, например, \n, \t и .1р. Пример объявления и инициализации широкого символа: wchar_t chl = 1087, ch2 = L'n', сhЗ = L'\u043F'; printf("%u\n", chl); // 1087 printf("%u\n", ch2); // 1087 printf("%u\n", сhЗ); // 1087 Коды первых 128 символов совпадают с кодами символов в кодировке ASCII (см. табл. 7 .1 ). Коды русских букв приведены в табл. 8 .1. Обратите внимание на то. что коды букв "ё" и "Ё" выпадают из последовательности кодов. Таблица 8.1 . Коды русских букв при использовании широких символов Буква Код Unicode Буква 1 Код 1 Unicode 1 А 1040 \u0410 а 1072 \u0430 - Б 1041 \u0411 б 1073 \u0431 в 1042 \u0412 в 1074 \u0432 г : 1043 \u0413 г 1075 \u0433 i д 1044 \u0414 д i 1076 \u0434 . - Е 1045 \u0415 е 1077 \u0435 � 1025 \u0401 ё 1 1105 \u0451 ж 1046 \u0416 ж 1078 \u0436 з 1047 \u0417 з 1079 \u0437 - и 1048 \u0418 и 1080 \u0438 й 1049 \u0419 й 1081 \u0439 к , 1050 \u041A к 1082 \u04ЗА - л 1051 1 \u041B л 1083 \u043B
Широкие символы и L-строки Буква Код м 1052 н 1053 о 1054 п 1055 р 1056 с 1057 т 1058 у 1059 ф 1060 х 1061 ц 1062 ч 1063 ш 1064 щ 1065 ъ 1066 ы 1067 ь 1068 э 1069 ю 1070 я 1071 Unicode \u041C \u041D \u041E \u041F \u0420 \u0421 \u0422 \u0423 \u0424 \u0425 \u0426 \u0427 \u0428 \u0429 \u042A \u042B \u042C \u042D \u042E \u042F 247 Таблица 8.1 (окончание) Буква Код Unlcode м 1084 \u043C н 1085 \u043D о 1086 \u043E п 1087 \u043F р 1088 \u0440 с 1089 \u0441 т 1090 \u0442 у 1091 \u0443 ф 1092 \u0444 х 1093 \u0445 ц 1094 \u0446 ч 1095 \u0447 ш 1096 \u0448 щ 1097 \u0449 ъ 1098 \u044A ы 1099 \u044B ь 1100 \u044C 3 1101 \u044D ю 1102 \u044E я 1103 \u044F 8.2. Вывод и ввод широких символов Прежде чем выводить широкие символы, нужно настроить локаль с помощью функции setlocale() : // #include <locale.h> setlocale(LC_ALL, "Russian_Russia.1251"); или с помощью функции _wsetlocale() : _wsetlocale(LC_ALL, L"Russian_Russia.1251"); Для вывода широких символов нужно использовать функцию putwchar() . Прототип функции: tinclude <wchar.h> /* или #include <stdio.h> */ wint_t putwchar(wchar_t ch);
248 Пример вывода символа: __wsetlocale(LC__ ALL, U'Russian_Russia .1251"); wchar t ch = L'n'; putwchar(ch); //п Главаl Можно также вос,юльзоваться спецификатором 'tc в строке формата функl.1Н8 wprintf() или спецификаторами 'tlc и ,,с в функции printf(): _w .setlocale(LC_ALL, L"Russiaп_Russia.1251"); wchar t с!1 = L'п'; wprintf(L"%c\п", ch); // п printf("%lc\n", ch); // п pri11tf("Y,C\n", ch); // п Для ввода широкого символа предназначена функция getwchar(). Прототип фун1- ции: *include <wchar.h> /* или *include <stdio.h> */ wint_t getwchar(void); Пример: _wsystem(L"chcp 1251"); // Смена кодировки консоли _wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar t ch; priпtf("ch = "); fflush(stdout); ch = getwchar(); printf("%u\п", ch); wprintf(L"ic\n", ch); // Вывод подсказки // Сброс буфера // Получение символа // Вывод кода // Вывод символа Функция getwchar() позволяет получить символ только после нажатия клавиш• <E11ter>. Если необходимо получить символ сразу после нажатия клавиши на кл� виатуре, то можtю воспользоваться функциями _getwche() и _getwch (). Прототипw функций: *include <wchar.h> /* или *include <conio.h> */ wint_t _ getwche(void); wint_t _ getwch(void); Функция _getwche() возвращает код символа и выводит его на экран. При нажатим клавиши на клавиатуре функция _getwch() возвращает код символа, но сам симво.1 на экран не выводится. Это обстоятельство позволяет использовать функцию _getwch() для получения конфиденциальных данных (например, пароля). Следует учитывать, что код символа возвращается и при нажатии некоторых служебных клавиш, например <Home>, <End> и др. Пример: _ wsystem(L"chcp 1251"); // смена кодировки консоли _ wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t ch; wprintf(L"Bвeдитe символ: "); fflush (stdout);
Широкие символы и L-строки ch = _getwcJ:i(); wprintf(L"кoд символа: %u\n", ch); 249 Для ввода широких символов и других данных предназначена функция wscanf (). Прототип функции: linclude <wchar.h> /* ипи #include <stdio .h> */ int wscanf(const wchar_t *fоппаt, ... ); В первом параметре указывается строка специального формата, внутри которой задаются спецификаторы, аналогичные применяемым в функции printf(), а таюке некоторые дополнительные спецификаторы. В последующих параметрах переда­ lОТСЯ адреса переменных. Функция возвращает количество произведенных при­ сваиваний. Пример получения символа: _wsystem(L"chcp 1251"); // Смена кодировки консоли _wsetlocale(LC_ALL, L"Russian_Russia.1251"); vchar_t ch; l!lprintf(L"Bвeдитe символ: "); fflush(stdout); fflush(stdin); // Сброс буфера вывода // Очистка буфера ввода int status = wscanf(L"%c", &ch); // Символ & обязателен! ! ! if (status == 1) ( wprintf(L"Bы ввели: %c\n", ch); wprintf(L"Koд: %u\n", ch); else ( wprintf(L"Oшибкa при вводе символа\n"); l!lprintf(L"status = %d\n", status); 8.3. Изменение регистра символов Дnя изменения регистра символов предназначены следующие функции. CJ towupper() - возвращает код символа в верхнем регистре. Если преобразования регистра не было, то код символа возвращается без изменений. Прототип функ­ ции: #include <wchar.h> /* ипи #include <ctype.h> */ wint_t towupper(wint_t ch); Пример: _w setlocale(LC_ALL, L"Russian Russia.1251"); wprintf(L"%c\n", towupper(L'w')); //W wprintf(L"%c\n", towupper(L'б')); //Б wchar_t str [] = L"�бвгдеёжзийклмнопрстуфхцчuшrъыьэюя"; for (int i = О, len = (int)wcslen(str); i < len; ++i) str[i] = towupper(str[i]);
250 Глава 1 wprintf(L"%s\n", str); // АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЩJJЩЬЫЬЭЮЯ □ towlower() - возвращает код символа в нижнем регистре. Если преобразоваюu регистра не было, то код символа возвращается без изменений. Прототип фуJП:­ ции: #include <wchar.h> /* или #include <ctype.h> */ wint_t towlower(wint_t ch); Пример: _wsetlocale(LC_ALL, L"Russian_Russia.1251"); wprintf(L"%c\n", towlower(L'W')); //w wprintf(L"%c\n", towlower(L'Б')); //б wchar_t str[] = L"АБВГдЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЬЭЮЯ"; for (int i = О, len = (int)wcslen(str); i < len; ++i) str[i] = towlower(str[i]); wprintf(L"%s\n", str); // абвгдеёжзийклмноnрстуфхцчl!llЦ'ЬЬlьэюя □ _wcsupr() - заменяет все буквы в L-строке str соответствующими прописны'°' буквами. Функция возвращает указатель на строку str. Прототип функции: #include <wchar.h> /* или #include <string.h> */ wchar_t *_wcsupr(wchar_t *str); Пример: _wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t str[] = L"абвгдеёжзийклмноnрстуфхцчшщъыьэюя"; _wcsupr(str); wprintf(L"%s\n", str); // АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЩJJЩЬЫЬЭЮЯ Вместо функции _wcsupr() лучше использовать функцию _wcsupr_s() . Протопm функции: #include <wchar.h> /* или #include <string.h> */ errno_t _wcsupr_s(wchar_t *str, size_t sizein Words); В параметре sizeinWords указывается размер строки. Функция возвращает з� чение о, если операция успешно выполнена, или код ошибки. Пример: _wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t str[40] = L"абвгдеёжзийклмнопрстуфхцчl!llЦ'ЬЬlьэюя"; _ wcsupr_s(str, 40); wprintf(L"%s\n", str); // АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЩШЦЬЬlЬЭЮЯ □ _wcslwr() - заменяет все буквы в L-строке str соответствующими строчны>а буквами. Функция возвращает указатель на строку str. Прототип функции:
Широкие символы и L-строки #include <wchar.h> /* или #include <string.h> */ wchar_t *_wcslwr(wchar_t *str); Пример: _ wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t str [] = L"АБВГдЕЁЖЗИЙКЛМНОПРСГУФХЦЧIШЦЬНЬЭЮЯ"; _wcslwr(str); wprintf(L"%s\n", str); // абвгдеёжзийклмнопрстуфхцЧIIШП:,ЫЬэюя 251 Вместо функции _wcslwr() лучше использовать функцию _wcslwr_s(). Прототип функции: #include <wchar.h> /* или #include <string.h> */ errno_t _ wcslwr_s(wchar_t *str, size_t sizeinWords); В параметре sizeinWords указывается размер строки. Функция возвращает зна­ чение о, если операция успешно выполнена, или код ошибки. Пример: _ wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t str[40] = L"АБВГдЕЁЖЗИЙКЛМНОПРСТУФХЦЧI.ШЦЬЫЬЭЮЯ"; _ wcslwr_s(str, 40); wprintf(L"%s\n", str); // абвгдеёжзийклмнопрстуфхцЧIIШП:,ЫЬэюя 8.4. Проверка типа содержимого широкого символа Для проверки типа содержимого широко . го символа предназначены следующие tункции. □ iswdigit() - возвращает ненулево� значение, если символ является десятичной цифрой, в противном случае - о. Прототип функции: #include <wchar.h> /* или #include <ctype.h> */ int iswdigit(wint_t ch); Пример: _ wsetlocale(LC_ALL, L"Russian Russia.1251"); wprintf(L"%d\n", iswdigit(L'w')); wprintf(L"%d\n", iswdigit(L'2')); wprintf(L"%d\n", iswdigit(L'б')); //о //4 //о □ iswxdigit() - возвращает ненулевое значение, если символ является шестна­ дцатеричной цифрой (число от о до 9 или буква от А до F; регистр не имеет зна­ чения), в противном случае - о. Прототип функции: #include <wchar.h> /* или #include <ctype.h> */ int iswxdigit(wint_t ch);
252 Пример: wsetlocale(LC_ALL, L"Russian_Russia.1251"); wprintf(L"%d\n", iswxdigit(L'8')); // 128 wprintf(L"%d\n", iswxdigit(L'a')); // 128 wprintf(L"%d\n", iswxdigit(L'F')); // 128 wprintf(L"%d\n", iswxdigit(L'g')); // О Глава 1 □ iswalpha() - возвращает ненулевое значение, если символ является буквой. в противном случае - о . Прототип функции: #include <wchar.h> /* или #include <ctype.h> */ int iswalpha(wint_t ch); Пример: wsetlocale(LC_ALL, L"Russian_Russia.1251"); wprintf(L"%d\n", is·walpha(L'w')); wprintf(L"%d\n", is•,1alpha(L' 2')); wprintf(L"%d\n", iswalpha(L'б')); wprintf(L"%d\n", iswalpha(L'Б')); // 258 //о // 258 // 257 □ iswspace() - возвращает ненулевое значение, если символ является пробе.11,,.. ным символом (пробелом, табуляцией, переводом строки или возвратом карет­ ки), в противном случае- о . Прототип функции: #include <wchar.h> /* или #include <ctype.h> */ int iswspace(wint_t ch); Пример: wsetlocale(LC_ALL, L"Russian_Russia.1251"); wprintf(L"%d\n", iswspace(L'w')); //О wprintf(L"%d\n", iswspace(L'\n')); //8 wprintf(L"%d\n", iswspace(L'\t')); //8 wprintf(L"%d\n", iswspace(L'б')); //О □ iswalnum() - возвращает ненулевое значение, если символ является буквой Н..18 цифрой, в противном случае - о . Прототип функции: #include <wchar.h> /* или #include <ctype.h> */ int iswalnum(wint_t ch); Пример: wsetlocale(LC_ALL, L"Russian_Russia.1251"); wprintf(L"%d\n", iswalnum(L'w')); wprintf(L"%d\n", iswalnum(L'8')); wprintf(L"%d\n", iswalnum(L'\t')); wprintf(L"%d\n", iswalnum(L'б')); // 258 //4 //о // 258 □ iswlower() - возвращает ненулевое значение, если символ является буквоi в н�жнем регистре, в противном случае- о . Прототип функции: #include <wchar.h> /* или #include <ctype.h> */ int iswlower(wint_t ch);
Широкие символы и L-строки Пример: _ wsetlocale(LC_ALL, L"Russian_Russia.1251"); wprintf(L"%d\n", iswlower(L'w')); wprintf(L"%d\n", iswlower(L'8')); wprintf(L"%d\n", iswlower(L'\t')); wprintf(L"%d\n", iswlower(L'б')); wprintf(L"%d\n", iswlower(L'Б')); //2 //о //о //2 //о 253 □ iswupper( J - возвращает ненулевое значение, если символ является буквой в верхнем регистре, в противном случае - о . Прототип функции: #include <wchar.h> /* или #include <ctype.h> */ int iswupper(wint_t ch); Пример: _wsetlocale(LC_ALL, L"Russian_Russia.1251"); wprintf(L"%d\n", iswupper(L'W')); //1 wprintf(L"%d\n", iswupper(L'8')); //О wprintf(L"%d\n", iswupper(L'\t')); //О wprintf(L"%d\n", iswupper(L'б')); //О wprintf(L"%d\n", iswupper(L'Б')); //1 □ iswpunct( J - возвращает ненулевое значение, если символ является символом пункrуации, в противном случае - о. Прототип функции: #include <wchar.h> /* или #include <ctype.h> */ int iswpunct(wint_t ch); Пример: _ wsetlocale(LC_ALL, L"Russian_Russia.1251"); wprintf(L"%d\n", iswpunct(L'8')); // о wprintf(L"%d\n", iswpunct(L'f')); // о wprintf(L"%d\n", iswpunct(L', ')); // 16 wprintf(L"%d\n", iswpunct(L'.')); // 16 wprintf(L"%d\n", iswpunct(L' ' ) ) ; // о CJ iswprint() - возвращает ненулевое значение, если символ является печатаемым (включая пробел), в противном случае - о. Прототип функции: #include <wchar.h> /* или #include <ctype.h> */ int iswprint(wint_t ch); Пример: _wsetlocale(LC_ALL, L"Russian_Russia.1251"); wprintf(L"%d\n", iswprint(L'8')); wprintf(L"%d\n", iswprint(L'\xS')); wprintf(L"%d\n", iswprint(L' ')); wprintf(L"%d\n", iswprint(L'б')); //4 //о // 64 // 258 □ iswgraph() - возвращает ненулевое значение, если символ является печатаемым (пробел печатаемым не считается), в противном случае - о . Прототип функции:
254 #include <wchar.h> /* или #include <ctype.h> */ int iswgraph(wint_t ch); Пример: wsetlocale(LC_ALL, L"Russian_Russia.1251"); wprintf(L"%d\n", iswgraph(L'B')); wprintf(L"%d\n", iswgraph(L'\x5')); wprintf(L"%d\n", iswgraph(L' ')); wprintf(L"%d\n", iswgraph(L'б')); //4 //о //о // 258 Главаl □ iswcntrl() - возвращает ненулевое значение, если символ является непеча-гаr-­ мым, в противном случае - о. Прототип функции: #include <wchar.h> /* или #include <ctype.h> */ int iswcntrl(wint_t ch); Пример: wsetlocale(LC_ALL, L"Russian_Russia.1251"); wprintf(L"%d\n", iswcntrl(L'B')); // О wprintf(L"%d\n", iswcntrl(L'\x5')); // 32 wprintf(L"%d\n", iswcntrl(L' ')); // О □ iswЫank() - возвращает ненулевое значение, если символ является пробе:n.­ ным (пробел, горизонтальная табуляция и др.), в противном случае - о. Проrо­ тип функции: #include <wchar.h> /* или #include <ctype.h> */ int iswЫank(wint_t ch); Пример: wsetlocale(LC_ALL, L"Russian_Russia.1251"); wprintf(L"%d\n", iswЫank(L'8')); // О wprintf(L"%d\n", iswЫank(L'\t')); // 1 wprintf(L"%d\n", iswЫank(L' ')); // 1 В качестве примера проверим регистр символа и выведем соответствующее сос6- щение, а затем преобразуем символ к верхнему регистру и выведем его в окно i.:� соли (листинг 8.1 ). Листинг 8.1. Пример работы с расширенными символами #include <stdio.h> #include <locale.h> #include <wchar.h> int main(void) { _wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar t ch = L'п'; if {iswlower(ch)) { wprintf(L"Cтpoчнaя\n");
Широкие символы и L-строки else { wprintf(L"Пpoпиcнaя\n"); // р·\зуль тат : Строчная ch = towupper(ch); wprintf(L"%c\n", ch); // П return О; 8.5. Преобразование широких символов в обычные и наоборот 255 Для преобразования широких символов (тип wchar_t) в обычные (тип char) и на­ оборот предназначены две функции. □ Ьtowc() - преобразует обычный символ в широкий. В случае ошибки функция возвращает значение макроопределения WEOF ( 65 535). Прототип функции: #include <wchar.h> wint_t Ьtowc(int ch); #define WEOF (wint_t)(0xFFFF) Пример: setlocale(LC_ALL, "Russian_Russia.1251"); char ch = 'п'; wchar_t wch = Ьtowc((unsigned char)ch); wprintf(L"%c\n", wch); // п □ wctob() - преобразует широкий символ в обычный. В случае ошибки функция возвращает значение -1. Обратите внимание: значение -1 также соответствует букве "я" в кодировке windows-1251 . В Visual С дополнительно устанавливается errno соответствующим значению EILSEQ (равно 42). В MinGW errno не устанав­ ливается. Прототип функции: #include <wchar.h> int wctob(wint_t ch); #include <errno.h> #define EILSEQ 42 Пример: // #include <stdlib.h> setlocale(LC_ALL, "Russian_Russia.1251"); wchar_t wch = L'п'; char ch = (char)wctoЬ(wch); printf("%c\n", ch); //п printf("%d\n", errno); //О ch = (char)wctoЬ(L'\u0lCB'); printf("%d\n", ch); // -1 printf("%d\n", errno); // В MinGW: О; в Visual С: 42
256 Глава 1 8.6. Объявление и инициализация L-строки L-строки являются массивами, содержащими символы типа wchar_t. Последн1t111 символом L-строки является нулевой символ. Объявляется L-строка так же, как ■ массив элементов типа wchar t: wchar_t str[7]; При инициализации L-строки можно перечислить широкие символы внутри фигу� ных скобок или указать строку внутри двойных кавычек, перед которыми добав.-� на буква 1: wchar t str1[7] = { L'т', L'e', L'к', L'c', L'т', L'\0' }; wchar t str2[] L'\u0442', L'\u0435', L'\u043A', wchar t strЗ[] wchar t str4 [] wchar t str5 [] L'\u0441', L'\u0442', L'\0' }; 1090, 1077, 1082, 1089, 1090, О }; L"\u0442\u0435\u043A\u0441\u0442"; L 11текст"; При использовании двойных кавычек следует учитывать, что длина строки на 0.1• символ больше, т. к. в конец будет автоматически вставлен нулевой символ. Ее.-. это не предусмотреть и объявить массив из шести элементов вместо семи, то эrо приведет к ошибке. Обратите внимание на то, что присваивать строку в двойных кавычках можнt' только при инициализации. Попытка присвоить строку позже приведет к ошибке: wchar_t str[7]; str = L"String"; // Ol1П1бка!!! В этом случае нужно воспользоваться функцией wcscpy() или wcscpy_s(): wchar_t str[7]; wcscpy_s(str, 7, L"String"); wprintf(L"%s\n", str); // String Внутри строки в двойных кавычках можно указывать специальные символы (�.­ пример, \n, \r и др.), которые мы уже рассматривали в разд. 7 .1 . Если внутри с� ки встречается кавычка, то ее необходимо экранировать с помощью обратноr't' слэша: _ wsetlocale(LC_ALL, L"Russian_Russia .1251"); wchar_t str[] = L"Группа \"КИно\"\n"; wprintf(1"%s\n", str); // Группа "КИно" Если внутри строки встречается обратный слэш, то его необходимо экранирова11L. Это обязательно следует учитывать при указании пути к файлу: wchar_t strl[] = L"C:\\temp\\new\\file.txt"; // Правильно wchar_t str2[] = L"C:\temp\new\file.txt"; //Неправильно!!! wprintf(L"%s\n", strl); // C:\temp\new\file.txt wprintf(L"%s\n", str2); // Строка с Оl!П1бками!!!
Широкие символы и L-строки 257 Обратите внимание на вторую строку. В этом пути присутствуют сразу три спе­ циальных символа: \t, \n и \f. После преобразования специальных символов путь будет выглядеть следующим образом: С:<Табуляция>еmр<Перевод строки>еw<Перевод фopмaтa>ile.txt Разместить L-строку при инициализации на нескольких строках нельзя. Переход на 1Ювую строку вызовет синтаксическую ошибку: vchar_t str[] = L"stringl string2"; // Ошибка! ! ! Для того чтобы разместить L-строку на нескольких строках, следует перед симво­ .-ом перевода строки указать символ \: vc:har_t str[] = L"stringl \ string2 \ stringЗ"; // После символа \ не должно быть никаких символов 11printf(L"%s\n", str); // stringl string2 stringЗ Кроме того, можно воспользоваться неявной конкатенацией. Если строки распо­ .1Ожены подряд внутри одной инструкции, то они объединяются в одну большую С11)0ку. Пример: -.:har_t str[] = L"stringl" L•string2" L'-'stringЗ"; 11printf(L"%s\n", str); //stringlstring2string3 8.7. Доступ к символам внутри L-строки После определения L-строки в переменной сохраняется адрес первого символа. Иными словами, название переменной является указателем, который ссылается на 8ерВЫЙ символ строки. Поэтому доступ к символу в строке может осуществляться ак по индексу (нумерация начинается с нуля), указанному внутри квадратных ско- 6ок, так и с использованием адресной арифметики. Например, следующие две ин­ сrрукции вывода являются эквивалентными: _vsetlocale(LC_ALL, L"Russian_Russia.1251"); -.:har_t str[] = L"строка"; 111printf(L"%c\n", str[l]); //т 111printf(L"%c\n", * (str + 1)); // т Символ можно не только получить таким образом, но и изменить: _vsetlocale(LC_ALL, L"Russian_Russia.1251"); -=har_t str[] = L"строка"; str[О] = L'С'; // Изменение с помощью индекса •(str + 1) = L'T'; // Изменение с помощью указателя 111printf(L"%s\n", str); // строка Обратите внимание на то, что отдельный символ указывается внутри апострофов, а • внутри кавычек. Если указать символ внутри кавычек, то вместо одного символа 6удет два: собственно сам символ плюс нулевой символ.
258 Глава 1 Объявить указатель и присвоить ему адрес строки можно следующим образом: _ wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t str[) = L"строка"; wchar_t *р = NULL; р=str; *р = L,С'; ++р; // Перемещаем указатель на второй символ *р = L'T'; wprintf(L"%s\n", str); // строка Обратите внимание на то, что перед названием строки не указывается оператор " т. к. название переменной содержит адрес первого символа. Если использовап. оператор &, то необходимо дополнительно указать индекс внутри квадратньп. скобок: р � &str[O); // Эквивалентно: р = str; При инициализации указателя ему можно присвоить строку. Такие строки не.,ьw изменять, поэтому обычно перед типом указывают ключевое слово const. Пример: const wchar_t *str = L"String"; wprintf(L"%s\n", str); // String 8.8. Определение длины L-строки Получить длину L-строки можно с помощью функции wcslen(). Протоnt11 функции: #include <wchar.h> /* или #include <string.h> */ size_t wcslen(const wchar_t *str); Функция wcslen() возвращает количество символов без учета нулевого симво.u. Тип size_t объявлен как беззнаковое целое: typedef unsigned int64 size t; #define int64 long long Пример: wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t str[) = L"строка"; int len = (int)wcslen(str); wprintf(L"%d\n", len); wprintf(L"%d\n", (int) sizeof(str)); wprintf(L"%d\n", //6 // 14 (int) (sizeof(str) / sizeof(wchar_t))); // 7 wchar_t *р = str; wprintf(L"%d\n", (int) sizeof(р)); // Значение в проекте Test64c: 8 wprintf(L"%s\n", str); // строка
ШJрокие символы и L-строки 259 Обратите внимание: длина L-строки и длина символьного массива - это разные .:щи. Длина L-строки - это количество символов с начала массива до первого ну­ аевого символа. Длина символьного массива - это общее количество символов, JIOC'IYПHoe под строку. Кроме того, следует учитывать, что один символ в L-строках 3811Имает два байта, поэтому, чтобы получить количество символов в символьном массиве, нужно разделить размер массива на размер типа wchar_t . Не нужно свято .ерить, что размер типа wchar_t всегда равен двум байтам. Для определения разме­ р! обязательно используйте оператор sizeof. 8.9. Перебор символов L-строки Для перебора символов удобно использовать цикл for. В первом параметре пере­ менной-счетчику присваивается значение о (символы в строке нумеруются с нуля), )'СЛОВием продолжения является значение переменной-счетчика меньше количества символов в строке. В третьем параметре цикла for указывается приращение на ециницу на каждой итерации цикла. Выведем все символы строки в прямом и об­ рпном порядке: _wsetlocale(LC_ALL, L"Russian_Russia.1251"}; м:::har_t str(] = L"строка"; int len = (int}wcslen(str}; // ВЫводим символы в прямом порядке for(inti=О;i<len;++i} wprintf(L"%c\n", str[i]}; _ putws(L"--- -- ----- - -- - - - --- "}; // ВЫводим символы в обратном порядке for(inti=len-1;i>=О;--i}{ wprintf(L"%c\n", str[i]); В этом примере количество символов сохраняется в переменной len вне цикла. Если функцию wcslen() указать внутри условия, то вычисление количества симво­ JЮВ будет выполняться на каждой итерации цикла. Поэтому количество символов JIУЧШе получать вне цикла или присваивать значение переменной в первом пара­ метре цикла for. Пример: _ wsetlocale(LC_ALL, L"Russian_Russia.1251"}; vchar_t str[] = L"строка"; for (int i = О, len = (int)wcslen(str); i < len; ++i) { wprintf(L"%c\n", str[i]}; Перебор символов L-строки с помощью указателя и цикла for выполняется так: _wsetlocale(LC_ALL, L"Russian_Russia.1251"}; wchar t str[] = L"строка";
260 for (wchar_t *р = str; *р; ++р) { wprintf(L"%c\n", *р); Глава 1 В этом примере начальным значением является адрес первого символа. Условие,, продолжения цикла является значение, на которое ссылается указатель. ЛКЮ(8 символ трактуется как истина, кроме нулевого символа, который имеет код о. Т• как L-строка завершается нулевым символом, то этот символ вернет значение .r:::ra и цикл завершится. В третьем параметре цикла for указывается приращение указ. теля на единицу на каждой итерации цикла. В этом случае используются правк.• адресной арифметики. Вместо цикла for всегда можно использовать цикл while: _wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t str[] = L"строка"; wchar_t *р = str; while (*р) { wprintf (L"%c\n", *р++); Вначале объявляются строка и указатель, которому присваивается адрес перВОП! символа. Цикл while выполняется до тех пор, пока значение, на которое ссылаета указатель, не равно нулевому символу. Внутри цикла while выводится символ. 1е который ссылается указатель, а затем указатель перемещается на следующий снv­ вол ( *р++). Выражение р++ возвращает текущий адрес, а затем увеличивает ern. Символ * позволяет получить доступ к символу по указанному адресу. Последоа. тельность выполнения соответствует следующей расстановке скобок: *(р++) к.w двум инструкциям: *р и р = р + 1. 8.1 О. Вывод и ввод L-строк Прежде чем выводить L-строки, нужно настроить локаль с помощью фуню.1.1• setlocale() : setlocale(LC_ALL, "Russian_Russia.1251"); ИЛИ С ПОМОЩЬЮ функции _wsetlocale (): _ wsetlocale(LC_ALL, L"Russian_Russia.1251"); Для вывода L-строк используется функция _putws(). Прототип функции: #include <wchar.h> /* или #include <stdio.h> */ int _putws(const wchar_t *str); Функция выводит строку str и вставляет символ перевода строки. Пример: _ putws(L"Stringl"); _putws(L"String2");
Широкие символы и L-строки Результат выполнения: Stringl String2 261 Для форматированного вывода L-строк и других данных используется функция wprintf(). Прототип функции: tinclude <wchar.h> /* или iinclude <stdio.h> */ int wprintf(const wchar_t *format, .. .); В параметре format указывается L-строка специального формата. Внутри этой строки можно указать обычные символы и спецификаторы формата, начинающиеся с символа %. Вместо спецификаторов формата подставляются значения, указанные 1 качестве параметров. Количество спецификаторов должно совпадать с количест- 80М переданных параметров. Спецификаторы мы уже рассматривали в разд. 2 .8 nрименительно к функции printf(). В качестве значения функция возвращает ко­ •нчество выведенных символов. Пример вывода L-строки и числа: _ wsetlocale(LC_ALL, L"Russian_Russia.1251"); wprintf(L"строка\n"); // строка wprintf(L"Count %d\n", 10); // Count 10 wprintf(L"%s %d\n", L"Count", 10); // Count 10 Для вывода L-строки можно также воспользоваться спецификаторами %1s и %S 1 строке формата функции printf(): setlocale(LC_ALL, "Russian_Russia.1251"); printf("%1s\n", L"строка"); // строка printf("%S\n", L"строка"); // строка Если спецификатор %S указать в строке формата функции wprintf(), то можно будет вывести С-строку: _wsetlocale(LC_ALL, L"Russian_Russia.1251"); wprintf(L"%S\n", "строка"); // строка Для ввода L-строки предназначена функция _getws() , однако применять ее в про­ rрамме не следует, т. к . функция не производит никакой проверки длины строки, "ПО может привести к переполнению буфера. Прототип функции _getws () : linclude <wchar.h> /* или iinclude <stdio .h> */ vchar_t *_getws(wchar_t *buffer); Лучше получать строку посимвольно с помощью функции getwchar() или восполь­ эоваться функцией fgetws(). Прототип функции fgetws() : linclude <wchar.h> /* или iinclude <stdio.h> */ wchar_t *fgetws(wchar_t *buffer, int maxCount, FILE *stream); 8 качестве параметра stream указывается стандартный поток ввода stdin. Считыва­ ние производится до первого символа перевода строки или до конца потока или пока не будет прочитано maxcount-1 символов. Содержимое строки buffer включает символ перевода строки. Исключением является последняя строка. Если она не за-
262 Глава& вершается символом перевода строки, то символ добавлен не будет. Если произоаr ла ошибка или достигнут конец потока, то функция возвращает нулевой указате:n,. Пример получения строки приведен в листинге 8.2. Листинг 8.2 . Получение строки с помощью функции fgetws о #include <stdio.h> #include <locale.h> #include <wchar.h> int main(void) { _wsystem(L"chcp 1251"); // смена кодировки консоли _wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t buf[256] = {01, *р = NULL; wprintf(L"Bвeдитe строку: "); fflush(stdout); fflush (stdin); р = fgetws(buf, 256, stdin); if(р){ // Удаляем символ перевода строки size t len wcslen(buf); if (len != О && buf[len - 1] == '\n') buf[len - 1] = L'\0'; // Выводим результат wprintf(L"Bы ввели: %s\n", buf); else _рutws(L"Возникла ошибка"); return О; Для получения и автоматического преобразования данных в конкретный тип ( � пример, в целое число) предназначена функция wscanf(). При вводе строки фун�:­ ция не производит никакой проверки длины строки, что может привести к пе� полнению буфера. Обязательно указывайте ширину при использовании специфиt.>­ тора %s (например, %255s). Прототип функции: #include <wchar.h> /* или #include <stdio.h> */ int wscanf(const wchar_t *format, ...); В первом параметре указывается строка специального формата, внутри котороi задаются спецификаторы, аналогичные применяемым в функции printf(), а тaJGU некоторые дополнительные спецификаторы. В последующих параметрах пере,1а­ ются адреса переменных. Функция возвращает количество произведенных при­ сваиваний. Пример получения целого числа: _ wsetlocale(LC_ALL, L"Russian_Russia.1251"); intх=О;
Широкие символы и L-cmpoкu wprintf(L"Bвeдитe число: "); fflush(stdout); fflush(stdin); // Сброс буфера вывода // Очистка буфера ввода int status = wscanf(L"%d", &х); // Символ & обязателен!!! if (status == 1) { wprintf(L"Bы ввели: %d\n", х); else { _putws(L"Oшибкa при вводе числа"); wprintf(L"status = %d\n", status); Пример ввода строки: _ wsystem(L"chcp 1251"); // Смена кодировки консоли _wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t str[256] = {О}; wprintf(L"Bвeдитe строку: "); fflush(stdout); fflush(stdin); int status = wscanf(L"%255s", str); // Символ & не указывается!!! if (status == 1) { wprintf(L"Вы ввели: %s\n", str); else { _ putws(L"Oшибкa при вводе строки"); 263 Считывание символов производится до первого символа-разделителя, например пробела, табуляции или символа перевода строки. Поэтому после ввода строки •нello, world!" переменная str будет содержать лишь фрагмент "Hello, ", а не всю строку. 8.11. Преобразование С-строки в L-строку и наоборот Для преобразования С-строки в L-строку и наоборот предназначены две функции. □ mЬstowcs() - преобразует С-строку source в L-строку dest. Кодировка символов С-строки указывается с помощью покали. ПрОТОl'ип функции: #include <stdlib.h> size_t mЬstowcs(wchar_t *dest, const char *source, size_t maxCount); В параметре maxcount змается максимальное количество символов строки dest. Для того чтобы узнать требуемое количество символов, нужно передать в пара­ метре ctest нулевой указатель. Функция возвращает количество записанных сим­ волов или код ошибки (size_t) (-1). Пример: _wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar t wstr[256] = {О};
264 Главаl char str[] = "строка"; size t count; // Узнаем требуемый размер wstr count = mЬstowcs(NULL, str, О); wprintf(L"%u + 1\n", (unsigned int)count); count = mЬstowcs(wstr, str, 255); //6+1 wprintf(L"count = %u\n", (unsigned int)count); // count 6 wprintf(L"%s\n", wstr); // строка □ wcstomЬs( J - преобразует L-строку source в С-строку dest. Требуемая коди,х. ка С-строки указывается с помощью локали. Прототип функции: #include <stdlib.h> size_t wcstomЬs(char *dest, const wchar_t *source, size_t maxCount); В параметре maxCount задается максимальное количество символов строки de:;:_ Для того чтобы узнать требуемое количество символов, нужно передать в п� метре ctest нулевой указатель. Функция возвращает количество записанных ба. тов или код ошибки (size_t)(-1). Пример: _wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t wstr[] = L"строка"; char str[256] = {О}; size t count; // Узнаем требуемый размер str count = wcstomЬs(NULL, wstr, О); wprintf(L"%u + 1\n", (unsigned int)count); count = wcstomЬs(str, wstr, 255); //6+1 wprintf(L"count = %u\n", (unsigned int)count); // count 6 printf("%s\n", str); // строка Функции mЬstowcs() и wcstomЬs () в Visual С выводят предупреждающее сообщенме warning С4996. Для того чтобы предупреждения не выводились, можно воспо:u,­ зоваться функциями mЬstowcs_ s() и wcstomЬs_s( J или в самое начало програмww добавить инструкцию: #define CRT SECURE NO WARNINGS Прототипы функций mЬstowcs_s() и wcstomЬs_s (): #include <stdlib.h> errno_t mЬstowcs_s(size_t *returnValue, wchar_t *dest, size_t sizeinWords, const char *source, size_t maxCount); errno t wcstomЬs_s(size_t *returnValue, char *dest, size_t destSizeinBytes, const wchar t *source, size_t maxCount); НА ЗАМЕТКУ В Windows для преобразования С-строки в различных кодировках в L-строку и наобо­ рот можно воспользоваться функциями из WinAPI MultiByteToWideChar ( • WideCharToMultiByte(). Описание функций приведено в разд. 8.12.
Широкие символы и L-строки 8.12. Преобразование кодировок 265 При использовании L-строк все символы кодируются двумя байтами. Не следует рассма-rривать L-строку, как строку в какой-то кодировке, например в UTF-16. Ду­ майте об L-строке как о строке в абстрактной кодировке, позволяющей закодиро­ вать более 65 тыс. символов. Когда мы говорим о строке в какой-либо кодировке, мы всегда подразумеваем С-строку. По умолчанию во всех проектах мы настроили АЛЯ С-строк кодировку windows-1251. Используя функции mЬstowcs() и wcstomЬs(), которые мы рассмотрели в предыду­ щем разделе, можно преобразовать строку из одной однобайтовой кодировки 1 другую. Кодировка символов С-строки указывается с помощью локали. В этом случае L-строка используется как промежуточная стадия. Преобразуем строку из кодировки windows-1251 в кодировку windows-866 (листинг 8.3 ). ldefine CRT SECURE NO WARNINGS - - - - linclude <stdio.h> linclude <locale.h> linclude <string.h> linclude <wchar.h> linclude <stdlib.h> int main(void) { size_t count; char ср866[256] = {О}; wchar_t wstr[256] = {О}; char ср1251[] = "строка"; // Исходная строка (windows-1251) for (int i = О, len = (int)strlen(cpl251); i < len; ++i) { printf("%d ", cpl25l[i]); }//-15-14-16-18-22 -32 printf("\n"); // Преобразование С-строки в кодировке windows-1251 в 1-строку _ wsetlocale(LC_ALL, L"Russian_Russia.1251"); count = mЬstowcs(wstr, ср1251, 255); if (count == (size_t)(-1)) yutws(L"Error 1"); exit(l); for (int i = О, len = (int)wcslen(wstr); i < len; ++i) { wprintf(L"%u ", wstr[i]); } // 1089 1090 1088 1086 1082 1072 printf("\n");
2бб // Преобразование L-строки в С-строку в кодировке windows-866 _ wsetlocale(LC_ALL, L"Russian_Russia .866"); count = wcstomЬs ' (cp866, wstr, 255); if (count == (size_t)(-1)) _putws(L"Error 2"); exit(l); for (int i = О, len = (int)strlen(ср866); i < len; ++i) { printf("%d ", cp866[i]); )//-31 -30 -32 -82 -86 -96 printf("\n"); return О; Глава 1 Для преобразования кодировок в Windows можно воспользоваться следующи\1:8 функциями из WinAPI (кодировки указаны для русской версии Windows): □ CharToOemA() - преобразует строку source в кодировке windows-1251 в стро�..� dest в кодировке windows-866 . Прототип функции: #include <windows.h> /* User32.lib */ WINBOOL CharToOemA(LPCSTR source, LPSTR dest); □ CharToOemВuffA() - преобразует строку source в кодировке windows-1251 в счх� ку dest в кодировке windows-866. Количество символов указывается в параметре length. Прототип функции: #include <windows.h> /* User32.lib */ WINBOOL CharToOemВuffA(LPCSTR source, LPSTR dest, DWORD length); □ OemToCharA() - преобразует строку source в кодировке windows-866 в стро�..� dest в кодировке windows-1251. Прототип функции: #include <windows.h> /* User32.lib */ WINBOOL OemToCharA(LPCSTR source, LPSTR dest); □ OemToCharBuffA() - преобразует строку source в кодировке windows-866 в с� ку dest в кодировке windows-125 l. Количество символов указывается в пара­ метре length. Прототип функции: #include <windows.h> /* User32.lib */ WINBOOL OemToCharBuffA(LPCSTR source, LPSTR dest, DWORD length); Пример использования функций CharтooemA() и oemToCharA() приведен в листин­ ге 8.4. 1 Листинг 8.4. Функции CharТoOelllA() и oemтoCharA() #include <stdio.h> #include <locale.h> #include <string.h> #include <windows.h>
Широкие символы и L-строки int main(void) ( setlocale(LC_ALL, "Russian_Russia.1251"); char ср866[256] = {О}; char ср1251[256] = {О}; char str[J = "строка"; // Исходная строка в кодировке windows-1251 // Преобразование windows-1251 в windows-866 CharТoOernA(str, ср866); for (int i = О, len = (int)strlen(cp866); i < len; ++i) { printf("%d ", cp866[i]); // -31 -30 -32 -82 -86 -96 printf("\n"); // Преобразование windows-866 в windows-1251 OernToCharA(cp866, ср1251); for (int i = О, len = (int)strlen(cp1251); i < len; ++i) { printf("%d ", cp1251[i]); // -15 -14 -16 -18 -22 -32 printf("\n"); return О; 267 Если вы используете компилятор Visual С в Eclipse и получили сообщение об от­ суrствии файла windows.h, то нужно в свойствах проекта указать пуrь к каталогу lnclude из Microsoft SDK. Если Microsoft SDK отсутствует на компьютере, то его нужно дополнительно установить. Если в Visual С в Eclipse вы получили ошибку error LNK2019, то в свойствах проек­ та на вкладке С Compiler I Preprocessor нужно указать путь к каталогу lnclude а Microsoft SDK, а также добавить пуrь к каталогу Lib и библиотеку user32. lib на аIСЛадке Linker I Libraries (рис. 8.1) или вставить в самое начало программы инст­ рукцию: tpragrna comment(lib, "User32. lib") Для преобразования кодировок в Windows можно также воспользоваться следую­ щими функциями из WinAPI. □ MultiByteToWideChar() - преобразует С-строку lpMultiByteStr в L-строку lpWideCharStr. Прототип функции: *include <windows.h> /* Kernel32.lib */ int MultiByteToWideChar(UINT codePage, DWORD dwFlags, LPCCH lpMultiByteStr, int cЬМultiByte, LPWSTR lpWideCharStr, int cchWideChar); Параметр codePage задает кодировку С-строки. Примеры указания кодировки: 866 (для кодировки windows-866), 1251 (для windows-1251), 65001 или CP_UTF8 (для UTF-8), 1200 (для UTF-16LE), 1201 (для UTF-16BE), 12000 (для UTF-32LE) и 12001 (для UTF-32BE). Полный список кодировок см. в документации.
268 Глава 1 В параметре dwFlags можно указать различные флаги (мв_PRECOMPOSED (: L МВ_ERR_INVALID_CНARS (8), МВ_СОМРОSIТЕ (2), МВ_USEGLYPHCНARS (4)), задающие по� дение функции при конверсии, или значение о. Полный список и описание ф.,. гов см. в документации. Параметр cЬMultiByte задает количество байтов в С-строке lpMultiByteStr, по.1- лежащих конверсии. Если С-строка завершается нулевым символом, то лучше указать значение -1. В этом случае размер строки вычисляется автоматичеса и в L-строку вставляется нулевой символ. Если указано конкретное число, w нулевой символ автоматически в L-строку не вставляется! Параметр cchWideChar задает максимальный размер L-строки lpWideChar-5:: в символах. Если указать значение о, то функция вернет требуемое количеств., символов. Если указано конкретное значение, то функция вернет количество за­ писанных символов или значение о в случае ошибки. ф , typ�_f1lter t�xt 1, Rtsource Builders • С/С++ Build Build V1ri!Ьl6 Environment lo9ging Тool Chain Editor i. С/С++ General linux Tools PAth Project N111tures Pro-ject R�erences Run/D•bug Settings :, ТitSk Repository Task Tags !> V!lidation Wilt:iText. Properties for ТestYisualC Settings , � С Сompil•r (<1} дddition,I Libpath (llibp,th) - □ @ Optimizotion !================== � Code G!-ner1tion @ D•bug9in9 i;,:j Prepгocessor �) Language (9 Miscell!neous А � Resource Compiler (rc) � Preprocessor � Miscellaneous , @ link•r (link) @Genшl � liЬraries -- -- · -- --- --- : librari6 4{J�'JI®{]<),i 1Kerne : 132�1ib : Рмс. 8.1 . Указание пути к каталогу Lib и добавление библиотек □ WideCharToMultiByte () - преобразует lpMultiByteStr. Требуемая кодировка codePage. Прототип функции: L-строку lpWideCharStr в С-строl\: С-строки указывается в параметре #include <windows.h> /* Kernel32.lib */ int WideCharToMultiByte(UINT codePage, DWORD dwFlags, LPCWCH lpWideCharStr, int cchWideChar,
Широкие символы и L-строки LPSTR lpМultiByteStr, int cЬМultiByte, LPCCH lpDefaultChar, LPВOOL lpUsedDefaultChar); 269 В параметре dwFlags можно указать различные флаги (wc_coмrosrтECНECK (512), WC_DEFAULTCНAR (64), WC_DISCARDNS (16), WC_SEPCНARS (32), WC_NO_BEST_FIT _CНARS (1024)), задающие поведение функции при конверсии, или значение о. Полный список и описание флагов см. в документации. Параметр cchWideChar задает количество символов в L-строке lpWideCharStr, подлежащих конверсии. Если L-строка завершается нулевым символом, то луч• ше указать значение -1. В этом случае размер строки вычисляется автоматиче­ ски и в С-строку вставляется нулевой символ. Если указано конкретное число, то нулевой символ автоматически в С-строку не вставляется! Параметр сЬМultiByte задает максимальный размер С-строки lpМuHiByteStr в байтах. Если указать значение о, то функция вернет требуемое количество бай­ тов. Если указано конкретное значение, то функция вернет количество записан­ ных байтов или значение о в случае ошибки. В параметре lpDefaultChar можно задать адрес символа, который будет исполь• зоваться, если символ в L•строке не может быть преобразован (символ отсутст• вует в кодировке codePage). Если задан нулевой указатель, то будет использо­ ваться символ по умолчанию. При использовании кодировки UTF·8 параметр lpDefaultChar должен иметь значение NULL. Параметр lpUsedDefaultchar позволяет задать адрес логической переменной. Если параметр не равен значению NULL, то в переменную будет записано логиче­ ское значение Истина, если какого•либо символа нет в кодировке codePage, и о - в противном случае. При использовании кодировки UTF-8 параметр lpUsedDefaultChar должен иметь значение NULL. Пример использования функций MultiByteToWideChar () и WideCharТoМultiByte() приведен в листинrе 8.5. linclude <stdio .h> linclude <locale.h> linclude <string.h> linclude <wchar.h> linclude <windows.h> int main(void) { setlocale(LC_ALL, "Russian_Russia.1251"); int count = О; char ср866[256] = {О}; char utf8[256] = {О}; wchar_t wstr[256] = {О}; wchar_t wstr2[256] = {О}; char str[] = "строка str"; // Исходная строка (windows-1251)
270 Глава 1 for (int i = О, len = (int)strlen(str); i < len; ++i) { printf("%d ", str[i]); )//-15-14-16 -18 -22 -32 32 115 116 114 printf("\n"); // Узнаем требуемый размер wstr count = MultiByteToWideChar(1251, О, str, -1, wstr, О); printf("тpeбyeтcя %d\n", count); // требуется 11 // Преобразование С-строки в кодировке windows-1251 в 1-строку count = MultiByteToWideChar(1251, О, str, -1, wstr, 255); printf("зaпиcaнo %d\n", count); // записано 11 for (int i = О, len = (int)wcslen(wstr); i < len; ++i) { wprintf(L"%u ", wstr[i]); } // 1089 1090 1088 1086 1082 1072 32 115 116 114 printf("\n"); // Узнаем требуемый размер utf8 count = WideCharToMultiByte(CP_UTF8, О, wstr, -1, utf8, О, NULL, NULL); printf("тpeбyeтcя %d\n", count); // требуется 17 // Преобразование 1-строки в С-строку в кодировке UTF-8 count = WideCharToMultiByte(CP_UTF8, О, wstr, -1, utf8, 255, NULL, NULL); printf("зaпиcaнo %d\n", count); // записано 17 for (int i = О, len = (int)strlen(utf8); i < len; ++i) { printf("%d ", utf8[i]); }//-47 - 127 -47 -126 -47 -1 28 -48 -66 -48 -70 -48 -80 32 115 116 114 printf("\n"); // Узнаем требуемый размер wstr2 count = MultiByteToWideChar(CP_UTF8, О, utf8, -1, wstr2, О); printf("тpeбyeтcя %d\n", count); // требуется 11 // Преобразование С-строки в кодировке UTF-8 в 1-строку count = MultiByteToWideChar(CP_UTF8, О, utf8, -1, wstr2, 255); printf("зaпиcaнo %d\n", count); // записано 11 for (int i = О, len = (int)wcslen(wstr2); i < len; ++i) { wprintf(L"%u ", wstr2[i]); } // 1089 1090 1088 1086 1082 1072 32 115 116 114 printf("\n"); // Узнаем требуемый размер ср866 char ch = '?'; int flag = 1; // wstr[l] = L'\u0263'; // символа нет в windows-866 count = WideCharToMultiByte(866, О, wstr, -1, ср866, О, &ch, &flag); printf("тpeбyeтcя %d\n", count); // требуется 11
Широкие символы и L-строки // Преобразование L-строки в С-строку в кодировке windows-866 count = WideCharToMultiByte(866, О, wstr, -1, ср866, 255, &ch, &flag); printf("эanиcaнo %d\n", count); // записано 11 for (int i = О, len = (int)strlen(cp866); i < len; ++i) { printf("%d ", cp866[i]); 1//-31-30-32-82-86 -96 32 115 116 114 printf("\n"); printf("%d\n", flag); // О return О; 8.13. Основные функции для работы с L-строками Перечислим основные функции для работы с L-строками. 271 □ wcslen() - возвращает количество символов в L-строке без учета нулевого сим­ вола. Прототип функции: #include <wchar.h> /* или #include <string.h> */ size_t wcslen(const wchar_t *str); Тип size_t объявлен как беззнаковое целое число: typedef unsigned _int64 size_t; #define int64 long long Пример: _wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t str[] = L"строка"; int len = (int)wcslen(str); wprintf(L"%d\n", len); //6,ане7 Определить общий размер массива можно с помощью оператора sizeof. Опе­ ратор возвращает размер в байтах. Для того чтобы получить длину в символах, полученное значение нужно разделить на размер типа wchar_t. Пример: wchar_t str[20] = L"строка"; wprintf(L"%d\n", (int) sizeof(str)); // 40 wprintf(L"%d\n", (int) (sizeof(str) / sizeof(wchar_t))); // 20 □ wcsnlen() - возвращает количество символов в L-строке без учета нулевого символа. Прототип функции: #include <wchar.h> /* или #include <string.h> */ size_t wcsnlen(const wchar_t *str, size_t maxCount); Во втором параметре указывается размер символьного массива. Если нулевой символ не встретился в числе maxcount символов, то возвращается значение maxCount. Пример:
272 _wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t str[7] = L"строка"; int len = (int)wcsnlen(str, 7); wprintf(L"%d\n", len); //6 Глава 1 □ wcscpy() - копирует символы из L-строки source в L-строку dest и вставляет нулевой символ. В качестве значения функция возвращает указатель на стро11.� dest. Если строка source длиннее строки dest, то произойдет переполненне буфера. Прототип функции: #include <wchar.h> /* или #include <string.h> */ wchar_t *wcscpy(wchar_t *dest, const wchar t *source); Пример использования функции: _wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t str[7]; wcscpy(str, L"String"); wprintf(L"%s\n", str); // String Вместо функции wcscpy() лучше использовать функцию wcscpy_s(). Прототип функции: #include <wchar.h> /* или #include <string .h> */ errno_t wcscpy_s(wchar_t *dest, rsize_t _ destSize, const wchar_t *source); Функция wcscpy_s() копирует символы из строки source в строку dest и встав.1я­ ет нулевой символ. В параметре destSize указывается максимальное количество элементов массива dest. Пример: #define SIZE 7 _ wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t str[SIZE]; wcscpy_s(str, SIZE, L"String"); wprintf(L"%s\n", str); // String Вместо функций wcscpy() и wcscpy_s() можно воспользоваться функциями wmemcpy() и wmemcpy_s(), которые копируют count первых символов из массива source в массив dest, но не вставляют нулевой символ. В параметре nurnЬerOfElernents указывается количество элементов массива dest. Прототипы функций: #include <wchar.h> wchar t *wmerncpy(wchar_t *dest, const wchar t *source, size t count); errno_t wmerncpy_s(wchar_t *dest, size_t nurnЬerOfElernents, const wchar t *source, size t count); Пример: _wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t str[B] = {О}; str[б] = L'l'; wmerncpy(str, L"String", 6); wprintf(1"%s\n", str);
Широкие символы и L-строки // Stringl (нулевой символ не вставляется!!!) wmemcpy_s(str, 8, L"string2", 7); wprintf(L"%s\n", str); // string2 273 Функции wmemcpy() и wmemcpy_s <) при пересечении указателей работают некор­ ректно. Для того чтобы копирование при пересечении выполнялось правильно, нужно воспользоваться функциями wmemmove() и wmemmove_s (). Функции копи­ руют count первых символов из массива source в массив dest. Нулевой символ функции не вставляют. В параметре numЬerOfElements указывается количество элементов массива dest. Прототипы функций: #include <wchar.h> wchar t *wmemmove(wchar_t *dest, const wchar_t *source, size t count); errno t wmeппnove_s(wchar_t *dest, size_t numЬerOfElements, const wchar t *source, size t count); Пример: _ wsetlocale(LC_ALL, L"Russian Russia.1251"); wchar_t str[8] = {О}; str[6] = L'l'; wmemmove(str, L"String", 6); wprintf(L"%s\n", str); // Stringl (нулевой символ не вставляется!!!) wmemmove_s(str, 8, L"string2", 7); wprintf(L"%s\n", str); // string2 D wcsncpy() - копирует первые count символов из L-строки source в L-строку dest. В качестве значения функция возвращает указатель на строку dest. Если строка source длиннее строки dest, то произойдет переполнение буфера. Прото­ тип функции: #include <wchar.h> /* или #include <string.h> */ wchar_t *wcsncpy(wchar_t *dest, const wchar_t *source, size_t count); Если количество символов в строке source меньше числа count, то строка dest будет дополнена нулевыми символами, а если больше или равно, то копируются только count символов, при этом нулевой символ автоматически не вставляется. Пример копирования шести символов: _ wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t str[7]; wcsncpy(str, L"String", 6); str[6] = L'\0'; // Нулевой символ автоматически не вставляется wprintf(L"%s\n", str); // String Вместо функции wcsncpy ( ) лучше использовать функцию wcsncpy_s ( ). Прототип функции: linclude <wchar .h> /* или #include <string.h> */ errno t wcsncpy_s(wchar_t *dest, size_t destSizeinChars, const wchar t *source, size t maxCount);
274 Глава 1 Функция wcsncpy_s() копирует первые rnaxcount символов из строки soc:: :-.. в строку dest и вставляет нулевой символ. В параметре destSizeinChars указ1r вается максимальное количество элементов массива dest. Пример: #define SIZE 7 _ wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t str[SIZE]; wcsncpy_s(str, SIZE, L"String", 5); wprintf(1"%s\n", str); // Strin □ wcscat() - копирует символы из L-строки source в конец L-строки dest и вста.­ ляет нулевой символ. В качестве значения функция возвращает указатель на строку dest. Если строка dest имеет недостаточный размер, то произойдет перс-­ полнение буфера. Прототип функции: #include <wchar.h> /* или #include <string.h> */ wchar_t *wcscat(wchar_t *dest, const wchar t *source); Пример использования функции: _wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t str[20] = L"ст"; wcscat(str, L"po"); wcscat(str, L"ка"); wprintf(L"%s\n", str); // строка Обратите внимание на то, что перед копированием в строке dest обязательнс должен быть нулевой символ. Локальные переменные автоматически не ини­ циализируются, поэтому этот код приведет к ошибке: wchar_t str[20]; wcscat(str, L"строка"); // Локальная переменная // O!1П1бка, нет нулевого символа! Для того чтобы избавиться от ошибки, нужно произвести инициализацию с� ки нулями следующим образом: _wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t str[20] = {О}; // Инициализация нулями wcscat(str, L"строка"); // Все нормально wprintf(L"%s\n", str); // строка Вместо функции wcscat() лучше использовать функцию wcscat s(). Прототип функции: #include <wchar.h> /* или #include <string.h> */ errno t wcscat_s(wchar_t *dest, rsize_t destSize, const wchar_t *source); Функция wcscat_s() копирует символы из строки source в конец строки de.o: В параметре destSize указывается максимальное количество элементов массива dest. Пример: #define SIZE 20 _ wsetlocale(LC_ALL, L"Russian_Russia.1251");
Широкие символы и L-строки wchar_t str[SIZE] = L"ст"; wcscat_s(str, SIZE, L"po"); wcscat_s(str, SIZE, L"ка"); wprintf(L"%s\n", str); // строка 275 □ wcsncat() -:-- копирует первые count символов из L-строки source в конец строки dest и вставляет нулевой символ. В качестве значения функция возвращает ука­ затель на строку dest. Обратите внимание на то, что перед копированием в стро­ ке dest обязательно должен быть нулевой символ. Если строка dest имеет недостаточный размер, то произойдет переполнение буфера. Прототип функции: #include <wchar .h> /* или #include <string.h> */ wchar_t *wcsncat(wchar_t *dest, const wchar t *source, size t count); Пример использования функции: _w setlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t str[20] = L"ст"; wcsncat(str, L"po", 2); wcsncat(str, L"какао", 2); wprintf(L"%s\n", str); // строка Вместо функции wcsncat() лучше использовать функцию wcsncat_s(). Прототип функции: #include <wchar .h> /* или #include <string.h> */ errno t wcsncat_s(wchar_t *dest . , size_t destSizeinChars, const wchar_t *source, size_t maxCount); Функция wcsncat_s() копирует первые maxCount символов из строки source в ко­ нец строки dest. В параметре destSizeinChars указывается максимальное коли­ чество элементов массива dest. Пример: #define SIZE 20 _w setlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t str[SIZE] = L"ст"; wcsncat_s(str, SIZE, L"po", 2); wcsncat_s(str, SIZE, L"какао", 2); wprintf(L"%s\n", str); // строка □ _wcsdup() ....:.... создает копию L-строки в динамической памяти и возвращает ука­ затель на нее. Прототип функции: #include <wchar.h> /* или #include <string.h> */ wchar_t *_wcsdup(const wchar_t *str); Память выделяется с помощью функции malloc (), поэтому после использования строки следует освободить память с помощью функции free( J. Пример: // #include <stdlib.h> _wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t st� [] = L"строка", *р = NULL; р = _wcsdup(str);
276 if(р){ wprintf(L"\!:s\п", р); // строка free(р); Глава 1 □ wcstok() - разделяет L-строку str на подстроки, используя в качестве разде.1• телей символы из L-строки delim. При первом вызове указываются оба парамет­ ра. В качестве значения возвращается указатель на первую подстроку или ну.�с­ вой указатель, если символы-разделители не найдены в [.-строке str. Для тоге чтобы получить последующие подстроки, необходимо каждый раз вызывап функцию wcstok(), указывая в первом параметре нулевой указатель, а во второ11 параметре те же самые символы-разделители. Обратите внимание: функция ю­ меняет исходную строку str, вставляя вместо символов-разделителей нулевоi символ. Прототип функции: #include <wchar.h> /* или #include <string.h> */ wchar_t *wcstok(wchar_t *str, const wchar t *delim); Пример использования функции: _ wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t str[] = L"a,b.c =d", *р = NULL; р = wcstok(str, L",.="); 1,hile (р) { wprintf(L"%s-", р); р = wcstok(NULL, L",. ="); // Результат: a-b -c-d ­ wprintf(L"\n"); // Функция изменяет исходную строку!!! int len = (int) (sizeof(str) / sizeof(wchar_t)); for(inti=О;i<len;++i) wprintf(L"%d ", str[i]); 1//97О98О99О100О wprintf(L"\n"I; Вместо функции wcstok() лучше использовать функцию wcstok_s(). Прототип функции: #include <wchar .h> /* или #include <string.h> */ wchar_t *wcstok_s(wchar_t *str, const wchar_t *delim, wchar_t **context); Первые два параметра аналогичны параметрам функции wcstok(). В параметре coпtext передается адрес указателя. Обратите внимание: функция изменяет исходную строку str, вставляя вместо символов-разделителей нулевой симво.1 Пример: _ wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t str [] = L"a Ь с, d", *р = NULL, *context NULL; р = wcstok_s(str, L" ,", &context); while (р) { wprintf(L"\!:s-", р);
Широкие символы и L-строки р = wcstok_s(NULL, L" // Результат: a-b-c-d ­ wprintf(L"\n"); " , , &context); // Функция изменяет исходную строку! ! ! int len = (int) (sizeof(str) / sizeof(wchar_t)); for(inti=О;i<len;++i) wprintf(L"%d ", str[i]); }//97О98О99О100О wprintf(L"\n"); 277 Практически все функции без суффикса _s, которые мы рассмотрели в этом разде­ .11е, в Visual С выводят предупреждающее сообщение warning С4996 , т. к. по умол­ чанию функции не производят никакой проверки корректности данных. В этом случае возможно переполнение буфера. Забота о корректности данных полностью вежит на плечах программиста. Если вы уверены в своих действиях, то можно по­ .uвить вывод предупреждающих сообщений. Сделать это можно двумя способами: С] определить макрос с названием _CRT_SECURE_NO_WARNINGS в самом начале про­ граммы, перед подключением заголовочных файлов. Пример: #define CRT SECURE NO WARNINGS #include <stdio.h> #include <wchar.h> С] вставить прагму warning. Пример: #pragma warning( disaЫe : 4996) ОБРАТИТЕ ВНИМАНИЕ Поспе директив #define и #pragma точка с запятой не указывается. 8.14. Поиск и замена в L-строке Для поиска и замены в L-строке предназначены следующие функции. CJ wcschr() - ищет в L-строке str первое вхождение символа ch. В качестве зна­ чения функция возвращает указатель на первый найденный символ в L-строке str или нулевой указатель, если символ не найден. Прототип функции: #include <wchar.h> /* и.ли #include <string.h> */ · wchar_t *wcschr(const wchar_t *str, wchar t ch); Пример использования функции: _wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t str[] = L"строка строка", *р = NULL; р = wcschr(str, L'к'); if(р){ wprintf(L"Индeкc: %d\n", (int)(р - str)); } // Индекс: 4
278 Глава 1 □ wcsrchr( J - ищет в L-строке str последнее вхождение символа ch. В качес� значения функция возвращает указатель на символ или нулевой указатель, ес.п символ не найден. Прототип функции: #include <wchar.h> /* или #include <string.h> */ wchar t *wcsrchr(const wchar_t' *str, wchar t ch); Пример: wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t str[] = L"строка строка", *р = NULL; р = wcsrchr(str, L'к'); if(р){ wprintf(L"Индекс: %d\n", (int)(р - str)); } // Индекс: 11 □ wmernchr() - ищет в L-строке str первое вхождение символа ch. Максимальнс просматривается rnaxcount символов. В качестве значения функция возвращает указатель на первый найденный символ в строке str или нулевой указате.,�.. если символ не найден. Прототип функции: #include <wchar.h> wchar t *wmernchr(const wchar t *str, wchar t ch, size t rnaxCount); Пример: wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t str[] = L"строка строка", *р = NULL; р = wmernchr(str, L'к', wcslen(str)); if(р){ wprintf(L"Индeкc: %d\n", (int)(р - str)); } // Индекс: 4 □ wcspbrk() - ищет в L-строке str первое вхождение одного из символов (ну.,е­ вой символ не учитывается), входящих в L-строку control. В качестве значенн.1 функция возвращает указатель на первый найденный символ или нулевой ук� тель, если символы не найдены. Прототип функции: #include <wchar.h> /* или #include <string.h> */ wchar_t *wcspbrk(const wchar t *str, const wchar t *control); Пример: wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t str[] = L"строка строка", *р = NULL; р = wcspbrk(str, L"кр"); if(р){ wprintf(L"Индeкc: %d\n", (int)(р - str)); } // Индекс: 2 (индекс первой буквы "р") □ wcscspn() - возвращает индекс первого символа в L-строке str, который совпа­ дает с одним из символов, входящих в L-строку control. Прототип функции: #include <wchar.h> /* или #include <string.h> */ size t wcscspn(const wchar t *str, const wchar t *control);
Широкие символы и L-строки Пример: ..:..wsetlocale(LC_ALL, L"Russian Russia.1251"); wchar_t str[] = L"строка строка"; int index = (int)wcscspn(str, L"кр"); wprintf(L"Индeкc: %d\n", index); // Индекс: 2 (индекс первой буквы "р") index = (int)wcscspn(str, L"ф"); wprintf(L"Индекс: %d\n", index); // Индекс: 13 (длина строки, если ничего не наЙдено) 279 □ wcsspn() - возвращает индекс первого символа в L-строке str, который не сов­ падает ни с одним из символов, входящих в L-строку control. Прототип функ­ ции: #include <wchar.h> /* или #include <string.h> */ size_t wcsspn(const wchar t *str, const wchar t *control); Пример: _ wsetlocale(LC_ALL, L"Russian Russia.1251"); wchar_t str[] = L"строка строка"; int index = (int)wcsspn(str, L"окстр"); wprintf(L"Индекс: %d\n", index); // Индекс: 5 ("а" не входит в "окстр") □ wcsstr() - ищет в L-строке str первое вхождение целого фрагмента из L-строки suЬStr. В качестве значения функция возвращает указатель на первое вхождение фрагмента в строку или нулевой указатель - в противном случае. Прототип функции: #include <wchar.h> /* или #include <string.h> */ wchar_t *wcsstr(const wchar t *str, const wchar t *suЬStr); Пример: _ wsetlocale(LC_ALL, L"Russian Russia.1251"); wchar_t str[] = L"строка строка", *р = NULL; р = wcsstr(str, L"ока"); if(р){ wprintf(L"Индeкc: %d\n", (int)(р - str)); } // Индекс: 3 [] _wcsset() - заменяет все символы в L-строке str указанным символом ch. Функция возвращает указатель на строку str. Прототип функции: #include <wchar.h> /* или #include <string.h> */ wchar_t *_wcsset(wchar_t *str, wchar t ch); Пример: wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t str[] = L"строка";. _ wcsset(str, L'*'); wprintf (L" '%s'\n", str); // '******'
280 Глава 1 Вместо функции _wcsset() лучше использовать функцию_wcsset_s(). Протоn111 функции: #include <wchar.h> /* или #include <string.h> */ errno t _wcsset_s(wchar_t *str, size_t sizeinWords, wchar_t ch); В параметре sizeinWords указывается размер строки. Функция возвращает зн. чение о, если операция успешно выполнена, или код ошибки. Пример: _wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t str[l0] = L"строка"; _wcsset_s(str, 10, L'* '); wprintf(L"'%s'\n", str); // '******' □ _wcsпset() - заменяет первые count символов в L-строке str указанным СИМIЮ­ лом ch. Функция возвращает указатель на строку str. Прототип функции: #include <wchar.h> /* или #include <striпg.h> */ wchar_t *_wcsпset(wchar_t *str, wchar t ch, size t count); Пример: wset1ocale(LC_ALL, L"Russiaп_Russia.1251"); wchar_t str[] = L"строка"; _wcsnset(str, L'*', 4); wprintf(L"'%s'\n", str); // '****ка' Вместо функции _wcsnset() лучше использовать функцию _wcsnset_s(). П� тип функции: #include <wchar.h> /* или #include <string.h> */ errno_t _wcsnset_s(wchar_t *str, size_t strSizeinWords, wchar_t ch, size_t count); В параметре strSizeinWords указывается размер строки. Функция возвращает значение о, если операция успешно выполнена, или код ошибки. Пример: _wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t str[l0] = L"строка"; _ wcsnset_s(str, 10, L'*', 4); wprintf(L"'%s'\n", str); // '****ка' □ wmemset() - заменяет первые count элементов массива dest символом ch. В ка­ честве значения функция возвращает указатель на массив dest. Прототип фун��;­ ции: #include <wchar.h> wchar_t *wmemset(wchar_t *dest, wchar t ch, size t count); Пример: wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t str[] = L"String"; wmemset(str, L'*', 4); wprintf(L"'%s'\n", str); // '****ng'
Широкие символы и L-строки 281 LI _wcsrev( J - меняет порядок следования символов внутри L-строки на противо­ положный, как бы переворачивает строку. Функция возвращает указатель на строку str. Прототип функции: iinclude <wchar.h> /* или iinclude <string.h> */ wchar_t *_wcsrev(wchar_t •str); Пример: _ws etlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t str[] = L"строка"; _ w c s rev(str); wprintf(L"%s\n", str); // акортс LI _wcsupr о - заменяет все буквы в L-строке str соответствующими прописными буквами. Функция возвращает указатель на строку str. Прототип функции: iinclude <wchar.h> /* или iinclude <string.h> */ wchar_t *_wcsupr(wchar_t •str); Пример: _w s etlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t str [] = L"абвгдеёжзийклмнопрстуфхцчuшrьыьэюя"; _w cs upr(str); wprintf(L"%s\n", str); // АБВГДЕЁЖЗИЙКЛМНОПРСГУФХЦЧIIIЩШЬЭЮЯ Вместо функции wcsupr( J лучше использовать функцию wcsupr s() . Прототиr1 функции: - - - , iinclude <wchar.h> /* или iinclude <string.h> */ errno_t _wc s upr_s(wchar_t *str, size_t sizeinWords); В параметре sizeinWords указывается размер строки. Функция возвращает зна­ чение о, если операция успешно выполнена, или код ошибки. Пример: _ws etlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t str[40] = L"абвгдеёжзийклмнопрстуфхцчшщьыьэюя"; _wc s upr_s(str, 40); wprintf(L"%s\n", str); // АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧ!ШЦЬЬIЬЭЮЯ LI _wcslwr( J - заменяет все буквы в L-строке str соответствующими строчными буквами. Функция возвращает указатель на строку str. Прототип функции: #include <wchar.h> /* или #include <string.h> */ wchar_t *_wcslwr(wchar_t *str); Пример: _ ws etlocale(LC_ALL, L"Russian_Russia .1251"); wchar_t str [] = L"АБВГДЕЁЖЗИЙКЛМНОПРСГУФХЦЧIШIЬЬIЬЭЮЯ"; _wcslwr(str); wprintf(L"%s\n", str); // абвгдеёжзийклмнопрстуфхцчшщьыьэюя
282 Глава 1 Вместо функции _wcslwr() лучше использовать функцию _wcslwr_s ( J. Протоnш функции:. #include <wchar.h> /* или #include <string.h> */ errno_t _ wcslwr_s(wchar_t *str, size_t sizeinWords); В параметре sizeinWords указывается размер строки. Функция возвращает зна­ чение о, если операция успешно выполнена, или код ошибки. Пример: _wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t str[40] = L"АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЬlЬЭЮЯ"; _wcslwr_s(str, 40); wprintf (L"%s\n", str); // абвгдеёжзийклмнопрстуфхцчunцъыьэюя 8.15. Сравнение L-строк Для сравнения L-строк предназначены следующие функции. □ wcscmp о - сравнивает L-строку strl с L-строкой str2 без учета настроек лока..­ и возвращает одно из значений: • отрицательное число - если strl меньше str2; • о - если строки равны; • положительное число - если strl больше str2. Сравнение производится с учетом регистра символов. Прототип функции: #include <wchar.h> /* или #include <string.h> */ int wcscmp(const wchar t *strl, const wchar t *str2); Пример: wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t strl[] = L"абв", str2(] = L"абв", strЗ[] wprintf(L"%d\n", wcscmp(strl, str2)); // О wprintf(L"%d\n", wcscmp(strl, strЗ)); // 1 L"АБВ"; strl[2] = L'б'; // strl[] = L"абб", str2[] L"абв"; wprintf(L"%d\n", wcscmp(strl, str2)); // -1 strl[2] = L'г'; // strl[] = L"абг", str2[] L"абв"; wprintf(L"%d\n", wcscmp(strl, str2)); // 1 □ wcsncmp() - сравнивает count первых символов в L-строках strl и str2. Ес.111 нулевой символ встретится раньше, то значение параметра count игнорируетс1. Функция возвращает одно из значений: • отрицательное число- если strl меньше str2; • о - если строки равны; • положительное число - если strl больше str2.
Широкие символы и L-строки Сравнение производится с учетом регистра символов. Прототип функции: #include <wchar.h> /* или #include <string.h> */ int wcsncmp(const wchar t *strl, const wchar t *str2, size t count); Пример: _ wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t strl[] = L"абв", str2[] = L"абг"; wprintf(L"%d\n", wcsncmp(strl, str2, 2)); // О wprintf(L"%d\n", wcsncmp(strl, str2, З)); // -1 283 □ wcscoll() - функция аналогична функции wcscmp(), но сравнение производится с учетом значения LC_COLLATE в текущей локали. Например, буква "Ё" в диапазон между буквами "Е" и "Ж" не попадает, т. к. буква "Ё" имеет код 1025, буква "Е" - 1045, а буква "Ж" - 1046. Если сравнение производится с помощью функции wcscmp(), то буква "Е" будет больше буквы "Ё" (1045 > 1025). При ис­ пользовании функции wcscoll() с настройкой локали буква "Ё" попадет в диапа­ зон между буквами "Е" и "Ж", т. к. используются локальные настройки по алфа­ виту. Если локаль не настроена, то эта функция эквивалентна функции wcs cmp(). Сравнение производится с учетом регистра символов. Прототип функции: #include <wchar.h> /* или #include <string.h> */ int wcscoll(const wchar t *strl,. const wchar t *str2); Пример: wchar_t strl[] L"E", str2[] wprintf(L"%d\n", strl[0]); L11Ё 11 ; // 1045 (Е) wprintf(L"%d\n", str2[О]); // 1025 (Ё) wprintf(L"%d\n", wcscmp(strl, str2)); // 1 wprintf(L"%d\n", wcscoll(strl, str2)); // 1 // Без настройки локали: Е (код 1045) больше Ё (код 1025) // Настройка локали _wsetlocale(LC_ALL, L"Russian_Russia.1251"); wprintf(L"%d\n", wcscmp(strl, str2)); // 1 wprintf(L"%d\n", wcscoll(strl, str2)); // -1 // После настройки локали: Е меньше Ё [J _wcsncoll() - сравнивает maxcount первых символов в L-строках strl и str2. Сравнение производится с учетом регистра символов. Функция аналогична функции wcsncmp(), но сравнение производится с учетом значения LC COLLATE в текущей локали. Прототип функции: #include <wchar.h> /* или #include <string.h> */ int _wcsncoll(const wchar t *strl, const wchar t *str2, size_t maxCount); Пример: _ wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t strl[] = L"абв", str2[] = L"абг"; wprintf(L"%d\n", _wcsncoll(strl, str2, 2)); // О wprintf(L"%d\n", _wcsncoll(strl, str2, З)); // -1
284 Глаееl □ wcsxfrm(J - преобразует L-строку source в строку специального формата н • писывает ее в dest. В конец вставляется нулевой символ. Записывается не бо.• rnaxcount символов. Если количество символов rnaxcount меньше необходи'-КУ8 количества символов после преобразования, то содержимое dest не опреде.,еwа В качестве значения функция возвращает число необходимых символов. Д.1Я � го чтобы просто получить количество необходимых символов (без учета ну.,е.­ го символа), в параметре rnaxcount указывается число о, а в параметре dest пере­ дается нулевой указатель. Прототип функции: #include <wchar.h> /* или #include <string.h> */ size_t wcsxfrm(wchar_t *dest, const wchar_t *source, size_t rnaxCount); Функция wcscoll(J, прежде чем произвести сравнение, неявно выполняет пресо­ разование переданных строк в строки специального формата. Функция wcsx::::: позволяет выполнить такое преобразование явным образом. Преобразован81 строки производится с учетом значения LC_COLLATE в текущей локали. Если ."'0- каль не настроена, то просто выполняется копирование. В дальнейшем строg специального формата можно сравнивать с помощью функции wcscmp(J • Резу.-в.­ тат сравнения будет соответствовать результату сравнения с помощью функl.D8 wcscoll(J. Функцию wcsxfrm() следует использовать, если сравнение строк про-­ изводится многократно. Пример: _ wsetlocale(LC_ALL, L"Russian_Russia.1251"); intх=О; wchar_t strl[] = L"E", str2[] = L"Ё"; wchar_t bufl[l0] = {О), buf2[10] {О); х = (int)wcsxfrm(NULL, strl, О); wprintf(L"%d\n", х); wcsxfrm(bufl, strl, 10); wcsxfrm(buf2, str2, 10); wprintf(L"%d\n", wcscrnp(strl, str2)); wprintf(L"%d\n", wcscoll(strl, str2)); wprintf(L"%d\n", wcscrnp(bufl, buf2)); //7(нуженбуфер7+1) // 1 ("Е" больше "Ё") // -1 ("Е" меньше "Ё") // -1 ("Е" меньше "Ё") □ _wcsicmp(J - сравнивает L-строки без учета регистра символов. Функция учм­ тывает настройки локали для категории LC_СТУРЕ. Для русских букв необходн"° настроить локаль. Прототип функции: #include <wchar.h> /* или #include <string.h> */ int _wcsicrnp(const wchar t *strl, const wchar t *str2); Пример: _wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t strl[] = L"абв", str2[] = L"АБВ"; wprintf(L"%d\n", _wcsicrnp(strl, str2)); // О □ _wcsnicrnp() - сравнивает rnaxCount первых символов в L-строках strl и str2 без учета регистра символов. Функция учитывает настройки локали для категории LC_СТУРЕ. Для русских букв необходимо настроить локаль. Прототип функции:
Широкие символы и L-строки #include <wchar.h> /* или #include <string.h> */ int _wcsnicrnp(const wchar_t *strl, const wchar t *str2, size_t maxCount); Пример: _wsetlocale(LC_ALL, L"Russian Russia.1251"); wchar_t strl[] = L"абве", str2[] = L"АБВЖ"; wprintf(L"%d\n", _wcsnicrnp(strl, str2, З)); // О wprintf(L"%d\n", _wcsnicrnp(strl, str2, 4)); // -1 285 [] _wcsicoll() - сравнивает L-строки без учета регистра символов. Функция учи­ тывает настройки локали для категорий 1с_стУРЕ и 1с_COLLATE. Дnя русских букв необходимо настроить локаль. Прототип функции: #include <wchar.h> /* или #include <string.h> */ int _wcsicoll(const wchar t *strl, const wchar t *str2); Пример: _ wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t strl[] = L"абв", str2[] = L"АБВ"; wprintf(L"%d\n", _wcsicoll(strl, str2)); // О С1 _wcsnicoll() - сравнивает maxCount первых символов в L-строках strl и str2 без учета регистра символов. Функция учитывает настройки локали для катего­ рий LС_СТУРЕ и LC_COLLATE. Дnя русских букв необходимо настроить локаль. Прототип функции: #include <wchar.h> /* или #include <string.h> */ int _wcsnicoll(const wchar_t *strl, const wchar t *str2, size t maxCount); Пример: _ wsetlocale(LC_ALL, L"Russian Russia.1251"); wchar_t strl[] = L"абве", str2[] = L"АБВЖ"; wprintf(L"%d\n", _wcsnicoll(strl, str2, З)); // О wprintf(L"%d\n", _wcsnicoll(strl, str2, 4)); // -1 Дnя сравнения строк можно также использовать функцию wmemcrnp(). Она сравнива­ ет первые count символов массивов ьuп и buf2. В качестве значения функция воз- 1ращает: С1 отрицательное число - если bufl меньше buf2; С1 о - если массивы равны; С1 положительное число - если bufl больше buf2. Прототип функции: linclude <wchar.h> int wmemcmp(const wchar t *strl, const wchar t *str2, size t count);
286 Глава В Пример: _wsetlocale(1C_A11, 1"Russian_Russia.1251"); wchar_t strl[] = 1"аЬс", str2[] = 1"аЬс"; wprintf(1"%d\n", wmemcmp(strl, str2, 3)); // о str1[2] = 1'Ь'; wprintf(1"%d\n", wmemcmp(strl, str2, 3)); // -1 str1(2] = 1'd'; wprintf(1"%d\n", wmemcmp(strl, str2, 3)); // 1 8.16. Преобразование L-строки в число Для преобразования L-строки в число используются следующие функции. □ _wtoi() - преобразует L-строку в число, имеющее тип int. Прототип функции: #include <stdlib.h> /* или #include <wchar.h> */ int _wtoi(const wchar_t *str); Считывание символов производится, пока они соответствуют цифрам. Ины-.,м словами, в строке могут содержаться не только цифры. Пробельные симво.1ы в начале строки игнорируются. Пример преобразования: wprintf(1"%d\n", wtoi(1"25")); // 25 - wprintf(1"%d\n", _wtoi(1"2.5")); //2 wprintf(1"%d\n", wtoi(1"5str")); //5 wprintf(1"%d\n", wtoi(1"5s10")); //5 wprintf(1"%d\n", _wtoi(1" \t\n 25")); // 25 wprintf(1"%d\n", _wtoi(1"-25")); // -25 wprintf(1"%d\n", wtoi(1"str")); // - Если значение выходит за диапазон значений типа int, то в Visual С возвращает­ ся максимальное или минимальное значение типа int и переменной errno при­ сваивается значение ERANGE: _ wsetlocale(1C_A11, 1"Russian_Russia.1251"); wprintf(1"%d\n", _wtoi(1"2147483649")); // 2147483647 wprintf(1"%d\n", errno); // 34 // #include <math.h> или #include <errno.h> if (errno == ERANGE) _putws(1"Bыxoд за диапазон значений"); В MinGW будет усечение значения: wprintf(1"%d\n", _wtoi(1"2147483649")); // -2147483647 wprintf(1"%d\n", errno); //О □ _wtol() - преобразует L-строку в число, имеющее тип long. Прототип функции: #include <stdlib.h> /* или #include <wchar.h> */ long _wtol(const wchar t *str);
Широкие символы и L-строки Пример преобразования: wprintf(L"%ld\n", _wtol(L"25")); wprintf(L"%ld\n", wtol(L" \n -25")); wprintf(L"%ld\n", _wtol(L"2.5")); wprintf(L"%ld\n", _wtol(L"5str")); wprintf(L"%ld\n", _wtol(L"5s10")); wprintf(L"%ld\n", _wtol(L"str")); 287 // 25 // -25 //2 //5 //5 //о Если значение выходит за диапазон значений типа long, то в Visual С возвраща­ ется максимальное или минимальное значение типа long и переменной errno присваивается значение ERANGE: _ w setlocale(LC_ALL, L"Russian_Russia.1251"); wprintf(L"%ld\n", _wtol(L"2147483649")); // 2147483647 wprintf(L"%d\n", errno); // 34 // #include <rnath.h> или #include <errno.h> if (errno == ERANGE) _ putws(L"Выxoд за диапазон значений"); В MinGW будет усечение значения: wprintf(L"%ld\n", _wtol(L"2147483649")); // -2147483647 wprintf(L"%d\n", errno); //О D _wtoll() - преобразует L-строку в число, имеющее тип long long. Функция доступна только в Visual С. Прототип функции: #include <stdlib.h> /* или #include <wchar.h> */ long long _wtoll(const wchar_t *str); Пример преобразования: wprintf(L"%I64d\n", _wtoll(L"9223372036854775807")); // 9223372036854775807 Если значение выходит за диапазон значений типа long long, то в Visual С воз­ вращается максимальное или минимальное значение типа long long и перемен­ ной errno присваивается значение ERANGE: _ wsetlocale(LC_ALL, L"Russian_Russia.1251"); wprintf(L"%I64d\n", _wtoll(L"9223372036854775809")); // 9223372036854775807 wprintf(L"%d\n", errno); // 34 // #include <rnath.h> или #include <errno.h> if (errno == ERANGE) _putws(L"Bыxoд за диапазон значений"); В MinGW можно воспользоваться функцией wtoll(). Прототип функции: tinclude <stdlib.h> long long wtoll(const wchar t *str);
288 Пример преобразования: wprintf(L"%I64d\n", wtoll(L"9223372036854775807")); // 9223372036854775807 wprintf(L"%I64d\n", wtoll(L"9223372036854775809")); // 9223372036854775807 wprintf(L"%d\n", errno); // 34 □ _wtoi64() - преобразует L-строку в число, имеющее тип функции: #include <stdlib.h> /* или #include <wchar.h> */ int64 _wtoi64(const wchar_t *str); Пример преобразования: wprintf(L"%I64d\n", _wtoi64(L"9223372036854775807")); // 9223372036854775807 Глава 1 int64. ПротоТ18 Если значение выходит за диапазон значений типа _int64, то возвращаетс:1 максимальное или минимальное значение типа _int64 и переменной errno Прlt­ сваивается значение ERANGE: _ wsetlocale(LC_ALL, L"Russian_Russia.1251"); wprintf(L"%I64d\n", wtoi64(1"9223372036854775809")); // 9223372036854775807 wprintf(L"%d\n", errno); // 34 // #include <math.h> или #include <errno.h> if (errno == ERANGE) _putws(L"Bыxoд за диапазон значений"); □ wcstol() и wcstoul() - преобразуют L-строку в число, имеющее типы и unsigned long соответственно. Прототипы функций: #include <stdlib.h> /* или #include <wchar.h> */ long wcstol(const wchar_t *str, wchar_t **endPtr, int radix); unsigned long wcstoul(const wchar_t *str, wchar_t **endPtr, int radix); Пробельные символы в начале строки игнорируются. Считывание символов з. канчивается на символе, не являющемся записью числа. Указатель на этот си� вол доступен через параметр endPtr, если он не равен NULL. В параметре ra:::J. можно указать систему счисления (число от 2 до 36). Если в параметре ra::::i задано число о, то система счисления определяется автоматически. Пример пре­ образования: _ wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar t *р = NULL; wprintf(L"%ld\n", wcstol(L"25", NULL, О)); // 25 wprintf(L"%ld\n", wcstol(L"025", NULL, О)); // 21 wprintf(L"%ld\n", wcstol(L"0x25", NULL, О)); // 37 wprintf(L"%ld\n", wcstol(L"lll", NULL, 2)); // 7
Широкие символы и L-строки wprintf(L"%ld\n", wcstol(L"025", wprintf(L"%ld\n", wcstol(L"0x25", wprintf(L"%ld\n", wcstol(L"5sl0", wprintf(L"%s\n", р); 289 NULL, 8)); // 21 NULL, 16)); // 37 &р, О)); //5 // sl0 Если в строке нет числа, то возвращается число о. Если число выходит за диапа­ зон значений для типа, то значение будет соответствовать минимальному (LONG_МIN или о) или максимальному (LONG_МAX или ULONG_МAX) значению для типа, а переменной errno присваивается значение ERANGE. Пример: _wsetlocale(LC_ALL, L"Russian_Russia.1251"); wprintf(L"%ld\n", wcstol(L"str", NULL, О)); //О wprintf(L"%ld\n", wcstol(L"2147483649", NULL, О)); // 2147483647 (соответствует LONG_МAX) wprintf(L"%d\n", ermo); // 34 // #include <math.h> или #include <errno.h> if (ermo == ERANGE) { _putws(L"Выxoд за диапазон значений"); CJ wcstoll() и wcstoull() - преобразуют L-строку в число, имеющее типы long long и unsigned long long соответственно. Прототипы функций: #include <wchar.h> long long wcstoll(const wchar_t *str, wchar_t **endPtr, int radix); unsigned long long wcstoull(const wchar_t *str, wchar_t **endPtr, int radix); Пробельные символы в начале строки игнорируются. Считывание символов заканчивается на символе, не являющемся записью числа. Указатель на этот символ доступен через параметр endPtr, если он не равен NULL. В параметре radix можно указать систему счисления (число от 2 до 36). Если в параметре radix задано число о, то система счисления оnреде,!lяется автоматически. При­ мер преобразования: _wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t *р = NULL; wprintf(L"%I64d\n", wcstoll(L"25", NULL, О)); // 25 wprintf(L"%I64d\n", wcstoll(L"025", NULL, О)); // 21 wprintf(L"%I64d\n", wcstoll(L"0x25", NULL, О)); // 37 wprintf(L"%I64d\n", wcstoll(L"lll", NULL, 2)); // 7 wprintf(L"%I64d\n", wcstoll(L"025", NULL, В)); // 21 wprintf(L"%I64d\n", wcstoll(L"0x25", NULL, 16)); // 37 wprintf(L"%I64d\n", wcstoll(L"5s10", &р, О)); //5 wprintf(L"%s\n", р); // s10 Если в строке нет числа, то возвращается число о. Если число выходит за диапа­ зон значений для типа, то значение будет соответствовать минимальному (LLONG_МIN или о) или максимальному (LLONG_МAX или ULLONG_МAX) значению для типа, а переменной errno присваивается значение ERANGE. Пример:
290 _wsetlocale(LC_ALL, L"Russian_Russia.1251"}; wprintf(L"%I64d\n", wcstoll(L"str", NULL, О}}; // О wprintf(L"%I64d\n", wcstoll(L"9223372036854775809", NULL, О}}; // 9223372036854775807 (соответствует LLONG_МAX} wprintf(L"%d\n", errno}; // 34 // #include <math.h> или #include <errno.h> if (errno == ERANGE} _putws(L"Bыxoд за диапазон значений"}; Главаl □ _wcstoi64(} и _wcstoui64(} - преобразуют L-строку в число, имеющее тиllli _int64 и unsigned _int64 соответственно. Прототипы функций: #include <stdlib.h> /* или #include <wchar.h> */ int64 _wcstoi64(const wchar_t *str, wchar_t **endPtr, int radix}; unsigned int64 _wcstoui64(const wchar_t *str, wchar_t **endPtr, int radix}; Пробельные символы в начале строки игнорируются. Считывание симво.1(8 заканчивается на символе, не являющемся записью числа. Указатель на этс.tr символ доступен через параметр endPtr, если он не равен NULL. В парамеч,е radix можно указать систему счисления (число от 2 до 36). Если в параметре radix задано число о, то система счисления определяется автоматически. П� мер преобразования: - wsetlocale(LC_ALL, L"Russian_Russia.1251"}; wchar_t *р = NULL; wprintf(L"%I64d\n", _wcstoi64(L"25", NULL, О}}; // 25 wprintf(L"%I64u\n", wcstoui64(1"25", NULL, О}); // 25 wprintf(L"%I64d\n", _wcstoi64(L"025", NULL, О}}; // 21 wprintf(L"%I64d\n", _wcstoi64(L"0x25", NULL, О}}; // 37 wprintf(L"%I64d\n", _wcstoi64(L"lll", NULL, 2)}; // 7 wprintf(L"%I64d\n", _wcstoi64(1"025", NULL, 8)}; // 21 wprintf(L"%I64d\n", _wcstoi64(L"0x25", NULL, 16)}; // 37 wprintf (L"%I64d\n", _wcstoi64(L"5s10", &р, О}}; //5 wprintf(L"%s\n", р}; // sl0 Если в строке нет числа, то возвращается число о. Если число выходит за диа� зон значений для типа, то значение будет соответствовать минимальному и.в максимальному значению для типа, а переменной errno присваивается значение ERANGE. Пример: _ wsetlocale(LC_ALL, L"Russian_Russia.1251"}; wprintf(L"%I64d\n", _wcstoi64(L"str", NULL, О}}; // О wprintf(L"%I64d\n", _wcstoi64(L"9223372036854775809", NULL, 0}}; // 9223372036854775807 wprintf(L"%d\n", errno}; // 34 // #include <rnath.h> или #include <errno.h> if (errno == ERANGE} _ putws(L"Bыxoд за диапазон значений"};
Широкие символы и L-строки 291 □ wcstoimax() и wcstoumax() - преобразуют L-строку в число, имеющее типы intmax_t и uintmax_t соответственно. Прототипы функций: #include <inttypes.h> intmax_t wcstoimax(const wchar_t *str, wchar_t **endPtr, int radix); typedef long long intmax_t; uintmax_t wcstoumax(const wchar_t *str, wchar t **endPtr, int radix); typedef unsigned long long uintmax_t; Пробельные символы в начале строки игнорируются. Считывание символов заканчивается на символе, не являющемся записью числа. Указатель на этот символ доступен через параметр endPtr, если он не равен NULL. В параметре radix можно указать систему счисления (число от 2 до 36). Если в параметре radix задано число о, то система счисления определяется автоматически. При­ мер преобразования: _wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t *р = NULL; wprintf(L"%I64u\n", wcstournax(L"25", NULL, О)); wprintf(L"%I64d\n", wcstoimax(L"25", NULL, О)); wprintf(L"%I64d\n", wcstoimax(L"025", NULL, О)); wprintf(L"%I64d\n", wcstoirnax(L"0x25", NULL, О)); wprintf(L"%I64d\n", wcstoimax(L"lll", NULL, 2)); wprintf(L"%I64d\n", wcstoimax(L"025", NULL, 8)); wprintf(L"%I64d\n", wcstoimax(L"0x25", NULL, 16)); wprintf(L"%I64d\n", wcstoimax(L"5s10", &р, О)); wprintf(L"%s\n", Р,); // 25 // 25 // 21 // 37 //7 // 21 // 37 //5 // sl0 Если в строке нет числа, то возвращается число о. Если число выходит за диапа­ зон значений дr1я типа, то значение будет соответствовать минимальному (INТМAX_MIN или о) или максимальному (INТМАХ_МА.'{ или UINТМАХ_МАХ) значению дr1я типа, а переменной errno присваивается значение ERANGE. Пример: _wsetlocale(LC_ALL, L"Russian_Russia.1251"); // #include <stdint.h> wprintf(L"%I64d\n", INТМAX_МIN); // -9223372036854775808 wprintf(L"%I64d\n", INТМАХ_МАХ); // 9223372036854775807 wprintf(L"%I64u\n", UINТМАХ_МАХ); // 18446744073709551615 wprintf(L"%I64d\n", wcstoimax(L"str", NULL, О)); // О wprintf(L"%I64d\n", wcstoimax(L"9223372036854775809", NULL, О)); // 9223372036854775807 (соответствует INТМАХ_МАХ) wprintf(L"%d\n", errno); // 34 // #include <math.h> или #include <errno.h> if (errno == ERANGE) _ putws(L"ВЫXoд за диапазон значений"); □ _wtof() - преобразует L-строку в вещественное число, имеющее тип douЬle. Прототип функции:
292 #include <stdlib.h> /* или #include <wchar.h> */ douЫe _wtof(const wchar_t *str); Глава 1 Пример преобразования (обратите внимание: от настроек локали зависит дес• тичный разделитель вещественных чисел): _ wsetlocale(1C_A11, 1"С"); wprintf(1"%.2f\n", _wtof(1"25")); // 25.00 wprintf(1"%.2f\n", wtof(1"2.5")); // 2.50 wprintf(1"%.2f\n", _wtof(1"2,5")); // 2.00 wprintf(1"%. 2f\n", _wtof(1"5.lstr")); // 5.10 wprintf(1"%.2f\n", wtof(1"5s10")); // 5.00 wprintf(1"%.2f\n", wtof(1"str")); // 0.00 _wsetlocale(1C_A11, 1"Russian Russia.1251"); wprintf(1"%.2f\n", wtof(1"2.5")); // 2,00 wprintf(1"%.2f\n", _wtof(1"2,5")); // 2,50 □ wcstod() - преобразует L-строку в вещественное число, имеющее тип doub:-:.. Прототип функции: #include <stdlib.h> /* или #include <wchar.h> */ douЫe wcstod(const wchar_t *str, wchar_t **endPtr); Пробельные символы в начале строки игнорируются. Считывание символов з. канчивается на символе, не являющемся записью вещественного числа. Указ. тель на этот символ доступен через параметр endPtr, если он не равен Nu:__:_ Пример преобразования (обратите внимание: от настроек локали зависит дес• тичный разделитель вещественных чисел): _ wsetlocale(1C_A11, 1"С"); wchar_t *р = NU11; wprintf(1"%.2f\n", wcstod(1" \t\n 25", NU11)); wprintf(1"%.2f\n", wcstod(1"2.5", NU11)); wprintf(1"%.2f\n", wcstod(1"2,5", NU11)); wprintf(1"%.2f\n", wcstod(1"5.lstr", NU11)); wprintf(1"%.2f\n", wcstod(1"str", NU11)); wprintf(1"%e\n", wcstod(1"14.5e5s10", &р)); wprintf(1"%s\n", р); _ wsetlocale(1C_A11, 1"Russian_Russia.1251"); wprintf(1"%.2f\n", wcstod(1"2.5", NU11)); wprintf(1"%.2f\n", wcstod(1"2,5", NU11)); // 25.00 // 2.50 // 2.00 // 5.10 // 0.00 // 1.450000е+006 // s10 // 2,00 // 2,50 Если в строке нет числа, то возвращается число о. Если число выходит за диа­ пазон значений для типа, то возвращается значение -HUGE VA1 или HUGE_Vf:й.... а переменной errno присваивается значение ERANGE; □ wcstof() - преобразует L-строку в вещественное число, имеющее тип floa:. Прототип функции: #include <stdlib.h> /* или #include <wchar.h> */ float wcstof(const wchar t *str, wchar t **endPtr);
Широкие символы и L-строки 293 Пробельные символы в начале строки игнорируются. Считывание символов заканчивается на символе, не являющемся записью вещественного числа. Указа­ тель на этот символ доступен через параметр endPtr, если он не равен NULL. Пример преобразования (обратите внимание: от настроек локали зависит деся­ тичный разделитель вещественных чисел): _ wsetlocale(LC_ALL, L"C"); wchar_t *р = NULL; wprintf(L"%.2f\n", wcstof(L" \t\n 25", NULL)); wprintf(L"%.2f\n", wcstof(L"2.5", NULL)); wprintf(L"%.2f\n", wcstof(L"5.lstr", NULL)); wprintf(L"%e\n", wcstof(1"14. 5e5s10", &р)); wprintf(L"%s\n", р); _ wsetlocale(LC_ALL, L"Russian_Russia.1251"); wprintf(L"%.2f\n", wcstof(L"2.5", NULL)); wprintf(L"%.2f\n", wcstof(L"2,5", NULL)); // // // // // // // 25.00 2.50 5.10 1.450000е+ОО6 s10 2,00 2,50 Если в строке нет числа, то возвращается число о. Если число выходит за диапа­ зон значений для типа, то возвращается значение -HUGE_VALF или HUGE _VALF, а пе­ ременной errno присваивается значение ERANGE; D wcstold() - преобразует L-строку в вещественное число, имеющее тип long douЫe. Прототип функции: linclude <stdlib.h> /* или #include-<wchar.h> */ long douЬle wcstold(const wchar_t *str, wchar_t **endPtr); Пробельные символы в начале строки игнорируются. Считывание символов за­ канчивается на символе, не являющемся записью вещественного числа. Указа­ тель на этот символ доступен через параметр endPtr, если он не равен NULL. Пример преобразования (обратите внимание: от настроек локали зависит деся­ тичный разделитель вещественных чисел): _ wsetlocale(LC_ALL, L"C"); wchar_t *р = NULL; _mingw_printf("%.2Lf\n", wcstold(L" \t\n 25", NULL)); _ mingw_printf("%.2Lf\n", wcstold(L"2.5", NULL)); _mingw_printf("%.2Lf\n", wcstold(L"5.lstr", NULL)); _mingw_printf("%Le\n", wcstold(L"14 .5e5s10", &р)); // 1.450000е+006 wprintf(L"%s\n", р); _wsetlocale(LC ALL, L"Russian_Russia.1251"); _ mingw_printf("%.2Lf\n", wcstold(L"2.5", NULL)); _ mingw_printf("%.2Lf\n", wcstold(L"2,5", NULL)); // 25.00 // 2.50 // 5.10 // s10 // 2,00 // 2,50 Если в строке нет числа, то возвращается число о. Если число выходит за диапа­ зон значений для типа, то возвращается значение -HUGE _VALL или .НUGE _VALL, а пе­ ременной errno присваивается значение ERANGE. ... преобразования L-строки в число можно также воспользоваться функциями 111Scanf() и _snwscanf(). Прототипы функций:
294 #include <stdio.h> /* или #include <wchar.h> */ int swscanf(const wchar_t *str, const wchar_t *format, ... ); int _snwscanf(const wchar t *str, size_t maxCount, const wchar_t *format, ... ); Глава8 В параметре str указывается L-строка, содержащая число, в параметре rnaxcouг.: - максимальное количество просматриваемых символов в строке, а в пapaмe-JJII: forrnat - строка специального формата , внутри которой задаются спецификаторi�i. аналогичные применяемым в функции printf(J (cAt. разд. 2 .8), а также некотор.­ дополнительные спецификаторы. В последующих параметрах передаются адреа� переменных. Функции возвращают количество произведенных присваиванна. Пример получения двух целых чисел: _ wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t str[] = 1"20 30 str"; intn=О,k=О; int count = swscanf(str, L"%d%d", &n, &k); // int count = _snwscanf(str, wcslen(str), L"%d%d", &n, &k); if (count == 2) { wprintf(L"n %d\n", n);//n=20 wprintf(L"k = %d\n", k); // k 30 else _putws(L"He удалось преобразовать"); При вводе строки функция swscanf() не производит никакой проверки длины с�-.. ки, что может привести к переполнению буфера. Обязательно указывайте шири� при использовании спецификатора %s (например, %255s). Кроме того, следует учи­ тывать, что функция не проверяет возможность выхода значения за диапазон зна­ чений для типа, что может привести к неправильному результату. Функция ни�.. об этом вас не предупредит, поэтому лучше использовать функции, рассмотренные в начале этого раздела. 8.17. Преобразование числа в L-строку Преобразовать целое число в L-строку позволяют следующие функции. □ _itow() и itow_s () - преобразуют число типа int в L-строку. Прототип:w функций: #include <stdlib.h> /* или #include <wchar.h> */ wchar t *_itow(int val, wchar_t *dest, int radix); errno t _itow_s(int val, wchar_t *dest, size_t sizeinWords, int radix); В параметре val указывается число, в параметре dest - указатель на симво.11:,­ ный массив, в который будет записан результат, в параметре sizer��ords - ма�: ­ симальный размер символьного массива dest, а в параметре radix - систе....а счисления (2, в, 10 или 16). Пример использования функции _itow_s():
Широкие символы и L-строки wchar_t str[l00] = {О}; intх=255; _ itow_s(x, str, 100, 10); wprintf(L"%s\n", str); // 255 Пример вывода двоичного, восьмеричного и шестнадцатеричного значений: wchar_t str[l00] = {О}; // Двоичное значение wprintf(L"%s\n", _itow(100, str, 2)); // 1100100 // Восьмеричное значение wprintf(L"%s\n", itow(l0, str, 8)); // 12 // Шестнадцатеричное значение wprintf(L"%s\n", _itow(255, str, 16)); // ff 295 □ _1tow() и _1tow_s() - преобразуют число типа long в L-строку. Прототипы функций: #include <stdlib.h> /* или #include <wchar.h> */ wchar t *_ltow(long val, wchar_t *dest, int radix); errno t _ltow_s(long val, wchar t *dest, size t sizeinWords, int radix); Пример: wchar_t str[l00] = {О}; long х = 255; _ ltow_s(x, str, 100, 10); wprintf(L"%s\n", str); wprintf(L"%s\n", ltow(x, str, 16)); // 255 // ff □ _ultow() и_ultow_s() - преобразуют число типа unsigned long в L-строку. Про­ тотипы функций: #include <stdlib.h> /* или #include <wchar.h> */ wchar t *_ultow(unsigned long val, wchar_t *dest, int radix); errno t _ultow_s(unsigned long val, wchar_t *dest, size t sizeinWords, int radix); Пример: wchar_t str[l00] = {О}; unsig,ned long х = 255; _ ultow_s(x; str, 100, 10); wprintf(L"%s\n", str); // 255 wprintf(L"%s\n", _ultow(x, str, 16)); // ff □ _i64tow() и _i64tow_s() - преобразуют число типа _int64 в L-строку. Прото­ типы функций: #include <stdlib.h> /* или #include <wchar.h> */ wchar t *_i64tow(_int64 val, wchar_t *dest, int radix); errno t _i64tow_s(_int64 val, wchar t *dest, size t sizeinWords, int radix);
296 Пример: wchar_t str[l00] = {О}; _int64 х = 255; i64tow_s(x, str, 100, 10); wprintf(1"%s\n", str); // 255 wprintf(1"%s\n", _i64tow(х, str, 16)); // ff Глава 1 □ _ui64tow() и _ui64tow_s() - преобразуют число типа unsigned _int64 в L-c� ку. Прототипы функций: #include <stdlib.h> !• или #include <wchar.h> •! wchar_t *_ui64tow(unsigned int64 val, wchar_t *dest, int radix); errno_t _ui64tow_s(unsigned int64 val, wchar_t *dest, size t sizeinWords, int radix); Пример: wchar_t str[l00] {О}; unsigned int64 х = 255; _ui64tow_s(x, str, 100, 10); wprintf (1"%s\n", str); // 255 wprintf(L"%s\n", _ui64tow(x, str, 16)); // ff Выполнить форматирование L-строки, а также преобразовать значения элементар­ ных типов в L-строку можно с помощью функции swprintf_ s (). Прототип функции: #include <stdio.h> /* или #include <wchar.h> */ int swprintf_s(wchar_t *buf, size_t sizeinWords, const wchar_t *forrnat, ...); В параметре forrnat указывается строка специального формата. Внутри этой стро1а1 можно указать обычные символы и спецификаторы формата, начинающиеся с си� вола %. Спецификаторы формата совпадают со спецификаторами, используемы'Ам в функции printf() (см. разд. 2.8). Вместо спецификаторов формата подставляютс:1 значения, указанные в качестве параметров. Количество спецификаторов должно совпадать с количеством переданных параметров. Результат записывается в буфер. адрес которого передается в первом параметре (buf). В параметре sizeinWords ука­ зывается размер буфера. Функция возвращает количество символов, записанны.\ в символьный массив. Пример: _wsetlocale(LC_ALL, L"Russian_Russia.1251"); wchar_t buf[50] = {О}; intх=100,count=О; count = swprintf_s(buf, 50, L"x %d", х); wprintf(1"%s\n", buf); //х=100 wprintf(L"%d\n", count); //7
Широкие символы и L-строки 8.18. Типь1 char16_t и char32_t 297 Стандарт CI I ввел в язык С два новых типа- charl6_t и char32_t . Тип charl6_t описывает символ в кодировке UTF-16. Объявление: linclude <uchar.h> typedef uint_leastl6_t charl6 t; linclude <stdint.h> typedef unsigned short uint least16_t; При инициализации символа или строки перед литералом указывается строчная буква u: setlocale(LC_ALL, "Russian_Russia.1251"); charl6_t ch = u'я'; printf("%u\n", ch); charl6_t str[] = u"строка"; size_t size = sizeof(char16_t); printf("%d\n", (int)size); size_t size_str = sizeof(str); // 1103 //2 printf("%d\n", (int)size_str); // 14 int len = (int) (size_str / size); printf("%d\n", (int)len); //7 for(inti=О;i<len;++i) piintf("lu ", str[i]); 1 // 1089 1090 1088 1086 1082 1072 О Тип char32_t описывает символ в кодировке UTF-32. Объявление: linclude <uchar.h> typedef uint_least32_t char32_t; finclude <stdint.h> typedef unsigned uint_least32 t; При инициализации символа или строки перед литералом указывается прописная буква u: setlocale(LC_ALL, "Russian_Russia.1251"); char32_t ch = U'я'; printf("lu\n", ch); char32_t str[] = U"строка"; size_t size = sizeof(char32_t); printf("%d\n", (int)size); size_t size_str = sizeof(str); // 1103 //4 printf("%d\n", (int)size_str); // 28 int len = (int) (size_str / size); printf("%d\n", (int)len); //7 for(inti=О;i<len;++i) printf("lu ", str[i]); 1 // 1089 1090 1088 1086 1082 1072 О
298 Глава 1 Для преобразования типов предназначены следующие функции. □ clбrtomЬ() - преобразует символ сlб из кодировки UTF-16 в кодировку, ука­ занную с помощью локали, и записывает результат в символьный массив t·..:: Нулевой символ в массив не добавляется . Функция возвращает число байтов.. записанных в массив, или значение (size_t)(-1) в случае ошибки. Прототип функции: #include <uchar.h> size_t clбrtomЬ(char *buf, charlб_t сlб, mЬstate_t *state); typedef int mЬstate_t; Пример преобразования символа в кодировку windows-125 l: setlocale(LC_ALL, "Russian_Russia .1251"); char buf[l0] = {О); charlб t ch = u'я'; mЬstate t state = О; size_t count = clбrtomЬ(buf, ch, &state); printf("%I64u\n", count); // 1 printf("%d\n", state); //О printf("%c\n", buf[0]); // я printf("%d\n", buf[0]); // -1 □ mЬrtoclб( J - преобразует многобайтовый символ, записанный в строку s::. в кодировку UTF-16 и сохраняет его в переменной рсlб. Кодировка указываете� с помощью локали. Максимально просматривается n символов строки s::. Функция возвращает число байтов, представляющих символ, о или значен.u (size_t)(-1), (size_t)(-2) или (size_t)(-3) в случае ошибки. Прототип функ­ ции: #include <uchar.h> size t mЬrtoclб(charlб_t *рсlб, const char *str, size t n, mЬstate t *state); Пример: setlocale(LC_ALL, "Russian_Russia.1251"); charlб_t рсlб = О; char str[l0] = "я"; mЬstate_t state = О; size_t count = mЬrtoclб(&pclб, str, 10, &state); printf("%I64u\n", count); // 1 printf("%d\n", state); printf("%u\n", рсlб); //о // 1103 □ c32rtomЬ() - преобразует символ с32 и записывает результат в символьный массив buf. Нулевой символ в массив не добавляется. Функция возвращает чис­ ло байтов, записанных в массив, или значение (size_t)(-1) в случае ошибки. Прототип функции: #include <uchar.h> size t c32rtomЬ(char *buf, char32 t с32, mЬstate t *state);
Широкие символы и L-строки 299 □ mЬrtoc32() - преобразует многобайтовый символ, записанный в строку str, в символ в кодировке UTF-32 и сохраняет его в переменной рсЗ2. Прототип функции: *include <uchar.h> size_t mЬrtoc32(char32_t *рс32, const char *str, size_t n, mЬstate_t *state); В Windows функции c32rtomЬ() и mЬrtoc32() работают с многобайтовыми симво­ .uми в кодировке UTF-8, а не используют настройки кодировки из локали: char buf[l0] = {о}; char32_t ch = U'я'; .Ьstate_t state = О; size_t count = c32rtomЬ(buf, ch, &state); printf("%I64u\n", count); // 2 printf("%d\n", state); //о 11rintf("%d\n", buf(0J); // -47 11rintf("%d\n", buf[l]); // -113 char32_t ch32 = О; count = mЬrtoc32(&ch32, buf, 10, &state); printf("%I64u\n", count); // 2 printf("%d\n", state); //о J1rintf("%u\n", ch32); // 1103
ГЛАВА 9 Работа с датой и временем Основные функции для работы с датой и временем в языке С объявлены в заголо­ вочном файле time.h. В этом файле объявлены также следующие типы данных: □ clock_t - возвращается функцией clock (J • Объявление типа: typedef long clock_t; □ time_t - используется для представления времени в виде целочисленного зна­ чения. Размер типа зависит от настроек компилятора. Объявление типа: typedef time32 t time t; // В проекте Test32c typedef _time64_t time_t; // В проекте Test64c Объявления типов time32 t и _time64_t выглядят так: typedef long time32_t; typedef int64 time64 t; #define _int64 long long Некоторые функции в качестве значения возвращают указатель на структуру Объявление структуры tm выглядит следующим образом: struct tm { 1; int tm_sec; int tm_min; int tm_hour; int tm_mday; int tm_mon; int tm_year; int tm_wday; // Секунды (число от О до 59, изредка до 61) // Минуты (число от О до 59) // Час (число от О до 23) // День месяца (число от l до 31) // Месяц (число от О (январь) до 11 (декабрь)) // Год, начиная с 1900 года // День недели (число от О до 6 (О - это воскресенье, // 1 - это понедельник, ... , 6 - это суббота)) int tm_yday; // День в году (число от О до 365) int tm_isdst; // Если больше О, значит, действует летнее время // Если О, значит, летнее время не установлено. // Если меньше О, то информации нет
Работа с датой и временем 301 9.1. Получение текущей даты и времени Получить текущую дату и время позволяют следующие функции. □ tirne() - возвращает количество секунд, прошедших с начала эпохи (с I января 1970 г.). Прототип функции: #include <tirne.h> tirne_t tirne(tirne_t *Tirne); Функцию можно вызвать двумя способами, передавая в качестве параметра нулевой указатель или адрес переменной, в которую будет записано возвращае­ мое значение. Пример: tirne_t tl = tirne(NULL); printf("%ld\n", (long)tl); tirne_t t2; // Передаем нулевой указатель // 1548383508 tirne(&t2); printf("%ld\n", (long)t2); // Передаем адрес переменной // 1548383508 Вместо функции tirne() можно использовать функцию _tirne64(). Число перед открывающей круглой скобкой означает количество битов. Прототип функции: _ tirne64_t _tirne64( tirne64 t *Tirne); Пример использования: _ tirne64_t tl = _tirne64(NULL); printf("%I64d\n", tl); // 1548383659 □ grntirne о - возвращает указатель на струК'l)'ру trn с универсальным временем (UTC) или нулевой указатель в случае ошибки. В качестве параметра указывает­ ся адрес переменной, которая содержит количество секунд, прошедших с начала эпохи. Для того чтобы получить текущую дату, в качестве параметра следует передать результат выполнения функции tirne() . Прототип функции: #include <time.h> struct trn * grntirne(const time_t *Time); Пример использования функции: struct trn *ptrn = NULL; tirne_t t = time(NULL); ptrn = grntime(&t); if (!ptrn) { printf("Error\n"); exit(l); printf("%d\n", ptm->trn_rnday); printf("%d\n", ptm->trn_rnon); printf("%d\n", ptm->trn_year); printf("%d\n", ptrn->trn_hour); printf("%d\n", ptrn->trn_rnin); printf("%d\n", ptrn->tm_sec); // 25 // о (январь) // 119 (1900 + //2 // 36 // 10 119 = 2019 г.)
302 printf("%d\n", pt.m->tm_wday); // 5 (пятница) printf("%d\n", ptm->tm_yday); // 24 printf("%d\n", ptm->tm_isdst); // О Глава 9 Вместо функции gmtime() можно использовать функцию _gmtime64(). Чис.,с перед открывающей круглой скобкой означает количество битов. Прототип функции: struct tm *_gmtime64(const time64 t *Time); Вместо функции gmtime() лучше использовать функцию gmtime_s (_gmtime64_s() вместо _gmtime64()). Прототипы функций: #include <time.h> errno t gmtime_s(struct tm *Тm, const time_t *Time); errno_t _gmtime64_s(struct t.m *Тm, const time64_t *Time); Если ошибок нет, то функции возвращают значение о. При наличии ошибки воз­ вращается значение макроса EINVAL (значение равно 22) и переменная erc: устанавливается равной EINVAL. Пример использования функции gmtime_s(): struct tm ptm; time_t t = time(NULL); errno_t err = gmtime_s(&ptm, &t); if (err) { printf("Error\n"); exit(l); printf("%d\n", pt.m.t.m _mday); printf("%d\n", pt.m. t.m _mon); printf("%d\n", pt.m. t.m_year); printf("%d\n", pt.m.t.m _hour); printf("%d\n", pt.m. t.m_min); printf("%d\n", pt.m.t.m _sec); printf("%d\n", pt.m. t.m_wday); printf("%d\n", pt.m. t.m_yday); printf("%d\n", pt.m.t.m _isdst); // // // // // // // // // 25 о (январь) 119 (1900 + 119 2019 г.) 2 41 10 5 (пятница) 24 о □ localtime() - возвращает указатель на структуру tm с локальным временем или нулевой указатель в случае ошибки. В качестве параметра указывается адрес пе­ ременной, которая содержит количество секунд, прошедших с начала эпохи. д,я того чтобы получить текущую дату, в качестве параметра следует передать результат выполнения функции time(). Прототип функции: #include <time.h> struct tm *localtime(const time_t *Time); Пример использования функции: struct tm *pt.m = NULL; time_t t = time(NULL); ptm = localtime(&t);
Работа с датой и временем if (!ptm) ( printf("Error\n"); exit(l); printf("%d\n", ptm->tm_mclay); printf("%d\n", ptm->tm_mon); printf("%d\n", ptm->tm_year); printf("%d\n", ptm->tm_hour); printf("%d\n", ptm->tm_min); printf("%d\n", ptm->tm_sec); printf("%d\n", ptm->tm_wday); printf("%d\n", ptm->tm_yday); printf("%d\n", ptm->tm_isdst); // // // // // // // // // 303 25 о (январь) 119 (1 900 + 119 2019 г.) 5 45 49 5 (пятница) 24 о Вместо функции localtime() можно использовать функцию _localtime64 (). Число перед открывающей круглой скобкой означает количество битов. Прото­ тип функции: struct tm *_localtime64(const _time64_t *Time); Вместо функции localtime() лучше использовать "функцию localtime_s() (_localtime64_s() вместо _localtime64 ()). Прототипы функций: #include <time.h> errno_t localtime_s(struct tm *Тm, const time_t *Time); errno_t _localtime64_s(struct tm *Тm, const _time64_t *Time); Если ошибок нет, то функции возвращают значение о. При наличии ошибки воз­ вращается значение макроса EINVAL (значение равно 22) и переменная errno устанавливается равной EINVAL. Пример использования функции localtime_s(): struct tm ptm; time_t t = time(NULL); errno t err = localtime_s(&ptm, &t); if (err) 1 printf("Error\n"); exit(l); printf("%d\n", ptm.tm_mday); printf("%d\n", ptm. tm_mon); printf("%d\n", ptm.tm_year); printf("%d\n", ptm.tm_hour); printf("%d\n", ptm. tm_min); printf("%d\n", ptm.tm_sec); printf("%d\n", ptm.tm_wday); printf("%d\n", ptm. tm_yday); printf("%d\n", ptm. tm_isdst); // // // // // // // // // 25 о (январь) 119 (1900 + 5 50 26 5 (пятница) 24 о 119 2019 г.) □ mktime() - возвращает количество секунд, прошедших с начала эпохи. В ка­ честве параметра передается указатель на структуру tm с локальной. датой и вре­ менем. В случае ошибки возвращается значение -1. Прототип функции:
304 #include <tirne.h> tirne_t rnktirne(struct trn *Тm); Пример использования функции: struct trn ptrn; tirne_t tl = tirne(NU11), t2 = О; errno_t err = localtirne_s(&ptrn, &tl); if (err) { printf("Error\n"); exit(l); t2 = rnktirne(&ptrn); printf("%I64d\n", tl); // 1548384762 printf("%I64d\n", t2); // 1548384762 Главаl Вместо функции rnktirne() можно использовать функцию _rnktirne64 (). Чис.10 перед открывающей круглой скобкой означает количество битов. Прототип функции: _tirne64_t _ rnktirne64(struct trn *Тm); □ difftirne() - возвращает разность между двумя датами (тirnel - Tirne2). В сл,­ чае ошибки возвращается значение о и переменная errno устанавливается рав­ ной EINVA1. Прототип функции: #include <tirne.h> douЫe difftirne(tirne_t Tirnel, tirne t Tirne2); Пример: tirne t tl = tirne(NU11), t2 = 152729842011; douЫe result = difftirne(tl, t2); printf("%.lf\n", result); Вместо функции difftirne() можно использовать функцию _difftirne64(). Чис,10 перед открывающей круглой скобкой означает количество битов. Прототип функции: douЫe _difftirne64(_tirne64_t Tirnel, _tirne64_t Tirne2); Выведем текущую дату и время таким образом, чтобы день недели и месяц былн написаны по-русски (листинг 9.1 ). j Листинr 9.1. Вывод текущей даты и·времени #include <stdio.h> #include <stdlib.h> #include <locale.h> #include <tirne.h> int rnain(void) { setlocale(1C_AL1, "Russian_Russia.1251"); l
Работа с датой и временем struct tm *ptm = NULL; time_t t = time(NULL); char d[] [25] {"воскресенье", "понедельник", "вторник", "среда", "четверг", "пятница", "суббота"); char m[] [25] {"января", "февраля", "марта", "апреля", "мая", "июня", "июля", "августа", "сентября", "октября", "ноября", "декабря"); ptm = localtime(&t); // Получаем текущее время if (!ptm) { printf("Er ror\n"); exit(l); printf("Сегодня:\n%s %d %s %d", d[ptm->tm_wday] , ptm->tm_mday, m[ptm->tm_m on], ptm->trn_year + 1900); printf(" %02d:%02d:%02d\n", ptm->tm_hour, ptm->tm_rnin, ptm->trn_sec); printf("%02d.%02d.%d\n", ptm->tm_rnday, ptm->tm_rnon + 1, ptm->tm_year + 1900); return О; Результат выполнения: Сегодня: пятница 25 января 2019 15:58:28 25.01.2019 9.2. Форматирование даты и · времени 305 Получить форматированный вывод даты и времени позволяют следующие функ­ ции. □ ascti.rne() и _wasctime() - возвращают указатель на строку специального фор­ мата или нулевой указатель в случае ошибки. В конец строки вставляется сим­ вол перевода строки (\n) и нулевой символ (\О). Прототипы функций: #include <time.h> char *ascti.rne(const struct tm *Тm); #include <time.h> /* или #include <wchar.h> */ wchar_t *_wasctirne(const struct tm *Тm); Пример использования функции ascti.rne() : struct tm *ptm = NULL; char *р = NULL; tirne_t t = ti.rne(NULL); ptm = localti.rne(&t); if (!ptm) { printf("Error\n"); exit(1);
306 р = asctime(ptm); if (!р) { printf("Error\n"); exit(l); printf("%s", р); // Fri Jan 25 06:01:48 2019\n Глава t Вместо функции asctime() лучше использовать функцию asctime_s(), а вмесТt' функции _wasctime() - _wasctime_s(). Прототипы функций: #include <time.h> errno_t asctime_s(char *buf, size_t sizeinBytes, const struct tm *Тm); #include <time.h> /* или #include <wchar.h> */ errno_t _ wasctime_s(wchar_t *buf, size_t sizeinWords, const struct tm *Тm); В первом параметре передается указатель на строку, во втором параметре - максимальный размер строки, а в третьем параметре - указатель на струкl)� tm. Если ошибок нет, то функции возвращают значение о. При наличии ошибки возвращается значение макроса EINVAL (значение равно 22). Пример использова­ ния функции asctime_s(): struct tm ptm; char str[BO] = {О}; time_t t = time(NULL); errno t err = localtime_s(&ptm, &t); if (err) { printf("Error\n"); exit (1); err = asctime_s(str, 80, &ptm); if (err) { printf("Error\n"); exit(l); printf("%s", str); // Fri Jan 25 06:05:04 2019\n □ ctime(}, _ctime64(), _wctime() и _wctime64() - функции аналогичны asctime но в качестве параметра принимают количество секунд, прошедших с начал эпохи. Прототипы функций: #include <time.h> char *ctime(const time_t *Time); char *_ctime64(const _time64_t *Time); #include <time.h> /* или #include <wchar.h> */ wchar_t *_wctime(const time_t *Time); wchar_t *_wctime64(const time64_t *Time); Пример использования функции ctime(): char *р = NULL; time t t = time(NULL);
Работа с датой и временем р = ctime(&t); if (!р) { printf("Error\n"); exit(1); printf("%s", р); // Fri :Jan 25 06:07:37 2019\n 307 Вместо этих функций лучше использовать функции ctime_s(), _ctime64_s(), _ wctime_s() и _wctime64_s(). Прототипы функций: #include <time.h> errno t ctime_s(char *buf, size_t sizeinBytes, const time_t *Time); errno t _ctime64_s(char *buf, size_t sizeinBytes, const _time64_t *Time); #include <time.h> /* ИJП1 #include <wchar.h> */ errno t _wctime_s(wchar_t *buf, size_t sizeinWords, const time_t *Time); errno t _wctime64_s(wchar_t *buf, size_t sizeinWords, const _time64_t *Time); Если ошибок нет, то функции возвращают значение о. При наличии ошибки воз­ вращается значение макроса EINVAL. Пример использования функции ctime_s(): char str[80] = {О}; time_t t = time(NULL); errno_t err = ctime_s(str, 80, &t); if (err) { printf("Error\n"); exit(l); printf("%s", str); // Fri Jan 25 06:10:19 2019\n □ strftime() и wcsftime() - записывают строковое представление даты Тm в соот­ ветствии со строкой формата format в строку buf. Прототипы функций: #include <time.h> size_t strftime(char *buf, size_t sizeinBytes, const char *format, const struct tm *Тm); #include <time.h> /* ИJП1 #include <wchar.h> */ size t wcsftime(wchar_t *buf, size_t sizeinWords, const wchar_t *format, const struct tm *Тm); В первом параметре передается указатель на символьный массив, в который будет записан результат выполнения функции. Во втором параметре задается максимальный размер символьного массива. В параметре format указывается строка специального формата, а в последнем параметре передается указатель на струюуру tm с представлением даты. Функции возвращают количество записан­ ных символов. В случае ошибки возвращается значение о и переменная errno устанавливается равной EINVAL. Функции зависят от настроек локали.
308 Глава■ В параметре foпnat в функциях strftime () и wcsftime () помимо обычных симво.108 могут быть указа11ы следующие комбинац�1и специальных с11мво;юв: □ '!,у- год из двух цифр (от оо до 99); □ У,У- год ш четырех цифр (на11ример, 2019); □ :m-1-1омер месяца с г1редваряющим нулем (от 01 до 12); □ ,�ь- аббрев�штура месяца в зависимости от настроек лакали (на�1ример. :з-. влокали Russian_Russia.1251 или Sep влокали с для сентября); □ ш- назва11ие месяца в зависимости от настроек локали (на11ример, сен-:-;;:� ИЛИ SeptemЬer); □ ",d- номер д11я в месяце с г1редваряющим 1-1улем (от 01 до 31); □ Yj - де11ь с начала года (от 001 до 366); □c - .u-номер 11едели в году (от оо до 53), неделя начинается с воскресенья; □ �,vl- ,юмер 1-1едели в году (от оо до 53), неделя начинается с понедельника; □ 'i.w- номер дня недели (о-для воскресенья, 6-для субботы); □ t.a- аббревиатура дня недели в зависимости от настроек лакали (11апример. :::; или Wed для среды); □ м- название д11я 1-1едели в зависимости от настроек лакал�, (например, ч-=� ИЛИ Wednesday); □ vн-часы в 24-часовом формате (от оо до 23); □ н- часы в 12-часовом формате (от 01 до 12); □ ч-1- минуты (от оо до 59); □ "cS- секунды (от оо до 59, изредка до 61); □ ·1 р- эквивалент значениям АМ и РМ в текущей лакали; □ �:с - представление даты и времени в текущей лакали (например, 25. 01. 2: � � 7:24:01 или 01/25/19 07:24:01); □ '!:#с- расширенное представление даты и времени в текущей лакали (например. 25 Январь 2019 г. 7:26:01 ИЛИ Friday, January 25, 2019 07:26:01); □ �,х- представление даты в текущей лакали (например, 25.01.2019 И.18 01/25/19); □·с . #х- расшире11ное представление даты в текущей лакали (например, 25 ЯнЕ=?= 2019 г. ИЛИ Friday, January 25, 2019); □ 'Х -представлеш1е времени в текущей лакали (на11ример, 07:29:44); □ :.z -название часовоr·о пояса или 11устая строка (на11ример, RTZ 2 (зима)); □ '!.�; - символ" · · Если после символа· • , указан символ# в КОl\iОi-tНациях � - #d, 1 . #н, " . #r, � , #j, ·: , #m, '' . #М, :*.:.. #U, 'i . #w, � : #уи ·, . #У, то 11редваряющие нули выводиться не будут. В качестве 11римера ис110ль·юва�шя фу11кции str·ftime () выведем текущую дату и время (листи1-1г 9.2).
Работа с датой и временем jinclude <stdio.h> jinclude <stdlib.h> jinclude <locale.h> jinclude <time.h> ldefine SIZE 100 int main(void) { setlocale(LC_ALL, "Russian_Russ�a.1251"); struct trn *ptrn = NULL; char str[SIZE] = {0}; time_t t = time(NULL); ptm = localtime(&t); if (!ptrn) { printf("Error\n"); exit(l); size t err2 strftime(str, SIZE, "Сегодня:\n%А %d %Ь %У %H:%M:%S\n%d.%m.%Y", ptrn); if (!err2) { printf("Error\n"); exit(l); printf("%s\n", str); return О; Результат выполнения: Сегодня: пятница 25 янв 2019 16:17:15 25.01.2019 9.3. «Засыпание)) программы 309 В MinGW прерывать выполнение программы на указанное время (по истечении cp<>ka программа продолжит работу) позволяют следующие функции. CJ sleep() - прерывает выполнение текущего потока на указанное количество секунд. Прототип функции: jinclude <unistd.h> Ш1signed int sleep(Ш1signed int seconds) Пример указания одной секунды: sleep(l); // Засьmаем на секунду
310 Глава 1 □ usleep() - прерывает выполнение текущего потока на указанное количестве микросекунд. Прототип функции: #include <unistd.h> int usleep(useconds_t usec); typedef unsigned int useconds t; Пример указания одной секунды: usleep(l000000); // Засьmаем на секунду □ nanosleep() - прерывает выполнение текущего потока на указанное количесТВL' секунд и наносекунд. Прототип функции: #include <time.h> int nanosleep(const struct timespec *request, struct timespec *remain); struct timespec { ); time t tv sec; /* Секунды*/ long tv nsec; /* Наносекунды */ В первом параметре указывается адрес структуры со временем задержки, а в.:­ втором - адрес структуры, в которую будет записан результат (можно указаn. значение NULL, если результат не важен). Пример указания одной секунды: struct timespec tw = {1, О), tr; nanosleep(&tw, &tr); // Засьmаем на секунду В Windows можно воспользоваться функцией из WinAPI Sleep(). Прототип фун1:­ ции: #include <windows.h> VOID Sleep(DWORD dwMilliseconds); В параметре dwMilliseconds указывается количество миллисекунд, на которое пре­ рывается выполнение текущего потока. Тип данных DWORD объявлен так: typedef unsigned long DWORD; Для примера выведем числа от 1 до 10 (листинг 9.3). Между выводом чисел ''за­ снем" на одну секунду. �стинr 9.3. "Засыпание" проrрамм1о1 #include <stdio.h> #include <windows.h> int main(void) { printf("Start\n"); for(inti=1;i<=10;++i){ Sleep(l000); // Засыпаем на секунду printf("i = %d\n", i); j
Работа с датой'и временем fflush(stdout); printf("End\n"); return О; 9.4. Измерение времени выполнения фрагментов кода 311 В некоторых случаях необходимо измерить время выполнения фрагментов кода, например, с целью оптимизации программы. Измерить время выполнения позволя­ ет функция clock(). Прототип функции: tinclude <time.h> clock_t clock(void); �ypedef long clock_t; Функция возвращает приблизительное время выполнения программы до вызова функции. Если время получить не удалось, функция возвращает значение (clock_t) (-1). Для того чтобы измерить время выполнения фрагмента, следует вы­ звать функцию перед фрагментом кода и сохранить результат. Затем вызвать функ­ цию после фрагмента и вычислить разность между двумя значениями. Для того чтобы получить значение в секундах, необходимо разделить результат на значение макроса c1ocкs_PER_SEC . Определение макроса выглядит следующим образом: tdefine CLOCKS PER SEC 1000 Для примера имитируем фрагмент кода с помощью функции Sleep() из WinAPI и произведем измерение времени выполнения (листинг 9.4). linclude <stdio.h> linclude <time.h> linclude <windows.h> int main(void) [ clock_t tl, t2, tЗ; tl = clock(); printf("tl = %ld\n", tl); fflush(stdout); Sleep(З000); t2 = clock(); printf("t2 = %ld\n", t2); tЗ=t2-tl; printf("tЗ = %ld\n", tЗ); // Метка 1 // Имитация фрагмента кода // Метка 2 // Разница.между метками printf("%.2f sec.\n", (douЬle)tЗ / CLOCKS_PER _SEC); return О;
ГЛАВА 10 Пользовательские функции Футщuя - это фрагмент кода, который можно неоднократно вызвать из любоп места программы. В предыдущих главах мы уже не один раз использовали ВСТ'р(" енные функции, входящие в состав стандартной библиотеки. Например, с помош" функции strlen() получали количество символов в С-строке. В этой главе мы рас­ смотрим создание пользовательских функций, которые позволят уменьшить из6'. точность программного кода и повысить er·o структурированность. 10.1. Создание функции и ее вызов Описание функции состоит из двух частей: объявлеиuя и определения. Объявлен• функции (называемое также прототипо.1,1 функции) содержит информацию о типе.. Используя эту информацию, компилятор может найти несоответствие типа и ко.1• чества параметров. Формат прототиr1а функции: <Тип результата> <Название функции>([<Тип> [<Название параметра 1>] [, ... , <Тип> [<Название параметра N>]]]); Параметр <Тип результата> задает тип значения, которое возвращает функция с � мощью оператора return. Если функция не возвращает никакого значения. тv вместо типа указывается ключевое слово void. Название функции должно быть .»­ пустимым идентификатором, к которому предъявляются такие же требования, ка и к названиям переменных. После названия функции, внутри круглых скобок, у� зывается тип и названия параметров через запятую. Названия параметров в про� типе функции можно не задавать вообще, т. к . компилятор интересует только ТМ11 данных и количество параметров. Если функция не принимает параметров, то у� зываются круглые скобки, внутри которых задается ключевое слово void. floc.11e объявления функции должна стоять точка с запятой. Пример объявления функций: int surn(int х, int у); void print(const char *str); void print_ok(void); // или int surn(int, int); // или void print(const char *); Определение функции содержит описание типа и названия параметров, а тa!Olir реализацию. Определение функции имеет следующий формат:
nопьзоввтвпьскив функции 313 <Тип результата> <Название функции>((<Тип> <Название параметра 1> [, .. . , <Тип> <Название параметра N>]J) <Тело функции> [return[ <Возвращаемое значение>];] В отличие от прототипа в определении функции после типа обязательно должно быть указано название параметра. которое является локqльной переменной. Эта переменная создается при вызове функции, а после выхода из функции она удаля­ стс11. Таким образом, локальная переменная видна только внутри функции. Если название локальной переменной совпадает с названием глобальной переменной, то 1ее операции будут производиться с локальной переменной, а значение глобальной не изменится. Пример: int sum(int х, int у) { intz=х+у;//Обращениеклокальнымпеременнымхиу return z; После описания параметров, внутри фигурных скобок, размещаются инструкции, которые будут выполняться при каждом вызове функции. Фигурные скобки указы­ ваются в любом случае, даже если тело функции состоит только из одной инструк­ ции. Точка с запятой после закрывающей фигурной скобки не указывается. Вернуть значение из функции по · зволяет оператор return. После исполнения этого оператора выполнение функции останавливается, и управление передается обратно • точку вызова функции. Это означает, что инструкции после оператора return ни­ когда не будут выполнены. При использовании оператора return не должно быть неоднозначных ситуаций. Например, в этом случае возвращаемое значение зависит от условия: int sum(int х, int у) if(х>О){ return х + у; Если переменная х имеет значение больше нуля, то все будет нормально, но если переменная равна нулю или имеет отрицательное значение, то возвращаемое з11а­ чение не определено и функция вернет произвольное значение, так называемый •мусор". В этом случае при компиляции выводится предупреждающее сообщение: varning: control reaches end of non-void function. Дпя того чтобы избежать по­ лобной ситуации, следует в конце тела функции вставить оператор return со значе­ нием по умолчанию: int sum(int х, int у) if(х>О){ return х + у;
314 Глава '8 return О; Если перед названием функции указано ключевое слово void, то оператора re: -= может не быть. Однако если необходимо досрочно прервать выполнение функUНL то оператор return указывается без возвращаемого значения. Пример: void print_ok(void) { puts("OK"); return; // Преждевр�енное завершение функции puts("Этa инструкция никогда не будет выполнена!!!"); При вызове функции из программы указывается название функции, после котороr11 внутри круглых скобок передаются значения. Если функция не принимает пара­ метров, то указываются только круглые скобки. Если функция возвращает зна'tf­ ние, то его можно присвоить переменной или просто проигнорировать. Необхо� мо заметить, что количество и тип параметров в объявлении функции должны се+ падать с количеством и типом параметров при вызове. Пример вызова тра: функций: print("Message"); print_ok(); // Функция вьrnедет сообщение Message // Вызываем функцию без параметров int z = sum(l0, 20); // Переменной z будет присвоено значение 30 Переданные значения присваиваются переменным, расположенным в той же пози­ ции в определении функции. Так, при использовании функции sum(J переменной 1. будет присвоено значение 10, а переменной у- значение 20. Результат выполнен1а функции присваивается переменной z. В качестве примера создадим три разные функции и вызовем их (листинг 10.1 ). Листинг 10.1 . Создание функций и их вызов #include <stdio.h> #include <locale.h> // Объявления функций int sum(int х, int у) ; void print(const char *str); void print_ok(void); int rnain(void) { // или int sum(int, int); // или void print(const char *); setlocale(LC_ALL, "Russian Russia.1251"); // Вызов функций рrint("Сообщение"); print_ok(); int z = sum(l0, 20); printf("%d\n", z); return О; // Сообщение // ок // 30
Пользовательские функции // Определения функций int sum(int х, int у) { return х + у; void print(const char *str) printf("%s\n", str); void print_ok(void) puts("OK"); // Два параметра // Один параметр // Без параметров 10.2. Расположение объявлений и определений функций 315 Все инструкции в программе выполняются последовательно сверху вниз. Это озна­ чает, что прежде чем использовать функцию в программе, ее необходимо предва­ рительно объявить. Поэтому объявление функции должно быть расположено перед вызовом функции. Обратите внимание на то, что размещать определение одной функции внутри другой нельзя. Таким образом, название функции всегда является глобальным идентификатором. В небольших программах допускается объявление функции не указывать, при условии, что определение функции расположено перед функцией main() (лис­ тинг 10.2). Кстати, функция main( J таюке не требует объявления, т. к. она вызыва­ ется первой. linclude <stdio.h> int sum_i(int х, int у) ( return х + у; int main(void) { int z = sum_i(l0, 20); printf("z = %d\n", z); // z 30 return О; При увеличении количества функций возникает ситуация, когда внутри функции вызывается вторая функция, а внутри второй - третья и т. д. В результате прихо­ дится решать вопрос: что было раньше - курица или яйцо? Для того чтобы избе­ жать такой ситуации, объявления функций следует размещать в начале программы перед функцией main ( J, а определения - после функции main ( J (листинг 10.3). В этом случае порядок следования определений функций не имеет значения.
316 f Листинr 10.3 . Расположен'4е объявлений и определений функций #include <stdio.h> int sum_i(int, int); int main(void) int z = sum i(l0, 20); printf("z = %d\n", z); return О; int sum_i(int х, int у) { return х + у; // Объявление //z 30 // Определение Глава 10 При увеличении размера программы объявлений и определений функций становит­ ся все больше и больше. В этом случае программу разделяют на несколько отде.11,, ­ ных файлов. Объявле11ия функций выносят в заголовочный файл с расширением h.. а определения функций размещают в одноименном файле с расширением с. в� файлы располагают в одном каталоге с основным файлом, содержащим функцнlС' main(). В дальнейшем с помощью директивы #include подключают заголовочныi файл во всех остальных файлах. Если в директиве #include название заголовочного файла указывается внутри угловых скобок, то поиск файла осуществляется в путн поиска заголовочных файлов. Если название указано внутри кавычек, то поиск вна­ чале производится в каталоге с основным файлом, а затем в путях поиска зaro.1<r вочных файлов. В качестве примера вынесем функцию sum_i() в отдельный файл (например, с на­ званием my_module.c), а затем подключим его к основному файлу. Для создан�u файла my_module.c в окне Project Explorer щелкаем правой кнопкой мыши на на­ звании каталога src и из контекстного меню выбираем пункт New I Source Filr (см. рис. 2.2). В открывшемся окне (см. рис. 2.4) в поле Source folder должно быn. уже вставлено значение, например, Test64c/src. В поле Source file вводи-. my_module.c, а из списка Template выбираем пункт <None>. Нажимаем кноп�..� Finish. В результате файл будет добавлен в каталог проекта, и его название отобразите• в окне Project Explorer, а сам файл будет открыт на отдельной вкладке. Вставляе-. в этот файл код из листинга 10.4 . 1 Листинr 10.4. Файл my_module.c #include "my_module.h" int sum_i(int х, int у) { // Определение return х + у;
Попьзоватепьские функции 317 Для создания файла my_module.h в окне Project Explorer щелкаем правой кtю11кой мыши на названии каталога src и из контекстного меню выбираем пункт New 1 Header File (см. рис. 2 .2). В открывшемся окне (см. рис. 2.6) в поле Source folder должно быть уже вставлено значение, например, тest64c/src. В поле Header file вводим my_module. h, а из списка Template выбираем пункт Default С header ten1- plate. Нажимаем кнопку Finish. В результате файл будет добавлен в каталог проекта, и его название отобразится в окне Project Explorer, а сам файл будет открыт на отдельной вкладке. Вставляем в этот файл код из листинга 10.5. tifndef МУ MODULE н ldefine МУ MODULE Н linclude <stdio.h> int sum_i(int, int); lendif /* МУ MODULE Н */ // Объявление функции Все содержимое файла my_module.h расположено внутри условия, которое проверя­ сn:я перед компиляцией. Условие выглядит следующим образом: lifndef МУ MODULE Н // Инструкции lendif /* МУ MODULE Н */ Эrо условие следует читать так: если не существует макроопределение 11r_мoouLE_н_, то вставить инструкции в то место, где подключается файл. Название акроопределения обычно совпадает с названием заголовочного файла. Только все liуквы указываются в верхнем регистре и точка заменяется символом подчеркива- 8UI. Условие начинается с директивы #ifndef и заканчивается директивой #endif. 1се это необходимо, чтобы объявления идентификаторов не вставлялись дважды. a,u этого в первой инструкции внутри условия определяется макрос МУ_мооu1Е_н_ с помощью директивы #define. В этом случае повторная проверка условия вернет IОЖНое значение. ,- Вместо этих директив можно указать в самом начале заголовочного файла дирек- 18ВУ препроцессора #pragma со значением once, которая таюке препятствует по- 81Орному включению файла (в старых компиляторах директива может не поддер- 811ваться): lpragma once // ООьявление функций и др. lеперь, чтобы использовать функцию sum _i(), достаточно подключить файл -,_module.h к основной программе. Пример подключения приведен в листинге 10.6.
318 Листинг 10.6 . Основная программа #include "my_module.h" int main(void) { int z = sum_i(l0, 20); printf("z = %d\n", z); // z 30 return О; В директиве #include допускается указывать не только название файла, но и а&:. лютный или относительный путь к нему. Это позволяет размещать файлы по рг3 личным каталогам. Примеры указания абсолютного и относительного пути: #include "C:\\cpp\\projects\\Test64c\\src\\my_module.h" #include "C:/cpp/projects/Test64c/src/my_module.h" #include "/cpp/projects/Test64c/src/my_module.h" #include "folder/my_module.h" При использовании больших программ создают статическую (файлы с расширеН8 ем lib в Visual С или с расширением а в MinGW) или динамическую (фай..-. с расширением dll в Windows) библиотеку. Статические библиотеки становг.:11 частью программы при компиляции, а динамические библиотеки подгружаюr.::1 при запуске программы. Процесс создания библиотек мы будем изучать в главе 1� 10.3. Способы передачи параметров в функцию Как вы уже знаете, после названия функции, внутри круглых скобок, указываю1'3 тип и названия параметров через запятую. Если функция не принимает параме� - . то внутри круглых скобок задается ключевое слово void. В определении фуню.118 после типа обязательно должно быть указано название параметра, которое являет.:а локальной переменной. Эта переменная создается при вызове функции, а после В.t­ хода из функции она удаляется. Таким образом, локальная переменная видна те,_,._ ко внутри функции, и ее значение между вызовами не сохраняется (исключею+а1 являются статические переменные). Если название локальной переменной совп� ет с названием глобальной переменной, то все операции будут производиться с ."J..� кальной переменной, а значение глобальной не изменится. При вызове функции указывается название функции, после которого внутри кr�•­ лых скобок передаются значения. Количество и тип параметров в объявлеНJ8 функции должны совпадать с количеством и типом параметров при вызове. Пе� данные значения присваиваются переменным, расположенным в той же позиW8 в определении функции. Так, при вызове функции sum(l0, 20) (прототип -�.: sum(int х, int у)) переменной х будет присвоено значение 10, а переменной , -
Пользовательские Функции 319 значение 20. Если функция не принимает параметров, то при вызове указываются только круглые скобки. По умолчанию в функцию передается копия значения переменной. Таким образом, изменение значения внутри функции не затронет значение исходной переменной. Пример передачи параметра по значению приведен в листинге 10.7 . finclude <stdio.h> void func(int х); int main(void) intх=10; func(х); printf("%d\n", х); // 10 return О; void func(int х) х=х+20; // Значение нигде не сохраняется! Передача копии значения для чисел является хорошим решением, но при использо­ ании массивов, а также при необходимости изменить значение исходной перемен­ ной, лучше применять передачу указателя. Для этого при вызове функции перед названием переменной указывается оператор & (взятие адреса), а в прототипе функ­ llНИ объявляется указатель. В этом случае в функцию передается не значение пере­ менной, а ее адрес. Внутри функции вместо переменной используется указатель. Пример передачи указателя приведен в листинге 10.8. tinclude <stdio.h> woid func(int *рх); int main(void) intх=10; func(&x); // Передаем адрес printf("%d\n", х); // 30, а не 10 return 01' void func(int *рх) *рх=*рх+20; // Изменяется значение переменной х
320 Глава Н1 10.4. Передача массивов и строк в функцию Передача одномерных массивов и строк осуществляется с помощью указате.,еi. Обратите внимание на то, что при вызове функции перед названием переменной ic нужно добавлять оператор &, т. к . название переменной содержит адрес первоn элемента массива. Пример передачи С-строки приведен в листинге 10.9. 1 Листинг 10.9. Передача С-строки в функцию #include <stdio.h> void funcl(char *s); void func2(char s[]); int rnain(void) [ char str[] = "String"; printf("id\n", (int) sizeof(str)); // 7 funcl(str); func2(str); // Оператор & перед str не указывается!!! printf("%s\n", str); // strinG recurn О; void funcl(char *s) { s[О]= 's'; // Изменяется значение элемента массива str printf("%d\n", (int) sizeof(s)); // В (размер указателя в проекте Test64c), а не 7! ! ! void :unc2(char s[]) { s[5] = 'G'; // Изменяется значение элемента массива str ] Объявление char *s эквивалентно объявлению char s [ J. И в том и в другом случаt объявляется указатель на тип char. Чаще используется первый способ. Обрат� внимание на значения, возвращаемые оператором sizeof вне и внутри функции Вне функции оператор возвращает размер всего символьного массива, в то вpe\UI как внутри функции оператор sizeof возвращает только размер указателя. Это про­ исходит потому, что внутри функции переменная s является указателем, а не массивом. Поэтому если внутри функции необходимо знать размер массива, то к� личество элементов (или размер в байтах) следует передавать в дополнительно.,. параметре. При вызове функции в качестве параметра можно передать составиой литерат Литерал состоит из круглых скобок, внутри которых указываются тип данных н количество элементов внутри квадратных скобок. После круглых скобок указыва­ ются фигурные скобки, внутри которых через запятую перечисляются значения:
Пользовательские функции const long *р = (long [3]) {1, 2, 3}; printf("%ld ", *р); //1 printf("%ld ", *(р + 1)); // 2 printf("%ld ", *(р + 2)); // 3 321 Количество элементов внутри квадратных скобок можно не указывать. Размер мас­ сива будет соответствовать количеству элементов внутри фигурных скобок: const long *р = (long []) {1, 2, 3}; При передаче многомерного массива необходимо явным образом указывать все размеры (например, int а[2 J [ 4 J ). Самый первый размер допускается не указывать (например, int а[ J [ 4J ). Пример передачи двумерного массива в функцию приведен ■листинге 10.10. linclude <stdio.h> ldefine ARR ROWS 2 ldefine ARR COLS 4 W"Oid func(int а[][4]); int main(void) { int i, j; int arr[ARR_ROWS] [ARR_COLS] {1, 2, 3, 41, {5,6,7,8} }; func(arr); // Выводим значения for(i=О;i<ARR_ROWS;++i){ for(j=О;j<ARR_COLS;++j) printf("%2d ", arr[i][j]); printf("\n"); return О; 1 8'id func(int а[][4]) { // или void func(int а[2] [4]) а[О][О] = 80; 1 J•ой способ передачи многомерного массива нельзя назвать универсальным, т. к . JJЩествует жесткая привязка к размеру. Одним из способов решения проблемы 8Uется создание дополнительного массива указателей. В этом случае в функцию
322 Глава 10 передается адрес первого элемента массива указателей, а объявление параметра в функции выглядит так: int *а(] или так: int **а Пример передачи массива указателей приведен в листинге 10.11. Листинг 10.11. Передача массива указателей #include <stdio.h> #define ARR ROWS 2 #define ARR COLS 4 void func(int *а[], int rows, int cols); int main(void) { int arr[ARR_ROWS][ARR_COLS] {1, 2, з, 41, (5,6,7,81 }; // Создаем массив указателей int *parr[] = {arr[0], arr[l] }; // Передаем адрес массива указателей func(parr, ARR_ROWS, ARR_COLS); return О; void func(int *а[], int rows, int cols) { // или void func(int **а, int rows, int cols) // Выводим значения for(inti=О,j;i<rows;++i) for(j=О;j<cols;++j){ printf("%2d ", a[i][j]); printf("\n"); Однако и этот способ имеет недостаток, т. к . нужно создавать дополнительный массив указателей. Кроме того, в Visual С выводится предупреждение (warn:.�.: С4221), т. к . мы сохраняем адреса локальной области видимости. Наиболее прие� лемым способом является передача многомерного массива как одномерноr(' В этом случае в функцию передается адрес первого элемента массива, а в парамет­ ре объявляется указатель. Так как все элементы многомерного массива располага-
Пользовательские функции 323 ются в памяти последовательно, зная количество элементов или размеры, можно вычислить положение текущего элемента, используя адресную арифметику. При­ мер передачи двумерного массива как одномерного показан в листинге 10.12. #include <stdio.h> #define ARR ROWS 2 #define ARR COLS 4 void func(int *ра, int rows, int cols); int main(void) { int arr[ARR_ROWS] [ARR_COLS] {1, 2, 3, 41, {5,6,7,8} }; // Передаем в функцию адрес первого элемента массива func(arr[0], ARR_ROWS, ARR_COLS); return О; void func(int *ра, int rows, int cols) // Выводим значения for(inti=О,j;i<rows;++i){ for(j=О;j<cols;++j){ // Вычисляем положение элемента printf("%2d ", *(ра + i * cols + j)); //printf("%2d ", pa[i * cols + j]); printf("\n"); Передача в функцию массива С-строк осуществляется так же, как передача массива указателей. Для того чтобы в функцию не передавать количество элементов, можно при инициализации массива С-строк последнему элеменrу присвоить нулевой ука­ затель. Этот элемент будет служить ориентиром конца массива. В качестве примера 8Ь1Ведем все строки внутри функции (листинг 10.13). linclude <stdio.h> void func(char *s[]);
324 int main(void) char *str[] { "Stringl", "String2", "StringЗ", NULL // Вставляем нулевой указатель, чтобы был ориентир 1; func(str); return О; void func(char *s[]) [ // или void func(char **s) while (*s) { // Выводим все строки printf("%s\n", *s); ++s; 10.5. Переменное количество параметров Глава 10 Количество параметров в функции может быть произвольным при условии, 'Пt' существует один обязательный параметр. В объявлении и определении фу1�кци■ переменное число параметров обозначается тремя точками. Например, прототнn функции printf() выглядит так: int printf(const char *format, ...); Получить доступ к этим параметрам внутри функции можно с помощью специа.11:r­ ных макросов va_start(), va_arg() и va_end(): #include <stdarg.h> void va_start(va_list <Указатель>, <Последний параметр>) <Тип> va_arg(va_list <Указатель>, <Тип данных>) void va_end(va_list <Указатель>) Вначале объявляется указатель типа va_list.Далее должна производиться инициа­ лизация указателя с помощью макроса va_start(). В первом параметре передаете� указатель, а во втором - название последнего обязательного параметра. Доступ к параметрам осуществляется с помощью макроса va_arg(), который возвращает значение текущего параметра и перемещает указатель на следующий парамеч�­ В первом параметре макроса va_arg() передается указатель, а во втором - назва­ ние типа данных. Макрос va_end() сообщает об окончании перебора параметро1- Количество параметров обычно указывается в обязательном параметре в виде чис­ ла или определяется другим способом (например, количеством спецификаторо8 внутри строки формата функции printf()). Если ожидается произвольное количество значений, то их может не быть и вовсе: printf("l0 20"); //1020 В качестве примера напишем функцию суммирования произвольного количества целых чисел (листинг 10.14).
пользовательские функции linclude <stdio.h> linclude <stdarg.h> int sum(int n, ... ); int main(void) printf("%d\n", sum(2, 20, 30)); printf("%d\n", SIJПI. ( 3, printf("%d\n", sum(4, return О; int sum(int n, ... ) int result = О; va_list р; va_start(р, n); 1, 2, 1, 2, for(inti=О;i<n;++i){ result += va_arg(p, int); va_end(p); return result; НА ЗАМЕТКУ 3)); 3, 4)); 325 // 50 //6 // 10 При передаче в функцию значения типов char и short автоматически расширяются до типа int, а значение типа float - до типа douЬle. 10.6. Константные параметры f.сли внутри функции значение параметра не изменяется, то такой параметр следу­ ет объявить константным. Для этого при объявлении перед параметром указывает­ а ключевое слово const. Например, функция sum(), предназначенная для суммиро- 88НИЯ чисел, не производит изменение значений параметров, поэтому параметры 11Ожно объявить константными: int sum(const int х, const int у) { return х + у; При использовании указателей важно учитывать местоположение ключевого слова const. Например, следующие объявления не эквивалентны: woid print(const char *s); 90id print(char const *s); 90id print(char * const s); woid print(const char * const s);
326 Глава 11 Первые два объявления являются эквивалентными. В этом случае изменить значе­ ние, на которое ссылается указатель, нельзя, но указателю можно присвоить друг� адрес: void print(const char *s) { char s2[] = "New"; s=s2; s[О] = 's'; printf("%s", s); // Нормально // ошибка При третьем объявлении изменить значение, на которое ссылается указатель, мож­ но, но указателю нельзя присвоить другой адрес: void print(char * const s) { char s2[] = "New"; s=s2; // ошибка s[O] = 's'; // Нормально printf("%s", s); Четвертое объявление запрещает изменение значения, на которое ссылается указа­ тель, и присвоение другого адреса: void print(const char * const s) { char s2[] = "New"; s=s2; // ошибка s[O] = 's'; // ошибка printf("%s", s); 1 О. 7. Статические переменные и функции Переменные, указанные в параметрах, а также переменные, объявленные внутрi! функции, являются локальными переменными. Эти переменные создаются при вы­ зове функции, а после выхода из функции они удаляются. Таким образом, лока.,ь­ ная переменная видна только внутри функции. Если внутри функции при объяв.,е­ нии локальной переменной не было присвоено начальное значение, то переменна� будет содержать произвольное значение, так называемый "мусор". Если название локальной переменной совпадает с названием глобальной переменной, то в� операции будут производиться с локальной переменной, а значение глобальной не изменится. Пример сохранения промежуточного значения между вызовами функции в г.10- бальной переменной приведен в листинге 1 О. 15. Листинг 10.15. Сохранение промежуточного значения в глобальной переменной #include <stdio.h> intх=О; void sum(int _х);
Пользовательские функции int main(void) sum(l0); sum(20); printf("ld\n", х); // 30 return О; void sum(int _х) х+= _х; 327 Статические переменные позволяют отказаться от использования глобальных пе­ ременных, для сохранения промежуточных значений между вызовами функции. Инициализация статической переменной производится только при первом вызове функции. Если при объявлении статической переменной не присвоено начальное значение, то переменная автоматически инициализируется нулевым значением. По­ сле завершения работы функции статическая переменная сохраняет свое значение, которое доступно при следующем вызове функции. При объявлении статической переменной перед типом данных указывается ключевое слово static. В качестве примера переделаем предыдущий пример и используем статическую переменную вместо глобальной (листинг 10.16). linclude <stdio.h> int sum(int _х); int main(void) { printf("%d\n", surn(l0)); // 10 printf("ld\n", surn(20)); // 30 printf("%d\n", surn(SS)); // 85 return О; int surn(int _х) static int х О; // Статическая переменная х+=_ х; return х; Ключевое слово static можно также указать для глобальной переменной или функции. В этом случае область видимости будет ограничена текущим файлом. Пример определения статической функции: static void print_ok(void) puts("OK");
328 10.8. Способы возврата значения из функции Глава 10 Вернуть значение из функции позволяет оператор return. После исполнения этого оператора выполнение функции останавливается, и управление передается обратно в точку вызова функции. Это означает, что инструкции после оператора return Нlt­ когда не будут выполнены. Если внутри функции нет оператора return, то по дос­ тижении закрывающей фигурной скобки управление будет передано в точку вызова функции. В этом случае возвращаемое значение не определено. Если функция не возвращает никакого значения, то вместо типа данных в объяв.1е­ нии и определении функции указывается ключевое слово void. Внутри такой фун�,;­ ции оператора return может не быть, однако его можно использовать без указан�u значения для преждевременного выхода из функции. Вызов функции, 11е возв� щающей никакого значения, нельзя размещать внутри какой-либо инструкции. Только в отдельной инструкции. Пример функции, которая не возвращает никакого значения: void print(const char *s) { printf("%s\n", s); В остальных случаях в объявлении и определении функции перед названием фун�,;­ ции задается возвращаемый тип данных. Значение этого типа должно быть указано в операторе return. В этом случае возвращается копия значения. Функция может вернуть значение любого типа, кроме массива. Работать с массивом необходи� через параметры функции, передавая указатель на него или возвращая указатель на конкретный элемент. Вызов функции, возвращающей какое-либо значение, можно разместить внутри выражения с правой стороны от оператора =. Возвращаемое зна­ чение можно присвоить переменной или просто проигнорировать. Пример функ­ ции, возвращающей копию значения: int surn(int х, int у) { return х + у; При использовании оператора return не должно быть неоднозначных ситуаций Например, в этом случае возвращаемое значение зависит от условия: int surn(int х, int у) if(х>О){ return х + у; Если переменная х имеет значение больше нуля, то все будет нормально, но ес.,и переменная равна нулю или имеет отрицательное значение, то возвращаемое зна­ чение не определено и функция вернет произвольное значение, так называемый
Г1опьзоеатепьские Функции 329 "мусор". В этом случае при компиляции выводится предупреждающее сообщение: warning: control reaches end of non-void functiori. Для того чтобы избежать по­ добноА ситуации, следует в конце тела функции вставить оператор return со значе­ нием по умолчанию: int surn(int х, int у) if(х>О)1 return х + у; return О; Функция может возвращать указатель. В этом случае в объявлении и определении функции указывается соответствующий тип указателя. В качестве примера вернем указатель на последний символ строки или нулевой указатель, если строка пустая или параметр имеет значение NULL (листинг 10.17). linclude <stdio.h> linclude <string.h> char *func(char *s); int main(void) { char *р = NULL, str[] "String"; р = func(str); if(р){ // Если ненулевой указатель printf("%c\n", *р); //g else puts("NULL"); return О; char *func(char *s) { if ( ! s) return NULL; size_t len = strlen(s); // Получаем длину строки if (!len) return NULL; // Нулевой указатель, если пусто else return &s[len - 1]; // Указатель на последний символ O&PATffTE BHIIMAHffE Нельзя возвращать указатель на переменные или массивы, объявленные внутри функции, т. к . при выходе из функции локальные переменные будут удалены. Рабо­ тать с массивом необхстимо через параметры функции, передавая указатель на него или возвращая указатель на конкретный элемент.
330 Глава 1, 10.9. Указатели на функции Функции так же, как и переменные, имеют адрес, который можно сохранить в УК4- зателе. В дальнейшем через указатель можно вызвать эту функцию. Кроме того" допускается передавать указатель на функцию в качестве параметра другой фун1- ции. Объявление указателя на функцию выглядит так: <Тип> (*<Название указателя>) ([<Тип 1>[, ... , <Тип N>]]); Для того чтобы присвоить указателю адрес функции, необходимо указать названнс функции без параметров и круглых скобок справа от оператора =. Типы параметр,-."'! указателя и функции должны совпадать. Пример объявления указателя и прис� вания адреса функции: int (*pfunc)(int, int) = NULL; pfunc = surn; Вызвать функцию через указатель можно так: int х = pfunc(З0, 10); // Аналогично х = surn(l0, 20); Или так: int у = (*pfunc) (30, 10); Пример объявления указателя, вызова функции через указатель и передачи указа­ теля в качестве параметра функции приведен в листинге 10.18. 1 Листинг 10:18. Указатели на функции #include <stdio.h> void print_ok(void); int add(int х, int у); int suЬ(int х, int у); int func(int х, int у, int (*pfunc)(int, int)); int main(void) { // Объявление указателей на функции void (*pfl)(void) = NULL; int (*pf2)(int, int) = NULL; // Присваивание адреса функции pfl = print_ok; pf2 = add; // Вызов функций через указатели pfl(); printf("%d\n", pf2(30, 10)); // Передача указателя на функцию // в качестве параметра // ок // 40 printf("%d\n", func(l0, 5, pf2)); // 15 printf("%d\n", func(l0, 5, add)); // 15
Пользовательские функции 331 printf("%d\n", func(lO, 5, suЬ)); // 5 return О; void print_ok(void) puts("OK"); int add(int х, int у) { return х + у; int suЬ(int х, int у) { return х - у; int func(int х, int у, int (*pfunc)(int, int)) { return pfunc(x, у); 10.10. Передача в функцию и возврат данных произвольного типа Если в функцию нужно передать данные произвольного типа или вернуть данные произвольного типа, то нужно использовать указатели с типом void *. Пример объявления указателя: void *р = NULL; Присвоить адрес указателю можно, как обычно: intх=10; р=&х; Для того чтобы получить значение, нужно привести указатель к определенному типу, а затем выполнить разыменование указателя: printf("%d\n", *((int *)р)); // 10 Нетипизированный указатель можно присвоить типизированному. В этом случае в языке С приведение типа выполняется автоматически: int*рх=р; printf("%d\n", *рх); // int *рх = (int *)р; // 10 Мы можем сохранить в указателе данные любого типа, но, чтобы получить данные, нужно знать, к какому типу выполнить приведение. Например, сохраним вещест­ венное число, а затем получим его и выведем на консоль: douЬle у= 20.2123; р=&у; printf("%.lf\n", *((douЬle *)р)); // 20.2 В качестве примера напишем функцию, позволяющую вывести на консоль данные типов int, long, long long и douЬle (листинг 10.19).
332 ! nисtмнr 10.19. Пер$дача ,в функцию Аанных nрои3воnьно'rо тмmr' #include <stdio.h> #include <limits.h> enum TypeParam { TINT, TLONG, TLONGLONG, TDOUBLE }; void println(const char *s, void *р, enum TypeParam t); int main(void) { int х = INT_МAX; println("x = ", &х, TINT); long у= LONG_МAX; //х 2147483647 println("y =" , &у, TLONG); //у= 2147483647 long long z = LLONG_МAX; println("", &z, TLONGLONG); //9223372036854775807 douЫe k = 20.2123; println("k = ", &k, TDOUBLE); //k = 20.21 return О; void println(const char *s, void *р, enum TypeParam t) { if (!р 11 !s) return; switch (t) { case TINT: printf("%s%d\n", s, *((int *)р)); break; case TLONG: printf("%s%ld\n", s, *((long *)р)); break; case TLONGLONG: printf("%s%I64d\n", s, *((long long *)р)); break; case TDOUBLE: printf("%s%.2f\n", s, *((douЫe *)р)); break; 10.11. Рекурсия Глава 11 Рекурсия - это возможность функции вызывать саму себя. При каждом вызоес функции создается новый набор локальных переменных. Рекурсию удобно испо.11r­ зовать для перебора объекта, имеющего заранее неизвестную структуру, н..в выполнения неопределенного количества операций. Типичным применение,�, рекурсии является вычисление факториала числа (листинг 10.20).
flопьзоватвпьские функции tinclude <stdio.h> unsigned long long factorial(unsigned long n); int main(void) { for(unsignedi=3;i<11;++i){ printf("%d! = %I64u\n", i, factorial(i)); return О; unsigned long long factorial(unsigned long n) { if(n<=1)return1; else return n * factorial(n - 1); Результат выполнения: 3!=6 4!=24 5! 120 б! 720 71 5040 8! 40320 9! 362880 10! = 3628800 10.12. Встраиваемь1е функции 333 Передача управления в функцию сопряжена с потерей скорости выполнения про­ граммы. т. к. вызов функции, передача в нее параметров и возврат значений требу­ ют дополнительных затрат времени. Если размер функции небольшой, то имеет смысл объявить такую функцию встраиваемой. В этом случае при компиляции со­ держимое функции будет вставлено в место вызова функции. Тем не менее следует помнить, что при этом происходит увеличение размера исполняемого файла. По­ этому если большая функция вызывается много раз, то лучше ее оставить обычной функцией, в то время как функция, состоящая из одной инструкции, является пер­ вым кандидатом для встраивания. Для объявления функции встраиваемой следует перед функцией добавить ключе­ вое слово _inline или inline. Для того чтобы функция не была видна в других файлах, лучше дополнительно добавить ключевое слово static. Следует учитывать, что ключевое слово _inline является лишь рекомендацией компилятору и может быть проигнорировано. В качестве примера объявим функцию surn ( J встраиваемой {листинг 10.21 ).
334 1 Листинг 10.21. Встраиваемые функции #include <stdio.h> static inline int surn{int х, int у) { return х + у; int main{void) { printf{"%d\n", surn{l0, 20)); return О; Глава 11 Создать встраиваемую функцию можно таюке с помощью директивы #define. 3Нr чение, указанное в этой директиве, подставляется в место вызова функции до коv­ пиляции. Название, указанное в директиве #define, принято называть макроопрео.г­ лением или 1иакросом. Директива имеет следующий формат: #define <Название функции>{<Параметры>) <Инструкция> Пример использования директивы #define приведен в листинге 10.22 . Листинг 10.22. Использование директивы #defiпe #include <stdio.h> #define SUМ{х, у) ((х) + (у)) int main{void) printf{"%d\n", SUМ(l0, 20)); return О; Обратите внимание на то, что в конце инструкции точка с запятой отсутствует Концом инструкции является конец строки. Если точку с запятой указать, то значе­ ние вместе с ней будет вставлено в выражение. Например, если определить макрос так: #define SUМ{х, у) (х + у); то после подстановки значения инструкция printf("%d\n", SUМ{l0, 20)); будет выглядеть следующим образом: printf{"%d\n", (10 + 20);); Точка с запятой после закрывающей круглой скобки является концом инструкции.. поэтому этот код вызовет ошибку при компиляции. Однако в следующем примере ошибки не будет, но результат окажется совершенно другим. Инструкция intz=SUМ(l0,20)+40;
Пользовательские функции после подстановки будет выглядеть следующим образом: intz=(10+20);+40; Подобная ситуация приводит к ошибкам, которые бывает трудно найти. 335 Обратите таюке внимание на то, что выражение внутри тела макроса расположено внутри круглых скобок. Если скобки не указать, то это может привести к недоразу­ мениям, т. к . никакого вычисления выражения не производится. Все выражение внутри тела макроса после подстановки формальных параметров целиком вставля­ ется в место вызова макроса. Например, если определить макрос следующим обра­ зом: tdefine SUМ(x, у) х + у то после подстановки значения инструкция intz=SUМ(l0,20)*40; будет выглядеть так: intz=10+20*40; Приоритет оператора умножения. выше приоритета оператора сложения, поэтому число 20 будет умножено на 40, а затем к результату прибавлено число 10. Таким образом, результат будет 010, а не 1200. При указании длинного выражения внутри тела функции следует учитывать, что определение макроса должно быть расположено на одной строке. Если нужно раз­ местить выражение на нескольких строках, то в конце строки необходимо добавить обратную косую черту. После косой черты не должно быть никаких символов, в том числе и комментариев. Пример: tdefine SUМ(x, у) \ ((х) + (у))
ГЛАВА 11 Обработка ошибок Если вы когда-нибудь учились водить автомобиль, то наверняка вспомните, чrо при первой посадке на водительское сиденье все внимание было приковано к тре1111 деталям: рулю, педалям и рычагу переключения передач. Происходящее вне авт� мобиля уходило на второй план, т. к. вначале нужно было стронуться с места. (1,:, мере практики навыки вождения улучшались, и эти три детали постепенно уходи.111 на задний план. Как ни странно, но руль и рычаг переключения передач всегда ока­ зывались там, куда вы не смотря протягивали руки, а ноги сами находили педа.1к Теперь все внимание стало занимать происходящее на дороге. Иными словами, вы стали опытным водителем. В программировании все абсолютно так же. Начинающие программисты боль� обращают внимание на первые попавшиеся на глаза операторы, функции и другн.: элементы языка, а сам алгоритм уходит на задний план. Если программа скомпи.11+­ ровалась без ошибок, то это уже большое счастье, хотя это еще не означает, чтс программа работает правильно. По мере практики мышление программиста мен.­ ется, он начинает обращать внимание на мелочи, на форматирование программы.. использует более эффективные алгоритмы и в результате всего этого допускает меньше ошибок. Подводя итоги, можно сказать, что начинающий программист просто пишет программу, а опытный программист пытается найти оптимальный алгоритм и предусмотреть поведение программы в различных ситуациях. Однакс­ от ошибок никто не застрахован, поэтому очень важно знать, как быстро найти ошибку. 11.1. Типы ошибок Существуют три типа ошибок в программе. □ Синтаксические - это ошибки в имени оператора или функции, отсутствие за­ крывающей или открывающей кавычки и т. д ., т. е . ошибки в синтаксисе языка. Как правило, компилятор предупредит о наличии ошибки, а программа не будет выполняться совсем. Пример синтаксической ошибки: printf("%s", "Нет завершающей кавычки!);
Обработка ошибок При компиляции будет выведено следующее сообщение об ошибке: .. \src\Test64c.c:4:17: error: missing terminating " character 337 Первое число после названия компилируемого файла и двоеточия обозначает номер строки, в которой обнаружена ошибка, а второе число - номер символа внутри строки. Если после попытки скомпилировать программу в окне Console редактора Eclipse (открыть окно можно из меню Window, выбрав пункт Show View 1 Console) выполнить щелчок мышью на строке с описанием ошибки, то инструк­ ция с ошибкой станет активной. Кроме того, строка с синтаксической ошибкой подсвечивается редактором желтой волнистой линией еще до компиляции про­ граммы. Так что обращайте внимание на различные подчеркивания кода. При ошибках подчеркивание будет красного цвета, а при предупреждениях - жел­ того. При предупреждениях слева от инструкции дополнительно отображается значок в виде желтого квадрата с вопросительным знаком. При наведении указа­ теля мыши на подчеркнутый фрагмент или на значок, расположенный слева, отобразится текст описания проблемы. Все ошибки и предупреждения (например, о том, что переменная не использует­ ся) можно посмотреть в окне ProЫems. Если окно не отображается, то в меню Window выбираем пункт Show View I ProЫems. □ Логические - это ошибки в логике работы программы, которые можно выявить только по результатам работы программы. Как правило, компилятор не преду­ преждает о наличии ошибки, а программа будет выполняться, т. к. не содержит . синтаксических ошибок. Основные ошибки в языке С связаны с указателями и массивами, поскольку компилятор не производит никакой проверки корректно­ сти указателя и не контролирует выход за границы массива. Весь контроль пол­ ностью лежит на плечах программиста. Логические ошибки достаточно трудно выявить, и их поиск часто заканчивается бессонными ночами. □ Ошибки времени выполнения - это ошибки, которые возникают во время рабо­ ты программы. В одних случаях ошибки времени выполнения являются следст­ вием логических ошибок, а в других случаях причиной служат внешние собы­ тия, например нехватка оперативной памяти, отсутствие прав для записи в файл идр. 11.2. Предупреждающие сообщения при компиляции Для того чтобы избежать множества проблем, на этапе отладки следует включить ВJ,lвод предупреждающих сообщений при компиляции программы. Как минимум нужно указать флаг -Wall. Если дополнительно указать флаг -Wconversion, то ком­ пилятор предупредит о несоответствии типов. Пример указания флагов: gcc -Wall -Wconversion -03 -о test.exe test.c
338 Глава 11 В редакторе Eclipse установка флагов выполняется в свойствах проекта. C.1eu из списка выбираем пункт С/С++ Build I Settings, а затем на вкладке Tool Settinp из списка выбираем пункт GCC С Compiler I Warnings (рис. 11 .1). В спис..-% Configuration находим режим компиляции, устанавливаем нужные флажки и с� храняем настройки проекта, нажимая кнопку Apply and Close. При использовании компилятора Visual С можно в начало программы вставнn, следующую инструкцию: #pragma warning( push, 4) Для отключения вывода каких-либо предупреждающих сообщений в Visual С �+.:­ пользуется следующая инструкция: #pragma warning( disaЫe: <Код предупреждения> Пример: #pragma warning( disaЫe ф , Re:sourc . e Builde:rs: Settings 4996) Proper1ies for Тest64c - □ ,,, CICт.,. BuiJd Build Var1aЫes: Configuration: · l� - � . 0!:!9.JA_ct_r, _ e___________ . ... _ ....� . · ·i "' Manage Configur11tions... Environrnent logging Settings * Т ool Sff.trr,gs Q Container Se : ttings ; ,.'1-· Build Steps Build Artifact ! !!!f Binary Parsers Q 4 ► Тool Chain Edrtor С/( ... + General Linux Tocls Path Prcject Nat,дes Prcject Re:fere:nces R.un/Debug Settings Тг�k. P.e:pository Task Tags Validatюn 1 /frkiText ,,, �! GCC Аие:mЫе:r l� Genero:,I ,,, �) GCC С Compiler � D1alect J:!': Pre:proпиor ,�1 lncludes �� Optim12:at1on J:..� Debugging � Wгirning� ��: Miscellane:ous ,,, �� MinG�V С l1nker � Gener�I -� l1brar1es с� Miscellan�oш J? Shared library Settings I [] Check synttx only {-f,;yntax-onl)') ГJ Ped,ntic [-pedantic) ·О Pedantic warnings as e:rrors {·pedant1c-errors) (.] lnhiЬit all wamings (-w) 1(�j AII \1'1arnin9s (-V/a11) О Extra warn1ngs (-Wextra) [J\�iarnings as errcrs (·Werror) ,.�; lmplicit con'n:rsion warnings (-\ i \1cc-nvers1on) Apply •�d Close Рис. 11 .1. Управление выводом предупреждающих сообщений 11.3. Переменная errno и вывод сообщения об ошибке Cancel Отдельные функции из стандартной библиотеки при ошибке возвращают некоторое значение и присваивают переменной errno номер ошибки. Например, функшu strtol (), предназначенная для преобразования С-строки в число типа long, в случае
Обработка ошибок 339 выхода за диапазон значений для типа, присваивает переменной errno значение макроса ERANGE: // #include <stdlib.h> // #include <errno.h> printf("%ld\n", strtol("2147483649", NULL, О)); // 2147483647 printf("%d\n", errno); // 34 printf("%d\n", ERANGE); // 34 Макрос ERANGE и остальные макросы с номерами ошибок определены в заголовоч­ ном файле ermo.h: #define ERANGE 34 В Visual С при использовании некоторых функций, например strtol( J, переменная errno не становится равной о при успешном преобразовании, что может привести к ошибкам при следующем преобразовании. Перед использованием функций лучше сбросить флаг ошибки явным образом, присвоив переменной errno значение о: printf("%d\n", errno); printf("%ld\n", strto1("2147483649", NULL, printf("%d\n", errno); printf("%1d\n", strtol("50", NULL, 0)); printf("%d\n", errno); // В Visual С 2013 errno = О; // Сбрасываем флаг printf("%ld\n", strtol("50", NULL, О)); printf("%d\n", errno); //о О) 1; // 2147483647 // 34 // 50 значение 34, а не О!!! ошибки // 50 //о Получить текстовое описание ошибки по ее коду позволяют функции strerror( J и_wcserror( J • Прототипы функций: linclude <string.h> char *strerror(int errNum); wchar_t *_wcserror(int errNum); Пример вывода сообщения: printf("%s\n", strerror(ERANGE)); // Result too large printf("%s\n", strerror(22)); // Invalid argument wprintf(L"%s\n", _wcserror(EILSEQ)); // Illegal byte sequence Вместо функций strerror( J и _wcserror( J лучше воспользоваться функциями strerror_s() и_wcserror_s(). Прототипы функций: #include <string.h> errno t strerror_s(char *buf, size_t sizeinBytes, int errNum); errno t _wcserror_s(wchar_t *buf, size_t sizeinWords, int errNum); В первом параметре функции принимают указатель на строку, а во втором пара­ метре - максимальный размер этой строки. Номер ошибки задается в третьем параметре. Пример использования функции strerror_s( J приведен в листинге 11.1 .
340 #include <stdio.h> #include <errno.h> #include <string.h> int main(void) { char err[256] = {О}; strerror_s(err, 255, EILSEQ); printf("%s\n", err); strerror_s(err, 255, ERANGE); printf("%s\n", err); return О; // Illegal byte sequence // Result too large Получить описание последней ошиб1ш позволяют функции _wcserror(). Прототипы функций: #include <string.h> char *_strerror(const char *errMsg); wchar t * _wcserror(const wchar · _ _t *errMsg); Глава n strerтor ( 1 Если в параметре указано значение NoЛ,L, то строка будет содержать тол1>ко оп�tс.а­ ние последней ошибки. Если передать строку, то o,ia будет добавлена перед оrнк.. нием ошибки. В строку вставляется символ 11еревода строки. llример: // #include <stdlib.h> printf("%ld\n", strtol("2147483649", NULL, О)); // 2147483647 printf("%s", strerror(NULL)); // Result too large printf("%s", strerror("Error")); // Error: Result too large wprintf(1"%s", _wcserror(NULL)); // Res1.ilt too large wprintf(L"%s", _wcserror(L"Error")); // Error: P.esult too large Вместо функций _strerror() и _wcserror() лучше восIюльзоваться функция\t■ _ strerror_s() и _wcserror_s(). Прототипы функций: #include <string.h> errno t _strerror_s(char *buf, size_t sizeinBytes, consl cttar •errMsg); errno t wcserror_s(wchar_t *buf, size_t sizeiпWoпjs, const wchar_t *errMsg); В первом параметре функции I1ринимают указатель Iш строку, а no втором пара­ метре - максимальный размер этой строки. Если в третьем I1араметре указа,ю зна­ чение NULL, то строка будет содержать только ог�исание 1юслед11ей ошибки. Ес.1м передать строку, то она будет добавлена перед 0IIиса�1ием ошибки. Пример: // #include <stdlib.h> printf("%ld\n", strtol("2147483649", NULL, О)); // 2147483647 char err[256] = {0};
Обработка ошибок _strerror_s(err, 255, NULL); printf("%s\n", err); _strerror_s(err, 255, "Error"); printf("%s\n", err); 341 // Result too large // Error: Result too large Функции perror() и _wperror() выводят текст сообщения об ошибке с кодом errno в стандартный поток вывода сообщений об ошибках stderr. Перед этим сообщени­ ем добавляется текст, переданный в параметре errMsg, и символ двоеточия. Прото­ типы функций: tinclude <stdio.h> /* или #include <stdlib.h> */ void perror(const char *errMsg); tinclude·<stdio.h> /* или #include <stdlib.h> или #include <wchar.h> */ void _wperror(const wchar_t *errMsg); Пример использования функций perror() и _wperror( J : errno = ERANGE; perror("Error"); // Error: Result too large _wperror(L"Error"); // Error: Result too large 11.4. Способы поиска ошибок в программе Наибольшее количество времени программист затрачивает на поиск логических ошибок. В этом случае программа компилируется без ошибок, но результат выпол­ нения программы не соответствует ожидаемому результату. Ситуация еще более осложняется, когда неверный результат проявляется лишь периодически, а не по­ стоянно. Инсценировать такую же ситуацию, чтобы получить этот же неверный результат, бывает крайне сложно и занимцет очень много времени. В этом разделе мы рассмотрим лишь "дедовские" (но по-прежнему актуальные) способы поиска ошибок, а также дадим несколько советов по оформлению кода, что будет способ­ ствовать быстрому поиску ошибок. Первое, на что следует обратить внимание, - это форматирование кода. Начи­ нающие программисты обычно не обращают на форматирование никакого внима­ ния, считая этот процесс лишним. А на самом деле зря! Компилятору абсолютно все равно, разместите вы все инструкции на одной строке или выполните формати­ рование кода. Однако при поиске ошибок форматирование кода позволит найти ошибку гораздо быстрее. Перед всеми инструкциями внутри блока должно быть расположено одинаковое количество пробелов. Обычно используется три или четыре пробела. От примене­ ния символов табуляции лучше отказаться. Если все же их используете, то не сле­ дует в одном файле совмещать и пробелы, и табуляцию. Для вложенных блоков количество пробелов умножают на уровень вложенности. Если для блока первого уровня вложенности использовалось три пробела, то для блока второго уровня вложенности поставьте шесть пробелов, для третьего уровня - девять пробелов и т. д. Пример форматирования вложенных блоков приведен в листинге 11.2 .
342 Листинг 11.2 . Пример форматирования вложенных блоков #define ARR ROWS 2 #define ARR COLS 4 int arr[ARR_ROWS] [ARR_COLS] {1, 2, 3, 41, (5,6,7,В1 1; int i, j; for (i=О;i<ARRROWS;++i){ for (j = О; j < ARR_COLS; ++j) printf("%3d", arr[i][j]); printf("\n"); Глава 1, Открывающая фигурная скобка может быть расположена как на одной стро�.с с оператором, так и на следующей строке. Какой способ использовать, зависит С''! предпочтений программиста или от требований по оформлению кода, приняты:х внутри фирмы. Пример размещения открывающей фигурной скобки на отдельн(\f!I строке: for(i=О;i<ARRROWS;++i) for (j = О; j < ARR_COLS; ++j) { printf("%3d", arr[i][j]); printf ("\n"); Одна строка кода не должна содержать более 80 символов. Если количество сим� лов больше, то следует выполнить переход на новую строку. При этом продолже­ ние смещается относительно основной инструкции на величину отступа или вы­ равнивается по какому-либо элементу. Иначе приходится пользоваться горизон­ тальной полосой прокрутки, а это очень неудобно при поиске ошибок. Если программа слишком большая, то следует задуматься о разделении програм,1ы на отдельные функции, которые выполняют логически законченные действия Помните, что отлаживать отдельную функцию гораздо легче, чем "спагетти"-ко.J Причем, прежде чем вставить функцию в основную программу, ее следует протес­ тировать в отдельном проекте, передавая функции различные значения и проверн результат ее выполнения. Обратите внимание на то, что форматирование кода должно выполняться при на­ писании кода, а не во время поиска ошибок. Этим вы сократите время поиска ошибки и, скорее всего, заметите ошибку еще на этапе написания. Если все же ошибка возникла, то вначале следует инсценировать ситуацию, при которой ошиб­ ка проявляется. После этого можно начать поиск ошибки.
Обработка ошибок 343 Причиной периодических ошибок чаще всего являются внешние данные. Напри­ мер, если числа получаются от пользователя, а затем производится деление чисел, то вполне возможна ситуация, при которой пользователь введет число о. Деление на ноль приведет к ошибке. Следовательно, все данные, которые поступают от пользователей, должны проверяться на соответствие допустимым значениям. Если данные не соответствуют, то нужно вывести сообщение об ошибке, а затем повтор­ но запросить новое число или прервать выполнение всей программы. Нужно также обработать возможность того, что пользователь может ввести вовсе не число, а строку. Также следует учитывать, что при использовании функции scanf() возможно пере­ полнение, если пользователь введет значение вне допустимого диапазона для ука­ занного типа. Функция при этом никак не проинформирует вас об ошибке. Пере­ менная errno будет иметь значение о. В результате положительное число может стать отрицательным, что приведет уже к логической ошибке. Пример получения числа от пользователя с проверкой корректности данных пока­ зан в листинге 11.J. . ·нr 11.з. nри_мер �;JОЛУ'!8Н�Я числа o:t �ла;.3оватеnя с проверкой данных �.� .; ' , . ' .., .,, ' � �... . ' finclude <stdio.h> finclude <errno.h> finclude <locale.h> finclude <stdlib.h> int rnain(void) { setlocale(LC_ALL, "Russian_Russia.1251"); longх=100,у О; char buf[256] = {О}, *р = NULL; for(;;)( // Бесконечный цикл printf("y = "); fflush(stdout); fflush(stdin); р = fgets(buf, 256, stdin); if (!р) { puts("Оимбка при вводе"); exit(l); р = NULL; errno = О; у= strtol(buf, &р, О); if(!р11р==buf)( puts("Bы ввели не число!"); continue; if (errno != О) { // Выводим подсказку // СбраСЫБаем буфер вывода // Очищаем буфер ввода // Получаем значение // Проверяем успешность операции // Сбрасываем флаг ошибки // Преобразуем строку в число // Проверяем успешность. операции // Проверяем диапазон puts("Bыxoд за диапазон значений");
344 continue; if(у==О){ // Проверяем допуС'I'имость значения puts("Значение О недопус'l'ИМо"); continue; break; // Если ошибок нет, то выходим из цикла printf("y = %ld\n", у); // Выводим результат printf("x / у= %.2f\n", (douЬle)x / у); return О; Глава n Функцию printf() удобно использовать для вывода промежуточных значенма. В этом случае значения переменных вначале выводятся в самом начале проrраю,м (внутри функции main()), и производится проверка соответствия значений. Ec.w значения соответствуют, то инструкция с функцией pr int f ( J г1еремещается на с.-.е-­ дующую строку программы, и опять производится проверка и т. д . Если значенМI не совпали, то ошибка возникает в инструкции, расположенной перед инструкцисi с функцией printf(). Если это пользовательская функция, то проверку значеннl производят внутри функции, каждый раз перемещая инструкцию с выводом зна� ний. На одном из этих многочисленных этапов ошибка обычно обнаруживаета­ В больших программах можно логически догадаться о примерном расположен" инструкции с ошибкой и начать поиск ошибки опуда, а не с самого начала п� граммы. Инструкции для вывода промежуточных значений можно расставить уже r1ри напм­ сании программы, не дожидаясь возникновения ошибки. В этом случае в нача.-.r программы определяется макрос, а внутри программы производится проверка � личия макроса. Если макрос определен, то выводятся значения. В противном с:�­ чае инструкция просто игнорируется. Создать макрос 110зволяет директива #defi:.-=: #define МУ DEBUG 1 Проверить существование макроса позволяет следующая конструкция: #ifdef МУ DEBUG // Здесь размещаем инструкции вывода значений printf("Maкpoc определен\n"); #endif Проверить существование макроса и его значение можно так: #if defined(МY_DEBUG) && MY_DEBUG == 1 printf("Maкpoc имеет значение l\n"); #endif Пример использования макросов приведен в листинге 11.4.
Обработка ошибок linclude <stdio.h> linclude <locale.h> •ctefine МУ DEBUG 1 int main(void) { setlocale(LC_ALL, "Russian_Russia.1251"); intх=10; lifdef МУ DEBUG // Эта инструкция будет выnолнена только при отладке printf("x = %d\n", х); lendif lif defined(МY_DEBUG) && МУ DEBUG == 1 // Эта инструкция будет выnолнена только если макрос // МY_DEBUG равен 1 printf("x = %d\n", х); lendif printf("Этoт текст выводится в любом случае. х %d\n", х); return О; Рез>:льтат в окне консоли: х=10 х=10 Эrот текст выводится в любом случае. х = 10 Если закомментировать инструкцию: ldefine МУ DEBUG 1 1'О вывод будет таким: Эrот текст выводится в любом случае. х = 10 11.5. Отладка программы в редакторе Eclipse 345 Сделать поиск ошибок более эффективным позволяет отладчик gdb.exe, который можно запустить в редакторе Eclipse. С его помощью можно выполнять программу оо шагам, при этом контролируя звачения переменных на каждом шаге. Оrладчик позволяет также проверить, соответствует ли порядок выполнения инструкций раз­ работанному ранее алгоритму. В качестве примера мы будем отлаживать програм­ му из листинга 11.5, которая содержит две наиболее часто встречающиеся ошибки: еыход за пределы массива и нумерация элементов массива с 1, а не с о.
346 1 Листинг 11.5. Оrладка программы #include <stdio.h> #define ARR ROWS 2 #define ARR COLS 4 void echo(int х); int main(void) { int arr[ARR_ROWS] [ARR_COLS] {1, 2, з, 41, {5,6,7,81 ); inti=О,j =О; for (i = О; i <= ARR_ROWS; ++i) { for (j = l; j < ARR_COLS; ++j) echo(аrr [i] [j ] ); printf("\n"); return О; void echo(int х) { printf(" "); printf("%d", х); //9 // 13 // 14 // 15 // 16 // 18 // 22 // 23 // 24 Глава 1� Прежде чем начать отладку, необходимо пометить строки внутри программы с п..-­ мощью точек останова. Для добавления точки останова делаем строку активной. 1 затем в меню Run выбираем пункт Toggle Breakpoint или нажимаем комбинашn.:­ клавиш <Shift>+<Ctrl>+<B>, - слева от строки появится кружок, обозначаюшкi точку останова. Повторное действие убирает точку останова. Добавить точку ОСТ';!­ нова можно еще быстрее. Для этого достаточно выполнить двойной щелчок лев...-i кнопкой мыши слева от строки. Повторный двойной щелчок позволяет уда.11� точку останова. Для того чтобы изменить свойства точки, временно отключить 11,_,. удалить ее, следует щелкнуть на ней правой кнопкой мыши и в открывшемся кон­ текстном меню выбрать пункт Toggle Breakpoint, DisaЫe Breakpoint или Enablr Breakpoint. Давайте установим в нашей программе две точки останова: напроnп строк 13 и 16. Когда точки останова расставлены, можно начать отладку. Для этого в меню Ru выбираем пункт Debug или нажимаем клавишу <Fl 1>. После запуска отладки ин­ терфейс редактора Eclipse может поменяться кардинальным образом (рис. 11.:, После запуска отладки выполнение программы прерывается, и отладчик ожи.1ает дальнейших действий программиста. Инструкция, которая будет выполняться на следующем шаге, помечается стрелкой слева от строки. Для того чтобы прерваn.
Обработка ошибок 347 отладку, в меню Run выбираем пункт Terminate или нажимаем комбинацию кла­ виш <Ctrl>+<F2>. 0БРА ТИТЕ ВНИМАНИЕ Если на компьютере установлена антивирусная программа или включен брандмауэр Windows, то они моrут заблокировать запуск отладчика. Необходимо разрешить запуск отладчика при запросе действия или добавить программу в список исключения брандмауэра, если отладчик не запустился. ф projects - Test64c/src(Тest64c.c - Edipse IDE - с:, fi� fdit So,urce. Rd.rqor ,Novi�e: Sчrch frcject Bun Wmdow tl�p �L11-i� г�-;;;,;;��- - - -�:Eт«tblc:�. V�;i �-:�:-...1l]t,'" •N:,,.•�•. [..."'->:'.J•• :r-3•:i,;�l�i�•O•�·\�G,4'• 1{;,.:,,,.1;,<?•<, • � "tf0юug $( <)tIi""''?=Е 1•)<, Vа�Ьlб �; с-,•о ��n��cs� & �.!.,� !П • � Tбt6ktxe.(Cl(++ Appli<ation) • iFf Test64c.u:e.(596] • � Thre:.rd #1 О (Suspmde.d: Br�kpoint) :Е m&inO at Тestfж.c: 13 0х401 )а5 � gdb(82.1) � Тбt64с.С � }; {1, 2, з, 4}, {5"б,7,8} int i �-� j' � Q:,:::.: Nomt ;)�4 1Т W=i (x),j for(i=0;i<=ARRROWS;++i){ for(j=1;j<ARR_COLS;++j){ echo(arr[i] [j]); printf("'\n . . ); return О; int(ZJ[4} int ,nt // 15 // 16 // 13 е Consofe. � '', r.': 'gisree,. � r-,�1;e1г.s 1) fX<'CJ!�.,;�� � �;vg�·(;,_>,-,;G,>! g Mt.-:)1;.}"'f Т�_о:�(C/C:,!.:_,�plic,1tion]_T�.�e. Value Ox23fe20 о Рис. 11 .2. Окно редактора Eclipse после запуска отладки �')u;скА�:�б · ;.!;�!цiJ[� �_rlrJ ���.J:?. =['j "�i ;i z���._J\• -., � stdio.h # ARR_Rows· # ARR_COLS Н �cho(int): .,::,d • m"in(void) ;п! • «ho(lnt): \�1d После запуска отладчика выполнение программы прервется на строке 9. Для того чтобы выполнить инструкции до первой точки останова (строка 13), нажимаем кла­ вишу <F8>. В режиме прерывания можно посмотреть значения различных пере­ менных. Для этого достаточно навести указатель мыши на название переменной. Попробуйте навести указатель на название массива arr - в нижней части всrmы­ вающего окна отобразятся значения всех элементов массива. Если навести указа­ тель мыши на переменН1'1е i и j, то никакого значения мы не получим, т. к . поток управления еще не дошел до этих переменных. Их значение мы увидим только по­ сле следующего шага. Посмотреть значения сразу всех переменных можно в окне VariaЫes (рис. 11.3). Если окно не отображается, то отобразить его можно, выбрав в меню Window
348 Глава 11 пункт Show View I VariaЫes. При отладке можно контролировать значения отдел,­ ных переменных, а не всех сразу. Для этого следует добавить название переменН<W в окне Expressions (рис. 11 .4), щелкнув левой кнопкой мыши на надписи Add мw expression. Для того чтобы отобразить это окно, в меню Window выбираем пунп Show View I Expressions. Можно также выделить название переменной и из к� текстноrо меню выбрать пункт Add Watch Expression. Давайте добавим в этоw окне переменные i и j. Сейчас значения этих переменных не определены, поэто� в поле Value отображаются произвольные значения. ф - □ \Х1"' \tan.ab;es �·:-; ·" fu§EJ1 =-:.,' L,,. :..___J ;.,.._; Name Туре Value !..L!!:;;;- i;t[2][4] ____ Ox23f� • � arr[O] int (4] Ox23fe20 (х), arr[O][O] int 1 (>Ф агг[О][1] int 2 (х), arr[0][2] int 3 (х), агг[О][З] int 4 • r.� arr[1] int [4] Ox23fe30 (х), arr[l][O] int 5 (х), агг[1][1] int б (х), arr[1][2] int 7 (х), arr[1][3] int 8 (х), i int о (х), j int о Рис. 11.З. Окно VariaЫes Expre:ssicn (х), i (>Фj v Aad nev, expression Туре int int Value о о Рис. 11.4 . Окно Expressions = EJ Для пошагового выполнения программы предназначены следующие пункты в \tё-­ ню Run или соответствующие кнопки на панели инструментов. □ Step Into (клавиша <FS>)- выполняет одну инструкцию. Давайте сделаем о.1Н1�1 шаг и посмотрим на значения переменных i и j в окне Expressions - обе пере­ менные стали видны и получили значение о. Делаем еще два шага и останав.,� ваемся на строке 16, которая у нас помечена точкой останова. Если мы сде.1ае,е еще один шаг, то попадем внутрь функции echo (). Обратите внимание на значе--
Обработка ошибок 349 ния переме11ных i и j в окне Expressions - теперь они не определены, т. к. мы находимся в области видимости фу•1кции echo ( J. □ Step Return (клавиша <F7>)- позволяет сразу выйти из функции. Если мы не ·на ходимся внутри функции, то команда будет недоступна. Давайте нажмем кла­ вишу <F7> и выйдем из функции echo ( J. В результате текущей станет строка 15. □ Step Over (клавиша <F6>)- выполняет одну инструкцию. Если в этой инст­ рукции производится вызов функции, то функция выполняется, и отладчик пе­ реходит в режим ожидания после выхода из функции. Давайте сделаем два шага и опять окажемся на строке 15, не входя в функцию echo ( J• □ Resume (клавиша <F8>)- выполняет инструкции до следующей точки остано­ ва или до конца программы при отсутствии точек. Нажимая клавишу <F8>, мы каждый раз будем останавливаться на строке 16 и сможем наблюдать за теку­ щими значениями переменных i и j в окне Expressions, а также за выводом ре­ зультатов работы программы в окне Console. На одном из шагов можно обнару­ жить, что значение переменной J стало равно 2, а строк в массиве всего две и нумеруются они с нуля. Смотрим внимательно на инструкции и обнаруживаем неправильное условие окончания цикла в строке 14 (i <= ARR_Rows). Исправляем ошибку, и выражение примет вид i < ARR_Rows. Опять запускаем отладку, чтобы убедиться в правильности внесенных изменений. □ Run to Line (комбинация клавиш <Ctrl>+<R>)- выполняет инструкции до те­ кущей строки при условии, что между инструкциями нет точки останова. Если точка есть, то выполнение будет идти до этой точки. Если мы попробуем перей­ ти сразу к строке 18, то это не получится, т. к. существует точка останова на строке 16. А нам бы хотелось увидеть вывод одной строки массива сразу. Для этого можно временно отключить точку останова, щелкнув на ней правой кноп­ кой мыши и выбрав в контекстном меню пункт DisaЫe Breakpoint (в отключен­ ном состоянии отображается лишь контур кружка без заливки, для повторной активации выбираем пункт ЕпаЫе Breakpoint). Если у нас точек будет много, то каждый раз выбирать пункт из контекстного меню станет утомительно. Мож­ но временно отключить сразу все точки останова, выбрав в меню Run пункт Skip AII Breakpoints (в отключенном состоянии точки отображаются перечерк­ нутыми, повторное действие сделает все точки останова снова активными). По­ сле этого действия можно сразу перейти к строке 18, предварительно сделав ее текущей и нажав комбинацию клавиш <Ctrl>+<R>. После этого действия мы за­ метим, что вместо четырех значений выводятся только три. Для того чтобы по­ нять причину, опять выполняем программу по шагам и наблюдаем за значения­ ми переменных в окне Expressions. В один прекрасный момент понимаем, что индексы нумеруются с о, а не с 1, и исправляем ошибку в строке 15 (j = о вме- ·стоj=1). � Terminate (комбинация клавиш <Ctrl>+<F2>)- останавливает отладку. Управлять отдельными точками останова или всеми сразу можно в окне Breakpoints (рис. 11.5). Для того чтобы отобразить это окно, в меню Window сле­ дует выбрать пункт Sbow View I Breakpoints.
350 0о Breakpoi ts �2 •� � о Т e.st64c.c [line: 13] � о Test64c.c[line:16] Рис. 11 .5 . Окно Breakpoints - □ Глава�· Применение отладки - самый эффективный способ нахождения ошибок, не � бующий вставки никаких инструкций вывода промежуточных значений в тек� программы. На каждом шаге мы и так можем наблюдать за значениями всех 1t:11 только избранных переменных. После отладки не надо удалять или временно о-:-­ ключать какие-либо инструкции, даже точки останова убирать не нужно. Пpii обычном выполнении точки останова ни на что не влияют. Пользуйтесь отла.Jксw при возникновении любой ошибки, и вы очень быстро ее найдете и исправите. НА ЗАМЕТКУ Дnя того чтобы из режима Debug вернуться в обычный режим, в меню Window в� раем пункт Perspective I Open Perspective I С/С++ или нажимаем соответствующ� кнопку на панели инструментов.
ГЛАВА 12 Чтение и запись файлов В разд. 2. 8 и 2. 9 мы уже рассматривали ввод/вывод данных в окно консоли. В этой главе мы научимся читать данные из файла и записывать их в файл. Все функции, используемые в этой главе, объявлены в заголовочном файле stdio.h . Не забудьте в начало программы добавить следующую инструкцию: tinclude <stdio.h> 12.1. Открытие и закрытие файла Для открытия файла предназначены функции fopen() и _wfopen() . Прототипы функций: tinclude <stdio.h> FILE *fopen(const char *filena me, const char *mode); tinclude <stdio.h> /* или #include <wchar.h> */ FILE *_wfopen(const wchar_t *filename, const wchar_ t *mode); В первом параметре функции принимают путь к файлу, а во втором - режим от­ крытия файла. Функции возвращают указатель на структуру FILE или нулевой ука­ затель в случае ошибки. Пример использования функции fopen(): // #include <locale.h> setlocale(LC_ALL, "Russian_Russia.1251"); ПLЕ *fp � NULL; // Открываем файл на запись в текстовом режиме fp = fopen("C:\\book\\filel.txt", "w"); if (fp) { // Проверяем успешность fputs("строка", fp); fclose(fp); // Записываем строку // Закрываем файл else puts("He удалось открыть файл"); Пример использования функции _wfopen() : setlocale(LC_ALL, "Russian_Russia.1251"); FILE *fp = NULL; fp = _wfopen(L"C:\\book\\file2.txt", L"w");
352 if (fp) fputws(L"cтpoкa", fp); fclose(fp); else _putws(L"He удалось открыть файл"); Глава 12 Количество одновременно открытых файлов ограничено значением макроса FOPEN_!'1АХ. Определение макроса выглядит так: #define FOPEN МАХ 20 В качестве результата функции возвращают указатель на структуру FILE. Эror указатель следует передавать функциям, которые предназначены для работы с у.: открытым файлом. Если открыть файл не удалось, функции возвращают нулевоl указатель и присваивают глобальной переменной errno код ошибки. СтрукrуJ18 FILE объявлена следующим образом: struct iobuf }; char * _ptr; int cnt; char * base; int flag; int file; int charbuf; int bufsiz; char *_tmpfname; typedef struct iobuf FILE; Вместо функций fopen() и wfopen( J лучше использовать функции fopen_s ■ _ wfopen_s(). Прототипы функций: #include <stdio.h> errno_t fopen_s(FILE **File, const char *filenarne, const char *mode); #include <stdio.h> /* или #include <wchar.h> */ errno_t _ wfopen_s(FILE **File, const wchar_t *filenarne, const wchar t *mode); В первом параметре функция принимает адрес указателя на файловую структу� Остальные параметры аналогичны параметрам функции fopen( J. В качесnr результата функции возвращают о при отсутствии ошибки или код ошибки. Пример использования функции fopen_s( J : setlocale(LC_ALL, "Russian Russia.1251"); FILE *fp = NULL; errno t err; err = fopen_s(&fp, "C:\\book\\filel.txt", "w"); if (!err) { fputs("cтpoкa", fp); fclose(fp); else puts("He удалось открыть файл");
Чтение и запись файлов Пример использования функции _wfopen_s( J: setlocale(LC_ALL, "Russian_Russia.1251"); FILE *fp = NULL; errno_t err; err = _wfopen_s(&fp, L"C:\\Ьook\\file2.txt", L"w"); if (!err) { fputws(L"cтpoкa", fp); fclose(fp); else puts("He удалось открыть файл"); 353 Оrкрыть файл в режиме совместного доступа позволяют функции _fsopen() и _wfsopen(). Прототипы функций: #include <stdio.h> FILE *_fsopen(const char *filenarne, const char *rnode, int shareFlag); #include <stdio.h> /* или #include <wchar.h> */ FILE *_wfsopen(const wchar_t *filenarne, const wchar_t *rnode, int shareFlag); В первом параметре функции принимают путь к файлу, а во втором - режим от­ крытия файла. В параметре shareFlag можно указать один из следующих макросов: linclude <share.h> □ _sн_DENYRW- запрещает доступ на чтение и запись. Определение макроса: #define SH DENYRW 0xl0 □ _sн_DENYWR- запрещает доступ на запись. Определение макроса: #define SH DENYWR Ох20 □ _sн_DENYRD- запрещает доступ на чтение. Определение макроса: #define SH DENYRD ОхЗО □ _sн_DENYNO - разрешает доступ на чтение и запись. Определение макроса: #define SH DENYNO Ох40 Функции возвращают указатель на структуру FILE или нулевой указатель в случае ошибки. Пример использования функции _fsopen( J : setlocale(LC_ALL, "Russian_Russia.1251"); ПLЕ *fp = NULL; fp = _fsopen("С:\\book\\filel. txt", "w", ..:_SH_DENYWR); if (fp) { fputs("строка", fp); fclose(fp); else puts("He удалось открыть файл"); Закрыть файл позволяет функция fclose( J. Прототип функции: linclude <stdio.h> int fclose(FILE *File);
354 Глава 12 В единственном параметре передается указатель на открытый ранее файл. В каче­ стве значения функция возвращает: □ о - если файл успешно закрыт; □ EOF - если при закрытии файла произошла ошибка. Макрос EOF определен с.,е­ дующим образом: #define EOF (-1) Закрыть все открытые ранее файлы позволяет функция _fcloseall(). Стандартные потоки stdin, stdout и stderr функция не закрывает. Прототип функции: #include <stdio.h> int fcloseall(void); 12.2. Указание пути к файлу В параметре filename в функциях fopen() и _wfopen() указывается путь к фай:т� Длина пути ограничена значением макроса FILENAМE_МAX. Определение макроса: #include <stdio.h> #define FILENAМE МАХ 260 Существует также макрос _МАХ _РАТН, содержащий максимальную длину пути: #include <stdlib.h> #define МАХ РАТН 260 Путь может быть абсолютны.и или относительным. При указании абсолютн� пути следует учитывать, что слэш является специальным символом. По этой п� чине его необходимо удваивать: "C:\\temp\\new\\file.txt" Если вместо двух слэшей использовать только один, то в пути будут присутс�.. вать сразу три специальных символа: \t, \n и \f. После преобразования специа.1.­ ных символов путь будет выглядеть следующим образом: С:<Табуляция>еmр<Перевод строки>еw<Перевод фopмaтa>ile.txt Вместо абсолютного пути к файлу можно указать относительный путь. В этом с:т:-­ чае путь определяется с учетом местоположения текущего рабочего катаrюга. � ратите внимание на то, что текущий рабочий каталог не всегда совпадает с ката....... rом, в котором находится исполняемый файл. Если файл запускается из коман,1ноi строки, то текущим рабочим каталогом будет каталог, из которого запускаетса файл. Получить строковое представление текущего рабочего каталога позво.lЯI\."! функции _getcwd() и _wgetcwd(). Прототипы функций: #include <direct.h> char *_getcwd(char *buf, int sizeinBytes); #include <wchar.h> /* или #include <direct.h> */ wchar t *_wgetcwd(wchar_t *buf, int sizeinWords);
Чтение и запись файлов 355 В первом параметре функции принимают указатель на буфер, а во втором парамет­ ре - максимальный размер буфера. Пример использования функции _getcwd(): setlocale(LC_ALL, '"Russian_Russia.1251"); char buf[260] = (0), *р = NULL; р = _getcwd(buf, 260); if(р){ printf("%s\n", buf); // C:\cpp\projects\Test64c Если в качестве параметров заданы нулевой указатель и нулевое значение, то стро­ ка создается динамически с помощью функции malloc(). В этом случае функции возвращают указатель на эту строку или нулевой указатель в случае ошибки. После окончания работы со строкой следует освободить память с помощью функции free(). Пример: setlocale(LC_ALL, "Russian_Russia.1251"); char *pbuf = NULL; pЬuf = _getcwd(NULL, О); if (pbuf) { printf("%s\n", pbuf); // C:\cpp\projects\Test64c free(pbuf); pbuf = NULL; Возможны следующие варианты указания относительного пути в Windows: □ если открываемый файл находится в текущем рабочем каталоге, то можно ука­ зать только название файла или перед названием файла добавить одну точку и прямой или обратный слэш: "file.txt" "./file.txt" ".\\file.txt" • □ если открываемый файл расположен во вложенном каталоге, то перед названием файла перечисляются названия вложенных каталогов через nрямой или обрат­ ный слэш: "folderl\\file.txt" "folderl/file.txt" "folderl\\folder2\\file.txt" "folderl/folder2/file.txt" □ если каталог с файлом расположен выше уровнем, то перед названием файла указываются две точки и прямой или обратный слэш: "..\\file. txt" "../file.txt" "..\\..\\file.txt" "../. . /file.txt"
356 Глава 11 □ если в начале пути расположен слэш, то путь отсчитывается от корня текушеrt'I диска. В этом случае местоположение текущего рабочего каталога не имеет зна­ чения. Пример: "\\book\\test\\file.txt" "/book/test/file.txt" 12.3. Режимы открытия файла Параметр mode в функциях fopen () и _wfopen () может принимать следующие значс-­ ния внутри строки. □ r - только чтение. После открытия файла курсор устанавливается на нача.w файла. Если файл не существует,то функции возвращают нулевой указатель. □ r+ - чтение и запись. После открытия файла курсор устанавливается на нача.• файла. Если файл не существует,то функции возвращают нулевой указатель. □ w - запись. Если файл не существует, то он будет создан. Если файл сущее� ет, то он будет перезаписан. После открытия файла курсор устанавливается 18 начало файла. □ w+ - чтение и запись. Если файл не существует, то он будет создан. Если фш существует, то он будет перезаписан. После открытия файла курсор устанав..1• вается на начало файла. □ а - запись. Если файл не существует,то он будет создан. Запись осуществляет­ ся в конец файла. Содержимое файла не удаляется. □ а+ - чтение и запись. Если файл не существует, то он будет создан. Чтенмr производится с начала файла. Запись осуществляется в конец файла. Содерж. мое файла не удаляется. После режима может следовать модификатор: □ ь - файл будет открыт в бинарном режиме; □ t- файл будет открыт в текстовом режиме (значение по умолчанию • Windows). В этом режиме при чтении символ \r будет удален, а при записи. наоборот,добавлен. ПРИМЕЧАНИЕ В Visual С имеются дополнительные модификаторы: с, n, N, s, R, т и о. За информа�,­ ей обращайтесь к документации. После режима и модификатора через запятую можно указать параметр ccs. Па� метр принимает значения umcooE, uтF-B или uтF-lбLE. Если указано значение с,-� или uтF-lбLE, то файл создается в соответствующей кодировке и в начало фай."11 вставляются служебные байты, сокращенно называемые ВОМ (Byte Order Mark - метка порядка байтов). Если открываемый файл содержит ВОМ, то значение, ук. занное в параметре ccs, игнорируется. При отсутствии параметра ccs автоматиче-
Чтение и запись файлов 357 ское определение кdдировки файла по ВОМ не производится. Пример записи в файл в кодировке UTF-8 (содержимое файла включает метку порядка байтов): setlocale(LC_ALL, "Russian_Russia.1251"); FILE *fp = NULL; fp = _wfopen(L"C:\\book\\file.txt", L"w,ccs=UTF-8"); if (fp) { fputws(L"cтpoкa", fp); fclose(fp); else _putws(L"He удалось открыть файл"); Оrкроем файл на запись в текстовом режиме и запишем в него две строки, разде­ ленные символом \n (листинг 12.1). Затем откроем тот же файл на чтение в бинар­ ном режиме и считаем первую строку. Для того чтобы продемонстрировать факт добавления символа \r при записи в текстовом режиме, выведем коды символов, которые расположены в конце считанной строки. tinclude <stdio.h> tinclude <stdlib.h> tinclude <locale.h> int main(void) { setlocale(LC_ALL, "Russian_Russia.1251"); char buf[200] = {О}; FILE *fp = NULL; // открываем файл на запись в текстовом режиме fp = fopen("C:\\Ьook\\file.txt", "w"); if (!fp) { // Проверяем успешность perror("Error"); exit(l); fputs("Stringl\nString2", fp); fflush(fp); fclose(fp); // ВЫво.ш,�м сообщение об ошибке // Выхо.ш,�м при ошибке // Записываем строку // Сбрасываем буфер // Закрываем файл // открываем файл на чтение в бинарном режиме fp = fopen("C:\\book\\file.txt", "rb"); if (!fp) { // Проверяем успешность perror("Error"); // ВЫводИМ сообщение об ошибке exit(l); // выходим при ошибке fqets(buf, 30, fp); // читаем одну строку printf("%d %d\n", (int)buf[7], (int)buf[B]); // 13 10 == \r \n
358 fclose(fp); return О; Глава 12 // Закрываем файл Попробуйте во втором случае изменить режим с rь на r. В результате вывод бу,1е7 10 о, а не 13 10. Это доказывает, что при чтении в текстовом режиме символ удаляется. Функция perror ( J выводит текст сообщения об ошибке. Перед этим сообщение\1 добавляется текст, переданный в параметре errMsg, и символ двоеточия. Например. если в первом случае файл доступен только для чтения, то получим следующее с� общение в окне консоли: Error: Permission denied. Если во втором случае файл нс существует, то сообщение будет другим: Error: No such file or directory. Прот� тип функции: #include <stdio.h> /* или #include <stdlib.h> */ void perror(const char *errMsg); Вместо функции perror() можно воспользоваться функцией strerror(), которой в параметре передается значение переменной errno. Вывести сообщение об ошибке в поток stderr позволяет функция fprintf(): fprintf(stderr, "%s", strerror(errno)); Функция fputs < J записывает С-строку в файл. Для ускорения работы производите� буферизация записываемых данных. Информация из буфера записывается в фай., полностью только в момент закрытия файла. Сбросить буфер в файл явным обра­ зом можно с помощью функции fflush ( J. Прототип функции: #include <stdio.h> int fflush(FILE *File); Прочитать одну строку из файла позволяет функция fgets( J • Считывание произв� дится, пока не встретится символ перевода строки или из файла не будет прочитан.:­ указанное количество символов минус один символ (используется для вставЮ! нулевого символа). 12.4. Запись в файл Для записи в файл предназначены следующие функции. □ fputc( J и putc < J - записывают символ в файл. Функции возвращают код запи­ санного символа или значение макроса EOF в случае ошибки. Прототипы функ­ ций: #include <stdio.h> int fputc(int ch, FILE *File); int putc(int ch, FILE *File); #define EOF (-1)
Чтение и запись файлов Пример: fputc( 'Т', fp); putc('S', fp); 359 □ fputwc() и putwc() - записывают широкий символ в файл. Функции возвраща­ ют код записанного символа или значение макроса WEOF в случае ошибки. Про­ тотипы функций: #include <stdio.h> /* или #include <wchar.h> */ wint_t fputwc(wchar_t ch, FILE *File); #define putwc(_c,_stm) fputwc(_c,_stm) #define WEOF (wint_t)(0xFFFF) Пример: // setlocale(LC_ALL, "Russian_Russia.1251"); fputwc(L'T', fp); putwc(L'S', fp); □ fputs() - записывает С-строку в файл. Функция возвращает неотрицательное число, если ошибок нет, и значение макроса EOF при наличии ошибки. Прототип функции: #include <stdio.h> int fputs(const char *str, FILE *File); Пример: fputs("Stringl\nString2", fp); □ fputws() - записывает L-строку в файл. Функция возвращает неотрицательное число, если ошибок нет, и значение макроса WEOF при наличии ошибки. Прото­ тип функции: #include <stdio.h> /* или #include <wchar.h> */ int fputws(const wchar t *str, FILE *file); Пример: // setlocale(LC_ALL, "Russian_Russia.1251"); fputws(L"Stringl\nString2", fp); □ fprintf() - производит форматированный вывод в файл. При наличии ошибки функция возвращает значение макроса EOF. Прототип функции: #include <stdio.h> int fprintf(FILE *File, const char *foпnat, ...); В параметре foпnat указывается строка специального формата. Внутри этой строки можно указать обычные символы и спецификаторы формата, начинаю­ щиеся с символа%. Эти спецификаторы мы рассматривали при изучении функ­ ции printf( J. Пример использования функции fprintf(): intа=10,Ь=20; fprintf(fp, "%d %d", а, Ь);
360 Можно также воспользоваться функцией fprintf_s(): #include <stdio.h> int fprintf_s(FILE *File, const char *format, .. . ); Пример: intа=10,Ь=20; fprintf_s(fp, "%d %d", а, Ь); Глава 12 □ fwprintf() - производит форматированный вывод в файл. При наличии ошиба функция возвращает значение макроса WEOF. Прототип функции: #include <stdio.h> /* или #include <wchar.h> */ int fwprintf(FILE *File, const wchar t *format, ...); Пример: // setlocale(LC_ALL, "Russian_Russia.1251"); int а=10, Ь = 20; fwprintf(fp, L"%d %d", а, Ь); Можно также воспользоваться функцией fwprintf_s(): #include <stdio.h> /* или #include <wchar.h> */ int fwprintf_s(FILE *File, const wchar t *format, ... ); Пример: // setlocale(LC_ALL, "Russian_Russia.1251"); fwprintf_s(fp, L"%d %d", 10, 20); 12.5. Чтение из файла Перечислим функции, предназначенные для чтения файла. □ fgetc() и getc() - считывают один символ из файла. В качестве значений функции возвращают код прочитанного символа. Если в файле нет больше сич­ волов или произошла ошибка, то функции возвращают значение макроса с:=:: Прототипы функций: #include <stdio.h> int fgetc(FILE *File); int getc(FILE *File); Пример использования функций: int chl = fgetc(fp); printf("%d\n", chl); printf("%c\n", (char)chl); int ch2 = getc(fp); printf("%d\n", ch2); printf("%c\n", (char)ch2); □ fgetwc() и getwc() - считывают широкий символ из файла. В качестве значений функции возвращают код прочитанного символа. Если в файле нет больше си\4-
Чтение и запись файлов 361 волов или произошла ошибка, то функции возвращают значение макроса WEOF. Прототипы функций: #include <stdio.h> /* или #include <wchar.h> */ wint_t fgetwc(FILE *File); #define getwc(_stm) fgetwc(_stm) Пример использования функций: // setlocale(LC_ALL, "Russian_Russia.1251"); wint_t chl = fgetwc(fp); wprintf(L"%u\n", chl); wprintf(L"%c\n", (wchar_t)chl); wint_t ch2 = getwc(fp); wprintf(L"%u\n", ch2); wprintf(L"%c\n", (wchar_t)ch2); □ fgets() - читает одну строку из файла. Прототип функции: #include <stdio.h> char *fgets(char *buf, int maxCount, FILE *File); Считывание производится до первого символа перевода строки или до конца файла, или пока не будет прочитано maxcount-1 символов. Если не достигнут предел буфера, то возвращаемая строка включает символ перевода строки. Ис­ ключением является последняя строка. Если она не завершается символом пере­ вода строки, то символ добавлен не будет. Если произошла ошибка или достиг­ нут конец файла, то функция возвращает нулевой указатель. Пример считывания одной строки из файла: char buf[200] = {О}, *р = NULL; р = fgets(buf, 200, fp); if (р) printf("%s", buf); □ fgetws () - читает одну строку из файла. Прототип функции: #include <stdio.h> /* или #include <wchar.h> */ wchar_t *fgetws(wchar_t *buf, int sizeinWords, FILE *File); Функция fgetws() аналогична функции fgets(), только работает с L-строками. Пример использования функции: // setlocale(LC_ALL, "Russian_Russia.1251"); wchar_t buf[200] = {О}, *р = NULL; р = fgetws(buf, 200, fp); if (р) wprintf(L"%s", buf); □ fscanf( J - позволяет считать данные из файла и автоматически преобразовать в конкретный тип, например, в целое число. Прототип функции: #include <stdio.h> int fscanf(FILE *File, const char *format, ...); В параметре format указывается строка специального формата, внутри которой задаются спецификаторы, аналогичные применяемым в функции scanf(). В по-
362 Глава 12 следующих параметрах передаются адреса переменных, в которых будут сохра­ нены значения. Функция возвращает количество преобразованных дани� Пример считывания из файла целого числа: intх=О,count=О; count = fscanf(fp, "%d", &х); // Символ & обязателен!!! printf("%d\n", count); printf("%d\n", х); □ fwscanf() - аналогична функции fscanf(), только работает с L-строками. П� тотип функции: #include <stdio.h> /* или #include <wchar.h> */ int fwscanf(FILE *File, const wchar t *format, ...); Пример: intх=О,count=О; count = fwscanf(fp, L"%d", &х); // Символ & обязателен!!! printf("%d\n", count); printf("%d\n", х); В качестве примера запишем строку в файл, а затем произведем посимвольное счи­ тывание (листинг 12.2). 1 Листинг 12.2. Посимвольное считываниё из файла #include <stdio.h> #include <stdlib.h> #include <locale.h> #include <string.h> int main(void) { setlocale(LC_ALL, "Russian_Russia.1251"); FILE *fp = NULL; intch=О; fp = fopen("C:\\book\\file.txt", "w+"); // Чтение и запись if (!fp) { perror("Error"); exit(l); fputs("Cтpoкal\nCтpoкa2", fp); // Записываем строку fflush(fp); // Сбрасываем буфер в файл rewind(fp); ch = fgetc(fp); while (!feof(fp)) if (ferror(fp)) { printf("Error: %s\n", exit(l); // Перемещаем курсор в начало файла // Считываем один символ // Проверяем наличие 011n1бок strerror(ferror(fp)));
Чтение и запись файлов printf("%c", (char)ch); ch = fgetc (fp); fclose(fp); retur n О; // Считываем один символ В этом примере были использованы три новые функции: □ rewind() - устанавливает курсор на начало файла. Прототип функции: #include <stdio.h> void rewind(FILE *File); 363 □ feof() - возвращает ненулевое значение, если достигнут конец файла. В про­ тивном случае возвращается значение о. Прототип функции: #include <stdio.h> int feof(FILE *File); □ ferror () - возвращает ненулевое значение, если при работе с файлом возникла ошибка. В противном случае возвращается значение о. Функцию следует вызы­ вать сразу после выполнения операции. Прототип функции: #include <stdio.h> int ferror(FILE *File); Удалить все ошибки, связанные с потоком, позволяет функция clearerr(). Прото­ тип функции: tinclude <stdio.h> void clearerr(FILE *File); Вместо функции clearerr() можно использовать функцию clearerr_s(). Если опе­ рация успешно произведена, то функция возвращает значение о. Прототип функ­ ции: tinclude <stdio.h> errno t clearerr_s(FILE *File); 12.6. Чтение и запись двоичных файлов Для чтения и записи бинарных файлов можно воспользоваться функциями, рабо­ тающими с отдельными символами, например, fgetc() и fputc(), а также следую­ щими функциями. □ fwrite() - записывает объекты произвольного типа в файл. Обратите внима­ ние: файл должен быть открыт в бинарном режиме. Функция возвращает коли­ чество записанных объектов. Прототип функции: #include <stdio.h> size t fwrite(const void *buf, size t size, size t cowit, FILE *File);
364 Глава 12 В первом параметре функция принимает указатель на объект. Во втором паJ»­ метре указывается размер объекта в байтах, а в третьем параметре - количесТ8С.' записываемых объектов. Размер объекта следует определять с помощью опе� тора sizeof. □ fread() - считывает из файла объект произвольного типа. Обратите внимаю� файл должен быть открыт в бинарном режиме. Прототип функции: #include <stdio.h> size_t fread(void *buf, size_t elementSize, size_t count, FILE *File); В первом параметре функция принимает указатель на объект. Во втором пара­ метре указывается размер объекта в байтах, а в третьем параметре - количесТ8С.' считываемых объектов. Размер объекта следует определять с помощью опера� ра sizeof. Функция возвращает количество считанных объектов. Запишем структуру в файл, открытый на запись в бинарном режиме, а затем сч� ем структуру из файла и выведем значения (листинг 12.3). 1лис-rмнr 12,3. (;охранение структуры в файn #include <stdio.h> #include <stdlib.h> #include <locale.h> struct Point { int х, у; } pointl, point2; int main(void) { setlocale(LC_ALL, "Russian_Russia.1251"); FILE *fp = NULL; // Запись в файл fp = fopen("C:\\book\\file.txt", "wb"); // Бинарный режим! if (!fp} { perror("Error"); exit(l); pointl.x = 10; pointl.y = 20; size t n = fwrite(&pointl, sizeof(struct Point), 1, fp); printf("%d\n", (int)n); // 1 fflush(fp); fclose(fp); // Чтение из файла fp = fopen("C:\\book\\file.txt", "rb"); // Бинарный режим! if (!fp) { perror("Error"); exit(l); j
./тение и запись файлов n = fread(&point2, sizeof(struct Point}, 1, fp}; printf("%d\n", point2.x); // 10 printf("%d\n", point2.y); // 20 printf("%d\n", (int}n}; // 1 printf("%d\n", (int} sizeof(struct Point)); fclose(fp); return О; 365 Обратите внимание: если струкrура содержит указатели, то в файл будут записаны адреса, на которые указывает указатель, а не данные. После восстановления струк­ туры из файла эти адреса мoryr быть уже не акrуальными. Поэтому при использо­ вании указателей нужно предусмотреть дополнительные действия для записи этих данных в файл и при чтении их из файла. 12.7. Файлы произвольного доступа Файл можно читать не только с самого начала, но и с произвольной позиции. Как правило, в этом случае файл открывают в бинарном режиме. Записывать в файл также можно с произвольной позиции. Для работы с файлами произвольного дос­ тупа предназначены следующие функции. □ fgetpos( J - записывает позицию курсора относительно начала файла в пере­ менную, адрес которой передается во втором параметре. В качестве результата функция возвращает значение о, если ошибок не возникдо, и ненулевое значение в противном случае. Прототип функции: #include <stdio.h> int fgetpos(FILE *File, fpos_t *pos}; Тип fpos_t объявлен как целочисленный тип: typedef _int64 fpos_t; #define _int64 long long Пример использования функции: fpos_t pos = О; fgetpos(fp, &pos); printf("%I64d\n", pos}; □ ftell() - возвращает позицию курсора относительно начала файла. При ошиб­ ке возвращается значение -lL. Прототип функции: #include <stdio.h> long ftell(FILE *File); Пример использования функции: long pos = О; pos = ftell(fp); printf("%ld\n", pos);
366 Глава тJ Вместо функции ftell( J можно воспользоваться функцией _ftelli64 ( J. Прот� тип функции: #include <stdio.h> int64 ftelli64(FILE *File); Пример: _int64 pos = О; pos = ftelli64(fp); printf("%I64d\n", pos); □ rewind( J - устанавливает курсор на начало файла. Функция дополните.,ьtt.:­ сбрасывает флаг ошибки для потока и флаг конца файла. Прототип функции: #include <stdio.h> void rewind(FILE *File); □ fsetpos() - перемещает курсор относительно начала файла. Адрес переменной.. в которой содержится значение, передается во втором параметре. В качестве ре­ зультата функция возвращает значение о, если ошибок не возникло, и ненулевое значение в противном случае. Прототип функции: #include <stdio.h> int fsetpos(FILE *File, const fpos_t *pos); Пример: fpos_t pos = 5; fsetpos(fp, &pos); □ fseek 1) - устанавливает курсор в позицию, имеющую смещение offset относи­ тельно позиции origin. В качестве результата функция возвращает значение : . если ошибок не возникло, и ненулевое значение в противном случае. Прототиn функции: #include <stdio.h> int fseek(FILE *File, long offset, int origin); В параметре origin могут быть указаны следующие макросы: • SEEK_SET - начало файла; • SEEK_CUR - текущая позиция курсора; • SEEK_END - конец файла. Определения макросов выглядят следующим образом: #include <stdio.h> #define SEEK SET О #define SEEK CUR 1 #define SEEK END 2 Вместо функции fseek () можно воспользоваться функцией _fseeki64 (). Прото­ тип функции:
Чтение и запись файлов 367 #include <stdio.h> int _fseeki64(FILE *File, _int64 offset, int origin); В ка'!естве примера использования файлов произвольного досrупа запишем массив в файл, а затем считаем из файла значения первого и предпоследнего элеме1ПОв массива (листинг 12.4). n�tиtir..12.4. ФaimJ·npolfЗвonыtoro #include <stdio.h> #include <stdlib.h> #include <locale.h> int main(void) { setlocale(LC_ALL, "Russian_Russia.1251"); FILE *fp = NULL; intarr[J={1,2,3,4,5},х=О; long pos = О; fp = fopen("C:\\Ьook\\file.txt", "w+b"); // Бинарный режим! if (!fp) { perror("Error"); exit(1); size t size = sizeof(int); fwrite(arr, size, 5, fp); // Записываем массив fflush(fp); fseek(fp, о, SEEK_SET); pos = ftell(fp); printf("%ld\n", pos); fread(&x, size, 1, fp); // Перемещаем курсор в начало файла //о printf("%d\n", х) ; //1 // Перемещаем курсор на предпоследний элемент fseek(fp, -2 * (long)size, SEEK_END); pos = ftell(fp); printf("%ld\n", pos); fread(&x, size, 1, fp); · printf("%d\n", х); fclose(fp); return О; // 12 //4 12.8. Создание временных файлов Для создания временного файла предназначена функция t.mpfile(). Она возвращает файловый указатель на поток, открытый в режиме w+Ь, или нулевой указатель в случае ошибки. После закрытия временный файл автоматически удаляется. Обра-
368 Глава fl тите внимание на то, что в некоторых операционных системах для создания в� менного файла могут потребоваться права администратора. Прототип функции: #include <stdio.h> FILE *tmpfile(void); В Visua\ С при использовании функции trnpfile() выводится предупреждаюшеr сообщение warning С4996. Для того чтобы избежать этого сообщения, следует п� менять функцию trnpfile_s(). При успешном выполнении операции функция ВОr вращает значение о, а в противном случае - код ошибки. В проектах тest32: ■ Test64c функция не работает. Прототип функции: #include <stdio.h> errno_t trnpfile_s(FILE **File); Вместо создания временного файла можно сгенерировать уникальное назван� с помощью функции trnpnarn(), а затем создать файл обычным образом. Прототип функции: #include <stdio.h> char *tmpnarn(char *buf); Если произошла ошибка, то функция возвращает нулевой указатель. Сгенери� ванное название записывается в символьный массив buf. Если в параметре пере..1ан нулевой указатель, то сгенерированное название размещается во внутреннем стати­ ческом буфере, а ссылка на него возвращается в качестве значения. Эти данные б�­ дут перезаписаны при следующем вызове функции trnpnarn(). Пример использова­ ния функции: char*р=О; р = tmpnarn(NULL); if (р) printf("%s", р); // Например, \s2rng. Вместо функции trnpnarn() можно воспользоваться функцией _wtrnpnarn(), котора.1 возвращает название временного файла в виде L-строки. Прототип функции: #include <stdio.h> /* или #include <wchar.h> */ wchar_t *_wtrnpnarn(wchar_t *buf); Пример: setlocale(LC_ALL, "Russian_Russia.1251"); wchar_t *р = О; р = _wtmpnarn(NULL); if (р) wprintf(L"%s", р); // Например, \s74g. Вместо функций trnpnarn() и _wtrnpnarn() можно использовать функции trnpnarn_s (; н _wtrnpnarn _s(). Прототипы функций: #include <stdio.h> errno_t trnpnarn_s(char *buf, rsize_t size); #include <stdio.h> /* или #include <wchar.h> */ errno_t _wtrnpnarn_s(wchar_t *buf, size t size);
Чтение и запись файпов 369 При успешном выполнении операции функции возвращают значение о, а в против­ ном случае- код ошибки. Сгенерированное название сохраняется в символьном массиве buf, максимальный размер которого указывается в параметре size. Пример: chat buf[l00] = {О}; if (tmpnarn_s(buf, 100) == О) printf("%s\n", buf); // Например, \sarg. Кроме перечисленных функций, для генерации названия временного файла можно воспользоваться функциями _tempnarn() и _wtempnarn () . Прототипы функций: lim::lude <stdio.h> char *_tempnarn(const char *dirNarne, const char *filePrefix); linclude <stdio.h> /* или #include <wchar.h> */ wchar_t *_wtempnarn(const wchar_t *dirNarne, const wchar_t *filePrefix); В первом параметре указывается путь к каталогу, который будет использоваться, если переменная окружения ТМР не определена или содержит некорректный путь. Если переменная ТМР корректна, то будет использоваться ее значение. Во втором параметре указывается префикс. Функции динамически выделяют под название необходимый объем памяти с помощью функции malloc() и возвращают указатель на строку. Обратите внимание: ответственность за освобождение динамической памяти лежит на плечах программиста, поэтому после использования памяти сле­ J!Ует обязательно вызвать функцию free() и передать ей указатель. В случае ошиб­ lDI функции возвращают нулевой указатель. Пример использования функции _ternpnarn() : char *р = _ternpnarn("C:\\book", "tmp_"); if(р){ printf("%s\n", р); // Например, C:\Users\Unicross\AppData\Local\Ternp\tmp_2 free(p); // Освобождаем память!!! 12.9. Перенаправление ввода/вывода Потоки ввода/вывода на консоль аналогичны потокам ввода/вывода в файл. При 31U1Уске программы автоматически открываются три потока: CJ stdin - стандартный ввод; CJ stdout - стандартный вывод; CJ stderr - стандартный вывод сообщений об ошибках. Все эти идентификаторы являются файловыми указателями, связанными по умол­ 'IUIИЮ с окном консоли. Следовательно, их можно использовать вместо обычных +айловых указателей в функциях, предназначенных для работы с файлами. Напри­ мер, вывести строку в окно консоли можно так:
370 fputs("Stringl\nString2", stdout); fflush(stdout); Пример вывода сообщения об ошибке в поток stderr: fрuts("Сообщение об ощибке", stderr); fflush(stderr); Ввод символа выполняется следующим образом: setlocale(LC_ALL, "Russian_Russia.1251"); printf("Bвeдитe символ: "); fflush(stdout); int ch = fgetc(stdin); printf("%c\n", (char) ch); Глава r2 Стандартные потоки можно перенаправить таким образом, чтобы данные запис�r вались в файл или считывались из файла. Для этого существуют три способа. □ Перенаправить из командной строки. В этом случае ничего в программе меняn. не нужно. Для того чтобы выполнить перенаправление вывода в файл, следуе,­ в командной строке выполнить одну из команд: test.exe > file.txt test.exe >> file.txt Первая строка записывает результат выполнения программы test.exe в фак.� file.txt. Если файл не существует, то он будет создан, а если существует, то C'tl будет перезаписан. Вторая строка производит дозапись в конец файла. Предыдущий пример сохранял в файл только данные из потока stdout. Для тоn:­ чтобы сохранить данные из потока stderr, нужно дополнительно указать деск­ риптор потока. Поток stdout имеет дескриптор 1, а поток stderr - дескрип­ тор 2: test.exe 1> путь_к _фай.лу_для_оut 2> путь_к _файлу_для_еrr test.exe 1>> путь_к _файлу_для_оut 2>> путь_к_файлу_для_еrr Пример: test.exe 1> out.txt 2> err.txt Для того чтобы ввести в программу данные из файла, следует выполнить кома�� test.exe < file.txt □ Перенаправить с помощью функции freopen() или _wfreopen(). Прототипы функций: #include <stdio.h> FILE *freopen(const char *filenarne, const char *rnode, FILE *oldFile); #include <stdio.h> /* или #include <wchar.h> */ FILE *_wfreopen(const wchar_t *filenarne, const wchar t *rnode, FILE *oldFile);
Чтение и запись файлов 371 Первые два параметра аналогичны параметрам функции fopen (). Пример пере­ направления стандартного вывода в файл: setlocale(LC_ALL, "Russian_Russia.1251"); FILE *fp = NULL; fp = freopen('-'C:\\book\\file.txt", "w", stdout); if (!fp) exit(l); printf("%s\n", "ПИшем в файл!"); Вместо функций freopen() и _wfreopen() можно использовать функции freopen_s() и_wfreopen_s(). Прототипы функций: #include <stdio.h> errno t freopen_s(FILE **File, const char *filename, const char *mode, FILE *oldFile); #include <stdio.h> /* или #include <wchar.h> */ errno_t _ wfreopen_s(FILE **File, const wchar_t *filename, const wchar�t *mode, FILE *oldFile); Первые три параметра аналогичны параметрам функции fopen_s (). Пример ис­ пользования функции freopen_s () : setlocale(LC_ALL, "Russian_Russia.1251"); FILE *fp = NULL; if (freopen_s(&fp, "C:\\book\\file.txt", "w", stdout) != О) { exit(l); printf("%s\n", "ПИшем в файл!"); □ Перенаправить с помощью функции _dup2(). В качестве параметров функция принимает дескрипторы файлов, которые можно получить с помощью функции _fileno(). Прототипы функций: #include <io.h> int _dup2(int fileHandleSrc, int fileHandleDst); #include <stdio.h> int _fileno(FILE *File); Пример: setlocale(LC_ALL, "Russian Russia.1251"); FILE *fp = fopen("C:\\Ьook\\file.txt", "w"); if (!fp) exit(l); if (_dup2(_fileno(fp), _fileno(stdout)) == -1) perror("He удалось перенаправить\n"); exit(l); printf("%s\n", "ПИшем в файл!"); Если после перенаправления нужно восстановить вывод на консоль, то можно воспользоваться кодом из листинга 13.5.
372 Глава 12 12.1О. Работа с буфером ввода и вывода Дr�я ускорения работы производится буферизация записываемых данных. Инфор­ мация из буфера записывается в файл полностью только при заполнении буфера или в момент закрытия файла. Сбросить буфер в файл явным образом можно с п� мощью функции fflush(). Прототип функции: #include <stdio.h> int fflush(FILE *File); Функция возвращает значение о, если буфер успешно сброшен, или значение мак­ роса EOF в случае ошибки. Пример: printf("%s\n", "строка"); fflush(stdout); fprintf(stderr, "%s\n", "сообщение об ошибке"); fflush(stderr); Функцию fflush() можно также использовать в некоторых версиях Windows ,1.u очистки буфера ввода stdin. Очистить буфер ввода особенно важно, например, при использовании функции scanf(), когда предыдущий ввод закончился ошибкой: intх=О; fflush(stdin); int status = scanf("%d", &х); if (status != 1) { puts("Bы ввели не число"); // Здесь при ошибке нужно обязательно очистить буфер fflush(stdin); else printf("x = %d\n", х); Проблема в том, что это нестандартный способ сброса буфера, который не работает даже в некоторых версиях Windows, а в других операционных системах вообще не работает. Тем не менее это единственный способ, позволяющий сбросить буфер при отсутствии ошибок. Способы, рассматриваемые далее, при пустом буфере будут ожидать ввода пользователя. Более универсальным способом является использование функции getchar(), нс только при наличии ошибки. Как уже говорилось, при пустом буфере функuи� будет ждать ввода пользователя, который нигде не сохранится! В этом примере -.,ы дополнительно проверим статус потока и при ошибке сбросим флаг ошибки: intх=О; fflush(stdin); // Здесь использовать функцию getchar() нельзя! int status = scanf("%d", &х); if (status != 1) { puts("Bы ввели не число"); // Сбрасываем флаг ошибки и очищаем буфер if (feof(stdin) 1 1 ferror(stdiп)) clear 0 rr(stdin); intch=О;
Чтение и запись файлов 373 while ((ch = getchar()) != '\n' && ch != EOF); else printf("x = %d\n", х); Зачем очищать буфер ввода перед функцией scanf ()? Если пользователь, например, введет Зstr, то число з будет считано функцией scanf(), а вот фрагмент str оста­ нется в буфере и приведет к считыванию без ожидания ввода пользователя, что в свою очередь приведет к ошибке при вводе числа. Следующий способ очистки буфера заключается в применении функции scanf (): intх=О; fflush(stdin); int status = scanf("%d", &х); if (status != 1) { puts("Вы ввели не число"); // Сбрасываем флаг ошибки и очищаем буфер if (feof(stdin) 11 ferror(stdin)) clearerr(stdin); scanf("%*["\n]"); // Считываем все символы до \n scanf("%*с"); // Считываем символ \n else printf("x = %d\n", х); На самом деле советую вообще отказаться от использования функции scanf ( J • Во­ первых, она оставляет часть данных в буфере, что может стать причиной ошибки при следующей операции ввода. Во-вторых, при вводе строки, если в составе спе­ цификатора не указать ширину, возможно переполнение буфера. В -третьих,· при вводе чисел функция никак не проверяет диапазон значений, и при переполнении вы никак об этом не узнаете. Лучше получать от пользователя строку с данными, а затем уже выполнять преобразования. Пример можно посмотреть в листинге 11.3. Функция _flushall() позволяет сбросить данные сразу всех потоков ввода и выво­ да. Она возвращает количество открытых потоков. Прототип функции: linclude <stdio.h> int flushall(void); Пример: printf("%d\n", _flushall());
ГЛАВА 13 Низкоуровневые потоки ввода и вывода В этой главе мы рассмотрим функции, предназначенные для выполнения низкС'­ уровневых операций ввода и вывода. Такие функции не поддерживают буфериза­ цию и позволяют читать и писать только байты. 13.1. Открытие и закрытие файла Для открытия файла предназначены функции _open() и _ wopen(J • Прототипы функ­ ций: #include <io.h> int _open(const char *filename, int openFlag[, int pmode]); #include <io.h> /* или #include <wchar.h> */ int _wopen(const wchar_t *filename, int openFlag[,· int pmode]); В первом параметре функции принимают путь к файлу, во втором -режим откры­ тия файла, а в третьем - режим разрешений. Функции открывают файл и возвра­ щают целочисленный дескриптор, с помощью которого производится дальнейша.s работа с файлом. Если файл открыть не удалось, то возвращается значение < н устанавливается переменная errno. В параметре openFlag могут быть указаны следующие флаги (или их комбинашu через оператор 1 ): #include <fcntl.h> □ _о _RDONLY - только чтение. Определение макроса: #define О RDONLY ОхОООО □ _О _WRОNLУ-только запись. Определение макроса: #define О WRONLY OxOOOl □ _о _RDWR -чтение и запись. Определение макроса: #define О RDWR Ох0002
Низкоуровневые потоки ввода и вывода □ _о _APPEND - добавление в конец файла. Определение макроса: #define О APPEND Ох0008 375 □ _о _CREAT - создать файл, если он не существует, и открыть его для записи. Если флаг указан, то параметр pmode является обязательным. Определение макроса: #define О CREAT Ох0100 □ _о_CREAT 1 _о _EXCL - создать файл. Если файл уже существует, то вернуrь код ошибки. Определение макроса_о_EXCL: #define О EXCL Ох0400 □ _о _CREAT 1 _о _TEMPORARY - создать временный файл. При закрытии файла он будет удален. Определение макроса_о_TEMPORARY: #define О TEMPORARY Ох0040 □ _о _TRUNC - очистить содержимое файла. Определение макроса: #define О TRUNC Ох0200 □ _о _ВINARY - файл будет открыт в бинарном режиме. Определение макроса: #define О BINARY Ох8000 □ _о _ТЕХТ - файл будет открыт в текстовом режиме. В Windows файлы по умол­ чанию открываются в текстовом режиме. Определение макроса: #define О ТЕХТ Ох4000 □ _о _WТЕХТ - открывает файл в режиме UNICODE. Определение макроса: #define О WTEXT 0xl0000 □ _о _uэтЕхт - открывает файл в режиме uтF-8 . Определение макроса: #define О U8TEXT Ох40000 □ _о_u1 бТЕхт - открывает файл в режиме UTF-1 бLE. Определение макроса: #define О UlбTEXT Ох20000 Существуют также следующие флаги (за подробностями обращайтесь к докумен­ тации): ldefine О NOINHERIT Ох0080 ldefine О SHORT LIVED 0xl000 ldefine _O_SEQUENTIAL Ох0020 ldefine О RANDOМ Ох0010 Если флаг _о_CREAT указан, то в параметре pmode нужно обязательно задать режим разрешений с помощью следующих флагов (или их комбинацию через оператор 1): linclude <sys/stat.h> □_s _IREAD - разрешено только чтение. Определение макроса: #define S IREAD Ох0100
376 Глава 13 □ _s_rwютE- разрешена запись. В Windows флаг одновременно разрешает и чте­ ние и запись, т. е . флаг эквивалентен комбинации флагов _s _IREAD I s rwR:� Определение макроса: #define S IWRITE Ох0080 Для открытия файла можно также воспользоваться функциями _sopen (, ■ _ wsopen(), которые дополнительно позволяют управлять совместным достуnО111 к файлу. Прототипы функций: #include <io.h> int _sopen(const char *filename, int openFlag, int shareFlag[, int pmode]); #include <io.h> /* или #include <wchar.h> */ int _wsopen(const wchar_t *filename, int openFlag, int shareFlag[, int pmode]); Параметр prnode является обязательным только при использовании флага _o _cR.::..:.:_ В параметре shareFlag можно указать один из следующих макросов: #include <share.h> □ _ sн_DENYRW -запрещает доступ на чтение и запись. Определение макроса: #define SH DENYRW 0xl0 □ _sн_DENYWR -запрещает доступ на запись. Определение макроса: #define SH DENYWR Ох20 □ _sн_DENYRD - запрещает доступ на чтение. Определение макроса: #define SH DENYRD ОхЗО □ _sн_DЕNУNО-разрешает доступ на чтение и запись. Определение макроса: #define SH DENYNO Ох40 Вместо функций _open(), _wopen(), _sopen () и _wsopen () можно использовать функ­ ции _sopen_s() и _wsopen_s(). Прототипы функций. #include <io.h> errno_t _sopen_s(int *fileHandle, const char *filename, int openFlag, int shareFlag, int prnode); #include <io.h> /* или #include <wchar.h> */ errno_t _wsopen_s(int *fileHandle, const wchar_t *_Filename, int openFlag, int shareFlag, int pmode); Параметр pmode обязателен, но используется только при наличии флага _о_cR.E.:.: . Функции возвращают ненулевое значение в случае ошибки и о- в противном слу­ чае. Пример открытия файла на чтение в текстовом режиме: int openFlag = _O _RDONLY 1 _О _ТЕХТ; intfd=О; if (_wsopen _s(&fd, L"C:\\book\\file.txt", openFlag, _SH_DENYWR, 0)) { perror("He удалось открыть файл");
Низкоуровневые потоки ввода и вывода exit(l); // Читаем данные из файла _close(fd); // Закрываем файл 377 Закрыть файл позволяет функция _close( J • В качестве параметра указывается деск­ риптор файла. Функция возвращает значение о, если файл успешно закрыт, или значение -1 - в случае ошибки. Прототип функции: llinclude <io.h> int _close(int fileHandle); 13.2. Чтение из файла и запись в файл Для чтения из файла и записи в файл предназначены следующие функции. □ �read() - читает байты из файла и записывает их в буфер. Прототип функции: ·#include <io.h> int _read(int fileHandle, void *buf, unsigned int maxCharCount); В первом параметре задается дескриптор файла, во втором - указатель на буфер, а в третьем - максимальное число байтов. Функция возвращает количе­ ство прочтенных байтов, значение о - если достигнут конец файла или значе­ ние -1 ___: в случае ошибки (дополнительно устанавливает переменную errno). Пример чтения строки: char buf[256] = {О}; int count = _read(fd, buf, 255); printf("count = %d\n", count); if(count>О&&count<256){ buf[count] = '\О'; printf("%s\n", buf); else puts("He удалось прочитать"); □ _write() - записывает последовательность байтов в файл. Прототип функции: llinclude <io.h> int _write(int fileHandle, const void *buf, unsigned int rnaxCharCount); В первом параметре указывается дескриптор файла, во втором - записываемые данные, а в третьем - число байтов. Функция возвращает количество записан­ ных байтов или значение -1 - в случае ошибки (дополнительно устанавливает переменную errno). Пример: char str[] = "Строкаl\nСтрока2"; unsigned int str_len = (unsigned int) strlen(str); int count = _write(fd, str, str_len); printf("count = %d\n", count);
378 Глава 13 Для явного сброса файла на диск предназначена функция _cornmi t(). Функция воз­ вращает значение о, если операция успешно выполнена, или значение -1 - вс.�- чае ошибки. Прототип функции: #include <io.h> int _cornmit(int fileHandle); Рассмотрим несколько примеров. Откроем файл на запись и запишем в него стро�..� (листинг 13.1). Если файл не существует, то создадим его. Если файл существует. то очистим его (режим w в функции fopen()). Листинг 13.1. Запись строк� в файл в текстовом режиме #include <stdio.h> !finclude <string.h> #include <locale.h> #include <io.h> #include <fcntl.h> #include <sys/stat.h> int rnain(void) { setlocale(LC_ALL, "Russian Russia.1251"); char str[] = "Строка1\nСтрока2"; unsigned int str_len = (unsigned int) strlen(str); int openFlag _O _WRONLY 1 _O _CREAT I О TRUNC О ТЕХТ; int prnode = _S_IREAD I S IWRITE; int fd _open("C:\\book\\file.txt", openFlag, prnode); if(fd!= -1){ // Проверяем успешность int count = _write(fd, str, str_len); // Записываем строку printf("count = %d\n", couпt); if (count == -1) { perror("He удалось записать строку"); else puts("Cтpoкa записана"); _ close(fd); else perror("Не удалось открыть файл"); return О; // Закрываем файл Добавим еще одну строку в конец файла (листинг 13.2). Если файл не существует. то создадим его. Если файл существует, то установим курсор на конец фай.1а (режим а в функции fopen()). Листинг 13.2..Запись строки в конец файла в текстовом режиме #include <stdio.h> #include <string.h>
Низкоуровневые потоки ввода и вывода linclude <locale.h> tlinclude <io.h> linclude <fcntl.h> linclude <sys/stat.h> int main(void) { setlocale(LC_ALL, "Russian_Russia.1251"); char str[] = "\nСтрокаЗ"; unsigned int str_len = (unsigned int) strlen(str); int openFlag = _O _WRONLY 1 _O_CREAT 1 _O _APPEND 1 _О_ТЕХТ; int pmode = _S _IREAD 1 _S _IWRITE; int fd = _open("C:\\Ьook\\file.txt", openFlag, pmode); if(fd!= -1){ // Проверяем успешность int count = _write(fd, str, str_len); // Записываем строку printf("count = %d\n", count); if (count == -1) { perror("He удалось записать строку"); else puts("Cтpoкa записана"); _ close(fd); else perror("He удалось открыть файл"); return о · ; // Закрываем файл 379 Прочитаем содержимое файла в текстовом режиме и выведем результат в окно кон­ соли (листинг 13.3). Если файл не существует, то выведем сообщение об ошибке (режим r в функции fopen()). tinclude <stdio.h> linclude <locale.h> linclude <io.h> linclude <fcntl.h> ldefine BUF LEN 10 int main(void) { setlocale(LC_ALL, "Russian_Russia.1251"); char buf[BUF_LEN] = {О}; int openFlag = _O_ROONLY 1 _О _ТЕХТ; int fd = _open("C:\\Ьook\\file.txt", openFlag); if(fd!= -1){ int count = О; while ( !_eof(fd) ) { // Пока не достигнут конец файла count _read(fd, buf, BUF LEN - 1);
380 if (count == -1) { perror("He удалось nрочитать"); break; if (count > О && count < BUF_LEN) buf[count] = '\О'; printf("%s", buf); close(fd); else perror("He удалось открыть файл"); return О; Глава 13 В этом примере, для проверки достижения конца файла, мы воспользовались функ­ цией _eof (). Функция возвращает значение 1, если был достигнут конец фай.u. и о - если это не так. При ошибке возвращает значение -1. Прототип функции: #include <io.h> int eof(int fileHandle); 13.3. Файлы произвольного доступа Файл можно читать не только с самого начала, но и с произвольной позиции. Ku правило, в этом случае файл открывают в бинарном режиме. Записывать в фак.l также можно с произвольной позиции. Для работы с файлами произвольного дос­ тупа предназначены следующие функции. □ _tel1 () - возвращает позицию курсора относительно начала файла. При ошн� ке возвращается значение -lL. Прототип функции: #include <io.h> long _tell(int fileHandle); Пример: long pos tel1 (fd); printf("%1d\n", pos); Вместо функции _tell() можно воспользоваться функцией _telli64(). Прот� тип функции: #include <io.h> int64 telli64(int fileHandle); Пример: int64 pos telli64(fd); printf("%I64d\n", pos);
Низкоуровневые потоки ввода и вывода 381 □ _lseek(} - устанавливает курсор в позицию, имеющую смещение offset отно­ сительно позиции origin. В качестве результата функция возвращает новое смещение в байтах от начала файла, если ошибок не возникло, или значение - lL - в противном случае. Прототип функции: #include <io.h> long _lseek(int fileHandle, long offset, int origin); В параметре origin могут быть указаны следующие макросы: • SEEK_SET - начало файла; • SEEK_CUR - текущая позиция курсора; • SEEK-END - конец файла. Определения макросов выглядят следующим образом: #include <stdio.h> #define SEEK SET о #define SEEK CUR 1 #define SEEK END 2 Пример: //· Относительно начала файла printf("%ld\n", _lseek(fd, 5L, SEEK_SET)); // 5 // Относительно указателя printf("%ld\n", _lseek(fd, 2L, SEEK_CUR)); // 7 // Перемещение в конец файла printf("%ld\n", _lseek(fd, OL, SEEK_END)); // Перемещение в начало файла printf("%ld\n", _lseek(fd, OL, SEEK_SET)); // О Вместо функции _lseek() можно воспользоваться функцией_lseeki64(). Прото­ тип функции: #include <io.h> int64 lseeki64(int fileHandle, int64 offset, int origin); Пример: // Относительно начала файла printf("%I64d\n", _lseeki64(fd, 5LL, SEEK SET)); // 5 13.4. Создание временных файлов Дпя создания временного файла нужно при открытии файла указать комбинацию флагов: _o_CREAT 1 _O_TEМPORARY. Файл будет автоматически удален при закрытии файла. Определение макросов: #include <fcntl.h> #define О CREAT Ох0100 #define О TEМPORARY Ох0040
382 Глава 13 Пример создания временного файла приведен в листинге 13.4. Понаблюдайте за каталогом C:\book в течение пяти секунд. Файл будет создан, а затем удален. l.листинr 13.4. Создание временного файла #include <stdio .h> #include <string.h> #include <locale.h> #include <io.h> #include <fcntl.h> #include <sys/stat.h> #include <windows.h> int rnain(void) { setlocale(LC_ALL, "Russian Russia.1251"); char str [] = "строка"; unsigned int str len = (unsigned int) strlen(str); int openFlag = О RDWR _O_CREAT 1 _O_TEMPORARY I О BINARY; int prnode = _S_IREAD I S IWRITE; int fd = _open("C:\\book\\trnpfile.txt", openFlag, prnode); if(fd!= -1){ _ write(fd, str, str_len); // Записываем строку рuts("Понаблюдайте за _ каталогом C:\\book в течение пяти секунд"); fflush(stdout); Sleep(S000); // Имитация работы с файлом рuts("Закрываем файл"); _close(fd); else perror("He удалось открыть файл"); return О; 13.5. Дескрипторы потоков ввода/вывода Низкоуровневые операции ввода и вывода можно применять и к стандартныч потокам. В этом случае указываются следующие макросы или просто их значения: #include <stdio.h> □ STDIN_ FILENO- стандартный ввод stdin. Определение макроса: #define STDIN FILENO О □ sтoouт_FILENO- стандартный вывод stdout. Определение макроса: #define STDOUT FILENO 1 □ STDERR_FILENO- стандартный вывод сообщений об ошибках stderr. Опреде.1е­ ние макроса: #define STDERR FILENO 2
Низкоуровневые потоки ввода и вывода Например, вывести строки в окно консоли можно так: setlocale(LC_ALL, "Russian_Russia.1251"); char str[] = "строка\n"; unsigned int str_len = (unsigned int) strlen(str); _ write(STOOUT_FILENO, str, str_len); _ write(l, str, str_len); fflush(stdout); Пример вывода сообщения об ошибке в поток stderr: setlocale(LC_ALL, "Russian_Russia.1251"); char str[] = "Сообщение об оwибке\n"; unsigned int str_len = (unsigned int) strlen(str); _ write(2, str, str_len); fflush(stderr); 383 Получить дескриптор открытого потока позволяет функция _fileno(). Она возвра­ щает дескриптор потока или значение -1 в случае ошибки. Если стандартный поток не связан с окном консоли, то функция вернет значение -2 . Прототип функции: linclude <stdio.h> int _fileno(FILE *File); Пример вывода сообщения об ошибке в поток stderr: setlocale(LC_ALL, "Russian_Russia.1251"); char str[] = "Сообщение об оwибке\n"; unsigned int str_len = (unsigned int) strlen(str); int fd = _fileno(stderr); if (fd > О) _write(fd, str, str_len); 13.6. Преобразование низкоуровневого потока в обычный Функции _fdopen() и wfdopen() преобразуют низкоуровневый поток в обычный. Прототипы функций: tinclude <stdio.h> ПLЕ *_fdopen(int fileHandle, const char *mode); linclude <stdio.h> /* или #include <wchar.h> */ ПLЕ *_wfdopen(int fileHandle, const wchar_t *mode); В первом параметре указывается дескриптор низкоуровневого потока, а во вто­ ром - режим открытия файла (см. разд. 12 .3). Функции возвращают указатель на открытый поток или нулевой указатель в случае ошибки. Пример: setlocale(LC_ALL, "Russian_Russia.1251"); int openFlag = _O_WRONLY 1 _O_CREAT 1 _O _TRUNC; int pmode = _S _IREAD 1 _S _IWRITE; int fd = _open("C:\\book\\file.txt", openFlag, pmode);
384 if (fd == -1) exit(l); FILE * fp = fdopen(fd, "w"); if (!fp) exit(l); fрuts("Пишем строку в файл", fp); fflush(fp); fclose(fp); // Закрываем fp и fd 13.7. Создание копии потока Глава fJ Функция _c tup() создает копию потока и возвращает дескриптор. При возникново­ нии ошибки функция вернет значение -1. Прототип функции: #include <io.h> int _dup(int fileHandle); Пример: int dup_stdout = _dup(_fileno(stdout)); printf("%d\n", dup_stdout); //3 printf("%d\n", fileno(stdout)); // 1 13.8. Перенаправление потоков Функция _dup2() связывает дескриптор потока fileHandleSrc с дескриmором п� ка fileHandleDst. Если операция успешно выполнена, то функция возвращает з.,. чение о. При ошибке возвращает значение -1 . Прототип функции: #include <io.h> int _dfJp2(int fileHandleSrc, int fileHandleDst); В качеств , е примера использования функций dup() и dup2() создадим коп... потока stdout, откроем файл и перенаправим поток stdout в файл, а затем восст. новим вывод на консоль (листинг 13 .5). Листинг 13.5. Временное перенаправление потока stdout в файл #include <stdio.h> #include <stdlib.h> #include <locale.h> #include <io.h> int main(void) ( setlocale(LC_ALL, "Russian_Russia.1251"); // Сохраняем поток stdout int dup_stdout = _dup(_fileno(stdout)); // Открываем файл FILE *fp = fopen("C:\\book\\file.txt", "w");
Низкоуровневые потоки ввода и вывода if ( ! fp) exit(1); // Перенаправляем поток в файл if (_dup2(_fileno(fp), 1) == -1) perror("He удалось перенаправить\n"); exit(l); printf("%s\n", "Пишем строку в файл"); fflush(stdout); fclose(fp); // Восстанавливаем вывод на консоль _dup2(dup_stdout, 1); printf("%s\n", "Выводим строку на консоль"); return О; 385
ГЛАВА 14 Работа с файловой системой В предыдущих двух главах были описаны способы работы с файлами в языке С.. Теперь мы переходим к рассмотрению вопросов переименования и удаления фа. лов, получения информации о файлах, а также созданию, удалению и чтению кат. логов. Следует сразу заметить, что реализация функций, описанных в этой главе. зависит от операционной системы и компилятора. Рассматриваемые функции пр• менимы для операционной системы Windows. 14.1. Преобразование пути к файлу или каталогу Преобразовать путь к файлу или каталогу позволяют следующие функции. □ _fullpath() и _wfullpath() - преобразуют относительный путь в абсолютныi путь, учитывая местоположение текущего рабочего каталога. Прототипы функ­ ций: #include <stdlib.h> ohar *_fullpath(char *fullPath, const char *path, size_t sizeinBytes); #include <stdlib.h> /* или #include <wchar.h> */ wchar_t *_wfullpath(wchar_t *fullPath, const wchar_t *path, size t sizeinWords); В первом параметре передается указатель на буфер, в который будет записан аб­ солютный путь. Если в первом параметре передать нулевой указатель, то память под абсолютный путь будет выделена динамически с помощью функции malloc(). В этом случае после использования памяти ее следует освободИ"n. с помощью функции free(). Во втором параметре указывается относительный путь, а в третьем параметре - максимальный размер буфера. В качестве макси­ мального размера буфера можно указать макрос _МАХ _РАТН . Определение макро­ са выглядит так: #include <stdlib.h> #define МАХ РАТН 260
Работа с файловой системой Пример преобразования относительного пуrи в абсолютный пуrь: setlocale(LC_ALL, "Russian_Russia.1251"); char full_path[_МAX_PATH] = {О}, *р = NULL; р = _fullpath(full_path, "test.txt", _МАХ_РАТН); if(р){ printf("%s\n", full_path); } // Результат: C:\cpp\projects\Test64c\test.txt р = fullpath(NULL, "..\\ . . \\test.txt", _МАХ_РАТН); if(р){ printf("%s\n", р); // C:\cpp\test.txt free(p); 387 □ _splitpath_s () и _wsplitpath_s()- разбивают абсолютный путь на составляю­ щие: имя диска, путь, имя файла и расширение. Прототипы функций: #include <stdlib.h> errno t _splitpath_s(const char *fullPath, char *drive, size_t driveSize, char *dir, size_t dirSize, char *filename, size_t filenameSize, char *ext, size_t extSize); #include <stdlib.h> /* или #include <wchar.h> */ errno t _wsplitpath_s(const wchar_t *fullPath, wchar_t *drive, size_t driveSize, wchar_t *dir, size_t dirSize, wchar t *filenarne, size_t filenameSize, wchar_t *ext, size_t extSize); В параметре fullPath задается абсолютный путь. В параметре drive передается указатель на буфер, в котором будет сохранено имя диска и двоеточие, а в пара­ метре driveSize- максимальный размер этого буфера. В параметре ctir переда­ ется указатель на буфер, в котором будет сохранен путь, а в параметре dirSize- максимальный размер этого буфера. В параметре filename передается указатель на буфер, в котором будет сохранено имя файла без расширения, а в параметре filenameSize- максимальный размер этого буфера. В параметре ext передается указатель на буфер, в котором будет сохранено расширение файла с предваряющей точкой, а в параметре extSize- максимальный размер этого буфера. Функции возвращают значение о, если ошибок нет, и код ошибки при неудачном выполнении. В качестве размеров обычно указываются следующие макросы: #include <stdlib.h> #define МАХ DRIVE ·З #define МАХ DIR 256 #define МАХ FNAМE 256 #define МАХ ЕХТ 256 Пример разбиения абсолютного пути на составляющие: setlocale(LC_ALL, "Russian_Russia.1251"); errnoterr=О;
388 char full_path[] = "C:\\book\\test.txt"; char drive[_МAX _DRIVE] = {О}, dir[_МAX_DIR] = {О}; char name[_МAX _FNAМE] = (О}, ехt[_МАХ _ЕХТ] = {О}; err = _splitpath_s(full_path, drive, _МAX_DRIVE, dir, _МAX _DIR, name, _МАХ_FNАМЕ, ext, МАХ_ЕХТ); if(err==О){ printf("drive: '%s'\n", drive); // drive: 'С:' printf("dir: '%s'\n", dir); // dir: '\book\' printf("name: '%s'\n", name); printf("ext: '%s'\n", ext); // пате: 'test' // ext: '.txt' Глава� Если какая-либо из составляющих не нужна, то в соответствующем параметр1 следует передать нулевой указатель и в качестве размера указать значение � Пример получения только имени файла: setlocale(LC_ALL, "Russian_Russia.1251"); errnoterr=О; char full_path[] = "C:\\book\\test.txt"; char пате[_МАХ _FNАМЕ] = {О}; err = _splitpath_s(full_path, NULL, О, NULL, О, name, МАХ_FNАМЕ, NULL, О); if(err==О){ printf("name: %s\n", name); // name: test Вместо функций _splitpath_s() и _wsplitpath _s() можно воспользоватьа функциями _splitpath() и _wsplitpath(), но они не проверяют размеры буере-­ ров. Прототипы функций: #inGlude <stdlib.h> void _splitpath(const char *fullPath, char *drive, char *dir, char *filename, char *ext); #include <stdlib.h> /* или #include <wchar.h> */ void _wsplitpath(const wchar_t *fullPath, wchar_t *drive, wchar t *dir, wchar t *filename, wchar t *ext); Пример: setlocale(LC_ALL, "Russian_Russia.1251"); char full_path[] = "C:\\book\\test.txt"; char drive[_МAX_DRIVE] = {О}, dir[_МAX _DIR] = {О}; char name[_МAX _FNAМE] = {О}, ехt[_МАХ_ЕХТ] = {О}; _ splitpath(full_path, drive, dir, name, ext); p·rintf("drive: '%s'\n", drive); // drive: 'С:' printf("dir: '%s'\n", dir); // dir: '\book\' printf("name: '%s'\n", name); printf("ext: '%s'\n", ext); // пате: 'test' //ext: '.txt'
Работа с файловой системой 389 □ _makepath_s() и _wmakepath_s()- соединяют элементы пути (имя диска, путь, имя файла и расширение) в абсолютный пуrь. Прототипы функций: #include <stdlib.h> errno t _makepath_s(char *pathResult, size_t sizelnWords, const char *drive, const char *dir, const char *filename, const char *ext); #include <stdlib.h> /* или #include <wchar.h> */ errno_t _wmakepath_s(wchar_t *pathResult, size_t sizelnWords, const wchar t *drive, const wchar_t *dir, const wchar t *filename, const wchar_t *ext); В первом параметре передается указатель на буфер, в который будет записан аб­ солютный путь, а во втором параметре указывается максимальный размер буф�­ ра. В параметре drive задается имя диска, в параметре dir- путь, в параметре filename- имя · файла, а в параметре ext - расширение файла. Некоторые из этих четырех параметров можно не указывать, в этом случае следует передать нулевой указатель или пустую строку. Функции возвращают значение о, если ошибок не было, и код ошибки при неудачном выполнении. Пример: setlocale(LC_ALL, "Russian_Russia.1251"); errno_t err = О; char full_path[_МAX_PATH] = {О}; err = _makepath_s(full_path, МАХ_РАТН, "С", "\\book\\", "test", "txt"); if(err== О){ printf("%s\n", full_path); // C:\Ьook\test.txt Вместо функций _makepath_s() и _wmakepath_s() можно воспользоваться функ­ циями _makepath() и _wmakepath(), но они не проверяют размер буфера. Прото­ типы функций: #include <stdlib.h> void _makepath(char *pathResult, const char *drive, const char *dir, const char *filename, const char *ext); #include <stdlib.h> /* или #include <wchar.h> */ void _wmakepath(wchar�t *pathResult, const wchar_t *drive, const wchar t *dir, const wchar t *filename, const wchar t *ext); Пример: setlocale(LC_ALL, "Russian_Russia.1251"); wchar_t full_path[_МAX _PATH] = {О}; _wmakepath(full_path, L"C", L"\\book\\", L"test", L"txt"); wprintf(L"%s\n", full_path); // c::-..Ьook\test.txt
390 14.2. Переименование, перемещение и удаление файла Глава 14 Для переименования, перемещения и удаления файла предназначены следующие функции. □ renarne() и _wrenarne() - переименовывают файл или каталог, а таюке позволяюr переместить файл в другой каталог. Прототипы функций: #include <stdio.h> /* или #include <io.h> */ int renarne(const char *oldFilenarne, const char *newFilenarne); #include <io.h> /* или #include <wchar.h> */ int _wrenarne(const wchar_t *oldFilenarne, const wchar_t *newFilenarne); В первом параметре задается старое название файла, а во втором параметре - новое название. Перед названием файлов можно указать путь. Если новый пуп. не совпадает со старым, то файл будет перемещен. Каталоги можно только переименовать, перемещать каталоги нельзя. Если операция успешно произве­ дена, то функции возвращают значение о. В противном случае функции возвра­ щают ненулевое значение и переменной errno присваивается код ошибки. При­ мер переименования и перемещения файла (каталог C:\book\folder1 должен суще­ ствовать): setlocale(LC_ALL, "Russian_Russia.1251"); int r = renarne("C:\\book\\filel.txt", "C:\\book\\fileЗ.txt"); if(r!=О)1 printf("%s\n", "Не удалось переименовать файл"); else { printf("%s\n", "Файл успешно переименован"); r = _wrenarne(L"C:\\book\\file2.txt", L"C:\\Ьook\\folderl\\file2.txt"); if(r== О)1 printf("%s\n", "Файл успешно перемещен в каталог folderl"); □ remove(), _unlink(), _wremove() и_wunlink() - удаляют файл, название которого передано в параметре. Если операция успешно произведена, то функции воз­ вращают значение о. В противном случае функции возвращают значение и переменной errno присваивается код ошибки. Прототипы функций: #include <stdio.h> /* или #include <io.h> */ int remove(const char *filename); int _unlink(const char *filename); #include <stdio.h> /* или #include <wchar.h> */ int _wremove(const wchar_t *filename); #include <io.h> /* или #include <wchar.h> */ int _wunlink(const wchar t *filename);
Работа с файловой системой Пример удаления файла: setlocale(LC_ALL, "Russian_Russia.1251"); if (remove("C:\\Ьook\\folderl\\file2.txt") != О) ( printf("%s\n", "Не удалось удалить файл"); else ( printf("%s\n", "Файл удален"); 14.3. Проверка прав доступа к файлу и каталогу 391 Для проверки существования файла и каталога, а также возможности чтения и записи файла предназначены функции _access() и_waccess( J. Прототипы функций: #include <io.h> int _access(const char *filename, int accessMode); #include <io.h> /* или #include <wchar.h> */ int _waccess(const wchar_t *filename, int accessMode); В первом параметре указывается путь к файлу или каталогу, а во втором параметре задается одно из следующих значений: □ о - проверка существования файла или каталога; □ 2 - проверка возможности записи в файл; □ 4 - проверка возможности чтения файла; □ 6 - проверка возможности чтения и записи файла. В MinGW вместо чисел можно указать следующие макросы: #include <io.h> #define F ок О #define W ОК 2 #define R ОК 4 В случае успешности проверки функции возвращают значение о. В противном слу­ чае функции возвращают значение -1 и переменной errno присваивается код ошиб­ ки. Пример проверки доступа к файлу приведен в листинге 14.1 . #include <stdio.h> #include <io.h> #include <locale.h> int main(void) { setlocale(LC_ALL, "Russian_Russia•.1251");
392 char path[] = "C:\\book\\file.txt"; if _access(path, О) != -1) { printf("%s\n", "Файл существует"); if (_access(path, 2) != -1) { printf("%s\n", "Файл доступен для записи"); else perror(""); if ( _access(path, printf("%s\n", else perror(""); if (_access(path, printf("%s\n", else perror(""); else perror(""); return О; 4)!= -1){ "Файл доступен для чтения"); 6)!= -1){ "Файл доступен для чтения и записи"); Глава М Вместо функций access () и _waccess() можно воспользоваться функция,­ _ ассеss_s() и _waccess_s() . Прототипы функций: #include <io.h> errno t access_s(const char *filenarne, int accessMode); #include <io.h> /* или #include <wchar.h> */ errno_t _waccess_s(const wchar_t *filenarne, int accessMode); В случае успешности проверки функции возвращают значение о, в противном с� чае - код ошибки. Пример проверки: setlocale(LC_ALL, "Russian_Russia.1251"); wchar_t path[] = L"C:\\book\\file.txt"; if (_waccess_s(path, О) == О) { printf("%s\n", "Файл существует"); if (_waccess_s(path, 2) == О) { printf("%s\n", "Файл доступен для записи"); 0БРАТИТЕ ВНИМАНИЕ Успешная проверка на чтение или запись еще не гарантирует, что можно проч� или записать файл. Например, файл может быть заблокирован другим процессом.
Работа с файловой системой 393 14.4. Изменение прав доступа к файлу Для изменения прав дос,упа к файлу предназначены функции _chmod( J и _wchmod( J • Прототипы функций: #include <io.h> int _chmod(const char *filenarne, int mode); #include <io.h> /* или #include <wchar.h> */ int _wchmod(const wchar_t *filenarne, int mode); В первом параметре указывается путь к файлу, а во втором параметре задается режим разрешений с помощью следующих флагов (или их комбинация через опе­ ратор 1): #include <sys/stat.h> □ _s _IREAD - разрешено только чтение. Определение макроса: #define S IREAD Ох0100 □ _s_IWRITE- разрешена запись. В Windows флаг одновременно разрешает и чте­ ние, и запись, т. е . флаг эквивалентен комбинации флагов _s_IREAD I s IWRITE. Определение макроса: #define S IWRITE Ох0080 В случае успешности установки прав дос,упа функции возвращают значение о. В противном случае функции возвращают значение -1 и глобальной переменной errno присваивается код ошибки. Пример установки прав дос,упа только для чте­ ния приведен в листинге 14.2. #include <stdio.h> #include <locale.h> #include <io.h> #include <sys/stat.h> int main(void) { setlocale(LC_ALL, "Russian_Russia.1251"); char path [] = "С:\\Ьook\\fileЗ.txt"; if (_chmod(path, _S_IREAD) != -1) { printf("%s\n", "ОК"); else perror("Error"); return О;
394 Глава 1◄ 14.5. Делаем файл скрытым Для того чтобы в Windows сделать файл скрытым, нужно воспользоваться макро, сами GetFileAttributes() и SetFileAttributes() из WinAPI. В зависимости от уста­ новки режима UNICODE макросы вызывают следующие функции: #include <windows.h> DWORD GetFileAttributesA(LPCSTR lpFileName); DWORD GetFileAttributesW(LPCWSTR lpFileName); WINBOOL SetFileAttributesA(LPCSTR lpFileName, DWORD dwFileAttributes); WINBOOL SetFileAttributesW(LPCWSTR lpFileName, DWORD dwFileAttributes); Две первые функции возвращают атрибуты файла lpFileName или значение (DWORD)(-1) - в случае ошибки. Две последние функции устанавливают атрибутw dwFileAttributes для файла lpFileName. Для того чтобы сделать файл скрытым. вначале нужно получить атрибуты файла, а затем установить флаг FILE_ATTRIB�_ HIDDEN (листинг 14.3). 1 Листинг 14.3 . Делаем файл скрытым #include <stdio.h> #include <locale.h> #include <io.h> #include <windows.h> int main(void) { setlocale(LC_ALL, "Russian_Russia.1251"); char path[] = "C:\\book\\fileЗ.txt"; if (_access(path, О) == -1) { рuts("Файл не существует"); return 1; DWORD attr = GetFileAttributes(path); if (attr != (DWORD)(-1)) { if (attr & FILE_ATTRIBUTE_HIDDEN) рuts("Файл уже скрьrгый"); // Сбросить флаг можно так //SetFileAttributes(path, attr л FILE_ATTRIBUTE_HIDDEN); else if (SetFileAttributes(path, attr I FILE_ATTRIBUTE_HIDDEN)) printf("%s\n", "ОК"); else puts("Error"); 1
Работа с файловой системой else puts("Error"); return О; 14.6. Получение информации о файле 395 Получить размер файла и время создания, изменения и доступа к файлу, а таюке значения других метаданных позволяют функции _stat(), _stat32(), _stat64(), 0 _stati64(), _ stat32i64(), stat64i32(), _wstat(), _ wstat32(), _ wstat64(), _wstati64(), _wstat32i64() И_wstat64i32(). По умолчанию в проекте тest64c функция _stat() эквивалентна функции _stat64i32(). Число 64 в этой функции означает количество битов, выделяемых под дату, а число з2 - количество битов, выделяемых под размер файла. Прототип функции _stat64i32(): #include <sys/types.h> #include <sys/stat.h> #define stat stat64i32 int _stat64i32(const char *narne, struct stat64i32 *stat); В первом параметре указывается путь к файлу. Результат записывается в структуру stat. Функция возвращает значение о, если ошибок не было, и значение -1 в случае ошибки. Код ошибки сохраняется в переменной errno. По умолчанию в проекте тest64c функция _wstat() эквивалентна ·фу�кции �wstat64i32(). Прототип функции: #include <sys/types.h> #include <sys/stat.h> /* или #include <wchar.h> */ #define wstat wstat64i32 int _wstat64i32(const wchar_t *narne, struct stat64i32 *stat); Структура _stat64i32 объявлена следующим образом: struct stat64i32 dev t st dev; // Номер диска ino t st ino; - - unsigned short st mode; // Маска прав доступа short st nlink; - short st uid; short st_gid; dev t st rdev; off t st size; // Размер в байтах time64 t st atime; // Дата последнего доступа - time64 t st mtime; // Дата изменения time64 t st ctime; // Дата создания }; Поле st_rnode содержит маску типа устройства и прав доступа. Возможна комбина­ ция следующих флагов:
396 Глава 14 #include <sys/stat.h> #define s пмт OxFOOO /* Маска для типа файла */ #define S IFDIR Ох4000 /* Каталог */ #define S IFCHR Ох2000 /* Символьное устройство */ #define S IFIFO OxlOOO /* Какал */ #define S IFREG ОхВООО /* Обычный файл */ #define S IREAD Ох0100 /* Разрешение на чтение */ #define S IWRITE ОхООВО /* Разрешение на запись */ #define S IEXEC Ох0040 /* Разрешение на вьmолнение/поиск */ При использовании функций _stat64 о и _wstat64 о 64 бита выделяется и под даl)·. и под размер файла. Прототипы функций: #include <sys/types.h> #include <sys/stat.h> int stat64(const char *name, struct _stat64 *stat); #include <sys/stat.h> /* или #include <wchar.h> */ int _wstat64(const wchar_t *name, struct stat64 *stat); Объявление структуры _stat64: struct _stat64 { }; _dev_t st_dev; _ino_t st_ino; unsigned short st_rnode; short st nlink; short st_uid; short st_gid; _ dev_t st_rdev; _ int64 st_size; tirne64 t st_atirne; tirne64 t st_rntirne; tirne64 t st_ctirne; Пример использования функции _stat о приведен в листинге 14.4. Лмстинr :14.4; Пример мсnоnwованмя функции etat О - . - #include <stdio.h> #include <locale.h> #include <tirne.h> #include <sys/types.h> #include <sys/stat.h> void print_tirne( tirne64 t t); int rnain(void) { setlocale(LC_ALL, "Russian_Russia.1251");
Работа с файловой системой struct _stat finfo; unsigned int ch = О; int err = _stat("C:\\book\\file.txt", &finfo); if(err==О)( printf("%ld\n", finfo.st_size); ch = finfo.st_dev + 'А'; printf("%c\n", · (char)ch); print_time(finfo.st_ctirne); print_tirne(finfo.st_atirne); print_time(finfo.st_rntirne); else perror("Error"); return О; void print_time( tirne64 t t) ( struct trn *ptrn = NULL; ptrn = _localtime64(&t); if (ptrn) ( char str[l00] = {О}; // Размер файла !/ Имя диска // Создан ие файла // Последний доступ // Изменение файла size_t count = strftirne(str, 100, "%d.%m.%Y %H:%M:%S", ptrn); if (count) ( printf("%s\n", str); 397 Получить информацию об уже открытом файле позволяют функции _fstat(), _fstat32(), _fstat64(), _fstati64(), _fstat64i32() и _fstat32i64(). По умолчанию в проекте Test64c функция _fstat() эквивалентна функции _fstat64i32(). Прото­ тип функции_fstat64i32(): #include <sys/types.h> #include <sys/stat.h> #define fstat fstat64i32 int _fstat64i32(int fileHandle, struct _stat64i32 *stat); В первом параметре указывается дескриптор открытого файла. Результат записы­ вается в структуру stat. Функция возвращает значение о, если ошибок не было, и значение -1 в случае ошибки. Код ошибки сохраняется в переменной errno. Определить размер открытого файла позволяют функции _filelength() и _ filelengthi64(). Прототипы функций: #include <io.h> long _filelength(int fileHandle); int64 filelengthi64(int fileHandle); В качестве параметра указывается дескриптор открытого файла. Пример: printf("%ld\n", _filelength(fd)); printf("%I64d\n", filelengthi64(fd));
398 Глава 14 Обновить время последнего досrупа и время изменения файла позволяют фунКЦИ11 _utime(), _utime32(), _utime64 (), _wutime(), _wutime32() И _wutime64 (). Прототип функции _utime(): #include <sys/utime.h> int _utime(const char *filename, struct _utimЬuf *utimЬuf); В первом параметре функция принимает путь к файлу. Во втором параметре пере­ дается адрес струкrуры _utimЬuf. Объявление струкrуры выглядит так: struct _utimЬuf { time t actime; /* Дата последнего доступа */ /* Дата изменения */ time t modtime; }; Если во втором параметре передать нулевой указатель, то дата будет текущей. Функция возвращает значение о, если ошибок не было, и значение -1 в противном случае. Код ошибки сохраняется в переменной errno. Пример использования функции _utime() приведен в листинге 14.5 . Листинг 14.5. Пример использования функции _uti.me О #include <stdio.h> #include <locale.h> #include <time .h> #include <sys/utime.h> int main(void) { setlocale(LC_ALL, "Russian_Russia.1251"); struct _utimЬuf new_time; new_time.actime = time(NULL) - 3600; new_time.modtime = time(NULL) - 7200; int err = _utime("C:\\book\\file.txt", &new_time); if(err== О){ printf("%s\n", "ОК"); else perror("Error"); // Текущее время err = _utime("C:\\book\\fileЗ.txt", NULL); if(err==О){ printf("%s\n", "ОК"); else perror("Error"); return О; Обновить время последнего досrупа для открытого файла позволяют функции futime(), _futime32() и_futime64 (). Прототип функции_futime():
Работа с файловой системой 399 iinclude <sys/uti.me.h> �nt _futi.me(int fileHandle, struct _utirnЬuf *utirnЬuf); В первом параметре указывается дескриптор открытого файла. Во втором парамет­ ре передается адрес структуры utirnЬuf. Функция возвращает значение о, если оши­ бок не бьmо, и значение -1 в случае ошибки. Код ошибки сохраняется в перемен­ ной errno. 14.7. Функции для работы с дисками Для работы с дисками используются следующие функции. □ _getdrives() - возвращает информацию о доступных дисках в виде набора битов. Прототип функции: iinclude <direct.h> unsigned long _getdrives(void); Пример: unsigned long mask = _getdrives(); int ch = 'А'; while (mask) { if (mask & 1) printf{"%c\n", (char)ch); ++ch; mask >>= 1; □ _getdrive () - возвращает номер текущего диска (1- диск А, 2 - диск в и т. д.). Прототип функции: iinclude <direct.h> int _getdrive{void); Пример: int drive = _getdrive{); intch=drive-1+'А'; printf{"%c\n", (char)ch); // С □ _chdrive <) - делает указанный диск текущим. Прототип функции: iinclude <direct.h> int _chdrive{int drive); В параметре drive указывается номер диска (1- диск А, 2 - диск в и т. д.) . Функция возвращает значение о, если ошибок нет, и значение -1 в противном случае. Код ошибки сохраняется_ в переменной errno. Пример: if (_chdrive{4) == О) { printf{"%d\n", _getdr . ive{)); // 4
400 Глава Н Для того чтобы получить информацию о типе диска в Windows, можно воспользо­ ваться макросом GetDriveтype() из WinAPI, который в зависимости от определеНИI макроса UNICODE вызывает одну из следующих функций: #include <windows.h> UINT GetDriveTypeA(LPCSTR lpRootPathName); UINT GetDriveTypeW(LPCWSTR lpRootPathName); Пример: UINT t = GetDriveType("C:\\"); if (t == DRIVE_REМOVAВLE) puts("DRIVE_REMOVAВLE"); else if (t DRIVE_FIXED) puts("DRIVE_FIXED"); else if (t DRIVE_REMOТE) puts("DRIVE_REMOТE"); else if (t DRIVE_CDROO) puts("DRIVE_CDROO"); else if (t DRIVE_RAМDISК) puts("DRIVE_RAМDISK"); else puts("He удалось определить тип диска"); Для того чтобы получить информацию о размерах диска в Windows, можно вос­ пользоваться макросом GetDiskFreeSpaceEx() из WinAPI, который в зависимости от определения макроса UNICODE вызывает одну из следующих функций: #include <windows.h> BOOL GetDiskFreeSpaceExA(LPCSTR lpDirectoryName, PULARGE INTEGER lpFreeBytesAvailaЬleToCaller, PULARGE_INTEGER lpTotalNumЬerOfBytes, PULARGE_INTEGER lpTotalNumЬerOfFreeBytes); BOOL GetDiskFreeSpaceExW(LPCWSTR lpDirectoryName, PULARGE INTEGER lpFreeBytesAvailaЬleToCaller, PULARGE INTEGER lpTotalNumЬerOfBytes, PULARGE INTEGER lpTotalNumЬerOfFreeBytes); Пример: unsigned unsigned int64 free_bytes; int64 total_bytes; unsigned int64 total_free_bytes; BOOL st = GetDiskFreeSpaceExA("C:\\", (PULARGE_INTEGER) &free_bytes, (PULARGE_INTEGER) &total_bytes, (PULARGE_INTEGER) &total_free_bytes); if (st) { printf("free_bytes = %I64u\n", free_bytes); printf("total_bytes = %I64u\n", total_bytes); printf("total_free_bytes = %I64u\n", total_free_bytes); else puts("He удалось определить размеры диска");
Работа с файловой системой 401 14.8. Функции для работы с каталогами Для работы с каталогами используются следующие функции. □ _getcwd() и _wgetcwd() - позволяют получить строковое представление текуще­ го рабочего каталога. Or этого значения зависит преобразование относительного пути в абсолютный. Кроме того, важно помнить, что текущим рабочим катало­ гом будет каталог, из которого запускается файл, а не каталог с исполняемым файлом. Прототипы функций: #include <direct.h> char *_getcwd(char *buf, int sizelnBytes); #include <wchar.h> /* или #include <direct.h> */ wchar_t *_wgetcwd(wchar_t *buf, int sizeinWords); В первом параметре функции принимают указатель на буфер, а во втором пара­ метре - максимальный размер буфера. В качестве максимального размера буфера можно указать макрос _МАХ_РАТН. В случае ошибки функци11 возвращают нулевой указатель. Пример: setlocale(LC_ALL, "Russian_Russia.1251"); char buf[_МAX_PATH] = {О}, *р = NULL; р = _getcwd(buf,, _МАХ_РАТН); if(р)( printf("%s\n' . ', buf); // С:\cpp\projects\Test64c Если в качестве параметров заданы нулевой указатель и нулевое значение, то строка создается динамически с помощью функции rnalloc(). В этом случае функции возвращают указатель на эту строку или нулевой указатель в случае ошибки. После окончания работы со строкой следует освободить память с по­ мощью функции free (). Пример: setlocale(LC_ALL, "Russian_Russia.1251"); char *pbuf = NULL; pbuf = _getcwd(NULL, О); if (pbuf) ·1 printf("%s\n", pbuf); // C:\cpp\projects\Test64c free(pbuf); pbuf = NULL; □ _getdcwd {) и _wgetdcwd() - функции аналогичны функциям getcwd() и _ wgetcwd(), но позволяют получить строковое представление текущего рабочего каталога не для текущего диска, а для указанного в первом параметре. Прото­ типы функций: #include <direct.h> char *_getdcwd(int drive, char *buf, int sizelnВytes); #include <direct.h> /* или #include <wchar.h> */ wchar t *_wgetdcwd(int drive, wchar t *buf, int sizeinWords);
402 Глава 14 В первом параметре указывается номер диска ( о - диск по умолчанию, 1 - диск д, 2 - диск ви т. д.). Пример: setlocale(LC_ALL, "Russian_Russia.1251"); char buf[_МAX _PATH] = {О}, *р = NULL; р = _getdcwd(З, buf, _МАХ_РАТН}; if(р){ printf("%s\n", buf); // C:\cpp\projects\Test64c □ chdir(I и _wchdir() - делают указанный каталог текущим. Функции возвра­ щают значение о, если ошибок не было, и значение -1 в противном случае. Коз ошибки сохраняется в переменной errno. Прототипы функций: #include <direct.h> int _chdir(const char *path); #include <direct .h> /* или #include <wchar.h> */ int _wchdir(const wchar t *path); Пример: setlocale(LC_ALL, "Russian_Russia.1251"); char buf[_МAX _PATH] = {О}; if (_chdir("C:\\book\\") == О) { _ getcwd(buf, _МАХ_РАТН); printf("%s\n", buf); // C:\book □ _mkdir 1) и _wmkdir 1) - создают новый каталог. Функции возвращают значе­ ние о, если каталог создан, и значение -1 в противном случае. Код ошибки сохраняется в переменной errno. Прототипы функций: #include <direct.h> int _mkdir(const char *path); #include <direct.h> /* или #include <wchar.h> */ int _wmkdir(const wchar t *path); Пример: if (_mkdir("C:\\book\\folderA") О){ printf("%s\n", "ОК"); if (_wmkdir(L"C:\\book\\folderB") О){ printf("%s\n", "ОК"); □ rmdir 1) и _wrmdir 1) - удаляют каталог. Обратите внимание: удалить можно только пустой каталог. Функции возвращают значение о, если каталог удален, и значение -1 в противном случае. Код ошибки сохраняется в переменной errno. Прототипы функций: #include <direct.h> int _rmdir(const char *path);
Работа с файловой системой #include <direct.h> /* или #include <wchar.h> */ int _wrrndir(const wchar_t *path); Пример удаления пустых каталогов: if ( rrndir ("С:\ \book\\folderA") == О) printf("%s\n", "ОК"); if (_wrrndir(L"C:\\book\\folderB") О){ printf ("%s\n", "ОК"); 14.9. Перебор объектов, расположенных в каталоге 403 Перебрать все (или только некоторые) объекты в указанном каталоге позволяют функции _findfirst(), _findnext() и _findclose(). Поиск осуществляется следую­ щим образом: 1. Вызывается функция _findfirst(). Функция возвращает дескриптор, с помощью которого производится дальнейший поиск. 2. В цикле вызывается функция _findnext(). Этой функции необходимо передать дескриптор, возвращаемый функцией _findfirst(). Если объектов больше нет, то функция возвращает значение -1 . 3. С помощью функции _findclose() завершается поиск. По умолчанию в проекте тest64c функция _findfirst() соответствует функции _ findfirst64i32(), а функция _findnext() - функции _findnext64i32(). Число 64 в этих функциях означает количество битов, выделяемых под дату, а число 32 - количество битов, выделяемых под размер файла. Прототипы функций: #include <io.h> #define findfirst findfirst64i32 intptr_t _findfirst64i32(const char *filenarne, struct finddata64i32 t *findData); #define findnext findnext64i32 int findnext64i32(intptr_t findНandle, struct finddata64i32 t *findData); int findclose(intptr_t findНandle); В первом параметре функции _findfirst64i32 () указываются путь к каталогу и маска поиска. Если путь не указан, то поиск производится в текущем рабочем каталоге. В маске можно использовать следующие специальные символы: □ ? - любой одиночный символ; □ * - любое количество символов.
404 Глава 1-t Рассмотрим несколько примеров масок: □ * - все файлы и подкаталоги; □ *. txt - все файлы с расширением txt; □ f???. t?? - файлы с именами из четырех букв, названия которых начинаюте11 с буквы f, имеющие расширение из трех букв, начинающееся с буквы t; □ f* - файлы и подкаталоги, названия которых начинаются с буквы f. В параметре findData передается адрес структуры _finddata64i32_t, в которую будут записаны атрибуты найденного объекта. Структура объявлена так: struct finddata64i32 t - unsigned attrib; // Флаги time64 t time create; // Дата создания - time64 t time access; // Дата последнего доступа - time64 t time write; // Дата изменения - fsize {: size; // Размер файла char name[260]; // Названи� объекта }; Поле attrib может содержать комбинацию следующих флагов: #define А NORМAL ОхОО /* Обычный файл */ #define А RDONLY 0x0l /* Файл только для чтения */ #define А HIDDEN Ох02 /* Скрытый файл */ #define А SYSTEM Ох04 /* Системньм файл */ #define А SUBDIR 0xl0 /* Подкаталог*/ #define А ARCH Ох20 /* Архивньм файл */ Если объект, соответствующий параметру filename, найден, то функци.1 _findfirst64i32 () записывает атрибуты объекта в структуру _finddata64i32_t и возвращает дескриптор. Если поиск окончился неудачей, то функция возвращает значение -1 и присваивает глобальной переменной errno код ошибки. Поиск остальных объектов производится в цикле с помощью функции findnext64i32 (). В первом параметре функция принимает дескриптор, который был получен с помощью функции _findfirst64i32 (), а во втором - адрес струкrу­ ры _finddata64i32_t . Если объект найден, то его атрибуты записываются в струкrу­ ру и возвращается значение о. Если поиск окончился неудачей, то функция возвра­ щает значение -1 и присваивает глобальной переменной errno код ошибки. Завершение поиска осуществляется с помощью функции _findclose (), которая в качестве параметра принимает дескриптор. Если ошибок не произошло, то функ­ ция возвращает значение о. В противном случае функция возвращает значение -: и присваивает переменной errno код ошибки. Пример поиска всех файлов и подкаталогов показан в листинге 14.6.
Работа с файловой системой *include <stdio.h> Hnclude <io.h> *include <locale.h> int rnain(void) { setlocale(LC_ALL, "Russian_Russia.1251"); struct _finddata_t info; intptr_t h = _findfirst("C:\\book\\*", &in · fo); if (h == -1) perror("Error"); else { do{ printf("%-30s", info.name); if (info.attrib & _A_SUBDIR) pr:intf("%s", "_ A_SUBDIR"); if (info.attrib & _A_RDONLY) printf("%s", "_ A_RDONLY"); if (info.attrib & _A_HIDDEN) printf("%s", " _A_HIDDEN"); if (info.attrib & _A _SYSTEM) printf("%s", " _A_SYSTEМ"); if (info.attrib & _A_ARCH) printf("%s", "_ A_ARCH"); printf("\n"); while(_findnext(h, &info) == О); _ findclose(h); return О; Примерный результат выполнения: А SUBDIR А SUBDIR file.txt А ARCH fileЗ.txt А RDONLY folderl А SUBDIR helloworld.c А ARCH helloworld.exe А ARCH script.bat А ARCH test.c А ARCH test.exe А ARCH А HIDDEN 405 А ARCH Обратите внимание на первые две строки. Одна точка обозначает текущий каталог. Две точки обозначают каталог выше уровнем, например, если поиск осуществляет­ ся в каталоге С:\Ьооk, то две точки указывают на корень диска С:. Если поиск произ­ водится в корневом каталоге диска, то этих двух строк не будет.
406 Глава 1-4 Помимо рассмотренных функций для поиска можно воспользоваться следующи1,0 функциями:. #include <io.h> intptr_t _findfirst32(const char *filename, struct finddata32_t *findData); int findnext32(intptr_t finc!Нandle, struct finddata32_t *findData); intptr_t _ findfirst32i64(const char *filename, struct finddata32i64_t *findData); int findnext32i64(intptr_t finc!Нandle, struct finddata32i64_t *findData); intptr_t findfirst64(const char *filename, struct finddata64 t *findData); int _findnext64(intptr_t finc!Нandle, struct finddata64 t *findData); #include <io.h> /* или #include <wchar.h> */ #define wfindfirst wfindfirst64i32 #define wfindnext wfindnext64i32 intptr_t _wfindfirst64i32(const wchar_t *filename, struct wfinddata64i32 t *findData); int _wfindnext64i32(intptr_t finc!Нandle, struct _wfinddata64i32_t *findData); intptr_t _wfindfirst32i64(const wchar_t *filename, struct _wfinddata32i64_t *findData); int _wfindnext32i64(intptr_t findHandle, struct _wfinddata32i64_t *findData); intptr_t _wfindfirst32(const wchar_t *filename, struct _wfinddata32_t *findData); int _wfindnext32(intptr_t finc!Нandle, struct _wfinddata32_t *findData); intptr_t _wfindfirst64(const wchar_t *filename, struct _wfinddata64_t *findData); int _wfindnext64(intptr_t finc!Нandle, struct _wfinddata64_t *findData); Струк,ура _wfinddata64i32_t объявлена так: struct _wfinddata64i32_t { 1; unsigned attrib; tiroe64 t time_create; _ time64_t time_access; _ tiroe64_t time_write; _ fsize_t size; wchar t name[260];
ГЛАВА 15 Потоки. и процессы В предыдущих главах мы познакомились с потоками ввода/вывода. В этой главе мы рассмотрим еще один вид потоков - потоки управления.. Благодаря потокам управления можно выполнять различные задачи параллельно. Если процессор является одноядерным, то будет происходить переключение между потоками, ими­ тируя параллельное выполнение. Когда следует использовать потоки управления? Во-первых, при выполнении дли­ тельной операции. Например, получение данных из Интернета может блокировать основной поток, если сервер не отвечает. Во-вторых, когда необходимо ускорить выполнение операции. В этом случае операция разбивается на отдельные задачи, и эти задачи раздаются потокам. В-третьих, при использовании оконных приложе­ ний - операция не должна выполняться в потоке диспетчера обработки событий, иначе приложение не сможет выполнить перерисовку компонентов и перестанет реагировать на действия пользователя. Пот _ оки выполняются в рамках процесса и имеют общий доступ к его ресурсам, на­ пример к глобальным переменным. Процесс содержит минимум один поток, кото­ рый создается при запуске приложения. В этом основном потоке выполняются ин­ струкции, расположенные внутри тела функции main ( >. Помимо запуска задачи в отдельном потоке можно запустить задачу в отдельном процессе, который будет выполняться параллельно или заменит текущий процесс. 15.1. Потоки в WinAPI В Windows нам доступны три способа создания потоков управления: □ с помощью функций из WinAPI; □ с помощью функций, объявленных в заголовочном файле process.h; □ с помощью функций, объявленных в заголовочном файле pthread.h. 15.1.1. Создание и завершение потока Для создания потока управления в WinAPI предназначена функция createThread ( J • Прототип функции:
408 #include <windows.h> НANDLE CreateThread(LPSECURITY ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD START ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadld); typedef void *НANDLE; typedef void *LPVOID; typedef unsigned _LONG32 DWORD; #define _LONG32 long typedef DWORD *LPDWORD; Рассмотрим параметры функции CreateThread(): Глава 1! □ lpThreadAttributes - указатель на структуру -SECURITY-ATTRIBUTES или значе­ ние NULL, если дескриптор не может быть унаследован. Объявление структуры: typedef struct SECURITY ATTRIBUTES DWORD nLength; LPVOID lpSecurityDescriptor; WINBOOL ЫnheritHandle; SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY ATTRIBUTES; □ ctwstackSize - размер стека или значение о, для размера по умолчанию; □ lpStartAddress - указатель на пользовательскую функцию, инструкции внуrри которой будут выполнены в отдельном потоке: typedef DWORD (WINAPI *PТHREAD_START_ROUTINE) LPVOID lpThreadParameter); typedef PTHREAD START ROUTINE LPTHREAD_START_ROUTINE; Пример функции: DWORD WINAPI thread func(LPVOID param) { // Инструкции, вьmолняемые в отдельном потоке return О; □ lpParameter - задает данные, указатель на которые будет доступен через пара­ метр param функции thread func() . Если данные не передаются, то указываем значение NULL; □ ctwcreationFlags - может принимать следующие основные значения (полный список см. в документации): о - поток запускается сразу после создания и CREATE_SUSPENDED - после создания поток приостанавливается до вызова функ­ ции ResumeThread(): #define CREATE SUSPENDED Ох4 DWORD ResumeThread(НANDLE hThread);
Потоки и процессы 409 □ lpThreadid - указывается адрес переменной, в которой будет сохранен иденти- фикатор созданного потока, или значение NULL. Если поток успешно создан, то функция createThread() возвращает дескриптор созданного потока, а при ошибке - значение NULL. Получить информацию об ошибке можно с помощью функции GetLastError(). Прототип функции: #include <windows.h> DWORD GetLastError(VOID); Поток завершает работу в следующих случаях. □ Функция thread_func() завершила свою работу (например, поток управления достиг оператора return). С помощью оператора return указывается статус за­ вершения: значение о при успешном завершении и любое другое - при ошибке. Получить статус завершения потока позволяет функция GetExitCodeThread() . Прототип функции: #include <windows.h> WINBOOL GetExitCodeThread(НANDLE hThread, LPDWORD lpExitCode); □ Внутри функции thread_func() вызывается функция ExitThread(). В параметре указывается статус завершения. Прототип функции: #include <windows.h> VOID ExitThread(DWORD dwExitCode); □ В другом потоке вызвана функция тerminateтhread(). В первом параметре функции указывается дескриптор потока, а во втором - статус завершения. Прототип функции: #include <windows.h> • WINBOOL TerminateThread(НANDLE hThread, DWORD dwExitCode); □ Завершается процесс, например, поток управления достиг конца функции main(). После запуска потоков мы должны дождаться завершения работы потоков в основ­ ном потоке, иначе они прекратят работу на любой стадии выполнения при завер­ шении процесса. Для этого предназначены следующие функции. □ WaitForSingleObject() - ожидает до тех пор, пока поток с дескриптором hHandle не будет завершен или не истечет время ожидания dwMilliseconds. Если время ожидания не ограничено, то в параметре dwMilliseconds задается значение макроса INFINITE. Прототип функции: #include <windows.h> DWORD WaitForSingleObject(НANDLE hHandle, DWORD dwMilliseconds); #define INFINITE 0xffffffff □ WaitForMultipleObjects() - ожидает до тех пор, пока потоки из массива lpHandles не будут завершены или не истечет врем11 ожидания dwМilliseconds. Если время ожидания не ограничено, то в параметре dwМilliseconds задается значение макроса INFINITE. Количество дескрипторов потоков в массиве
410 Глава 15 lpHandles указывается в параметре ncount. Если в параметре ьwaitAll задано значение TRUE, то функция будет ожидать завершения всех потоков, а если FALSE - то одного. Прототип функции: #include <windows.h> DWORD WaitForMultipleObjects(DWORD nCount, CONST НANDLE *lpHandles, WINBOOL bWaitAll, DWORD dwMilliseconds); После завершения работы потока нужно освободить ресурсы, вызвав функцию CloseHandle(). Прототип функции: #include <windows.h> WINBOOL CloseHandle(НANDLE hObject); При работе с потоками могут быть полезными следующие функции: □ GetcurrentThreadid() - возвращает идентификатор текущего потока. Прототип функции: #include <windows.h> DWORD GetCurrentThreadid(VOID); Пример: DWORD thread id = GetCurrentThreadid(); printf("%lu\n", thread id); □ GetcurrentThread() - возвращает дескриптор текущего потока. Прототип функ­ ции: #include <windows.h> НANDLE GetCurrentThread(VOID); □ Sleep() - прерывает выполнение текущего потока на указанное в параметре dwMilliseconds количество миллисекунд. Прототип функции: #include <windows.h> VOID Sleep(DWORD dwMilliseconds); Пример: Sleep(l000); // Засьmаем на секунду В качестве примера запустим три потока, внутри которых просто будем выводить данные в окно консоли (листинг 15.1). Программу следует запускать в командной строке, иначе в окне Console редактора Eclipse отобразятся сообщения только о создании потоков и завершении программы. 1 Листинг 15.1 . Создание потока #include <stdio.h> #include <locale.h> #include <windows.h> #include <strsafe.h>
Потоки и процессы #define NUМ TНREADS З #define MSG SIZE 256 DWORD WINAPI thread_func(LPVOID pararn) { // Инструкции, вьmолняемые в отдельном потоке DWORD thread_id = GetCurrentThreadid(); char msg[MSG_SIZE] = {О}; size_t msg_length; DWORD chars; // Получаем дескриптор потока вывода НANDLE hstdout = GetStdНandle(STD_OUTPUТ_НANDLE); if (hstdout = INVALID_НANDLE_VALUE) return 1; for(inti=О;i<10;++i){ Sleep(l000); // Имитация вьmолнения задачи // ВЫводим сообщение в консоль StriпgCchPrintfA(msg, MSG_SIZE, "%d %lu\n", i, thread_id); StringCchLengthA(msg, MSG_SIZE, &msg_length); WriteConsoleA(hstdout, msg, (DWORD)msg_length, &chars, NULL); StringCchPrintfA(msg, MSG_SIZE, "Exit: %lu\n", thread_id); StringCchLengthA(msg, MSG_SIZE, &msg_length); WriteConsoleA(hstdout, msg, (DWORD)msg_length, &chars, NULL); return О; int rnain(void) { НANDLE arr_threads[NUМ_TНREADS]; DWORD arr_threads_id[NUМ_TНREADS]; // Массив с дескрипторами // Массив с идентификаторами setlocale(LC_ALL, "Russian_Russia.1251"); DWORD main_id = GetCurrentThreadid(); printf("Bxoд поток main: %lu\n", main_id); // запускаем потоки for (int i = О; i < NUМ_TНREADS; ++i) { arr_threads[i) = CreateThread(NULL, О, thread_func, NULL, О, &arr_threads_id[i)); if ( !arr_threads[i]) { DWORD err = GetLastError(); printf("He удалось запустить поток. Error: %lu\n", err); return 1; else { printf("Coздaн поток: %lu\n", arr_threads_id(i]); fflush(stdout); 411
412 // Ожидаем завершения всех потоков WaitForMultipleObjects(NUМ_THREADS, arr_threads, TRUE, INFINITE); // Закрываем все дескрипторы потоков for (int i = О; i < NUM THREADS; ++i) CloseHandle(arr_threads[i]); printf("Выход поток main: %lu\n", main_id); return О; 0БРАТИТЕ ВНИМАНИЕ Глава 15 Многие функции из стандартной библиотеки языка С не являются потокобезопасныr.а. Поэтому внутри функции thread_func() мы пользуемся потокобезопасными ФУНl­ циями, объявленными в заголовочном файле strsafe.h, а также функциями из WinAPI.. При использовании внутри потоков функций из стандартной библиотеки языка С поведение программы может стать непредсказуемым, поэтому вместо фунКЦ181 CreateThread() лучше использовать функцию _beginthreadex() (см. разд. 15 .2). 15.1.2. Синхронизация потоков Если несколько потоков пытаются получить доступ к одному ресурсу, то результат этого действия может стать непредсказуемым. Для того чтобы не допустить про­ блем и избежать состояния гонок, необходимо выполнять синхронизацию критич­ ных секций (секций, к которым имеют доступ несколько потоков). При доступе к синхронизированной секции поток запрашивает блокировку. Если блокировка получена, то поток изменяет что-то внутри синхронизированного блока. Если не удалось получить блокировку, то поток блокируется до момента получения разре­ шения. Таким образом, код внутри критичной секции в один момент времени выполняется только одним потоком как атомарная операция. Например, если мы из разных потоков попробуем вызвать функцию print ; • содержащую следующий код: void print(DWORD id) { printf("l. %lu\n", id); printf("2. %lu\n", id); printf("З. %lu\n", id); fflush(stdout); то можем получить несколько проблем. Во-первых, мы используем функцию printf() из стандартной библиотеки языка С, что, как уже говорилось, может при­ вести к непредсказуемым результатам. Во-вторых, тело пользовательской функции print() состоит из четырех инструкций. При выполнении этой функции первый поток может быть прерван на любой из этих инструкций, второй поток получкr доступ, но опять его прервет третий поток, и т. д. В итоге никакой атомарности при
Потоки и процессы 413 выполнении функции print() не будет, и результат ее выполнения становится непредсказуемым. А если внутри этой функции мы меняем значения глобальных переменных, то данные будут испорчены. Если попробуем выполнить функцию print() из разных потоков, то получим вот такую "кашу" в окне консоли: 1. 11172 1. 4436 2. 4436 3. 4436 2. 11172 3. 11172 Как видно из результата, поток с идентификатором 111 . 72 был прерван после вы­ полнения первой инструкции внутри функции print(). Для синхронизации критичных секций можно воспользоваться мьютексами. Соз­ дать объект мьютекса позволяют функции createMutexA() и CreateMutexw(). Прото­ типы функщ,й: #include <windows.h> НANDLE CreateMutexA(LPSECURITY_ATTRIBUTES lpMutexAttributes, WINBOOL ЫnitialOwner, LPCSTR lpName); НANDLE CreateMutexW(LPSECURITY_ATTRIBUTES lpMutexAttributes, WINBOOL ЫnitialOwner, LPCWSTR lpName); Первый параметр принимает указатель на структуру _SECURITY_AТTRIBUTES или зна­ чение NULL, если дескриптор не может быть унаследован. Если во втором параметре указано значение TRUE, то текущий поток получит право владения мьютексом. В третьем параметре можно задать имя мьютекса или передать значение NULL. Функции возвращают дескриптор или значение NULL - в случае ошибки. Перед входом в критичную секцию нужно вызвать функцию WaitForSingleObject () и передать ей в первом параметре дескриптор мьютекса, а во втором - время ожи­ дания. Если время ожидания не ограничено, то в параметре dwMilliseconds задается значение макроса INFINITE. Прототип функции: #include <windows.h> DWORD WaitForSingleObject(НANDLE hНandle, DWORD dwMilliseconds); #define INFINITE 0xffffffff Получив блокировку, можно выполнять инструкции внутри критичной секции. По­ сле выхода из критичной секции нужно обязательно снять блокировку, вызвав функцию Releaseмutex(), иначе секция будет заблокирована навсегда. Прототип функции: #include <windows.h> WINBOOL ReleaseMutex(НANDLE hМutex); После завершения работы с мьютексом нужно освободить ресурсы, вызвав функ­ цию CloseHandle(). Прототип функции: #include <windows.h> WINBOOL CloseHandle(НANDLE hObject);
414 Давайте рассмотрим синхронизацию потоков на примере (листинг 15.2). Листинг 15:2. Синхронизация потоков (способ 1') #include <stdio.h> #include <locale.h> #include <windows.h> #define NUМ THREADS З НANDLE rnutex_print; void print(DWORD id) // Ожидаем получения блокировки DWORD status = WaitForSingleObject(rnutex_print, INFINITE); if (status == WAIT_OBJECT_O) { printf("l. %lu\n", id); printf("2. %lu\n", id}; printf("З. %lu\n", id); fflush(stdout); ReleaseMutex(rnutex_print); // Снимаем блокировку else { // Здесь обрабатываем 01Ш1бки DWORD WINAPI thread_func(LPVOID pararn) { DWORD thread id GetCurrentThreadid(); for(inti=О;i<10;++i){ Sleep(lOOO); // Имитация вьmолнения задачи print(thread_id); // Выводим сообщения в консоль return О; int rnain(void) НANDLE arr_threads[NUМ_TНREADS]; // Массив с дескрипторами rnutex_print = CreateMutexA(NULL, FALSE, NULL); if ( !rnutex_print) { puts("He удалось создать объект мьютекса"); return 1; setlocale(LC_ALL, "Russian_Russia.1251"); DWORD rnain_id = GetCurrentThreadid(); printf("Bxoд поток rnain: %lu\n", rnain_id); Глава 15
Потоки и процессы // Запускаем потоки for (iпt i = О; i < NUМ_TНREADS; ++i) { arr_threads[i] = CreateThread(NULL, О, thread_fuпc, NULL, О, NULL); if (!arr_threads(i]) returп 1; // Ожидаем завершения всех потоков WaitForMultipleObjects(NUМ_TНREADS, arr_threads, TRUE, INFINITE); // Закрываем все дескрипторы for (iпt i = О; i < NUМ_TНREADS; ++i) { CloseHaпdle(arr_threads(i]); CloseHaпdle(mutex_priпt); printf("Bыxoд поток main: %lu\n", main_id); returп О; 415 Дnя синхронизации потоков можно также воспользоваться следующими функ­ циями: □ InitializeCriticalSection () - выполняет инициализацию. Прототип функции: #include <wiпdows.h> VOID InitializeCriticalSectioп( LPCRITICAL SECTION lpCriticalSection); □ EntercriticalSection() - запрашивает блокировку и ожидает разрешение на вход в критичную секцию. Прототип функции: #include <windows.h> VOID EnterCriticalSection(LPCRITICAL_SECТION lpCriticalSection); □ TryEnterCriticalSection() - запрашивает блокировку без ожидания разреше­ ния. Возвращает значение, отличное от нуля, если блокировка получена, и о - в противном случае. Прототип функции: #include <window . s.h> WINBOOL TryEпterCriticalSection( LPCRITICAL SECTION lpCriticalSection); □ LeaveCriticalSection() - снимает блокировку. Прототип функции: #include <wiпdows.h> VOID LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection); □ DeleteCriticalSection() -удаляет объект. Прототип функции: #include <wiпdows.h> VOID DeleteCrit1calSection(LPCRITICAL_SECТION lpCriticalSection);
416 Пример синхронизации потоков приведен в листинге 15.3. Листинг 15.3. Синхронизация потоков (способ 2) #include <stdio.h> #include <locale.h> #include <windows.h> #define NUМ THREADS 3 CRITICAL SECTION lock_print; void print(DWORD id) ( // Ожидаем получения блокировки EnterCriticalSection(&lock_print); printf("l. %1u\n", id); printf("2. %lu\n", id); printf("З. %1u\n", id); fflush(stdout); LeaveCriticalSection(&lock_print); // Снимаем блокировку DWORD WINAPI thread_func(LPVOID param) { DWORD thread id GetCurrentThreadid(); for(inti=О;i<10;++i){ Sleep(1000); // Имитация выполнения задачи print(thread_id); // Выводим сообщения в консоль return О; int main(void) { НANDLE arr threads[NUМ_THREADS]; // Массив с дескрипторами InitializeCriticalSection(&lock_print); setlocale(LC_ALL, "Russian_Russia.1251"); DWORD main_id = GetCurrentThreadid(); printf("Bxoд поток main: %lu\n", main_id); // Запускаем потоки for (int i = О; i < NUM_THREADS; ++i) { Глава 15 arr_threads[i] = CreateThread(NULL, О, thread_func, NULL, О, NULL); if (!arr_threads[i]) return 1; // Ожидаем завершения всех потоков WaitForMultipleObjects(NUМ_THREADS, arr_threads, TRUE, INFINITE);
Потоки и процессы // Закрываем все дескрипторы for (int i = О; i < NUМ_THREADS; ++i) 1 CloseHandle(arr_threads[i]); DeleteCriticalSection(&lock_print); printf("Bыxoд поток main: %lu\n", main_id); return О; НА ЗАМЕТКУ 417 Для синхронизации потоков помимо рассмотренных способов можно использовать и другие средства, например семафоры. За подробной информацией обращайтесь к документации. 15.2. Функции для работы с потоками, объявленные в файле process.h Вместо функции createThread() из WinAPI для создания потока управления лучше использовать функцию _beginthreadex(), объявленную в заголовочном файле process.h, т. к. в этом случае внутри потока можно вызывать функции из стандарт­ ной библиотеки языка С. Прототип функции: #include <process.h> uintptr_t _beginthreadex(void *lpThreadAttributes, unsigned dwStackSize, unsigned (_stdcall *lpStartAddress) (void *), void *lpParameter, unsigned dwCreationFlags, unsigned *lpThreadid); Все параметры аналогичны одноименным параметрам в функции CreateThread () (см. разд. 15.1 .1). Поток завершает работу при выходе из функции потока или при вызове функции _endthreadex(). В качестве параметра функция принимает код воз­ врата. Прототип функции: #include <process.h> void _endthreadex(unsigned retval); Переделаем код из листинга 15.1 и используем функцию _beginthreadex() вместо функции CreateThread() (листинг 15.4). #include <stdio.h> #include <locale.h> #include <process.h> #include <windows.h>
418 #define NUМ THREADS 3 unsigned thread_func(void *param) { DWORD thread id GetCurrentThreadid(); for(inti=О;i<10;++i){ Sleep(l000); // Имитация выполнения задачи // Выводим сообщение в консоль printf{"%d %lu\n", i, thread_id); fflush(stdout); return О; int rnain(void) { НANDLE arr threads[NUМ_TНREADS]; // Массив с дескрипторами setlocale(LC_ALL, "Russian_Russia.1251"); DWORD rnain id = GetCurrentThreadid(); printf("Bxoд поток rnain: %lu\n", rnain id); // Запускаем потоки for (int i = О; i < NUM_THREADS; ++i) { arr_threads[i] = (НANDLE)_beginthreadex(NULL, О, thread_func, NULL, О, NULL); if (!arr_threads[i]) return 1; // Ожидаем завершения всех потоков WaitForMultipleObjects(NUМ_THREADS, arr_threads, TRUE, INFINITE); // Закрываем все дескрипторы for (int i = 0; i < NUМ_THREADS; ++i) { CloseHandle(arr_threads[i]); printf("Выход поток rnain: %lu\n", rnain_id); return О; Глава 15 Для создания потока можно также воспользоваться функцией _beginthread(). Про­ тотип функции: #include <process.h> uintptr_t _beginthread(void _cdecl *lpStartAddress) (void *), unsigned dwStackSize, void *lpParameter); В параметре lpStartAddress указывается адрес пользовательской функции, инст­ рукции внутри которой будут выполняться в отдельном потоке. Обратите внима--
Потоки и процессы 419 ние: функция потока ничего не возвращает. Для завершения работы потока можно вызвать функцию _endthread(), которая закроет дескриmор потока, поэтому ис­ пользовать фун.кцию CloseHandle() не нужно. Функция _endthread() вызывается автоматически при выходе из пользовательской функции потока. Прототип функ­ ции: #include <process.h> void _endthread(void); В параметре dwStackSize задается размер стека или значение о для размера по умолчанию. Параметр lpParameter задает данные, указатель на которые будет дос­ тупен через параметр в функции потока. Если данные не передаются, то указываем значение NULL. Функция _beginthread() возвращает дескриптор потока или значе­ ние -1 в случае ошибки. Пример использования функции _beginthread() приведен в листинге 15.5 . #include <stdio.h> #include <locale.h> #include <process.h> #include <windows.h> #define NUМ THREADS 3 void thread_func(void *param) { DWORD thread id = GetCurrentThreadid(); for(inti=О;i<10;++i){ Sleep(l000); // Имитация вьшолнения задачи // Быводим сообщение в консоль printf("%d %lu\n", i, thread_id); fflush(stdout); printf("Bыxoд поток: %lu\n", thread_id); int main(void) uintptr_t handle; setlocale(LC_ALL, "Russian_Russia.1251"); DWORD main_id = GetCurrentThreadid(); printf("Bxoд поток main: %lu\n", main id); // Запускаем потоки for (int i = О; i < NUМ_THREADS; ++i) handle = _beginthread(thread_func, О, NULL); if (handle <= О) return l;
420 Sleep(15000); // Ожидаем завершения потоков printf("Bыxoд поток main: %lu\n", main_id); return О; 15.3. Потоки POSIX Глава 15 Для создания потоков и управления ими можно воспользоваться функциями, объ­ явленными в заголовочном файле pthread.h . В этом случае при компиляции внуrри команды нужно указать флаги -lpthread и -D _REENTRANT или только флаг -pthread: C:\book>gcc -Wall -Wconversion -03 -finput-charset=cp1251 - fexec-charset=cp1251 -lpthread -D_REENTRANT -о test.exe test.c Компиляторы, которые мы установили в главе 1, собраны с различной поддержкой потоков: win32 или posix. Для того чтобы увидеть поддерживаемую компилятором модель потоков, выполняем следующую команду: C:\book>gcc -v При использовании компилятора из каталога C:\msys64\mingw64\Ьin (этот компилятор мы используем по умолчанию) в результате выполнения команды можно найти следующие строки: - -e naЫe-threads=posix Thread model: posix При использовании компилятора из каталога C:\MinGW64\mingw64\bln результат будет таким: --e naЬle-threads=win32 Thread model: win32 Если модель win32, то нужно при компиляции указать флаг -pthread или при воз­ никновении ошибки установить компилятор с поддержкой модели posix: C:\book>gcc -Wall -Wconversion -03 -finput-charset=cpl251 -fexec-charset=cp1251 -pthread -о test.exe test.c 15.3.1. Создание и завершение потока Создать поток в модели posix позволяет функция pthread_create(). Прототио функции: #include <pthread.h> int pthread_create(pthread_t *th, const pthread attr_t *attr, void *(* func)(void *), void *arg); typedef uintptr_t pthread_t; typedef struct pthread_attr_t pthread_attr_t;
Потоки и процессы struct pthread_attr_t { unsigned p_state; void *stack; size t s_size; struct sched_param param; }; Рассмотрим параметры функции pthread_create() : 421 □ th - указывается адрес переменной, в которую будет записан дескриптор соз­ данного потока; □ attr - задает атрибуты потока. Дnя использования атрибутов по умолчанию следует указать значение NULL; □ func - указатель на пользовательскую функцию, инструкции внутри которой будут выполнены в отдельном потоке. Пример функции: void *thread_func(void *param) { // Инструкции, вьтолняемые в отдельном потоке return NULL; □ arg - задает данные, указатель на которые будет доступен через параметр param функции thread_func( J • Если данные не передаются, то указываем значение NULL. Если поток успешно создан, то функция pthread_create( J возвращает значение о, в противном случае - код ошибки: #include <errno.h> #define ЕРЕRМ 1 #define EAGAIN 11 #define EINVAL 22 Максимально возможное количество потоков содержится в макросе РТНRЕАD TНREADS МАХ: #include <pthread.h> #define PTHREAD THREADS МАХ 2019 - - Поток завершает работу в следующих случаях. □ Функция thread_func() завершила свою работу (например, поток управления . достиг оператора return). С помощью оператора return указывается статус завершения. Получить статус завершения можно посредством функции pthread_join(). □ Внутри функции thread_func( J вызывается функция pthread_exit( J • В парамет­ ре указывается статус завершения. Прототип функции: #include <pthread.h> void pthread_exit(void *res);
422 Глава 15 □ В другом потоке вызвана функция pthread_cancel (). В параметре функции ука­ зывается дескриптор потока. Прототип функции: #include <pthread.h> int pthread_cancel(pthread_t th); □ Завершается процесс, например, поток управления достиг конца функции rnain(). После запуска потоков мы должны дождаться завершения работы потоков в основ­ ном потоке, иначе они прекратят работу на любой стадии выполнения при завер­ шении процесса. Дождаться завершения работы потока позволяет функцИJ1 pthread_j oin(). В первом параметре указывается дескриптор потока, а через второй параметр доступно значение, возвращаемое функцией потока. Прототип функции: #include <pthread.h> int pthread_join(pthread_t t, void **res); Если ошибок не было, то функция pthread_join() возвращает значение о, в против­ ном случае - код ошибки: #include <errno.h> #define ESRCH 3 #define EINVAL 22 #define EDEADLK 36 Для того чтобы дождаться завершения работы потоков, можно в основном потоке (например, внутри функции rnain()) вызвать функцию pthread_exit(). В этом слу­ чае основной поток будет блокирован до момента завершения всех потоков. Создадим три потока, внутри которых будем выводить данные в окно консоли (листинг 15.6). Листи-.r 1 S.6. Создание потоков с помощью функции pthxead_create () #include <stdio.h> #include <locale.h> #include <pthread.h> #include <unistd.h> #define NUМ THREADS 3 void *thread_func(void *pararn) { int thread id = *(int *)pararn; for(inti=О;i<10;++i){ sleep(l); // Имитация вьmолнения задачи // Выводим сообщение в консоль printf("i = %d поток: %d\n", i, thread id); fflush(stdout);
Потоки и процессы printf("Выxoд поток: %d\n", thread_id); return NULL; int rnain(void) pthread_t arr_threads[NUМ_TНREADS]; int arr_threads_id[NUМ_TНREADS]; int status = О; setlocale(LC_ALL, "Russian_Russia.1251"); printf("Bxoд поток rnain\n"); // Запускаем потоки for(inti=О;i<NUМ_'l'НREADS;++i){ arr_threads_id[i] = i + 1; status = pthread_create(&arr_threads[i], NULL, thread func, (void *)&arr_threads_id[i]); if (status != О) { printf("Ошибка pthread_create(): %d\n", status); return 1; // Ожидаем завершения потоков for (int j = О; j < NUМ_THREADS; ++j) { status = pthread_join(arr_threads[j], NULL); if (status != О) { printf("Ошибка pthread_join() : %d\n", status); printf("Bыxoд поток rnain\n"); return О; 15.3.2. Синхронизация потоков 423 Дпя того чтобы избежать проблем при доступе к одному ресурсу, необходимо выполнить синхронизацию критичных секций (секций, к которым имеют доступ несколько потоков), например, с помощью мьютексов. Дпя этого предназначены следующие функции: #include <pthread.h> □ pthread_rnutex_init() - выполняет инициализацию мьютекса. В первом пара­ метре указывается адрес переменной, а во втором параметре можно передать атрибуты или значение NULL. При успешном выполнении функция возвращает значение о, а при возникновении ошибки - код ошибки. Прототип функции:
424 Глава 15 int pthread_mutex_init(pthread_mutex_t *m, const pthread_mutexattr_t *а); □ pthread_mutex_destroy() - удаляет объект мьютекса. При успешном выполне­ нии функция возвращает значение о, а при возникновении ошибки - код ош� ки. Прототип функции: int pthread_mutex_destroy(pthread_mutex_t *m); □ pthread_mutex_lock(} - запрашивает блокировку и ожидает разрешение на вхо.1 в критичную секцию. При получении блокировки функция возвращает значе­ ние о, а при возникновении ошибки - код ошибки. Прототип функции: int pthread_mutex_lock(pthread_mutex_t *m); □ pthread_mutex_trylock() - запрашивает блокировку без ожидания разрешенНА.. При получении блокировки функция возвращает значение о, а при возникнове­ нии ошибки - код ошибки. Прототип функции: int pthread_mutex_trylock(pthread_mutex_t *m); □ pthread_mutex_unlock() - снимает блокировку. После выхода из критичной секции нужно обязательно снять блокировку, иначе секция будет заблокирована навсегда. При успешном выполнении функция возвращает значение о, а при возникновении ошибки - код ошибки. Прототип функции: int pthread_mutex_unlock(pthread_mutex_t *m); Пример синхронизации потоков приведен в листинге 15.7. 1 Листинг 15.7. Синхронизация потоков #include <stdio.h> #include <locale.h> #include <pthread.h> #include <unistd.h> #define NUМ THREADS 3 pthread mutex t mutex_print; void print(int id) { // Ожидаем получения блокировки if (pthread_mutex_lock(&mutex_print) О){ printf("l. %d\n", id); printf("2. %d\n", id); printf("З. %d\n", id); fflush(stdout); pthread mutex unlock(&mutex_print); // Снимаем блокировку else { // Эдесь обрабатываем ошибки
Потоки и процессы void *thread_func(void *param) { int thread id = *(int *)param; for(inti=О;i<10;++i){ sleep(l); // Имитация выполнения задачи print(thread_id); // Выводим сообщения в консоль return NULL; int main(void) { pthread_t arr_threads[NUМ TНREADSJ; int arr_threads_id[NUМ_TНREADS]; int status = О; setlocale(LC_ALL, "Russian_Russia.1251"); printf{"Bxoд поток main\n"); pthread_mutex_init(&mutex_print, NULL); // ИНициализация мьютекса // Запускаем потою,: for(inti=О;i<NUМ_THREADS;++i){ arr_threads_id(i] = i + 1; status = pthread_create(&arr_threads(i], NULL, thread func, (void *)&arr_threads_id(i]); if (status != О) { printf("Oшибкa pthread_create(): %d\n", status); return 1; // Ожидаем завершения потоков for(intj=О;j<NUМ_TНREADS;++j){ status = pthread_join(arr_threads[j], NULL); if (status != О) { printf("Oшибкa pthread_join(): %d\n", status); pthread_mutex_destroy(&mutex_priпt); // Удаляем мьютекс printf("Bыxoд поток main\n"); return О; НА ЗАМЕТКУ 425 Для синхронизации потоков помимо мьютексов можно использовать и другие средст­ ва. За подробной информацией обращайтесь к документации.
426 15.4. Запуск процессов Для запуска дочернего процесса предназначены следующие функции: #include <process.h> intptr_t _execl(const char *filename, const char *argList, ...); intptr_t _wexecl(const wchar_t *filename, const wchar_t *argList, ...); Глава 15 'iritptr_t _ execle(const char *filename, const char *argList, . . . ); intptr_t _wexecle(const wchar_t *filename, const wchar_t *argList, ...); intptr_t _execlp(const char *filename, const char *argList, ...); intptr_t _wexeclp(const wchar_t *filename, const wchar_t *argList, ...); intptr_t _execlpe(const char *filename, const char *argList, ...); intptr_t _wexeclpe(const wchar_t *filename, const wchar_t *argList, ...); intptr_t _ execv(const char *filename, const char *const *argList); intptr_t _ wexecv(const wchar_t *filename, const wchar_t *const *argList); intptr_t _ execve(const char *filename, const char *const *argList, const char *const *env); intptr_t _ wexecve(const wchar t *filename, const wchar t *const *argList, const wchar_t *const *env); intptr_t _ execvp(const char *filename, const char *const *argList); intptr_t _ wexecvp(const wchar_t *filename, const wchar_t *const *argList); intptr_t _ execvpe(const char *filename, const char *const *argList, const char *const *env); intptr�t _wexecvpe(const wchar t *filename, const wchar t *const *argList, const wchar t *const *env); typedef _int64 intptr_t; Буквы после префиксов _ехес и _wexec имеют следующий смысл: □ р - файл filename будет искаться в путях, перечисленных в системной пере­ менной Рдтн; □ 1 - аргументы командной строки передаются по отдельности. Первый аргумент должен содержать путь к исполняемому файлу. В последнем аргументе нужно передать значение NULL: // Код программы test.exe приведен в листинге 17.3 _execl("C:\\book\\test.exe", "C:\\book\\test.exe", " -paraml", " -param2" , NULL); // Сюда мы попадем только при ошибке printf("%s\n", "Не удалось запустить процесс"); □ е - передается массив указателей env на параметры среды (последний элемент массива должен иметь значение NULL): const char *my_env [ ] = "МYDIR=C:\\book\\",
Потоки и процессы NULL }; _execle("C:\\book\\test.exe", "C:\\book\\test.exe", "-paraml", "-pararn2", NULL, my_env); // Сюда мы попадем только при ошибке printf("%s\n", "Не удалось запустить процесс"); 427 □ v- передается массив указателей argList на аргумешы командной строки (по­ следний элемент массива должен иметь значение NULL): const char *my_args[4] = "C:\\book\\test.exe", "-paraml", "-pararn2", NULL }; const char *my_env _ []= "МYDIR=C:\\book\\", NULL }; _ execve("C:\\book\\test.exe", my_args, my_env); // Сюда мы попадем только при ошибке printf("%s\n", "Не удалось запустить процесс"); Если процесс успешно запущен, то функции ничего не возвращают. Текущий про­ цесс при этом будет замещен запущенным процессом. Инструкции после вызова этих функций выполняются только в случае ошибки при запуске процесса. Следует учитывать, что строки, описывающие аргументы, не должны содержать пробелов. Если пробелы присутствуют, то нужно дополнительно заключить значе­ ние в кавычки: setlocale(LC_ALL, "Russian_Russia.1251"); _ wexecl(L"C:\\book\\test.exe", L"C:\\book\\test.exe", L"\" -param 1\'"', L"\" -param 2\"", NULL)-; // Сюда мы попадем только при ошибке wprintf(L"%s\n", L"He удалось запустить процесс"); Для запуска процесса можно таюке воспользоваться следующими функциями: #include <process.h> intptr_t _spawnl(int mode, const char *filename, const char *argList, .. .); intptr_t _wspawnl(int mode, const wchar_t *filename, const wchar_t *argList, .•. ); intptr_t _spawnle(int mode, const char *filename, const char *argList, ...); intptr_t _ wspawnle(int mode, const wchar_t *filename, con , st wchar_t *argList, ... ); intptr_t _spawnlp(int mode, const char *filename, const char *argList, ... );
428 intptr_t _wspawnlp(int mode, const wchar_t *filename, const wchar_t *argList, ...); intptr_t _ spawnlpe(int mode, const char *filename, const char *argList, ... ); intptr_t _wspawnlpe(int mode, const wchar_t *filename, const wchar_t *argList, ...); intptr_t _spawnv(int mode, const char *filename, const char •const *argList); intptr_t _wspawnv(int mode, const wchar_t *filename, const wchar_t *const *argList); intptr_t _spawnve(int mode, const char *filename, const char *const *argList, const char *const *env); intptr_t _wspawnve(int mode, const wchar_t *filename, const wchar_t *const *argList, const wchar_t *const *env); intptr_t _spawnvp(int mode, const char *filename, const char *const *argList); intptr_t _wspawnvp(int mode, const wchar_t *filename, const wchar_t *const *argList); intptr_t _ spawnvpe(int mode, const char *filename, const char *const *argList, const char *const *env); intptr_t _ wspawnvpe(int mode, const wchar_t *filename, const wchar t *const *argList, const wchar t *const *env); typedef _int64 intptr_t; В параметре mode указываются следующие макросы: #include <process.h> Глава 15 □ _Р_OVERLAY - дочерний процесс перекрывает текущий процесс. Аналог вызова функций с префиксами _ехес и _wexec. Определение макроса: #define Р OVERLAY 2 Пример: // Код программы test.exe приведен в листинге 17.3 _spawnl(_P_OVERLAY, "C:\\book\\test.exe", "C:\\book\\test.exe", "- paraml", " -param2", NULL); // Сюда мы попадем только при ошибке printf("%s\n", "Не удалось запустить процесс"); □ _Р_WAIT - текущий процесс будет ждать завершения нового процесса. Функции в этом случае возвращают код возврата нового процесса или значение -1 в слу­ чае ошибки. Определение макроса: #define Р WAIT О
Потоки и процессы Пример: const char *my_args[4] = "C:\\book\\test.exe", "-paraml", "-param2", NULL }; intptr_t st; st = _spawnv(_P _ WAIT, "C:\\Ьook\\test.exe", my_args); // Сюда мы попадем после завершения программ,� test.exe printf("Koд завершения: %I64d\n", st); 429 □ _P_NOWAIT и _P_NowArтo- новый процесс будет выполняться параллельно с те­ кущим процессом. Функции в этом случае возвращают дескриптор процесса. Определения макросов: #define ·р NOWAIT 1 #define Р NOWAITO 3 Пример: intptr_t d; d = _spawnl(_P_NOWAIT, "C:\\book\\test.exe", "C:\\book\\test.exe", "-paraml", "-param2", NULL); // Сюда мы попадем не дожидаясь завершения прогр� test.exe рrintf("Дескриптор процесса: %I64d\n", d); □ _Р _ DETACH - новый процесс будет выполняться параллельно с текущим про­ цессом в фоновом режиме без доступа к консоли и клавиатуре. Определение макроса: #define Р DETACH 4 15.5. Получение идентификатора процесса Получить идентификатор текущего процесса позволяет функция _getpid(). Прото­ тип функции: #include <process.h> iпt _getpid(void); Пример: setlocale(LC_ALL, "Russian_Russia.1251"); рrintf("Идентификатор текущего процесса: %d\n", _getpid());
ГЛАВА 16 Создание библиотек В разд. 10.2 мы научились размещать функции в отдельных файлах, а затем по.1- ключать их к основной программе. В больших программах функции принято объ­ единять в библиотеки- статические (файлы с расширением lib в Visual С или с расширением а в MinGW) или динамические (файлы с расширением dl в Windows). Статические библиотеки становятся частью программы при компиш1- ции, а динамические библиотеки подгружаются при запуске программы или при ее выполнении. Давайте научимся создавать библиотеки, а также пользоваться ими. 16.1. Статические библиотеки При запуске программы из редактора Eclipse вы наверняка заметили, что процесс компиляции программы состоит из нескольких этапов. На первом этапе файл с ис­ ходным кодом преобразуется в объектный файл (файл с расширением о). Причем каждый файл с исходным кодом преобразуется отдельно. Этим достигается уско­ рение компиляции, т. к . нужно преобразовать лишь файлы, которые были измене­ ны, а не все файлы с исходным кодом. На втором этапе на основе объектных фай­ лов создается ЕХЕ-файл. Так вот на втором этапе вместо ЕХЕ-файла мы можем создать статическую библиотеку. 16.1.1. Создание статической библиотеки из командной строки Вначале попробуем создать статическую библиотеку из командной строки. Для этого в каталоге C:\book\mylib создаем файлы module1 .h (листинг 16.1), module1 .с (лис­ тинг 16.2), module2.h (листинг 16.3) и module2.c (листинг 16.4). Листинг 16.1. Содержимое файла C:\book\myliЬ\module1.h #ifndef MODULEl Н #define MODULEl Н
Создание библиотек #ifdef _cplusplus extern "С" { #endif int surn_int(int х, int у); #ifdef _cplusplus #endif #endif /* MODULEl Н */ #include "modulel.h" int surn_int(int х, int у) { return х + у; #ifndef MODULE2 Н #define MODULE2 Н #ifdef _cplusplus extern "С" { #endif douЬle surn_douЬle(douЬle х, douЬle у); #1fdef _cplusplus #endif #endif /* MODULE2 Н */ #include ''module2. h" douЬle surn_douЬle(douЬle х, douЬle у) { return х + у; 431 Таким образом, мы создали два модуля, содержащих по одной функции. Можно все функции объединить в один модуль, но в качестве примера компиляции нескольких файлов будем использовать два модуля. Обратите внимание на содержимое заrоло-
432 Глава 16 вочных файлов: чтобы библиотеку можно было использовать в программах на язы­ ке С++, мы поместили объявления функций внутри следующего условия: #ifdef _cplusplus extern "С" { #endif // Объявления функций.и т. д. #ifdef _cplusplus #endif Теперь создадим объектные файлы: C:\Users\Unicross>cd C:\book\mylib C:\book\mylib>set Path=C:\msys64\mingw64\bin;%Path% C:\book\mylib>gcc -Wall -Wconversion -с - finput-charset=cpl251 -fexec-charset=cpl251 -о modulel.o modulel.c C:\book\mylib>gcc -Wall -Wconversion -с -finput-charset=cpl251 -fexec-charset=cpl251 -о module2.o module2.c В этих командах мы указали флаг -с, чтобы создавались объектные файлы, а также флаг -о, для указания названий объектных файлов. Последние две команды можно объединить в одну команду: C:\book\mylib>gcc -Wall -Wconversion -с -finput-charset=cpl251 -fexec-charset=cpl251 modulel.c module2.c В результате компиляции были созданы два файла: module1 .о и module2.o. Эти два объектных файла можно объединить в статическую библиотеку с помощью сле­ дующей команды: ar rcs liЬ<Название библиотеки>.а <Объектные файлы через пробел> В этой команде мы воспользовались программой аг.ехе, расположенной в каталоге C:\msys64\mingw64\Ьin. По существу программа создает архив, содержащий объект­ ные файлы статической библиотеки. После названия программы указываются сле­ дующие флаги: □ r - замена существующих или вставка новых файлов в архив; □ с - создать новый архив, если нужно; □ s - создание индекса архива (действовать как программа ranlib.exe). Далее указывается слово lib, после которого задается название статической биб­ лиотеки и расширение а. В конце команды через пробел перечисляются объектные файлы, добавляемые в архив. Давайте создадим статическую библиотеку с назва­ нием modules 1 2: ar rcs libmodules 1 2.а modulel.o module2.o
Создание библиотек 433 В результате в каталоге C:\Ь�ok\mylib будет создан файл libmodules_1 _2.a. Создавать статическую библиотеку научились, теперь попробуем ее использовать внутри программы. Для этого в каталоге С:\Ьооk создаем файл test.c с кодом из лис­ тинга 16.5. #include <stdio.h> #include <locale.h> #include <rnodulel.h> #include <rnodule2.h> int main(void) { setlocale(LC_ALL, "Russian_Russia.1251"); рrintf("Функция surn_int(): %d\n", surn_int(5, 10)); рrintf("Функция surn_douЫe(): %.2f\n", surn_douЬle(З.125, 5.6)); return О; Скомпилируем и запустим программу: C:\book\mylib>cd C:\book C:\book>gcc -Wall -Wconversion -IC:\book\rnylib -с -finput-charset=cpl251 -fexec-charset=cpl251 test.c C:\Ьook>gcc test.o -LC:\book\mylib -lmodules 1 2 -о test.exe C:\book>test.exe Функция surn_int(): 15 Функция surn_douЬle(): 8,72 В первой команде мы переходим в каталог С:\Ьооk. Вторая команда преобразует файл с исходным кодом в объектный файл. На этом этапе компилятору нужно знать сигнатуры методов, которые находятся в заголовочных файлах. Указать местопо­ ложение заголовочных файлов можно с помощью флага - r . В третьей команде вы­ полняется сборка приложения. На этом этапе с помощью флага -L нужно передать местоположение статических библиотек, а после флага -1 задать название подклю­ чаемой библиотеки. Обратите внимание: название библиотеки указывается без префикса нь и расширения файла. При использовании статических библиотек за­ головочные файлы обычно размещают в каталоге include (например, C:\msys64\ mingw64\include), а сами библиотеки - в каталоге lib (например, C:\msys64\mingw64\lib). Последние две команды можно объединить в одну: C:\Ьook>gcc test.c - Wall -Wconversion -finput-charset=cpl251 - fexec-charset=cpl251 -IC:\book\mylib - LC:\book\rnylib -lrnodules 1 2 -о test.exe
434 Глава 11 Если все библиотеки нужно компоновать статически, то в составе команды можно указать флаг -static: C:\book>gcc test.c -Wall -Wconversion -finput-charset=cpl251 - fexec-charset=cpl251 -static -IC:\book\mylib -L C:\book\mylib -lmodules_1 _2 -о test.exe Если библиотеки должны компоноваться по-разному, то можно воспользоватьа флагами -Wl, -Bdynamic и -Wl, -Bstatic: -Wl,-Bdynamic <динамические библиотеки через пробел> - Wl,-Bstatic <статические библиотеки через пробел> Используя флаг -Wl, -вstatic, предыдущую команду можно записать так: C:\book>gcc test.c - Wall -Wconversion -finput-charset=cp1251 - fexec-charset=cp1251 -IC:\book\mylib -LC:\book\mylib -Wl,-Bstatic -lmodules_1 _2 -о test.exe 16.1.2. Создание статической библиотеки в редакторе Eclipse Теперь попробуем создать статическую библиотеку в редакторе Eclipse. В меню File выбираем пункт New I Project. В открывшемся окне (см. рис. 1.30) в списR выбираем пункт С/С++ 1 С Project и нажимаем кнопку Next. На следующем шаге (рис. 16.1) в поле Project name вводим module_З, в списке слева выбираем пункr Static Library I Empty Project, а в списке справа - пункт MinGW GCC. Для соэ­ дания проекта нажимаем кнопку Finish. Проект статической библиотеки отоб�:-­ зится в окне Project Explorer. Далее в корневой каталог проекта добавляем файл с исходным кодом moduleЗ.c (листинг 16.6) и заголовочный файл moduleЗ.h (листинг 16.7). 1 Листинг 16.6. Содержимое. файла module3.c #include "moduleЗ.h" long sum_long(long х, long у) { return х + у; Листинг 16.7. Содержимое файла moduleЗ.h #ifndef MODULEЗ Н #define MODULEЗ Н #ifdef _cplusplus extern "С" { #endif ]
Создание библиотек long surn_long(long х, long у); #ifdef _cplusplus } #endif #endif /* MODULEЗ Н */ С Projкt Create С project of selected type Eroject name: i module_3 -- -- � Use default location С Prbject Project type: Тoolchains: ,. (:3, GNU Autotools Cross GCC " (:3, ExecutaЫe Microsoft Visual С++ • Empty Project MinGWGCC • Hello 't/orld ANSI С Project "' (:3, Shared Library • Empty Project "' (:3, Static Library 1• Empty Project1 v (:3, Makefile project - □ � Show project types and toolchains only if they are �upported on the platform <J1ack 1! Next> __J I ...__ _ E i _ n _ ish _� I i Cancel Рис. 16.1. Соэдание проекта 435 Оrкрываем свойства проекта и на вкладке С/С++ Build I Environment проверяем настройки компилятора. Значение переменной окружения MINGW_НОМЕ должно быть таким: с: \msys64 \mingw64. Переходим на вкладку С/С++ Build I Settings и из списка Contiguration выбираем пункт АН contigurations. На вкладке Tool Settings из списка выбираем пункт GCC С Compiler I Miscellaneous. В поле Other flags через пробел к имеющемуся значению добавляем следующие инструкции: -finput-charset=cpl251 -fexec-charset=cpl251
436 Глава 16 Содержимое поля Other flags после изменения должно быть таким: -с - fmessage-length=O -finput-charset=cpl25l -fexec-charset=cpl251 Переходим на вкладку GCC Archiver I General и в поле Archiver flags вводим зна­ чение: rcs. На вкладке Build Artifact (рис. 16 .2) можно настроить название библио­ теки, префикс и расширение файла. Оставляем значения по умолчанию. Сохраняем настройки проекта. type: filter text '> Resource Builders � С/С+ - Build Build VariaЫes Environment Logging Settings Тool Chain Editor С/С++ General LlnuxTools Path Projec.t Naturб Project References Rc : f.эctoring History Run/Debug Settings Тask Repositcнy TaskTags Validation \IVikiTe,,.t Properties for module_3 Settings Configuration: [ AII configurations ] - □ .., Manage Config� . rations : . ·: .J ф Tool Settings !) ContainerSettings' i•· Build Steps . . . 8.uild "-rtifactj :ill Bin,ry Parsers Г!Э Artifact Тур,с Art . ifact name: Static library S{ProjName} Artifact extensicn: а Outpu1 prefЬc lib Рис. 16.2. Вкладка Build Artifact Cancel Для создания статической библиотеки в меню Project выбираем пункт Build Configurations I Set Active I Release, а затем пункт Build Project. После ком­ пиляции в каталоге C:\cpp\projects\module_З\Release будет создан файл libmodule_З.a . В итоге мы имеем две статические библиотеки: libmodules_1_2.а (расположена в ка­ талоге C:\book\mylib) и libmodule_З.a (расположена в каталоге C:\cpp\projects\module_З \Release). Давайте подключим их к проекту тest64c. Открываем свойства проекта Test64c и из списка Configuration выбираем пункт AII configurations. Из списка слева выбираем пункт С/С++ Build I Settings, а затем на вкладке Tool Settings из списка выбираем пункт GCC С Compiler I Includes (рис. 16 .3). В список Include paths добавляем пути к каталогам с заголовочными файлами: C:\book\mylib и C:\cpp\projects\module_З. Далее из списка выбираем пункт MinGW С Linker I Libraries (рис. 16 .4). В список Library search path добавляем пути к каталогам со статическими библиотеками: C:\book\mylib и C:\cpp\projects\ module_З\Release, а в список Libraries - б1Аб.1иотеки modules_ 1 _ 2 и module_з. Обра­ тите внимание: название библиотеки указывается без префикса lib и расширения файла. Сохраняем настройки проекта.
Создание библиотек Properties for Тest64c - □ itypeblter t,xt __ __ __, · 1Settinp t, R60urce Вuild<rs • (/С++ Build Conftgur.иion: /[Altc��fщur.mons] • BuildV1riaЫ6 Environment Logging Settings Тool Chain Editor � С/С++ Geмr.tl linux Тools Path ProjюNotur6 Proje<t Reftrf:nc� Run/Dl!!bug Settings t\ Тиk Repository Tosk Т,gs i) Validation WikiТert 1 typf: filter tm , R�urce Builders • С/С•+ Build BuildV•riaЬlб Environment logging Settings Тоо1 Chain Editor 1> С/С•+ General linux-Тook P.rth Project Natur6 Projкt: References Run/Debug Sdtings t:> Тask Repository T•sk T•g, 1)- V•lidation WikiТext � Tools.ttings : /Гё;;;;;�i���! }'- BuildSt�' BuildArtifкtJ !ill Bi� О [Е • t.i) GCC Аssм,Ыf:г � G,n,ral • � GCC С Compil,r @Di•l«t ��r � Optimiution @ D,bugging @W•rrnng, @ MisceHaneous • * MinGW С linker @ G,n.,•1 � libr!r� � Miscellanю1JS @Sh•r«f librory s.ttings lnclud• paths (-1) С \bcok\n, ltb� - "C�\cpp\projects\module_З w j lncludef i lts (-include) 1Apply •nd c1os,i г-- z.;;�;г l Рис. 16.3 . Указание пути к заголовочным файлам Properties for Тest64c □- Conf,gurotion: [! АН conf,gцrotions] v, �Configurilions...j !!) тool S,ttings 1JJ Conl<lin<f s.tti�gs 1 ,6'· Build St,j,s1 )' Bu�d Atlifкt l lill Вiмrу Pors,rs 1 1Э r�� • � GCC Ass,mЬler � Gmeral • � GCC С Compil,r @ Dial,ct � Pr�roc.шor @ lnclud,s � Optimiиtion � D,bugging @Warnings @ Mi:scel\aneous • \'!) MinGW С link" � Gener•I !!!i__�j @ Miscello1neous � Shared library �ings : Libraries Н} 1ффАf module_З ! •c:\cpp\projIOs\module_З\Rdeaп-" : [Apply •nd Oos, j cl___c_a _n __ c.i_c-J Рис. 16.4. Указание пути к библиотекам и названий библиотек 437
438 Глава 16 В файл Test64c.c добавляем код из листинга 16.8 и компилируем проект в режиме Release. В каталоге C:\cpp\projects\Тest64c\Release будет создан файл Test64c.exe, кот� рый мы можем запустить из командной строки: C:\book>C:\cpp\projects\Test64c\Release\Test64c.exe Функция surn int(): 15 Функция surn_douЫe(): 8,72 Функция surn_long(): 11 1 Листинг 16.8 . Содержимое файла Test64c.c #include <stdio.h> #include <locale .h> #include <rnodulel .h> #include <rnodule2 .h> #include <rnoduleЗ.h> int main(void) { setlocale(LC_ALL, "Russian Russia.1251"); рrintf("Функция surn_int(): %d\n", surn_int(5, 10)); рrintf("Функция surn_douЫe(): %.2f\n", surn_douЫe(З.125, 5.6)); рrintf("Функция surn_long(): %ld\n", surn_long(бL, 5L)); return О; 16.2. Динамические библиотеки Статические библиотеки становятся частью программы при компиляции, а динами­ ческие библиотеки подгружаются при запуске программы. В итоге размер пр<r граммы при использовании статических библиотек будет больше размера програм­ мы, использующей динамические библиотеки. 16.2.1. Создание динамической библиотеки из командной строки Давайте рассмотрим процесс создания динамических библиотек из командной строки. В примере используем файлы module1 .h (см. листинг 16.1), module1 .с (см. листинг 16.2), module2.h (см. листинг 16.3) и module2.c (см. листинг 16.4), на основе которых мы создавали статическую библиотеку (см. разд. 16 .1.1). Запускаем командную строку и выполняем следующие команды: C:\Users\Unicross>cd C:\book\mylib C:\book\mylib>set Path=C:\msys64\mingw64\bin;%Path% C:\book\mylib>gcc -Wall -Wconversion -fPIC -с -finput-charset=cpl251 -fexec-charset=cpl251 modulel.c module2.c C:\book\mylib>gcc -shared -о libmodules 1 2 O.dll modulel.o module2.o
Создание библиотек 439 В первой команде мы делаем текущим каталог C:\book\mylib, а во второй - добав­ ляем путь к компилятору в переменную окружения РАТН. Третья команда создает объектные файлы на основе файлов с исходным кодом. Обратите внимание: мы дополнительно указал11 флаг -fPIC, который задает использование относительной адресации. Четвертая команда создает динамическую библиотеку. Во-первых, в составе команды указывается флаг -shared, а во-вторых, расширение файла dll, а не а. В результате в каталоге C:\Ьook\mylib будет создан файл libmodules_1 _2 _0.dll. Теперь скомпилируем файл C:\book\test.c с кодом из листинга 16.5. C:\book\mylib>cd C:\book C:\book>gcc -Wall -Wconversion -IC:\book\mylib -с -finput-charset=cp1251 -fexec-charset=cp1251 test.c C:\Ьook>gcc test.o -о test.exe -LC:\book\mylib -lmodules l 2 О Последняя команда почти ничем не отличается от команды подключения стати­ ческой библиотеки. Если в каталоге C:\book\mylib расположены одноименные стати­ ческие и динамические библиотеки, то выбор библиотеки будет зависеть от ис­ пользуемого компилятора. Для того чтобы избежать проблем, лучше размещать статические и динамические библиотеки в каталогах с разными названиями (обыч­ но lib и Ьin) или, как мы поступили в этом примере, давать библиотекам разные имена. Если мы сейчас попробуем запустить программу test.exe из командной строки, то будет выведено сообщение о том, что библиотека libmodules_1 _2_0 .dll не найдена (рис. 16 .5). Избежать этой ошибки в Windows можно, воспользовавшись следую­ щими способами: □ поместить динамическую библиотеку рядом с ЕХЕ-файлом; □ скопировать динамическую библиотеку в системный каталог, например в ката­ лог C:\Windows\System32 (однако лучше так не делать); □ поместить динамическую библиотеку в каталог, прописанный в системной пе­ ременной окружения РАТН. test.exe - Системная ошибка � Запуск программы неео;можен, так как на компьютере отсутствует W! libmodules_1 _2_0 .dll. Попробуйте переустановить программу. Рис. 16.5. Сообщение при отсутствии динамической библиотеки
440 Глава 16 В разд. 1 .1 мы создали каталог C:\cpp\lib, а в разд. 1.2 добавили его в пути поиска. поэтому копируем библиотеку libmodules_1_2_0 .dll в этот каталог и запускаем про­ грамму из командной строки: C:\book>test.exe Функция sшn_int(): 15 Функция sшn_douЫe(): 8,72 16.2.2. Создание динамической библиотеки в редакторе Eclipse Теперь попробуем создать динамическую библиотеку в редакторе Eclipse. В меню File выбираем пункт New I Project. В открывшемся окне (см. рис. 1.30) в списке выбираем пункт С/С++ 1 С Project и нажимаем кнопку Next. На следующем шаге (см. рис. 16 .1) в поле Project name вводим module_ з _о, в списке слева выбираем пункт Shared Library I Empty Project, а в списке справа - пункт MinGW GCC. Для создания проекта нажимаем кнопку Finish. Проект динамической библиотеки отобразится в окне Project Explorer. Далее в корневой каталог проекта добавляем файл с исходным кодом moduleЗ.c (см. листинг 16.6) и заголовочный файл moduleЗ.h (см. листинг 16.7). Открываем свойства проекта и на вкладке С/С++ Build I Environment проверяем настройки компилятора. Значение переменной окружения MINGW_НОМЕ должно быть таким: с: \msys64 \mingw64. Переходим на вкладку С/С++ Build I Settings и из списка Configuration выбираем пункт АН configurations. На вкладке Tool Settings из спи­ ска выбираем пункт GCC С Compiler I Miscellaneous. В поле Other flags через пробел к имеющемуся значению добавляем следующие инструкции: - finput-charset=cpl251 -fexec-charset=cpl251 Содержимое поля Other flags после изменения должно быть таким: -с - fmessage-length=O -finput-charset=cpl251 -fexec-charset=cpl251 Далее из списка выбираем пункт MinGW С Linker I Shared Library Settings и проверяем установку флажка Shared (-shared). На вкладке Build Artifact (рис. 16.6) можно настроить название библиотеки, префикс и расширение файла. Оставляем значения по умолчанию. Сохраняем настройки проекта. Для создания динамической библиотеки в меню Project выбираем пункт Build Coofigurations I Set Active I Release, а затем пункт Build Project. После компиля­ ции в каталоге C:\cpp\projects\module_З_O\Release будет создан файл libmodule_З _O.dll. Для того чтобы не было проблем с запуском программы, копируем фай.1 libmodule_З_O .dll в каталог C:\cpp\Jib. В итоге мы имеем две динамические библиотеки: libmodules_1_2 _0 .dll (в ка­ талоге C:\book\mylib) и libmodule_З_O.dll (в каталоге C:\cpp\projects\module_З_O\Release). Копии этих библиотек должны быть расположены в каталоге C:\cpp\lib. Давайте подключим их к проекту Test64c.
Создание библиотек i type filter text ? Reюurc� Builders .11 С/С++ Build Build VariaЫe:s Environment Logging �ings Т ool Chain Editor i) С/С•• General linux Тools Path Project Notures Project Rб1::rences Run/Dd>ug Srtt:ings iJ Taslc R�itory Task Tags ;, Validation WikiText Properties for module_З _O AM•ctType: lvtifact nam� S{ProjNome} Artifact e:rtension: · dll Output profix: lib Рис. 16 .6 . Вкладка Build Artifact - □ 441 Оrкрываем свойства проекта тest64c и из списка Configuration выбираем пункт AII configurations. Из списка слева выбираем пункт С/С++ Build I Settings, а затем на вкладке Tool Settings из списка выбираем пункт GCC С Compiler I Includes (рис. 16 . 7). В список Include paths добавляем пути к каталогам с заголовочными jt-;pe filter t�-:t 1� Reource Builders " С/С++ Buifd Build VariaЫes Environment logging Settings Тool Chain Editor � С/С+ + GNoeral Linux Тools Path ProjЮ Nc1tures Project References Run/Debug Settings r- Тask Repository Task Tags t, Validation WikiТext Properties for Тest64c Settings - □ -- - -- - -- --- --- Configuration: : [ AII confi9t1rations} " ·· • ·· · · · · · · · · · · - · · ··-- · ··' l���A�--�-�--�-�nfiguratioм... J .,,. • � GCC AssemЫer �) General � � GCC С Compite:r С�� Dialect � Preproceяor @·lndudosj ® Optimizaticn @ Debugging � V-/arnings � Miscellan�us � 0 MinGW С linker @ General � librarie:s @ MiS<e:llaneous �) Shгred Library �ttings " (;\cpp\projec:ts\module:_3 _0 � lnc.lude files {·include) Рис. 16 .7 . Указание пути к заголовочным файлам
442 Глава 16 файлами: C:\book\mylib и C:\cpp\projects\module_З_O. Далее из списка выбираем пункт MinGW С Linker I Libraries (рис. 16.8). В список Library search path добавляем пути к каталогам с динамическими библиотеками: C:\book\mylib и C:\cpp\projects\ module_З _O\Release, а в список Libraries - библиотеки modules_ 1_2_О и module_З _с. Обратите внимание: название библиотеки указывается без префикса lib и расши­ рения файла. Сохраняем настройки проекта. _ type fil!_�! - - ��rt__ ;;, Resource Builders " С/С+• BuiLd Build VariaЫes Environment Logging Settings Tool Chain Editor r- , С/С•+ General linux Тools Path Project Natures Project Referenc:б Run/Debug Settings Тask Repository TaskTags ,.,. Validation Wiki1ext Properties for Тestб4c - □ Settings Configuration.: [ AII configuration!; 1 "' Manage Configurations... , -- --� -- - · т·ооl Settings о· Contain� S�i� - 9�� Build Steps t Build Artifact j "!ill Binary Parsers , о· ◄:.1• • �; GCC AssemЫer [� General • ® GCC С Compiler � Dialect � Preproceяcr � lncludes � Optimization � Debugging 8 \.Yarnings 3 Miscellaneo - us • @ MinGVI С Linker 8 General !@ Librari61 @Mi . scellaneo-us @ 5ha _ red library 5ettings !! Libraries {-IJ '===== i' !\ mcdule_3_0 ! Library search path {-L) jf "C:\cpp\proje:cts\module_З _O\Release:" ! Apply and Clo;e J _ Cancel Рис. 16.8. Указание пути к библиотекам и названий библиотек В файл Test64c.c добавляем код из листинга 16.8 и компилируем проект в режиме Release. В каталоге C:\cpp\projects\Тest64c\Release будет создан файл Test64c.exe, кото­ рый мы можем запустить из командной строки: C:\book>C:\cpp\projects\Test64c\Release\Test64c.exe Функция sum_int(): 15 Функция sum_douЬle(): 8,72 Функция sum_long(): 11 Для того чтобы увидеть все динамические библиотеки, используемые программой. и полные пути к ним, следует запустить режим отладки и отобразить окно Modules (рис. 16.9), выбрав в меню Window пункт Show View I Modules.
Создание библиотек - □ 1М Modules � \ 0 �1l@i � С:\Windo�\SYSТEМ32\ntdll.dll • C,\Windows\system32\kerne132.dll • C:\Windows\system32\Kerne1Base.dll • С:\Windows\system32\msvcrt.dll • C:\cpp\lib\libmodule_З_O.dll � C:\cpp\lib\libmodules_1 _2 _0.dll Рис. 16.9. Окно Modules 16.2.3. Загрузка динамической библиотеки во время выполнения программы 443 В предьщущих примерах динамические библиотеки загружаются при запуске программы. Если библиотека не найдена, то выводится сообщение об этом (см: рис. 16.5) и выполнение программы прекращается. В Windows существует воз­ можность загрузки динамической библиотеки во время выполнения программы. Для этого предназначены функции LoadLibraryA () и LoadLibraryW () из WinAPI. Функции возвращают указатель на библиотеку или значение NULL в случае ошибки. Прототипы функций: #include <windows.h> НМODULE LoadLibraryA(LPCSTR lpLibFileName); НМODULE LoadLibraryW(LPCWSTR lpLibFileName); В параметре lpLibFileName можно указать просто название библиотеки (например, liЬmodules_1 _2 _0) или название с расширением (например, liЬmodules_1 _2_D.dll). Перед названием можно задать абсолютный или относительный путь: HINSTANCE hdll = LoadLibraryA("C: \\book\\mylib\\liЬmodules_1 _2 _0 .dll"); Указать местоположение библиотек позволяют функции setDllDirectoryA() и SetDllDirectoryW( >. Прототипы функций: #include <windows.h> WINВOOL SetDllDirectoryA(LPCSTR lpPathName); WINBOOL SetDllDirectoryW(LPCWSTR lpPathName); Пример: SetDllDirectoryA("С:\\Ьооk\\mylib"); HINSTANCE hdll = LoadLibraryA("liЬmodules_1 _2_О . dl 1"); После завершения работы с библиотекой следует вызвать функцию FreeLibrary() . Прототип функции: #include <windows.h> WINВOOL FreeLibrary(НМODULE hLibModule);
444 Глава 16 Получить указатель на функцию, расположенную в загруженной библиотеке. позволяет функция GetProcAddress(). Если функции с названием lpProcName нет в библиотеке hМodule, то возвращается значение NULL. Прототип функции: #include <windows.h> FARPROC GetProcAddress(НМODULE hМodule, LPCSTR lpProcName); Получить полный путь к загруженной библиотеке позволяют функции GetModuleFileNameA() и GetModuleFileNameW() . Прототипы функций: #include <windows.h> DWORD GetModuleFileNameA(НМODULE hМodule, LPSTR lpFilename, DWORD nSize); DWORD GetModuleFileNameW(НМODULE hМodule, LPWSTR lpFilename, DWORD nSize); Пример загрузки динамической библиотеки libmodules_1_2 _0 .dll при выполнении программы приведен в листинге 16.9 . Предварительно в свойствах проекта удалите пути к заголовочным файлам, к библиотекам и названия подключаемых библиотек. т. к. эти данные не нужны при компиляции и компоновке программы. Листинг 16,9. Содержимое файла C;\book\test.c #include <stdio.h> #include. <locale.h> #include <windows.h> typedef int (*psurn_int) (int, int); typedef douЫe (*psurn_douЬle) (douЬle, douЬle); int rnain(void) setlocale(LC_ALL, "Russian_Russia.1251"); // Путь поиска библиотек SetDllDirectoryA(".\\rnylib"); // Загружаем библиотеку НINSTANCE hdll = LoadLibraryA("librnodules_l_2 _0.dll"); if (hdll == NULL) { рuts("Библиотека не найдена"); return 1; // Получаем полный путь к библиотеке char buf[500] = {О}; GetModuleFileNameA(hdll, buf, 499); printf("Пyть: %s\n", buf); // Получаем указатели на функции из библиотеки psurn_int surn_int = (psurn_int)GetProcAddress(hdll, "surn int"); if (surn_int == NULL) [ puts("Функция surn_int() не найдена"); return 1;
Создание библиотек psum_douЬle surn_douЬle (psum_douЬle)GetProcAddress(hdll, "surn_douЬle"); if (surn_douЬle == NULL) { ) puts("Функция surn _douЫe() не найдена"); return l; // Используем функции рrintf("Функция surn_int(): %d\n", surn_int(5, 10)); рrintf("Функция surn_douЬle(): %.2f\n", surn_douЫe(З.125, 5.6)); // Освобождаем ресурсы библиотеки FreeLibrary(hdll); return О; Скомпилируем программу в командной строке и запустим ее на выполнение: C:\Users\Unicross>cd C:\book C:\book>set Path=C:\msys64\mingw64\bin;%Path% C:\Ьook>gcc -Wall -Wconversion -03 -f input-charset=cp1251 -fexec-charset=cpl251 -о test.exe test.c C:\Ьook>test.exe Путь: C:\Ьook\rnylib\liЬmodules_1 _2 _0.dll Функция surn_int(): 15 Функция surn_douЬle(): 8,72 445 В программе из листинга 16.9 мы указали относительный путь к библиотекам. Библиотека libmodules_1 _2 _0 .dll должна быть расположена во вложенном каталоге mylib. Если библиотека не найдена, то в окне консоли получим следующее сооб­ щение: C:\Ьook>test.exe Библиотека не найдена При использовании функций LoadLibraryA( J и LoadLibraryw( J поиск динамической библиотеки выполняется в следующем порядке: □ в каталоге с запускаемой программой; □ в каталоге, указанном с помощью функций SetDllDirectoryA() и SetDllDirectoryW(); □ в системном каталоге Windows\System32. Путь к этому каталогу можно получить с помощью функций GetSysternDirectoryA( J и GetSysternDirectoryW( J. Прототипы функций: #include <windows.h> UINT GetSysternDirectoryA(LPSTR lpBuffer, UINT uSize); UINT GetSysternDirectoryW(LPWSTR lpBuffer, UINT uSize);
446 Глава 16 □ в системном каталоге Windows\system; □ в системном каталоге Windows. Путь к этому каталогу можно получить с по­ мощью функций GetWindowsDirectoryA() и GetWindowsDirectoryW(). Прототипы функций: #include <windows.h> UINT GetWindowsDirectoryA(LPSTR lpBuffer, UINT uSize); UINT GetWindowsDirectoryW(LPWSTR lpBuffer, UINT uSize); □ в каталогах, прописанных в системной переменной окружения РАТН. 16.2.4. Экспортируемые и внутренние функции Экспортируемые функции могут использоваться внешними программами, а внут­ ренние функции могут вызываться только из функций внутри динамической биб­ лиотеки. По умолчанию MinGW делает все функции экспортируемыми, однако ес­ ли в программе перед определением функции явно указана инструкцИJ1 _declspec (dllexport), то экспортироваться будут только функции с этой инструк­ цией, а другие станут недоступными для внешнего использования. Например, если мы изменим код из листинга 16.2 следующим образом: #include "modulel.h" _ declspec(dllexport) int sum int(int х, int у) { return х + у; то функция sum_int () будет доступна, а вот функция sum_douЬle() из листинга 16.4 станет недоступной. 16.2.5. Функция DI/Main() Динамические библиотеки могут и�еть глобальные переменные, которые для каж­ дого процесса создаются отдельно. Для инициализации этих переменных, а таюке для других целей, внутри библиотеки можно создать функцию с предопределенны� названием DllMain(), которая является точкой входа в библиотеку. Функция вызы­ вается при подключении библиотеки процессом или потоком, а также при отклю­ чении от нее. Определить причину вызова можно через второй параметр функции DllMain(). Помните, что внутри функции DllMain() можно выполнять далеко не все операции. Существует очень большой список ограничений, который можно найти в документации. Пример реализации функции: // #include <windows.h> В001 WINAPI DllMain(HINSTANCE hinst, DWORD dwReason, LPVOID lpRes) { switch (dwReason) { case DLL PROCESS АТТАСН: - - // Загрузка DLL процессом break;
Создание библиотек case DLL TНREAD АТТАСН: // Загрузка DLL ПОТОКОМ break; case DLL TНREAD DETACH: // Отсоединение от DLL потоком break; case DLL PROCESS DETACH: // Отсоединение от DLL процессом break; returп TRUE; 447
ГЛАВА 17 Прочее В этой главе мы рассмотрим возможность запуска функции при завершении работw приложения, научимся выполнять системные команды и получать значения пере-­ менных окружения, изучим директивы препроцессора, а также создадим значок д.111 ЕХЕ-файла. 17.1. Регистрация функции, выполняемой при завершении программы Функции atexit() и _onexit() позволяют зарегистрировать функцию, которая бу­ дет вызвана при завершении программы. Внутри этой функции можно выполниn различные завершающие действия. Прототипы функций: #include <stdlib.h> int atexit (void (*func) (void)); _ onexit_t _o nexit(_onexit_t func); typedef int (*_onexit_t)(void); Функция аtex i t() возвращает значение о, если функция успешно зарегистрирована и ненулевое значение при ошибке. Функция _onexit() возвращает указатель на за­ регистрированную функцию или значение NULL при ошибке. Можно зарегистрировать сразу несколько функций. В этом случае QНИ будут вызы­ ваться в обратном порядке: вначале будет вызвана последняя зарегистрированнu функция, а последней - самая первая зарегистрированная функция (листинг 17.1). Листинг 17.1 . Регистрация функций, выполняемых при завершении программы #include <stdio.h> #include <locale.h> #include <stdlib.h> void funcl(void) { puts( "Выход из программы: funcl");
Прочее void func2(void) puts("Bыxoд из программы: func2"); int funcЗ(void) { рuts("Выход из программы: funcЗ"); return О; int rnain(void) setlocale(LC_ALL, "Russian_Russia .1251"); if (atexit(funcl) != О) { puts("He удалось зарегистрировать функцию funcl"); if (atexit(func2) != О) { puts("He удалось зарегистрировать функцию func2"); if ( ! _onexit{funcЗ) ) { puts("He удалось зарегистрировать функцию funcЗ"); ·printf("%s\n", "Текст, выводимый на консоль"); return О; Результат в окне консоли: Текст, выводимый на консоль Выход из программы: funcЗ Выход из программы: func2 Выход из программы: funcl 449 Зарегистрированные функции будут вызваны при нормальном завершении про­ граммы, а также при вызове функции exit(). Если для досрочного завершения про­ граммы использовались функции _Exit{) и _exit{), то зарегистрированные функ­ ции вызваны не будуг. Прототипы функций: #include <stdlib.h> void exit(int code); void _Exit{int code); void _exit(int code); 17.2. Выполнение системных команд Функции system() и _wsystem() позволяют выполнить системную команду command. Прототипы функций: #include <stdlib.h> int system(const char *coппnand); int _wsystem(const wchar t *command);
450 Глава 17 Если в качестве параметра указать значение NULL, то функция проверит наличие интерпретатора команд и вернет ненулевое значение, если интерпретатор найден. или значение о - в противном случае. Если задана команда, то при удачном вы­ полнении команды функция вернет значение о, в противном случае - значение - :. Код ошибки присваивается переменной errno. Очистим окно консоли от команд с помощью функции system(): system("cls"); Зададим для консоли кодировку windows-1251 с помощью функции_wsystem(): _wsystem(L"chcp 1251"); Если нужно не просто выполнить команду, но и получить результат ее выполненИL то можно воспользоваться функциями_popen() и_wpopen(). Прототипы функций: #include <stdio .h> FILE *_popen(const char *command, const char *mode); FILE *_wpopen(const wchar_t *command, const wchar_t *mode); В первом параметре функции принимают команду в виде строки. Во втором пара­ метре можно указать следующие режимы (или их комбинацию): □ r-чтение; □ w-запись; □ ь - бинарный режим; □ t - текстовый режим. Функции возвращают указатель на структуру FILE или нулевой указатель в случае ошибки. Используя этот указатель, можно читать и записывать данные с помощью функций для работы с файлами. После окончания работы нужно вызвать функцию _pclose() . Функция возвращает код завершения или значение -1 в случае ошибки. Прототип функции: #include <stdio.h> int _pclose(FILE *File); Пример получения результата выполнения команды приведен в листинге 17.2. ,Листинг 17.2. Получение результата выnолне1:1ия системной команды #include <stdio.h> #include <stdlib.h> #include <locale.h> #include <string.h> #include <windows.h> int main(void) { setlocale(LC_ALL, "Russian_Russia.1251"); FILE *fp = _popen("dir", "rt");
Прочее if (fp) char buf[200) = {О}; while (fgets(buf, 200, fp)) if (ferror(fp)) { // Проверяем наличие ошибок } printf("Error: %s\n", strerror(ferror(fp))); exit(l); // Преобразование windows-866 в windows-1251 OemToCharA(buf, buf); printf("%s", buf); printf("\n Код возврата: %d\n", _pclose(fp)); else рuts("Ошибка"); return О; 17.3. Получение и изменение значений системных переменных 451 Все системные переменные досrупны через третий параметр функции main( J • Давайте сохраним код из листинга 17 .3 в файле C:\Ьook\test.c, скомпилируем его и запустим. #include <stdio.h> int main(int argc, char *argv[), char **penv) { printf ("argc = %d\n", argc); for(inti=О;i<argc;++i) printf("%s\n", argv[i]); while (*penv != NULL) { printf("%s\n", *(penv++)); return О; Запускаем командную строку и компилируем программу: C:\Users\Unicross>cd C:\book C:\tюok>set Path=C:\msys64\mingw64\bin;%Path% C:\tюok>gcc -Wall -Wconversion -03 -о test.exe test.c
452 Глава 17 Для запуска программы вводим команду: C:\book>test.exe -paraml -param2 В результате получим все аргументы, переданные в командной строке, а затем спи­ сок всех системных переменных в формате переменная=значение: ComSpec=C:\Windows\system32\cmd.exe PATHEXT=.COM;.EXE;.BAT;.CМD;.VВS;.VВE;.JS;.JSE;.WSF;.WSH;.MSC Для того чтобы получить значение какой-либо одной переменной по ее имени. можно воспользоваться следующими функциями. □ getenv() и _wgetenv() - возвращают указатель на значение или нулевой указа­ тель в случае ошибки. Прототипы функций: #include <stdlib.h> char *getenv(const char *varName); wchar_t *_wgetenv(const wchar_t *varName); Пример получения значения системной переменной РАтн: setlocale(LC_ALL, "Russian_Russia.1251"); char *р = getenv("PATH"); if (р) printf("%s\n", р); else puts("He удалось получить значение"); Вместо функций getenv() и _wgetenv() можно использовать функции getenv_s ( и _wgetenv_s(). Функция getenv_s() в MinGW недоступна. Прототипы функций: #include <stdlib.h> errno t getenv_s(size_t *returnSize, char *destBuf, rsize_t destSize, const char *varName); errno t _wgetenv_s(size_t *returnSize, wchar_t *destBuf, size_t destSizeinWords, const wchar_t *varName); Если в параметре destвuf указать значение NULL, то в переменную returnSize будет записан требуемый размер буфера. Если переменная varName не найдена. то переменная returnsize будет иметь значение о. В третьем параметре указыва­ ется размер буфера destBuf. Функции вернут значение о, если операция успешно произведена, или код ошибки, например, если размер буфера недостаточен или переменная не существует. Пример: setlocale(LC_ALL, "Russian_Russia.1251"); wchar_t buf[256] = {О}; size t returnSize = О; errno t st = _wgetenv_s(&returnSize, buf, 256, L"PATHEXT"); if(st==О){ wprintf(L"%I64u\n", returnSize); wprintf(L"%s\n", buf); else _putws(L"He удалось получить значение");
Прочее 453 □ _dupenv_s() и _wdupenv_s() - получают значение переменной varName и сохра­ няют его в динамической памяти. После завершения работы с переменной нуж­ но освободить память с помощью функции free(). В MinGW функции не рабо­ тают. Прототипы функций: #include <stdlib.h> errno t _dupenv_s(char **pBuf, size_t *pBufSizeinВytes, const char *varName); errno t _wdupenv_s(wchar_t **pBuf, size_t *pBufSizeinWords, const wchar_t *varName); Пример (в проектах тest32c и Test64c пример не работает): setlocale(LC_ALL, "Russian_Russia.1251"); char *р = NULL; size_t bufSize = О; errno_t st = _dupenv_s(&p, &bufSize, "РАТН"); if(st==О){ printf("%I64u\n", bufSize); printf("%s\n", р); free(р); else puts("He удалось получить значение"); Добавить новую переменную для текущего процесса, изменить значение сущест­ вующей переменной или удалить переменную позволяют функции _putenv() и _wputenv(). Прототипы функций: _#include <stdlib.h> int _putenv(const char *envString); int _wputenv(const wchar_t *envString); В качестве параметра указывается строка, имеющая формат переменная=значение. Если указать значение переменная=, то переменная будет удалена. Функции возвра­ щают значение о, если операция успешно выполнена, или значение -1 - в случае ошибки. Пример: _putenv("МY_VAR=lO"); char *р = getenv("МY_VAR"); if (р) printf("%s\n", р); // 10 Можно также воспользоваться функциями _putenv _s() и _wputenv_s(). Прототипы функций: #include <stdlib.h> errno_t _putenv_s(const char *name, const char *value); errno_t _wputenv_s(const wchar_t *name, const wchar_t *value); В первом параметре задается название переменной, а во втором - ее значение. Для того чтобы удалить переменную, нужно во втором параметре передать пустую
454 Глава 17 строку. Функции возвращают значение о, если операция успешно выполнена, или код ошибки. Пример: _putenv_s("МY_VAR", "10"); char *р = getenv("МY_VAR"); if (р) printf("%s\n", р); // 10 17.4. Директивы препроцессора Перед компиляцией запускается специальная программа - препроцессор, котора.1 подготавливает код к компиляции. В исходном коде мы можем использовать сле­ дующие директивы препроцессора: □ #include- позволяет вставить все содержимое указанного файла на место директивы (директиву мы уже рассматривали в разд. 2. 4): #include <stdio.h> #include "HelloWorld.h" #include "C:\\cpp\\projects\\HelloWorld\\src\\HelloWorld.h" □ #define - создает константу (см. разд. 3 .8) или встраиваемую функцию (см. разд. 10 .12). Название, указанное в директиве #define, принято называть макроопределением или макросом. Пример определения макросов: #define МАХ РАТН 260 #define _max(a,b) (((а) > (bl) ? (а) : (Ь)) □ #undef - удаляет макрос: #undef МУ CONST □ #if- проверяет условие. Формат директивы: #if <Условие 1> <Если условие 1 истинно> #elif <Условие 2> <Если условие 2 истинно> #elif <Условие N> <Если условие N истинно> #else <Если все условия ложны> #endif Пример: #define МУ CONST 3 #ifМУCONST==1 puts("МY_CONST 1"); #elif МУ CONST >= 2 puts("МY_CONST >= 2"); #else рuts("Блок else"); #endif
Прочее . 455 После обработки препроцессор оставит только одну инструкцию (остальные инструкции будут удалены): puts("МY_CONST >= 2"); □ #ifdef- проверяет наличие макроса с указанным именем. Формат директивы: Jtifdef <Макрос> <Е= макрос определен> #else <Е= макрос не определен> #endif Пример: #ifdef POSIX #define _P _tmpdir "/" #else #define _P_tmpdir "\\" #endif □ #ifndef- проверяет отсутствие макроса с указанным 1:tменем. Формат дирек­ тивы: #ifndef <Макрос> <Если макрос не определен> #else <Е= макрос определен> #endif □ #pragrna - задает действие, зависящее от реализации компилятора. Например, указать, что заголовочный файл должен быть включен только один раз, можно так: #pragrna once Включить вывод предупреждающих сообщений при использовании Visual С можно так: #pragrna warning( push, 4) Добавить библиотеку User32.lib при использовании Visual С позволяет такая ди­ ректива: #pragrna comment(lib, "User32.lib") □ #warning - выводит предупреждающее сообщение; □ #error- выводит сообщение об ошибке. 17.5. Создание значка приложения По умолчанию для ЕХЕ-файла используется стандартный значок. В Windows мы можем добавить собственный значок, внедрив его в ЕХЕ-файл как ресурс. Попро­ буем создать значок для приложения. Вначале в каталог С:\Ьооk добавляем файл со
456 Глава 17 значком, например my_icon.ico. Затем создаем текстовый файл с названием resources.rc и вставляем в него следующую инструкцию: 001 ICON "my_icon.ico" Запускаем командную строку и переходим в каталог C:\book: C:\Users\Unicross>cd C:\book Настраиваем переменную окружения РАтн: C:\book>set Path=C:\msys64\mingw64\bin;%Path% Компилируем файл ресурсов с помощью программы windres.exe: C:\book>windres --use-temp-file -iresources.rc -oresources.o В результате в каталоге C:\book будет создан файл resources.o, который мы должны передать компилятору при сборке программы. Для этого в редакторе Eclipse откры­ ваем свойства проекта (в меню Project выбираем пункт Properties) и из списка сле­ ва выбираем пункт С/С++ Build I Settings. На отобразившейся вкладке из списка Configuration выбираем пункт Release, а затем на вкладке Tool Settings выбираем пункт MinGW С Linker I Miscellaneous. В список Otber objects (рис. 17.1) добав­ ляем следующее значение: с:\\book\\resources. о . Сохраняем настройки проекта.. нажимая кнопку Apply and Close. i type filter text : Settings !'> Resource Builders • С/Сн Build Build VariaЫes Environment Logging Settings Тool Chain Editor � С/С++ General linuxToorsPath Project Natures Project References Run/Debug Settings :, Тask Repository Task Tags ;> Validation WikiText • � GCC AssemЫer � General • � GCC С Compiler @ Dialect ® Preprocessor � lncludes @ Optimi : iation @ Debugging @ Warnings � Miscellaneous • � MinGW С Linker k_� General ® libraries � Miscellaneous Properties for Тest64c Linker flags Other <>ptions (-Xlinker [option]) @ Shared Libra,y Settings Other objects "C:\\book\\resources.o" Рис. 17 .1. Указание местоположения файла ресурсов - □
Прочее IQ,111)(!1. Release - Главная Общий доауn Вид • 1',j;i«срр•proj«ts►Т6t64<►Releмe � Бибпиоте.и ■ Видео §1 Документы � И:юбр<IЖ�ИЯ Ji Музыка 4 ДомашН>IЯ группа 111 Компьютер Мi10S(C:J d, DATA(D:) ,., � Сеть "< Элементов.: 2 Выбран 1 >лемент Имя □ Поис,: Rclea« Дата измене11н:я Тип 2.7.012019 9:19 Паnка �. файлами 27.01.2019 9:19 Приложен►1е Рис. 17.2. Пользовательский значок слева от названия приложения ! type fi�er text i,, Rl5ource Builders Properties for Тest64c Settlnp ..,о J) � С/С++ Build Build VariaЫes Environment Logging Settings Configurotion: � [ Acliw] "1!м...аg,,ConfiguRtions... 1 Тool Chain Editor � С/С++ General LinuxToolsPath Project Natures Project References Run/Debug Settings � Тask Repository Task Tags � Validation Wiblext �Тоо1Settings ОCoruinerSettingsj.;,.BuildSteps i '8uildArtilкt 1m8iNryРовев О�..� Pre-build steps Command: windres •·use-temp-file -iC:\book\resources.rc •oC:\Ьook\resources,o Oescription: Post-build steps Comm!nd: Oescription: у, у 457 " " 1дрр1у -ic1ose1 1.___с _..се1 �__, Рис. 17.3. Добавление команды для компиляции файла ресурсов
458 Глава 17 Осталось скомпилировать программу в режиме Release. В окне Console редактора Eclipse должна быть инструкция: gcc -о Test64c.exe "src\\Test64c.o" "C:\\book\\resources.o" Заходим в каталог C:\cpp\projects\Тest64c\Release. Слева от названия исполняемого файла должен быть виден значок (на рис. 17 .2 значок отображается в виде установ­ ленного флажка). Если вы хотите, чтобы файл ресурсов компилировался каждый раз перед компиля­ цией программы, то на вкладке Build Steps в разделе Pre-build steps в поле Command (рис. 17.3) нужно ввести следующую инструкцию: windres --use-temp-file -iC:\book\resources.rc - oC:\book\resources.o В окне Console редактора Eclipse перед командами компиляции должна быть команда: windres --use-temp-file "-iC:\\book\\resources.rc" "-oC:\\book\\resources.o"
Заключение Вот и закончилось наше путешествие в мир языка С. Материал книги описывает лишь основы языка. А здесь мы уточним, где найти дополнительную информацию. Самым важным источником информации является официальный сайт библиотеки MinGW: http://www.mingw.org/. На этом сайте вы найдете новости, а также ссылки на все другие ресурсы в Интернете, посвященные MinGW. Не следует таюке забы­ вать о существовании страницы https://sourceforge.net/projects/mingw-w64/, на которой доступна для скачивания самая последняя версия библиотеки MinGW-W64. На странице https://gcc.gnu.org/ расположена документация по GCC, которая об­ новляется в режиме реального времени. Библиотека постоянно совершенствуется, появляются новые функции, изменяются параметры функций и т. д. Регулярно по­ сещайте эту страницу, и вы будете в курсе самых свежих новшеств. На странице https://ru.wikipedia.org/wiki/Cи_(язык_nporpaммиpoвaния) вы най­ дете историю развития языка С, а также действующие ссылки на все другие ресур­ сы в Интернете, посвященные этому языку. На языке С можно создавать оконные приложения с помощью библиотеки GTK+. Подробную информацию смотрите на странице: https://www.gtk.org/. Если в процессе изучения языка С у вас возникнут какие-либо недопонимания, то не следует забывать, что Интернет предоставляет множество ответов на самые раз­ нообразные вопросы. Достаточно в строке запроса поискового портала (например, http://www.google.com/) набрать свdй вопрос. Наверняка уже кто-то сталкивался с подобной проблемой и описал ее решение на каком-либо сайте. Свои замечания и пожелания вы можете оставить на странице книги на сайте изда­ тельства "БХВ-Петербурr": http://www.bhv.ru/. Все замеченные опечатки и неточ­ ности прошу присылать на e-mail: mail@bhv.ru- не забудьте только указать на­ звание книги и имя автора.
ПРИЛОЖЕНИЕ Описание электронного архива По ссылке ftp:l/ftp.bhv.ru/9785977541169.zip можно скачать электронный архив с исходными кодами рассмотренных примеров к книге. Ссьmка доступна также со страницы книги на сайте www.bhv.ru. Структура архива представлена в табл. Пl. Таблица П1. Структура электронного архива Файл Описание Listings.doc Содержит все пронумерованные листинги из книги, а также некоторые полезные фрагменты кода Readme.txt Описание электронного архива
Предметный указатель # #define 51,63,101, 102,187,317,334,344, 454 #elif454 #else 455 #endif51,63,317,454,455 #error 455 #if454 #ifdef455 #ifndef51,63,317,455 #include 45, 51, 62,316,318,454 #pragma 51,63,455 #undef103,454 #waming 455 DATE 103 _declspec(dllexport) 446 _FILE_103 _ inline 333 _ intl6 97 _ int32 97 _ int64 97 _int8 97 _ LINE_ 103 _max() 163 _min() 163 _ mingw_printf{) 69,70, 100 _SТ DC_VERSION_ 9 _ TlME_ 103 _time32_t 300 _ time64_t 300 VERSION 9 ....,...wcserror() 340 _wcserтor_s() 340 А ARCH 404 А HIDDEN 404 А NORМAL404 А RDONLY 404 А SUBDIR 404 А SYSTEM 404 _ abs64() 160 _access() 391 _a cces s _s() 392 _ atof_l() 171 _atoi _l() 165 _atoi64() 167 _ atol_l() 166 _ beginthread() 418 _ beginthreadex() 417 _ Bool 93, 156 _ chdir() 402 _ chdrive() 399 _ chmod() 393 _close() 377 _ commit() 378 _countof() 183 _ create_locale() 214 _ CRT_SECURE_NO_WARNINGS 232,277 _ctime64() 306 _ ctime64_s() 307 _ diffiime64() 304 _ dup() 384 _ dup2() 371,384 _ dupenv_s() 453 _ endthread() 419 _ endthreadex() 417 _ eof() 380 _execl() 426 _ e xecle() 426 _ex eclp() 426 _ execlpe() 426
464 _execv() 426 _execve() 426 _execvp() 426 _ execvpe() 426 _exit() 449 _Exit() 449 _ fcloseall() 354 _fdopen() 383 _fi lelength() 397 _ fi lelengthi64() 397 _ fileno() 371,383 _ findclose() 403,404 finddata64i32 t 404 - - _ findfirst() 403 _findfirst32() 406 _ findfirst32i64() 406 _findfirst64() 406 _ findfirst64i32() 403,404 _ findnext() 403 _ findnext32() 406 _ findnext32i64() 406 _findnext64() 406 _ findnext64i32() 403,404 _finite() 179 _ finitef() 179 _ free_locale() 214 _ fseeki64() 366 _ fsopen() 353 _ fstat() 397 _ fstat32() 397 _ fstat32i64() 397 _ fstat64() 397 _ fstat64i32() 397 _ fstati64() 397 _ ftelli64() 366 _ fullpath() 386 _futime() 398 _ futime32() 398 _ futime64() 398 _ get_current_locale() 214 _ getch() 80, 81,84 _ getche() 80 _ getcwd() 354,401 _ getdcwd() 401 _getdrive() 399 _ getdrives() 399 _ getpid() 429 _ getwch() 248 _getwche() 248 _ getws() 261 _gmtime64() 302 _gmtime64_s() 302 _ i64toa() 175 _ i64toa_s() 175 _ i64tow() 295 _i64tow_s() 295 _isalnum_l() 219 _ isalpha_l() 219 _iscntrl_l() 221 _ isdigit_l() 218 _isgraph_l() 221 _ islower_l() 220 _isnan() 180 _ isnanf() 180 _isprint_l() 221 _ispunct_l() 220 _isspace_ 1() 219 _isupper_l() 220 _isxdigit_l() 218 _itoa() 174 _itoa_s() 174 _ itow() 294 _itow_s() 294 locale t 214 - - _localtime64() 303 Предметный указатель _ localtime64_s() 303 _lseek() 381 _ lseeki64() 381 _ltoa() 175 _ltoa_s() 175 _ltow() 295 _ltow_s() 295 _ makepath() 389 _makepath_s() 389 _МАХ_РАТН 354,386,401 _m emicmp() 203,241 _m emicmp_l() 203,241 _mkdir() 402 _ mktime64() 304 О APPEND 375 _O _BINARY 375 _O _CREAT 375,376 О EXCL 375 О NOINHERIТ 375 О RANDOM 375 О RDONLY 374 • _O _RDWR 374 _ O_SEQUENТIAL 375 О SHORT LIVED 375 - - - _O _TEMPORARY 375,381 О ТЕХТ 375 О TRUNC 375 _O _Ul6TEXT 375 О U8ТЕХТ 375
Предметный указатель _ O_WRONLY 374 _ О _WTEXT 375 _onexit() 448 _open() 374 _Р_DETACH 429 _ P_NOWAIТ 429 _Р_NOWAIТO 429 Р OVERLAУ 428 Р WAIТ 428 _ pclose() 450 _popen() 450 _printf_l() 65,66 _ putenv() 453 _ putenv_s() 453 _ putws() 260 _ r ead() 377 _ rmdir() 402 S IEXEC 396 S IFCHR 396 _S _IFDIR 396 _ S_IFIFO 396 _S _IFMT 396 _ S_IFREG 396 S IREAD 375,393,396 =S)WRIТE 376,393,396 _ SH_DENYNO 353,376 _ SH_DENYRD 353,376 _ SH_DENYRW 353,376 _ SH_DENYWR 353,376 _ snscanf() 173 _snwscanf() 293 _ s open() 376 _sopen_s() 376 _ spawnl() 427 _ spawnle() 427 _ spawnlp() 427 _ spawnlpe() 428 _spawnv() 428 _spawnve() 428 _ spawnvp() 428 _ spawnvpe() 428 _splitpath() 388 _splitpath_s() 387 _sprintf_l() 176, 242 _sprintf_s _l() 176,242 _ stat() 395,396 _stat32() 395 _stat32i64() 395 stat64 396 _stat64() 395,396 _stat64i32 395 _stat64i32() 395 _stati64() 395 _ strcoll_l() 238 _ strdup() 230 _ strerror() 340 _ strerror_s() 340 _ stricmp() 240 _ stricmp_l() 240 _ stricoll() 240 _ stricoll_l() 240 _strlwr() 217,236 _strlwr_s() 218, 237 _stmcoll() 238 _ strncoll_l() 238 _strnicmp() 240 _ strnicmp_l() 240 _strnicoll() 240 _strnicoll_l() 240 _stmset() 235 _stmset_s() 235 _ strrev() 236 _ strset() 234 _ strset_s() 235 _ strtod_l() 171 _strtoi64() 169 _ strtol_l() 168 _strtoui64() 169 _ strtoul_l() 168 _ strupr() 217,236 _ strupr_s() 217,236 _ strxfrm_l() 239 _tell() 380 _telli64() 380 _tempnam() 369 _ time64() 301 _ tolower_l() 217 _ toupper_ 1() 216 _ ui64toa() 176 _ ui64toa_s() 176 _ ui64tow() 296 _ ui64tow_s() 296 _ цltoa() 175 _ ultoa_s() 175 _ ultow() 295 _ ultow_s() 295 _ unlink() 390 USE МАТН DEFINES 160 - - utimbuf 398 _utime() 398 _ utime32() 398 _ utime64() 398 _waccess() 391 _ waccess_s() 392 465
466 _wasctime() 305 _wasctime_s() 306 _wchdir() 402 _wchmod() 393 _w c reate_locale() 214 _ wcsdup() 275 _wcserror() 339 _w cserror_s() 339 _ wcsicmp() 284 _ wcsicoll() 285 _w cslwr() 250,281 _ wcslwr_s() 251,282 _ wcsncoll() 283 _w csnicmp() 284 _ wcsnicoll() 285 _ wcsnset() 280 _w csnset_s() 280 _ wcsrev() 281 _wcsset() 279 _ w csset_s() 280 _ wcstoi64() 290 _ wcstoui64() 290 _wcsupr() 250,281 _ wcsup(_s() 250,281 _ wctime() 306 _ wctime_s() 307 _wctime64() 306 _ wctime64_s() 307 _ wdupenv_s() 453 _ wexecl() 426 _wexecle() 426 _ wexeclp() 426 _ wexeclpe() 426 _wexecv() 426 _wexecve() 426 _ wexecvp() 426 _wexecvpe() 426 _wfdopen() 383 wfinddata64i32 t 406 - - _wfindfirst32() 406 _ wfindfirst32i64() 406 _ wfindfirst64() 406 _wfindfirst64i32() 406 _ wfindnext32() 406 _ wfindnext32i64() 406 _ wfindnext64() 406 _wfindnext64i32() 406 _wfopen() 351,356 _ wfopen_s() 352 _ wfreopen() 370 _ wfreopen_s() 371 _wfsopen() 353 Предметный указатель _wfullpath() 386 _ wgetcwd() 354,401 _ wgetdcwd() 401 _wgetenv() 452 _wgetenv_s() 452 _wmakepath() 389 _ wmakepath_s() 389 _wmkdir() 402 _wopen() 374 _wperror() 341 _ wpopen() 450 _wputenv() 453 _wputenv_s() 453 _wremove() 390 _ wrename() 390 _ write() 377 _wrmdir() 402 _ wsetlocale() 213,247,260 _ wsopen() 376 _ wsopen_s() 376 _ wspawnl() 427 _ wspawnle() 427 _ wspawnlp() 428 _wspawnlpe() 428 _ wspawnv() 428 _ wspawnve() 428 _ wspawnvp() 428 _wspawnvpe() 428 _ wsplitpath() 388 _wsplitpath_s() 387 _ wstat() 395 _wstat32() 395 _ wstat32i64() 395 _wstat64() 395,396 _wstat64i32() 395 _wstati64() 395 _ wsystem() 449,450 _ wtempnam() 369 _ wtmpnam() 368 _ wtmpnam_s() 368 .:_wtof() 291 _wtoi() 286 _ wtoi64() 288 _wtol() 286 _ wtoll() 287 _w unlink() 390 _w utime() 398 _ wutime32() 398 _w utime64() 398
Предметный указатель А abort() 89 аЬs() 160 acos() 165 acosf() 165 acosl() 165 ar.exe 432 ASCII 207 asctime() 305 asctime_s() 306 asin() 165 asinf() 165 asinl() 165 atan() 165 atanf() 165 atanl() 165 atexit() 448 atof() 171 atoi() 165 atol() 166 atoll() 167 auto 104 в ВОМ356 Ьоо193 break 148, 149,151,153 bsearch() 200 btowc() 255 Byte Order Mark 356 с -с 432 cl6rtomb() 298 c32rtomb() 298 calloc() 119,189 case 147,148,149 ceil() 164 ceilf() 164 ceill() 164 char 93,110,156,206,207,222,255 CHAR BIT 93 CHAR МАХ93 CHAR MIN 93 charl6 t 297 char32 t 297 CharToOemA() 266 CharToOemBuffA() 266 clearerr() 363 clearerr_s() 363 clock() 300,311 clock t 300 CLOCKS PER SEC 311 CloseHandle() 410,413 Cmake 28 467 const 101,116,182,183,187,225,258,325 continue 153 cos() 165 cosf() 165 cosl() 165 ср1251 209 ср866 209 CreateMutexA() 413 CreateMutexW() 413 CreateThread() 407 ctime() 306 ctime_s() 307 С-строка 110,206 D -D REENTRANT 420 Debug 54 default 147 DeleteCriticalSection() 415 diffiime() 304 div() 162 DLL 438 DIIMain() 446 do ... while 152,153 douЫe 94, 158 DWORD 71 Е Eclipse 29,34,47,64 else 142-144 EnterCriticalSection() 415 enum 129 EOF 354 ERANGE 339 errno 339, 341,358,374 exit() 89,449 EXIТ_FAILURE 61,89 EXIТ_SUCCESS 61,89 · ExitThread() 409 ехр() 161 expf() 161 expl() 161 extem 104
468 F FОК391 fabs() 160 fabsf() 160 fabsl() 160 false 93 fclose() 353 feof() 363 ferror() 363 -fexec-charset 38, 39,47,244 fflush() 71,75,85,358,372 fgetc() 360 fgetpos() 365 fgets() 77, 358,361 fgetwc() 360 fgetws() 261,361 FILE 351,352 FILE_ATTRIBUTE HIDDEN 394 FILENAME_MAX 354 -finput-charset 38,39,47,244 tloat 94,158 floor() 164 tloorf() 164 tloorl() 164 fmax() 163 fmaxf() 163 fmaxl() 163 fmin() 163 fminf() 163 fminl() 163 fmod() 162 fmodf() 162 fmodl() 162 fopen() 351,356 FOPEN_MAX 352 fopen_s() 352 for 150,151,185,226,259 - fPIC 439 fpos_t 365 fprintf() 358,359 fprintf_s() 360 fputc() 358 fputs() 358,359 fputwc() 359 fputws() 359 fread() 364 free() 119,189 FreeLibrary() 443 freopen() 370 freopen_s() 371 fscanf() 361 Предметный указаmе/8J fseek() 366 fsetpos() 366 ftell() 365 -fwide-exec-charset 244 fwprintf() 360 fwprintf_s() 360 fwrite() 363 fwscanf() 362 G g++.exe 18 gcc.exe 9,15,18,20,47 gdb.exe 345 getc() 360 getchar() 71,77,85 GetCurrentThread() 41О GetCurrentThreadld() 41 О GetDiskFreeSpaceEx() 400 GetDiskfreeSpaceExA() 400 GetDiskFreeSpaceExW() 400 GetDriveType() 400 GetDriveTypeA() 400 GetDriveTypeW() 400 getenv() 452 getenv_s() 452 GetExitCodeThread() 409 GetFileAttributes() 394 GetLastError() 409 GetModuleFileNameA() 444 GetModulefileNameW() 444 GetProcAddress() 444 gets() 77 GetSystemDirectoryA() 445 GetSystemDirectoryW() 445 getwc() 360 getwcщir() 248,261 GetWindowsDirectoryA() 446 GetWindowsDirectoryWO 446 Glade 28 gmtime() 301 gmtime_s() 302 goto 154 GTK+ 28 н HUGE_VAL 172 Нулевой указатель 112
Предметный указатель 469 iswupper() 253 iswxdigit() 251 - 151,433 isxdigit() 218 if 142,144,146 imaxabs() 161 J imaxdiv() 163 INFINITE 409 Java 29 INFINITY 134,179 Java Development Kit 29 InitializeCriticalSection() 415 Java Runtime Environment 29 inline 333 JDK 29 int 93,97,156 JRE29 INT_MAX 94 INT MIN 94 к INТI 6_МАХ98 INТ16 MIN 98 koi8- r 209 intl6 t 98 INT32_MAX 98 L INT32_MIN 98 int32_t 98 -1433 INT64 МАХ98 -L 433 INT64 MIN 98 labs() 160 int64_t 98,99 LC ALL 88,212 INT8 МАХ98 LC=COLLATE 88,212 INT8_MIN 98 LC СТУРЕ 88,212 int8 t 98 LC-MONETARY 88,212 INТМАХ_МАХ170 LC=NUMERIC 88,212 INТМAX_MIN 170 LC_ТIME 88,212 intmax t 170 lconv 215 isalnum() 219 ldiv() 162 isalpha() 219 LeaveCriticalSection() 415 isЫank() 221 llabs() 160 iscntrl() 221 ll div() 162 isdigit() 218 LLONG_МАХ97 isgraph() 221 LLONG_MIN 97 isinf() 180 LoadLibraryA() 443 islower() 220 LoadLibraryW() 443 isnan() 180 localeconv() 215 iso8859- 5 209 localtime() 302 isprint() 221 localtime_s() 303 ispunct() 220 log() 161 isspace() 219 logl0() 161 isupper() 220 logl0f() 161 iswalnum() 252 logl 01( ) 161 iswalpha() 252 logf() 161 iswЫank() 254 logl() 161 iswcntrl() 254 long 69,96,156 iswdigit() 251 long douЫe 70, 97, 158 iswgraph() 253 long int 96 iswlower() 252 long long 69,96, 156 iswprint() 253 long long int 96 iswpunct() 253 LONG_LONG_MAX 97 iswspace() 252 LONG_LONG_MIN 97
470 LONG_MAX96 LONG MIN 96 -lpthread 420 Lucida Console 86 L-строки 244 м M_l_PI 159 М2PI159 M_2_SQRTPI 160 МЕ159 М LNl0 160 М LN2 159 М LOG l0E 159 М LOG2E 159 МPI159 М_Р1 _2 159 М PI 4159 M_SQRТl_2 160 M_SQRT2 160 mac-cyrillic 209 main() 45,60,61,82,451 Make 28 make.exe 23 malloc() 118,119,189 mbrtoc16() 298 mbrtoc32() 299 mbstowcs() 263,265 mbstowcs_s() 264 memchr() 233 memcmp() 203,241 memcpy() 201 memcpy_s() 202 memmove() 202 memmove_s() 202 memset() 235 Microsoft Visual С++ 42 MinGW 14 MINGW_HOME 37,41 mingw32.exe 24 mingw64.exe 24 MinGW-W64 20,24 mktime() 303 modf() 162 modff() 162 modfl() 162 MSYS 14 MSYS_HOME 37, 41 MSYS2 24 msys2.exe 24 MultiByteToWideChar() 267 N NAN 134,179,180 nanosleep() 31О Notepad++ 19 NULL 79,112, 113 о - о 47,432 -03 47 Предметный указатель OemToCharA() 266 OemToCharBuffA() 266 р РАТН 12,17,37,426,446,452 pause 85 perror() 341,358 pow() 161 powf() 161 powl() 161 PRld l6 99 PRld32 99 PRld64 99 PRid8 99 printf() 45,65,66,324,344 PRlu l6 99 PRlu32 99 PRlu64 99 PRlu8 99 -pthread 420 pthread_cancel() 422 pthread_create() 420 pthread_exit() 421,422 pthreadjoin() 42 - 1,422 pthread_mutex_destroy() 424 pthread_mutex_init() 423 pthread_mutex_lock() 424 pthread_mutex_trylock() 424 pthread_mutex_unlock() 424 PTHREAD_THREADS_MAX421 putc() 358 putchar() 65 puts() 65 putwc() 359 putwchar() 247 Q qsort() 196
Предметный указатель R R ОК391 rand() 177 RAND_МAX 177 ranlib.exe432 realloc() 122,189 register 104 Release 54 ReleaseMutex() 413 remove()390 rename()390 retum 45,60,312,313,328 rewind()363,366 round() 164 roundf() 164 roundl() 164 s scanf() 72,343 SCHAR_MAX 95 SCHAR MIN95 SEEK_CUR366,381 SEEK_END366,381 SEEK_SET366,381 SetDIIDirectoryA()443 SetDIIDirectoryW() 443 SetFileAttributes()394 setlocale()73,74,78,88,212,247,260 -shared439 short69,95, 156 short int95 SHRT МАХ96 SHRT MIN96 signed94,156 signed char94 signed int 95 signed long 96 signed long int96 signed long long96 signed long long int96 signed short 95 signed short int 95 sin() 165 sinf() 165 sinl() 165 size_t99,225,258 sizeof99,118,124,183,246 sleep() 309 Sleep()71,310,311,410 sprintf()176, 242 sprintf_s() 176,242 sqrt() 161 . sqrtf() 161 sqrtl() 161 srand() 177 sscanf() 173 static104,105,327,333 - static434 - std9 stderr341,358,369,382, 383 STDERR FILENO382 stdin75,78,369, 382 STDIN FILENO382 stdout71,369,382,384 STDOUT_FILENO382 strcat() 229 strcat_s() 230 strchr() 232 strcmp() 237 strcoll() 238 strcpy() 223,228 strcpy_s()223,228 strcspn() 234 strerror() 339,358 strerror_s() 339 strftime() 307,308 strlen() 225-227 strncat() 230 strncat_s() 230 stmcmp()237 stmcpy()228 stmcpy_s() 229 stmlen()228 strpbrk() 233 strrchr()233 strspn() 234 strstr() 234 strtod()171 strtof() 172 strtoimax()170 strtok() 231 strtok_ s() 231 strtol() 168 strtold() 172 strtoll() 168 strtoul() 168 strtoull() 168 strtoumax() 170 struct123,126 strxfrm() 239 switch 147, 148 swprintf_s() 296 471
472 swscanf() 293 system() 73,78,85,87,449,450 т tan() 165 tanf() 165 tanl() 165 TerminateThread() 409 time() 301 time t 300 tm 300 tmpfile() 367 tmpfile_s() 368 tmpnam() 368 tmpnam_s() 368 tolower() 217 toupper() 216 towlower() 250 towupper() 249 true 93 TryEnterCriticalSection() 415 typedef 100 u UCHAR МАХ95 UINT МАХ95 UfNTl6 МАХ98 uint\6 t 98 UfNT32 МАХ98 uint32 t 98 UfNT64 МАХ98 uint64 t 98 UfNT8 МАХ98 uint8 t 98 UfNTMAX МАХ170 uintmax t 170 ULLONG МАХ97 ULONG LONG МАХ97 - - ULONG МАХ96 UNICODE 356,375 union 127 unsigned 95,156,207 unsigned chat 95,156,222 unsigned int 95,222 unsigned long 96,156 unsigned long int 96 unsigned long long 156 unsigned long long int 97 unsigned short 96,156 unsigned short int 96 USHRT МАХ96 usleep() 31О UTF-16 297 Предметный указатель UТF-16BE 267 UТF-16LE 267,356,375 UTF-32 297 UTF-32BE 267 UTF-32LE 267 UTF-8 244,267,356,375 V va_arg() 324 va_end() 324 va list 324 va_start() 324 Visual С++ 42 Visual Studio 42 void 60,94,118,312,314,328 void* 331 volatile 97 w W ок391 WaitForMultipleObjects() 409 WaitForSingleObject() 409,413 -Wall 38,39,47,337 WCHAR МАХ245 WCHAR MfN 245 wchar_t 244,245,255,256 -Wconversion 38,39,47,58,131,337 wcscat() 274 wcscat_s() 274 wcschr() 277 wcscmp() 282 wcscoll() 283 wcscpy() 256,272 wcscpy_s() 256,272 wcscspn() 278 wcsftime() 307,308 wcslen() 258,271 wcsncat() 275 wcsncat_s() 275 wcsncmp() 282 wcsncpy() 273 wcsncpy_s() 273 wcsnlen() 271 wcspbrk() 278 wcsrchr() 278 wcsspn() 279 wcsstr() 279
Предметный указатель wcstod() 292 wcstof() 292 wcstoimax() 291 wcstok() 276 wcstok_s() 276 wcstol() 288 wcstold() 293 wcstoll() 289 wcstombs() 264,265 wcstombs_s() 264 wcstoul() 288 wcstoul\() 289 wcstoumax() 291 wcsxfrm() 284 wctob() 255 WEOF 255,360 while 152, 185,227,260 WideCharToMultiByte() 268 А Абсолютное значение l 61 Адрес переменной 112 Адресная арифметика 116 Арккосинус 165 Арксинус 165 Арктангенс 165 Б Бесконечность l 34, 179 Библиотека 430 ◊ динамическая 438 ◊ статическая 430 Бинарный поиск 199 Битовые поля 126 Блок 106, 144,155 в Ввод данных 71 Вещественные числа 158 ◊ точность вычислений 158 Возведение в степень l 61 Восьмеричные числа 157,175,295 Время 300 Встраиваемая функция 333 windows-1251 86,209,265, 267 windows-866 86, 209, 265, 267 windres.exe 456 WINT МАХ246 WINT MIN 246 wint t 246 - Wl,-Bdynamic 434 - Wl,-Bstatic 434 wmemchr() 278 wmemcmp() 285 wmemcpy() 272 wmemcpy_s() 272 wmemmove() 273 wmemmove_s() 273 wmemset() 280 wprintf() 248,261 wscanf() 249, 262 wtol\() 287 Вывод данных 65 Выполнение программы по шагам 345 Вычитание 133 г Глобальная переменная 105 д Дата 300 ◊ форматирование 305 Двоичные числа 175,295 Декремент 134 Деление 133 Десятичные числа 157 Десятичный логарифм 161 Динамическая библиотека 438 Динамическое выделение памяти 118 Директивы препроцессора 454 Диск 399 з 473 Завершение выполнения программы 89, 448 Заголовочный файл 50 Значок приложения 455
474 и Именование переменных92 Инициализация переменных100 Инкремент134 к Каталог401 ◊ перебор объектов403 ◊ преобразование пути386 ◊ создание402 ◊ текущий рабочий354 ◊ удаление402 Квадратный корень161 Ключевые слова92 Кодировка49,86,263,265 ◊ файла с программой244 Командная строка14 Комментарии63 ◊ многострочные64 ◊ однострочные63 Компилятор14 Консоль14 ◊ предотвращение закрытия окна 84 Консольное приложение 44 Константа1 О1 Косинус165 л Логарифм ◊ десятичный161 ◊ натуральный 161 Локаль212 Локальная переменная105 м Макроопределение101,334 Макрос101,334 Массив107,181 ◊ двумерный109,190 ◊ динамический189 ◊ доступ к элементам184 ◊ зубчатый121 ◊ изменение значения элемента184 ◊ инициализация181 ◊ количество элементов 183 ◊ копирование элементов 201 ◊ максимальное значение193 ◊ минимальное значение193 Предметный указатель ◊ многомерный109,190 ◊ объявление181 ◊ перебор элементов185 ◊ переворачивание204 ◊ поиск значения 198 ◊ получение значения элемента 184 ◊ проверка наличия значения 198 ◊ размер183 ◊ сортировка195 ◊ составной литерал188,320 ◊ сравнение203 ◊ указателей114,189 ◊ указатель на элемент186 Метка порядка байтов356 Многострочный комментарий64 Мьютекс413,423 н Натуральный логарифм161 Нулевой указатель 79 о Области видимости переменных105 Объединение127 Объявление ◊ переменной91 ◊ функции312 Однострочный' комментарий63 Операторы133 ◊ ?:146 ◊ break153 ◊ continue153 ◊ do...while152 ◊ for149 ◊ goto154 ◊ if142 ◊ switch147 ◊ while152 ◊ ветвления142 ◊ выбора147 ◊ декремента134 ◊ запятая138 ◊ инкремента 134 ◊ математические133 ◊ побитовые135 ◊ приоритет выполнения 141 ◊ присваивания138 ◊ сравнения139 ◊ ЦИКЛЫ 149
Предметный указатель Определение ◊ переменной 91 ◊ функции 60,312 Остаток от деления 134,162 Отладка программы 345 Ошибки 336 ◊ времени выполнения 337 ◊ логические 337,341 ◊ отладка программы 345 ◊ поиск 341 ◊ синтаксические 336 ◊ типы ошибок 336 п Пароль ◊ ввод 80 ◊ генерация 178 Переменная 91 ◊ автоматическая 104 ◊ адрес 112 ◊ глобальная 58,105 ◊ именование 92 ◊ инициализация 100 ◊ локальная 58, 105 ◊ область видимости 105 ◊ объявление 91 ◊ объявление в другом месте 104 ◊ окружения 451 ◊ регистровая l 04 ◊ статическая 104,105,326 Перенаправление потоков 369,384 Перечисление 129 Потоки ввода/вывода 369 Потоки управления 407 ◊ POSIX420 ◊ WinAPI 407 ◊ синхронизация 412,423 Препроцессор 454 Приведение типов 130,131 Приоритет выполнения операторов 141 Присваивание 100 Прототип функции 59,312 Процесс 426 Псевдоним 100 Пузырьковая сортировка 195 Путь ◊ абсолютный 354 ◊ относительный 354 ◊ преобразование 386 р Разыменование указателя 112 Рекурсия 332 с Символы 206 ◊ широкие 245 Синус 165 Синхронизация потоков 412,423 Системные команды 449 Системные переменные 451 Сложение 133 Сортировка массива 195 Специальные символы 206 Спецификаторы хранения 104 Статическая библиотека 430 Статическая переменная 105,326,327 Статическая функция 327 Строки 11О,206 ◊ длина 225,258 ◊ доступ к символам 224,257 ◊ замена 234,279 ◊ из широких символов 244 ◊ кодировки 263, 265 ◊ конкатенация 223, 257 ◊ поиск 232,277 ◊ регистр символов 216,249 ◊ специальные символы 206 ◊ сравнение 237,282 ◊ форматирование 242 Структура 123 т Тангенс 165 Текущий рабочий каталог 354 Типы данных 93 ◊ приведение 13 1 Точки останова 346 у Указатель 111 ◊ void* 331 475 ◊ арифметические и логические операции 116 ◊ на структуру 126 ◊ на функцию 330 ◊ нулевой 112 ◊ разыменование 112
476 Умножение 133 Унарный минус 133 Установка программ 11 ф Файл 351 ◊ временный 367,381 ◊ ДВОИЧНЫЙ 363 ◊ закрытие 353,377 ◊ запись 358,377 ◊ низкоуровневые потоки 374 ◊ открытие 351,374 ◊ переименование 390 ◊ перемещение 390 ◊ получение информации 395 ◊ права доступа 393 ◊ преобразование пути 386 ◊ произвольного доступа 365, 380 ◊ режим открытия 356,374 ◊ ресурсов 456 ◊ скрытый 394 ◊ удаление 390 ◊ чтение 360, 377 Файл с программой 49 Факториал 332 Форматирование программы 143,341 Функция 312 ◊ возврат значения 328 ◊ встраиваемая 333 ◊ объявление 312 ◊ определение 312 ◊ передача данных произвольного типа 331 ◊ передача массива 320 ◊ передача параметров 318 ◊ передача строки 320 ◊ переменное число параметров 324 ◊ прототип 312 ◊ расположение определений 315 Предметный указатель ◊ рекурсия 332 ◊ статическая 327 ц Целые числа 156,157 Цикл ◊ do ...while 152 ◊ for 149 ◊ while 152 ◊ переход на следующую итерацию 153 ◊ прерывание 153 ч Числа 156 ◊ NAN 179 ◊ бесконечность 179 ◊ вещественные 158 D точность вычислений 158 ◊ восьмеричные 157,175,295 ◊ двоичные 175,295 ◊ десятичные 157 ◊ округление 164 ◊ основные функции 160 ◊ преобразо�ание 165,286 ◊ преобразование в строку 174,294 .◊ случайные 177 ◊ тригонометрические функции 165 ◊ целые 156,157 ◊ шестнадцатеричные 157,175,295 ш Шестнадцатеричные числа 157, i75, 295 э Экспонента 161